From a3fac708b6bbeafd8a1fa49b5987a079ba3be6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Sat, 23 May 2026 20:32:38 +0800 Subject: [PATCH 1/2] fix(builtin-agent): don't delete session jsonl until process exits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously runBuiltinWithMessages deleted the session jsonl after each run/continue call. This meant the createAgent retry mechanism (which calls continue on frontmatter validation failure) would lose all previous turn data — each continue started with an empty jsonl. Now the session jsonl accumulates across run + continue calls, so the final storeBuiltinDetail captures all turns. The jsonl file is left behind for debugging; it's small and can be cleaned up on next startup. Also add a workflow hint to the system prompt reminding the LLM to use tools before outputting frontmatter, preventing premature text-only responses on the first turn. --- packages/workflow-agent-builtin/src/agent.ts | 6 +----- packages/workflow-agent-builtin/src/prompt.ts | 10 ++++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/workflow-agent-builtin/src/agent.ts b/packages/workflow-agent-builtin/src/agent.ts index cf43948..b282dbd 100644 --- a/packages/workflow-agent-builtin/src/agent.ts +++ b/packages/workflow-agent-builtin/src/agent.ts @@ -13,7 +13,7 @@ import { storeBuiltinDetail } from "./detail.js"; import type { ChatMessage } from "./llm/index.js"; import { BUILTIN_CONTINUE_MAX_TURNS, BUILTIN_MAX_TURNS, runBuiltinLoop } from "./loop.js"; import { buildBuiltinMessages } from "./prompt.js"; -import { initSessionDir, removeSession } from "./session.js"; +import { initSessionDir } from "./session.js"; const log = createLogger({ sink: { kind: "stderr" } }); @@ -62,7 +62,6 @@ async function runBuiltinWithMessages( if (loopResult.turnCount === 0) { log("5RWTK9NB", "no turns produced, returning empty output"); - await removeSession(storageRoot, session.sessionId); return { output: "", detailHash: "", sessionId: session.sessionId }; } @@ -75,9 +74,6 @@ async function runBuiltinWithMessages( session.startedAtMs, ); - // Clean up session jsonl - await removeSession(storageRoot, session.sessionId); - return { output: loopResult.finalText, detailHash, sessionId: session.sessionId }; } diff --git a/packages/workflow-agent-builtin/src/prompt.ts b/packages/workflow-agent-builtin/src/prompt.ts index 94f7379..5dd3c38 100644 --- a/packages/workflow-agent-builtin/src/prompt.ts +++ b/packages/workflow-agent-builtin/src/prompt.ts @@ -59,6 +59,16 @@ export function buildBuiltinMessages(ctx: AgentContext): ChatMessage[] { } systemParts.push(rolePrompt); + systemParts.push( + "", + "## Workflow", + "", + "You have tools available (read_file, write_file, run_command). " + + "Use them to complete your task — read files, run commands, make changes as needed. " + + "When you are done, output your final response with the YAML frontmatter block as specified above. " + + "Do NOT output the frontmatter until you have completed all necessary work.", + ); + const messages: ChatMessage[] = [{ role: "system", content: systemParts.join("\n") }]; const roleVisitIndices: number[] = []; From 0eeb4a8ed8af69afde5a9058d7f906c9dfb8904d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Sat, 23 May 2026 20:37:14 +0800 Subject: [PATCH 2/2] fix(builtin): strip preamble before frontmatter + stronger prompt - Add stripPreamble() to handle LLM output with text before --- - Strengthen system prompt: CRITICAL instruction for --- at position 0 - Fixes frontmatter parsing failures on first output turn --- packages/workflow-agent-builtin/src/agent.ts | 20 ++++++++++++++++++- packages/workflow-agent-builtin/src/prompt.ts | 4 +++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/workflow-agent-builtin/src/agent.ts b/packages/workflow-agent-builtin/src/agent.ts index b282dbd..a71633c 100644 --- a/packages/workflow-agent-builtin/src/agent.ts +++ b/packages/workflow-agent-builtin/src/agent.ts @@ -17,6 +17,24 @@ import { initSessionDir } from "./session.js"; const log = createLogger({ sink: { kind: "stderr" } }); +const FRONTMATTER_FENCE = "---"; + +/** + * Strip any text before the first `---` fence. + * LLMs sometimes emit preamble text before the frontmatter block. + */ +function stripPreamble(text: string): string { + if (text.startsWith(FRONTMATTER_FENCE)) { + return text; + } + const idx = text.indexOf(`\n${FRONTMATTER_FENCE}\n`); + if (idx !== -1) { + log("6GWRP3QX", `stripped ${idx + 1} chars of preamble before frontmatter`); + return text.slice(idx + 1); + } + return text; +} + type SessionRecord = { sessionId: string; model: string; @@ -74,7 +92,7 @@ async function runBuiltinWithMessages( session.startedAtMs, ); - return { output: loopResult.finalText, detailHash, sessionId: session.sessionId }; + return { output: stripPreamble(loopResult.finalText), detailHash, sessionId: session.sessionId }; } async function runBuiltin(ctx: AgentContext): Promise { diff --git a/packages/workflow-agent-builtin/src/prompt.ts b/packages/workflow-agent-builtin/src/prompt.ts index 5dd3c38..45033e6 100644 --- a/packages/workflow-agent-builtin/src/prompt.ts +++ b/packages/workflow-agent-builtin/src/prompt.ts @@ -66,7 +66,9 @@ export function buildBuiltinMessages(ctx: AgentContext): ChatMessage[] { "You have tools available (read_file, write_file, run_command). " + "Use them to complete your task — read files, run commands, make changes as needed. " + "When you are done, output your final response with the YAML frontmatter block as specified above. " + - "Do NOT output the frontmatter until you have completed all necessary work.", + "Do NOT output the frontmatter until you have completed all necessary work. " + + "CRITICAL: Your final output MUST start with the `---` fence on the very first line — " + + "no preamble text, no explanation before it. The parser requires `---` at position 0.", ); const messages: ChatMessage[] = [{ role: "system", content: systemParts.join("\n") }];