From 44147da419689369f909084f57964d5165128f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Sat, 23 May 2026 17:15:23 +0800 Subject: [PATCH] fix(builtin): split prompt into system/user messages System message = agent identity (role prompt + output format instruction) User message = moderator speech (task + edge prompt + history) This reflects the workflow's core model: moderator speaks to agent via the graph's edge prompt. Previously all content was in a single system message with no user message, causing Claude API 400 errors. - buildBuiltinPrompt now returns { system, user } instead of string - agent.ts sends system + user as separate messages - Tests updated accordingly --- .../__tests__/prompt.test.ts | 30 ++++++++++++------- packages/workflow-agent-builtin/src/agent.ts | 7 +++-- packages/workflow-agent-builtin/src/cli.ts | 0 packages/workflow-agent-builtin/src/prompt.ts | 28 ++++++++++++----- 4 files changed, 45 insertions(+), 20 deletions(-) mode change 100644 => 100755 packages/workflow-agent-builtin/src/cli.ts diff --git a/packages/workflow-agent-builtin/__tests__/prompt.test.ts b/packages/workflow-agent-builtin/__tests__/prompt.test.ts index 70eb75a..58fedf0 100644 --- a/packages/workflow-agent-builtin/__tests__/prompt.test.ts +++ b/packages/workflow-agent-builtin/__tests__/prompt.test.ts @@ -26,22 +26,30 @@ function minimalContext(overrides: Partial = {}): AgentContext { start: { workflow: "wf-hash", prompt: "Fix the bug" }, steps: [], outputFormatInstruction: "---\nstatus: done\n---", + edgePrompt: "Implement the fix described in the plan.", + isFirstVisit: true, ...overrides, }; } describe("buildBuiltinPrompt", () => { - test("includes output format, task, and role goal", () => { - const prompt = buildBuiltinPrompt(minimalContext()); - expect(prompt).toContain("status: done"); - expect(prompt).toContain("## Goal"); - expect(prompt).toContain("Ship the fix"); - expect(prompt).toContain("## Task"); - expect(prompt).toContain("Fix the bug"); + test("system includes output format and role goal", () => { + const { system } = buildBuiltinPrompt(minimalContext()); + expect(system).toContain("status: done"); + expect(system).toContain("## Goal"); + expect(system).toContain("Ship the fix"); }); - test("includes history when steps exist", () => { - const prompt = buildBuiltinPrompt( + test("user includes task and edge prompt", () => { + const { user } = buildBuiltinPrompt(minimalContext()); + expect(user).toContain("## Task"); + expect(user).toContain("Fix the bug"); + expect(user).toContain("## Current Step Instruction"); + expect(user).toContain("Implement the fix"); + }); + + test("user includes history when steps exist", () => { + const { user } = buildBuiltinPrompt( minimalContext({ steps: [ { @@ -53,7 +61,7 @@ describe("buildBuiltinPrompt", () => { ], }), ); - expect(prompt).toContain("## Previous Steps"); - expect(prompt).toContain("planner"); + expect(user).toContain("## Previous Steps"); + expect(user).toContain("planner"); }); }); diff --git a/packages/workflow-agent-builtin/src/agent.ts b/packages/workflow-agent-builtin/src/agent.ts index cc738b4..c56dcc8 100644 --- a/packages/workflow-agent-builtin/src/agent.ts +++ b/packages/workflow-agent-builtin/src/agent.ts @@ -69,8 +69,11 @@ async function runBuiltin(ctx: AgentContext): Promise { const provider = resolveModel(config, config.defaultModel); const sessionId = generateUlid(Date.now()); - const systemPrompt = buildBuiltinPrompt(ctx); - const messages: ChatMessage[] = [{ role: "system", content: systemPrompt }]; + const promptParts = buildBuiltinPrompt(ctx); + const messages: ChatMessage[] = [ + { role: "system", content: promptParts.system }, + { role: "user", content: promptParts.user }, + ]; const session: BuiltinSessionState = { sessionId, diff --git a/packages/workflow-agent-builtin/src/cli.ts b/packages/workflow-agent-builtin/src/cli.ts old mode 100644 new mode 100755 diff --git a/packages/workflow-agent-builtin/src/prompt.ts b/packages/workflow-agent-builtin/src/prompt.ts index 37861ab..72e2ea9 100644 --- a/packages/workflow-agent-builtin/src/prompt.ts +++ b/packages/workflow-agent-builtin/src/prompt.ts @@ -19,18 +19,32 @@ function buildHistorySummary(steps: AgentContext["steps"]): string { return lines.join("\n"); } -/** Assemble output format, role prompt, task, and history (aligned with buildHermesPrompt). */ -export function buildBuiltinPrompt(ctx: AgentContext): string { +export type BuiltinPromptParts = { + system: string; + user: string; +}; + +/** Assemble system prompt (role + format) and user prompt (task + edge + history). */ +export function buildBuiltinPrompt(ctx: AgentContext): BuiltinPromptParts { const roleDef = ctx.workflow.roles[ctx.role]; const rolePrompt = roleDef !== undefined ? buildRolePrompt(roleDef) : ""; - const parts: string[] = []; + const systemParts: string[] = []; if (ctx.outputFormatInstruction !== "") { - parts.push(ctx.outputFormatInstruction, ""); + systemParts.push(ctx.outputFormatInstruction, ""); + } + systemParts.push(rolePrompt); + + const userParts: string[] = ["## Task", ctx.start.prompt]; + if (ctx.edgePrompt !== "") { + userParts.push("", "## Current Step Instruction", ctx.edgePrompt); } - parts.push(rolePrompt, "", "## Task", ctx.start.prompt); const historyBlock = buildHistorySummary(ctx.steps); if (historyBlock !== "") { - parts.push("", historyBlock); + userParts.push("", historyBlock); } - return parts.join("\n"); + + return { + system: systemParts.join("\n"), + user: userParts.join("\n"), + }; }