From 695e79f3eb8f74c8082cfd7c5710802733cdb251 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Tue, 16 Sep 2025 20:41:13 +0900 Subject: [PATCH 01/10] feat: implement DB Agent routing in LangGraph workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add priority-based routing system to classify messages and direct workflow to the DB Agent when schema issues are detected. This enables the workflow to handle database schema validation and fixes before proceeding with other agents. Key changes: - Add dbAgent conditional edge to graph routing - Implement shouldRouteDBAgent utility function with schema issue detection - Update classifyMessage with priority-based routing (DB Agent > QA completion > PM Agent) - Add comprehensive test coverage for all routing scenarios 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../agent/src/createGraph.ts | 1 + .../lead-agent/classifyMessage/index.test.ts | 128 ++++++++++++++++++ .../src/lead-agent/classifyMessage/index.ts | 14 +- .../src/lead-agent/utils/workflowStatus.ts | 8 ++ 4 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts diff --git a/frontend/internal-packages/agent/src/createGraph.ts b/frontend/internal-packages/agent/src/createGraph.ts index 1c83a71ebd..0bc619d46c 100644 --- a/frontend/internal-packages/agent/src/createGraph.ts +++ b/frontend/internal-packages/agent/src/createGraph.ts @@ -92,6 +92,7 @@ export const createGraph = (checkpointer?: BaseCheckpointSaver) => { .addConditionalEdges('leadAgent', (state) => state.next, { pmAgent: 'pmAgent', + dbAgent: 'dbAgent', [END]: END, }) .addEdge('pmAgent', 'dbAgent') diff --git a/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts b/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts new file mode 100644 index 0000000000..7e2c91da6f --- /dev/null +++ b/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts @@ -0,0 +1,128 @@ +import { END } from '@langchain/langgraph' +import { describe, expect, it } from 'vitest' +import type { WorkflowState } from '../../types' +import { classifyMessage } from './index' + +const createMockState = ( + overrides: Partial = {}, +): WorkflowState => ({ + messages: [], + analyzedRequirements: { + businessRequirement: '', + functionalRequirements: {}, + nonFunctionalRequirements: {}, + }, + testcases: [], + schemaIssues: [], + schemaData: { tables: {}, enums: {}, extensions: {} }, + buildingSchemaId: 'test-schema-id', + latestVersionNumber: 1, + organizationId: 'test-org-id', + userId: 'test-user-id', + designSessionId: 'test-session-id', + next: END, + ...overrides, +}) + +describe('classifyMessage', () => { + it('should route to dbAgent when schema issues exist', async () => { + const state = createMockState({ + schemaIssues: [ + { + requirementId: 'req-1', + description: 'Missing foreign key constraint', + }, + ], + }) + + const result = await classifyMessage(state) + + expect(result.goto).toEqual([END]) + expect(result.update).toEqual({ next: 'dbAgent' }) + }) + + it('should route to dbAgent when multiple schema issues exist', async () => { + const state = createMockState({ + schemaIssues: [ + { + requirementId: 'req-1', + description: 'Missing foreign key constraint', + }, + { requirementId: 'req-2', description: 'Invalid table name' }, + ], + }) + + const result = await classifyMessage(state) + + expect(result.goto).toEqual([END]) + expect(result.update).toEqual({ next: 'dbAgent' }) + }) + + it('should prioritize schema issues over QA completion', async () => { + const state = createMockState({ + schemaIssues: [ + { requirementId: 'req-1', description: 'Schema issue found' }, + ], + testcases: [ + { + id: 'test-1', + requirementId: 'req-1', + requirementType: 'functional', + requirementCategory: 'test', + requirement: 'Test requirement', + title: 'Test case', + description: 'Test case', + dmlOperation: { + operation_type: 'SELECT', + sql: 'SELECT 1', + dml_execution_logs: [], + }, + }, + ], + }) + + const result = await classifyMessage(state) + + expect(result.goto).toEqual([END]) + expect(result.update).toEqual({ next: 'dbAgent' }) + }) + + it('should route to summarizeWorkflow when QA completed and no schema issues', async () => { + const state = createMockState({ + schemaIssues: [], + testcases: [ + { + id: 'test-1', + requirementId: 'req-1', + requirementType: 'functional', + requirementCategory: 'test', + requirement: 'Test requirement', + title: 'Test case', + description: 'Test case', + dmlOperation: { + operation_type: 'SELECT', + sql: 'SELECT 1', + dml_execution_logs: [], + }, + }, + ], + }) + + const result = await classifyMessage(state) + + expect(result.goto).toEqual(['summarizeWorkflow']) + expect(result.update).toBeUndefined() + }) + + it('should route to pmAgent when no schema issues and QA not completed', async () => { + const state = createMockState({ + schemaIssues: [], + testcases: [], + }) + + const result = await classifyMessage(state) + + expect(result.goto).toEqual([END]) + expect(result.update).toEqual({ next: 'pmAgent' }) + }) +}) diff --git a/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.ts b/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.ts index 61adeac92b..75dd60385e 100644 --- a/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.ts +++ b/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.ts @@ -1,14 +1,22 @@ import { Command, END } from '@langchain/langgraph' import type { WorkflowState } from '../../types' -import { isQACompleted } from '../utils/workflowStatus' +import { isQACompleted, shouldRouteDBAgent } from '../utils/workflowStatus' export async function classifyMessage(state: WorkflowState): Promise { - // 1. Check if QA is completed first (highest priority) + // 1. Check if DB Agent routing is needed (highest priority) + if (shouldRouteDBAgent(state)) { + return new Command({ + update: { next: 'dbAgent' }, + goto: END, + }) + } + + // 2. Check if QA is completed (second priority) if (isQACompleted(state)) { return new Command({ goto: 'summarizeWorkflow' }) } - // 2. Direct routing to pmAgent + // 3. Direct routing to pmAgent (default) return new Command({ update: { next: 'pmAgent' }, goto: END, diff --git a/frontend/internal-packages/agent/src/lead-agent/utils/workflowStatus.ts b/frontend/internal-packages/agent/src/lead-agent/utils/workflowStatus.ts index 80082f4a4a..91d6afb1ee 100644 --- a/frontend/internal-packages/agent/src/lead-agent/utils/workflowStatus.ts +++ b/frontend/internal-packages/agent/src/lead-agent/utils/workflowStatus.ts @@ -14,3 +14,11 @@ export function isQACompleted(state: WorkflowState): boolean { return false } + +function hasSchemaIssues(state: WorkflowState): boolean { + return state.schemaIssues.length > 0 +} + +export function shouldRouteDBAgent(state: WorkflowState): boolean { + return hasSchemaIssues(state) +} From 7f232119aa172aece14ca38939267b33b8484ea8 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Tue, 16 Sep 2025 20:58:36 +0900 Subject: [PATCH 02/10] fix: update Mermaid diagrams to include leadAgent -> dbAgent routing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add missing leadAgent -.-> dbAgent; edge to test expectation - Update README.md diagram to match actual graph structure - Ensures test consistency with implemented workflow routing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- frontend/internal-packages/agent/README.md | 1 + frontend/internal-packages/agent/src/createGraph.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/internal-packages/agent/README.md b/frontend/internal-packages/agent/README.md index 5bb80cc57e..d0f8d999af 100644 --- a/frontend/internal-packages/agent/README.md +++ b/frontend/internal-packages/agent/README.md @@ -21,6 +21,7 @@ graph TD; __start__ -.-> validateInitialSchema; __start__ -.-> leadAgent; leadAgent -.-> pmAgent; + leadAgent -.-> dbAgent; leadAgent -.-> __end__; classDef default fill:#f2f0ff,line-height:1.2; classDef first fill-opacity:0; diff --git a/frontend/internal-packages/agent/src/createGraph.test.ts b/frontend/internal-packages/agent/src/createGraph.test.ts index d986653245..70da169a4d 100644 --- a/frontend/internal-packages/agent/src/createGraph.test.ts +++ b/frontend/internal-packages/agent/src/createGraph.test.ts @@ -20,6 +20,7 @@ graph TD; __start__ -.-> validateInitialSchema; __start__ -.-> leadAgent; leadAgent -.-> pmAgent; + leadAgent -.-> dbAgent; leadAgent -.-> __end__; classDef default fill:#f2f0ff,line-height:1.2; classDef first fill-opacity:0; From aa36dccd3822b45c127a2e5bdf79af6146777e07 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Tue, 16 Sep 2025 21:04:13 +0900 Subject: [PATCH 03/10] fix: clear schemaIssues after DB agent processing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevents infinite loops by clearing schemaIssues array after DB agent completes its processing, ensuring proper workflow termination. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- frontend/internal-packages/agent/src/createGraph.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/internal-packages/agent/src/createGraph.ts b/frontend/internal-packages/agent/src/createGraph.ts index 0bc619d46c..67bd14851c 100644 --- a/frontend/internal-packages/agent/src/createGraph.ts +++ b/frontend/internal-packages/agent/src/createGraph.ts @@ -36,7 +36,8 @@ export const createGraph = (checkpointer?: BaseCheckpointSaver) => { const modifiedState = { ...state, messages: [], prompt } const output = await dbAgentSubgraph.invoke(modifiedState, config) - return { ...state, ...output } + // Clear schemaIssues after DB agent processing to prevent infinite loops + return { ...state, ...output, schemaIssues: [] } } const callQaAgent = async (state: WorkflowState, config: RunnableConfig) => { From 9f3e8c2d03181bbb957ae23ca7d87969b7fa8462 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Wed, 8 Oct 2025 17:44:18 +0900 Subject: [PATCH 04/10] fix: update classifyMessage test to match new state structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update analyzedRequirements structure from old format to new { goal, testcases } - Change schemaIssues from requirementId to testcaseId - Update testcase structure to match new TestCase schema - Remove obsolete latestVersionNumber field from mock state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../lead-agent/classifyMessage/index.test.ts | 76 +++++++++---------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts b/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts index 7e2c91da6f..504fd2a76a 100644 --- a/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts +++ b/frontend/internal-packages/agent/src/lead-agent/classifyMessage/index.test.ts @@ -8,15 +8,12 @@ const createMockState = ( ): WorkflowState => ({ messages: [], analyzedRequirements: { - businessRequirement: '', - functionalRequirements: {}, - nonFunctionalRequirements: {}, + goal: '', + testcases: {}, }, - testcases: [], schemaIssues: [], schemaData: { tables: {}, enums: {}, extensions: {} }, buildingSchemaId: 'test-schema-id', - latestVersionNumber: 1, organizationId: 'test-org-id', userId: 'test-user-id', designSessionId: 'test-session-id', @@ -29,7 +26,7 @@ describe('classifyMessage', () => { const state = createMockState({ schemaIssues: [ { - requirementId: 'req-1', + testcaseId: 'test-1', description: 'Missing foreign key constraint', }, ], @@ -45,10 +42,10 @@ describe('classifyMessage', () => { const state = createMockState({ schemaIssues: [ { - requirementId: 'req-1', + testcaseId: 'test-1', description: 'Missing foreign key constraint', }, - { requirementId: 'req-2', description: 'Invalid table name' }, + { testcaseId: 'test-2', description: 'Invalid table name' }, ], }) @@ -61,24 +58,22 @@ describe('classifyMessage', () => { it('should prioritize schema issues over QA completion', async () => { const state = createMockState({ schemaIssues: [ - { requirementId: 'req-1', description: 'Schema issue found' }, + { testcaseId: 'test-1', description: 'Schema issue found' }, ], - testcases: [ - { - id: 'test-1', - requirementId: 'req-1', - requirementType: 'functional', - requirementCategory: 'test', - requirement: 'Test requirement', - title: 'Test case', - description: 'Test case', - dmlOperation: { - operation_type: 'SELECT', - sql: 'SELECT 1', - dml_execution_logs: [], - }, + analyzedRequirements: { + goal: 'Test goal', + testcases: { + functional: [ + { + id: 'test-1', + title: 'Test case', + type: 'SELECT', + sql: 'SELECT 1', + testResults: [], + }, + ], }, - ], + }, }) const result = await classifyMessage(state) @@ -90,22 +85,20 @@ describe('classifyMessage', () => { it('should route to summarizeWorkflow when QA completed and no schema issues', async () => { const state = createMockState({ schemaIssues: [], - testcases: [ - { - id: 'test-1', - requirementId: 'req-1', - requirementType: 'functional', - requirementCategory: 'test', - requirement: 'Test requirement', - title: 'Test case', - description: 'Test case', - dmlOperation: { - operation_type: 'SELECT', - sql: 'SELECT 1', - dml_execution_logs: [], - }, + analyzedRequirements: { + goal: 'Test goal', + testcases: { + functional: [ + { + id: 'test-1', + title: 'Test case', + type: 'SELECT', + sql: 'SELECT 1', + testResults: [], + }, + ], }, - ], + }, }) const result = await classifyMessage(state) @@ -117,7 +110,10 @@ describe('classifyMessage', () => { it('should route to pmAgent when no schema issues and QA not completed', async () => { const state = createMockState({ schemaIssues: [], - testcases: [], + analyzedRequirements: { + goal: 'Test goal', + testcases: {}, + }, }) const result = await classifyMessage(state) From ef325e0070c848ace81954a55fcbbd5dae4e41b8 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Wed, 8 Oct 2025 19:02:59 +0900 Subject: [PATCH 05/10] refactor: separate schemaIssues annotation for workflow and QA agent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split schemaIssues annotation into two separate annotations with different reducers to handle their distinct update patterns: - workflowSchemaIssuesAnnotation: Uses replacement reducer for clearing issues after DB agent processing - qaSchemaIssuesAnnotation: Uses concat reducer for parallel issue collection in QA agent This fixes the issue where schemaIssues could not be cleared because the concat reducer prevents empty array updates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/qa-agent/shared/qaAgentAnnotation.ts | 4 ++-- .../testcaseGeneration/testcaseAnnotation.ts | 21 ++++++++++++------ .../agent/src/workflowAnnotation.ts | 4 ++-- .../src/workflowSchemaIssuesAnnotation.ts | 22 +++++++++++++++++++ 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.ts diff --git a/frontend/internal-packages/agent/src/qa-agent/shared/qaAgentAnnotation.ts b/frontend/internal-packages/agent/src/qa-agent/shared/qaAgentAnnotation.ts index b0119751bf..2a9ed3b97c 100644 --- a/frontend/internal-packages/agent/src/qa-agent/shared/qaAgentAnnotation.ts +++ b/frontend/internal-packages/agent/src/qa-agent/shared/qaAgentAnnotation.ts @@ -3,7 +3,7 @@ import type { AnalyzedRequirements } from '@liam-hq/artifact' import type { Schema } from '@liam-hq/schema' import { generatedSqlsAnnotation, - schemaIssuesAnnotation, + qaSchemaIssuesAnnotation, } from '../testcaseGeneration/testcaseAnnotation' /** @@ -27,7 +27,7 @@ export const qaAgentAnnotation = Annotation.Root({ }), designSessionId: Annotation, buildingSchemaId: Annotation, - schemaIssues: schemaIssuesAnnotation, + schemaIssues: qaSchemaIssuesAnnotation, generatedSqls: generatedSqlsAnnotation, next: Annotation({ reducer: (x, y) => y ?? x ?? END, diff --git a/frontend/internal-packages/agent/src/qa-agent/testcaseGeneration/testcaseAnnotation.ts b/frontend/internal-packages/agent/src/qa-agent/testcaseGeneration/testcaseAnnotation.ts index a0794cd454..9653c0880a 100644 --- a/frontend/internal-packages/agent/src/qa-agent/testcaseGeneration/testcaseAnnotation.ts +++ b/frontend/internal-packages/agent/src/qa-agent/testcaseGeneration/testcaseAnnotation.ts @@ -1,13 +1,20 @@ import { Annotation, MessagesAnnotation } from '@langchain/langgraph' import type { Schema } from '@liam-hq/schema' +import type { SchemaIssue } from '../../workflowSchemaIssuesAnnotation' import type { TestCaseData } from '../distributeRequirements' -type SchemaIssue = { - testcaseId: string - description: string -} - -export const schemaIssuesAnnotation = Annotation>({ +/** + * Schema issues annotation for QA agent's parallel processing. + * + * Uses concat reducer because: + * - Multiple testcase nodes run in parallel + * - Each node may discover different schema issues + * - All issues must be collected together + * + * This is different from workflow-level annotation which uses replacement + * for clearing issues after DB agent processing. + */ +export const qaSchemaIssuesAnnotation = Annotation>({ reducer: (prev, next) => prev.concat(next), default: () => [], }) @@ -27,6 +34,6 @@ export const testcaseAnnotation = Annotation.Root({ currentTestcase: Annotation, schemaData: Annotation, goal: Annotation, - schemaIssues: schemaIssuesAnnotation, + schemaIssues: qaSchemaIssuesAnnotation, generatedSqls: generatedSqlsAnnotation, }) diff --git a/frontend/internal-packages/agent/src/workflowAnnotation.ts b/frontend/internal-packages/agent/src/workflowAnnotation.ts index e2350449d2..e9ea82542b 100644 --- a/frontend/internal-packages/agent/src/workflowAnnotation.ts +++ b/frontend/internal-packages/agent/src/workflowAnnotation.ts @@ -1,7 +1,7 @@ import { Annotation, END, MessagesAnnotation } from '@langchain/langgraph' import type { AnalyzedRequirements } from '@liam-hq/artifact' import type { Schema } from '@liam-hq/schema' -import { schemaIssuesAnnotation } from './qa-agent/testcaseGeneration/testcaseAnnotation' +import { workflowSchemaIssuesAnnotation } from './workflowSchemaIssuesAnnotation' export const workflowAnnotation = Annotation.Root({ ...MessagesAnnotation.spec, @@ -17,7 +17,7 @@ export const workflowAnnotation = Annotation.Root({ organizationId: Annotation, userId: Annotation, designSessionId: Annotation, - schemaIssues: schemaIssuesAnnotation, + schemaIssues: workflowSchemaIssuesAnnotation, next: Annotation({ reducer: (x, y) => y ?? x ?? END, diff --git a/frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.ts b/frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.ts new file mode 100644 index 0000000000..b8de853676 --- /dev/null +++ b/frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.ts @@ -0,0 +1,22 @@ +import { Annotation } from '@langchain/langgraph' + +export type SchemaIssue = { + testcaseId: string + description: string +} + +/** + * Schema issues annotation for workflow-level state management. + * + * Uses a replacement reducer instead of concat because: + * - Workflow needs to clear issues after DB agent processing + * - Setting schemaIssues: [] should actually clear the array + * - With concat reducer, [] would be concatenated (prev.concat([]) = prev) + * + * This is different from QA agent's annotation which uses concat + * for parallel issue collection. + */ +export const workflowSchemaIssuesAnnotation = Annotation>({ + reducer: (prev, next) => next ?? prev, + default: () => [], +}) From 427b1b84fc83dc1164c1b4fc23e42f272065cb07 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Wed, 8 Oct 2025 19:03:42 +0900 Subject: [PATCH 06/10] test: add integration tests for workflow schemaIssues annotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests to verify replacement reducer behavior: - Replace operation (not concat) - Clear with empty array - Preserve value when not updated 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../workflowSchemaIssuesAnnotation.test.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.test.ts diff --git a/frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.test.ts b/frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.test.ts new file mode 100644 index 0000000000..0d312954e2 --- /dev/null +++ b/frontend/internal-packages/agent/src/workflowSchemaIssuesAnnotation.test.ts @@ -0,0 +1,79 @@ +import { StateGraph } from '@langchain/langgraph' +import { describe, expect, it } from 'vitest' +import type { WorkflowState } from './types' +import { workflowAnnotation } from './workflowAnnotation' + +describe('workflowSchemaIssuesAnnotation', () => { + it('should replace schemaIssues (not concat)', async () => { + const graph = new StateGraph(workflowAnnotation) + .addNode('step1', (_state: WorkflowState) => ({ + schemaIssues: [{ testcaseId: 'test-1', description: 'Issue 1' }], + })) + .addNode('step2', (_state: WorkflowState) => ({ + schemaIssues: [{ testcaseId: 'test-2', description: 'Issue 2' }], + })) + .addEdge('__start__', 'step1') + .addEdge('step1', 'step2') + .addEdge('step2', '__end__') + .compile() + + const result = await graph.invoke({ + messages: [], + schemaData: { tables: {}, enums: {}, extensions: {} }, + }) + + // Should be replaced (only step2's issue), not concatenated + expect(result.schemaIssues).toEqual([ + { testcaseId: 'test-2', description: 'Issue 2' }, + ]) + }) + + it('should clear schemaIssues when set to empty array', async () => { + const graph = new StateGraph(workflowAnnotation) + .addNode('step1', (_state: WorkflowState) => ({ + schemaIssues: [ + { testcaseId: 'test-1', description: 'Issue 1' }, + { testcaseId: 'test-2', description: 'Issue 2' }, + ], + })) + .addNode('step2', (_state: WorkflowState) => ({ + schemaIssues: [], + })) + .addEdge('__start__', 'step1') + .addEdge('step1', 'step2') + .addEdge('step2', '__end__') + .compile() + + const result = await graph.invoke({ + messages: [], + schemaData: { tables: {}, enums: {}, extensions: {} }, + }) + + // Should be cleared + expect(result.schemaIssues).toEqual([]) + }) + + it('should keep schemaIssues when not updated', async () => { + const graph = new StateGraph(workflowAnnotation) + .addNode('step1', (_state: WorkflowState) => ({ + schemaIssues: [{ testcaseId: 'test-1', description: 'Issue 1' }], + })) + .addNode('step2', (_state: WorkflowState) => ({ + // Not updating schemaIssues + })) + .addEdge('__start__', 'step1') + .addEdge('step1', 'step2') + .addEdge('step2', '__end__') + .compile() + + const result = await graph.invoke({ + messages: [], + schemaData: { tables: {}, enums: {}, extensions: {} }, + }) + + // Should keep step1's value + expect(result.schemaIssues).toEqual([ + { testcaseId: 'test-1', description: 'Issue 1' }, + ]) + }) +}) From 8aac5437b81739100fb8c7b6ad953cc96c012283 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Wed, 8 Oct 2025 19:42:34 +0900 Subject: [PATCH 07/10] refactor: rename "Test Cases" to "Requirements" in DB Agent prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the prompt text from "Test Cases:" to "Requirements:" to better reflect the DB Agent's role of designing schemas based on requirements rather than executing tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../convertAnalyzedRequirementsToPrompt.test.ts | 14 +++++++------- .../utils/convertAnalyzedRequirementsToPrompt.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts index 0103a0c246..a677f883e2 100644 --- a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts +++ b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts @@ -71,7 +71,7 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Design a user management system with authentication - ## Test Cases + ## Requirements - authentication: User login (SELECT), User logout (UPDATE), Password reset (UPDATE) - userManagement: Create new user (INSERT), Update user info (UPDATE), Delete user (DELETE)" @@ -96,7 +96,7 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Build a simple system - ## Test Cases" + ## Requirements" `) }) @@ -128,7 +128,7 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Add a basic feature - ## Test Cases + ## Requirements - basic: Basic feature test (INSERT)" `) @@ -156,7 +156,7 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Design a user management system with authentication - ## Test Cases + ## Requirements - authentication: User logout (UPDATE)" `) @@ -185,7 +185,7 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Design a user management system with authentication - ## Test Cases + ## Requirements - authentication: User login (SELECT), User logout (UPDATE), Password reset (UPDATE) - userManagement: Create new user (INSERT), Update user info (UPDATE), Delete user (DELETE)" @@ -213,7 +213,7 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Design a user management system with authentication - ## Test Cases + ## Requirements - authentication: User login (SELECT)" `) @@ -243,7 +243,7 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Design a user management system with authentication - ## Test Cases" + ## Requirements" `) }) diff --git a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts index 4c06fc5a2f..79c8f739f4 100644 --- a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts +++ b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts @@ -45,7 +45,7 @@ ${requirements.goal} ${userInput} -## Test Cases +## Requirements ${testCasesSection}`.trim() } From 2414945809e950f3baadf8ce03de66c7c8ca5363 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Wed, 8 Oct 2025 19:49:13 +0900 Subject: [PATCH 08/10] feat: add "Schema Issues to Fix" section to DB Agent prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new section that lists schema issues when schemaIssues are present. This helps DB Agent understand what specific problems need to be fixed. Prompt structure: - Session Goal - Requirements (filtered by schemaIssues if present) - Schema Issues to Fix (numbered list, only shown when issues exist) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ...onvertAnalyzedRequirementsToPrompt.test.ts | 19 ++++++++++++++++--- .../convertAnalyzedRequirementsToPrompt.ts | 7 ++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts index a677f883e2..b4ccbdabd3 100644 --- a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts +++ b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts @@ -158,7 +158,11 @@ describe('convertAnalyzedRequirementsToPrompt', () => { ## Requirements - - authentication: User logout (UPDATE)" + - authentication: User logout (UPDATE) + + ## Schema Issues to Fix + + 1. Missing logout table" `) }) @@ -215,7 +219,11 @@ describe('convertAnalyzedRequirementsToPrompt', () => { ## Requirements - - authentication: User login (SELECT)" + - authentication: User login (SELECT) + + ## Schema Issues to Fix + + 1. Login form missing" `) }) @@ -243,7 +251,12 @@ describe('convertAnalyzedRequirementsToPrompt', () => { Design a user management system with authentication - ## Requirements" + ## Requirements + + + ## Schema Issues to Fix + + 1. Non-existent testcase issue" `) }) diff --git a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts index 79c8f739f4..aaad806f2d 100644 --- a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts +++ b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.ts @@ -37,6 +37,11 @@ export const convertRequirementsToPrompt = ( ) .join('\n') + const schemaIssuesSection = + schemaIssues && schemaIssues.length > 0 + ? `\n\n## Schema Issues to Fix\n\n${schemaIssues.map((issue, index) => `${index + 1}. ${issue.description}`).join('\n')}` + : '' + return `## Session Goal ${requirements.goal} @@ -47,5 +52,5 @@ ${userInput} ## Requirements -${testCasesSection}`.trim() +${testCasesSection}${schemaIssuesSection}`.trim() } From 895ca3d4787b2d16b6bf584751e9a9b798f869db Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 9 Oct 2025 17:47:34 +0900 Subject: [PATCH 09/10] fix: set recursion limit to 10 and update integration test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Set DEFAULT_RECURSION_LIMIT to 10 to prevent infinite loops - Added detailed documentation explaining temporary limitation - Updated integration test to explicitly use DEFAULT_RECURSION_LIMIT The recursion limit of 10 allows multiple iterations of the workflow (PM Agent → DB Agent → QA Agent → Lead Agent → DB Agent) while preventing infinite loops caused by schemaDesignTool issues. This provides more opportunities for the DB Agent to refine the schema compared to the previous limit of 3, while still preventing long-running failures. See: route06/liam-internal#5642 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../internal-packages/agent/src/constants.ts | 23 +++++++++++-------- .../agent/src/createGraph.integration.test.ts | 2 ++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/frontend/internal-packages/agent/src/constants.ts b/frontend/internal-packages/agent/src/constants.ts index c4848a8594..55b4544c50 100644 --- a/frontend/internal-packages/agent/src/constants.ts +++ b/frontend/internal-packages/agent/src/constants.ts @@ -5,15 +5,18 @@ * Important: Node retries do NOT count toward this limit. The limit only * applies to transitions between nodes. * - * The workflow has 8 nodes: - * - Normal execution: 9 transitions (START → 8 nodes → END) - * - With error loops: May have additional transitions when errors occur - * (e.g., validateSchema → designSchema) + * TEMPORARY LIMITATION (set to 10): + * Due to issues with schemaDesignTool, the DB Agent cannot resolve schema issues + * on the second and subsequent attempts, causing infinite loops between: + * leadAgent → dbAgent → qaAgent → leadAgent (when schemaIssues exist) * - * Setting this to 50 ensures: - * - Complete workflow execution under normal conditions - * - Ample headroom for complex error handling loops and retries - * - Protection against infinite loops while allowing for complex workflows - * - Sufficient capacity for finding optimal workflow patterns + * Current behavior with limit=10: + * - Allows multiple iterations of: PM Agent → DB Agent → QA Agent → Lead Agent → DB Agent + * - The workflow will fail after 10 loops if issues persist + * - Provides more opportunities for the DB Agent to refine the schema + * + * TODO: Increase this limit after fixing schemaDesignTool to properly handle + * schema modifications (e.g., unique constraint issues, JSON patch errors) + * See: route06/liam-internal#5642 */ -export const DEFAULT_RECURSION_LIMIT = 50 +export const DEFAULT_RECURSION_LIMIT = 10 diff --git a/frontend/internal-packages/agent/src/createGraph.integration.test.ts b/frontend/internal-packages/agent/src/createGraph.integration.test.ts index 98cb4b0ff9..da475b022f 100644 --- a/frontend/internal-packages/agent/src/createGraph.integration.test.ts +++ b/frontend/internal-packages/agent/src/createGraph.integration.test.ts @@ -6,6 +6,7 @@ import { getTestConfig, outputStreamEvents, } from '../test-utils/workflowTestHelpers' +import { DEFAULT_RECURSION_LIMIT } from './constants' import { createGraph } from './createGraph' import type { WorkflowState } from './types' @@ -36,6 +37,7 @@ describe('createGraph Integration', () => { // Act const streamEvents = graph.streamEvents(initialState, { ...config, + recursionLimit: DEFAULT_RECURSION_LIMIT, streamMode: 'messages', version: 'v2', subgraphs: true, From ef1030796f3b3cc5953ba1101ee8b1b08559e861 Mon Sep 17 00:00:00 2001 From: MH4GF Date: Thu, 9 Oct 2025 18:17:23 +0900 Subject: [PATCH 10/10] test: fix convertAnalyzedRequirementsToPrompt test snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update test snapshots to match the new Schema Issues section format: - Add blank lines between Requirements and Schema Issues sections - Update test to verify Schema Issues are displayed (not hidden) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../utils/convertAnalyzedRequirementsToPrompt.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts index b4ccbdabd3..3f4156a5ad 100644 --- a/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts +++ b/frontend/internal-packages/agent/src/db-agent/utils/convertAnalyzedRequirementsToPrompt.test.ts @@ -254,13 +254,14 @@ describe('convertAnalyzedRequirementsToPrompt', () => { ## Requirements + ## Schema Issues to Fix 1. Non-existent testcase issue" `) }) - it('should filter testcases without showing IDs or issue details', () => { + it('should filter testcases and show issue details in Schema Issues section', () => { const userInput = 'Design a user management system with authentication' const schemaIssues = [ { testcaseId: '4', description: 'User table structure issue' }, @@ -278,7 +279,8 @@ describe('convertAnalyzedRequirementsToPrompt', () => { ) expect(result).toContain('Create new user') expect(result).not.toContain('[4]') - expect(result).not.toContain('User table structure issue') + expect(result).toContain('## Schema Issues to Fix') + expect(result).toContain('User table structure issue') }) }) })