From 131700097a732b97b3d17c5e2efa1c5a44b013ef Mon Sep 17 00:00:00 2001 From: Luyu Cheng <2239547+chengluyu@users.noreply.github.com> Date: Tue, 7 Jul 2026 22:29:40 +0800 Subject: [PATCH] fix: clarify goal blocked audit guidance (#1481) --- .changeset/goal-blocked-audit-prompt.md | 5 ++++ .../agent-core/src/agent/injection/goal.ts | 26 +++++++++++++------ packages/agent-core/src/agent/turn/index.ts | 26 ++++++++++++------- .../src/tools/builtin/goal/update-goal.md | 6 ++--- .../src/tools/builtin/goal/update-goal.ts | 4 ++- .../test/agent/injection/goal.test.ts | 12 +++++++++ .../test/harness/goal-session.test.ts | 6 +++++ packages/agent-core/test/tools/goal.test.ts | 17 +++++++++++- 8 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 .changeset/goal-blocked-audit-prompt.md diff --git a/.changeset/goal-blocked-audit-prompt.md b/.changeset/goal-blocked-audit-prompt.md new file mode 100644 index 000000000..4f498ddb1 --- /dev/null +++ b/.changeset/goal-blocked-audit-prompt.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Tighten goal-mode guidance for blocked and complete status updates. diff --git a/packages/agent-core/src/agent/injection/goal.ts b/packages/agent-core/src/agent/injection/goal.ts index 75f008bcf..b3af1dfce 100644 --- a/packages/agent-core/src/agent/injection/goal.ts +++ b/packages/agent-core/src/agent/injection/goal.ts @@ -147,14 +147,24 @@ function buildGoalReminder(goal: GoalSnapshot): string { 'after completing a useful slice, if material work remains, end the turn normally without ' + 'calling UpdateGoal so the runtime can continue the goal in the next turn. Call UpdateGoal ' + 'with `complete` only when all required work is done, any stated validation has passed, and ' + - 'there is no useful next action. Do not mark complete after only producing a plan, summary, ' + - 'first pass, or partial result. Before calling `complete`, check that every required part of ' + - 'the objective is done, completion criteria are satisfied, requested or expected validation ' + - 'passed or has been reported as impossible, and no known material task remains. Call ' + - 'UpdateGoal with `blocked` only for a genuine impasse: an external condition, required user ' + - 'input, missing credentials or permissions, a persistent technical failure, or an impossible, ' + - 'unsafe, or contradictory objective. Do not use `blocked` because the work is large, hard, ' + - 'slow, uncertain, partial, still needs validation, or needs more goal turns.', + 'there is no useful next action. Completion audit: before calling `complete`, verify the ' + + 'current state against the actual objective and every explicit requirement. Treat weak or ' + + 'indirect evidence as not complete. Do not mark complete after only producing a plan, ' + + 'summary, first pass, or partial result. Do not mark complete merely because a budget is ' + + 'nearly exhausted or you want to stop. Blocked audit: do not call UpdateGoal with `blocked` ' + + 'the first time you hit a blocker. Use `blocked` only for a genuine impasse: an external ' + + 'condition, required user input, missing credentials or permissions, or a persistent ' + + 'technical failure. For those non-terminal blockers, the same blocking condition must ' + + 'repeat for at least 3 consecutive goal turns before you call `blocked`, counting the ' + + 'original/user-triggered turn and automatic continuations. If a previously blocked goal is ' + + 'resumed, treat the resumed run as a fresh blocked audit. Exception: if the objective ' + + 'itself is impossible, unsafe, or contradictory, call UpdateGoal with `blocked` in the same ' + + 'turn; do not run more goal turns just to satisfy the audit. Do not use `blocked` because ' + + 'the work is large, hard, slow, uncertain, incomplete, still needs validation, would ' + + 'benefit from clarification, or needs more goal turns. Once the 3-turn threshold is met ' + + 'and you cannot make meaningful progress without user input or an external-state change, ' + + 'call UpdateGoal with `blocked`; do not keep reporting the blocker while leaving the goal ' + + 'active.', ); return lines.join('\n'); } diff --git a/packages/agent-core/src/agent/turn/index.ts b/packages/agent-core/src/agent/turn/index.ts index d77d81029..54bec107c 100644 --- a/packages/agent-core/src/agent/turn/index.ts +++ b/packages/agent-core/src/agent/turn/index.ts @@ -93,15 +93,23 @@ const GOAL_CONTINUATION_PROMPT = [ 'useful slice, if material work remains, end the turn normally without calling UpdateGoal so', 'the runtime can continue the goal in the next turn. Call UpdateGoal with `complete` only when', 'all required work is done, any stated validation has passed, and there is no useful next', - 'action. Do not mark complete after only producing a plan, summary, first pass, or partial', - 'result. Before calling `complete`, check that every required part of the objective is done,', - 'completion criteria are satisfied, requested or expected validation passed or has been', - 'reported as impossible, and no known material task remains. Call UpdateGoal with `blocked`', - 'only for a genuine impasse: an external condition, required user input, missing credentials', - 'or permissions, a persistent technical failure, or an impossible, unsafe, or contradictory', - 'objective. Do not use `blocked` because the work is large, hard, slow, uncertain, partial,', - 'still needs validation, or needs more goal turns. Do not ask the user for input unless a real', - 'blocker prevents progress.', + 'action. Completion audit: before calling `complete`, verify the current state against the', + 'actual objective and every explicit requirement. Treat weak or indirect evidence as not', + 'complete. Do not mark complete after only producing a plan, summary, first pass, or partial', + 'result. Do not mark complete merely because a budget is nearly exhausted or you want to stop.', + 'Blocked audit: do not call UpdateGoal with `blocked` the first time you hit a blocker. Use', + '`blocked` only for a genuine impasse: an external condition, required user input, missing', + 'credentials or permissions, or a persistent technical failure. For those non-terminal', + 'blockers, the same blocking condition must repeat for at least 3 consecutive goal turns before', + 'you call `blocked`, counting the original/user-triggered turn and automatic continuations.', + 'If a previously blocked goal is resumed, treat the resumed run as a fresh blocked audit.', + 'Exception: if the objective itself is impossible, unsafe, or contradictory, call UpdateGoal', + 'with `blocked` in the same turn; do not run more goal turns just to satisfy the audit. Do not', + 'use `blocked` because the work is large, hard, slow, uncertain, incomplete, still needs', + 'validation, would benefit from clarification, or needs more goal turns. Once the 3-turn', + 'threshold is met and you cannot make meaningful progress without user input or an', + 'external-state change, call UpdateGoal with `blocked`; do not keep reporting the blocker while', + 'leaving the goal active. Do not ask the user for input unless a real blocker prevents progress.', ].join(' '); export class TurnFlow { diff --git a/packages/agent-core/src/tools/builtin/goal/update-goal.md b/packages/agent-core/src/tools/builtin/goal/update-goal.md index 35f7d19c1..7b9aa26d8 100644 --- a/packages/agent-core/src/tools/builtin/goal/update-goal.md +++ b/packages/agent-core/src/tools/builtin/goal/update-goal.md @@ -1,7 +1,7 @@ Set the status of the current goal. This is how you resume, complete, or block an autonomous goal. - `active` — resume a paused or blocked goal when the user explicitly asks you to work on that goal. -- `complete` — the objective is satisfied and any stated validation has passed. The goal ends and a completion summary is recorded. -- `blocked` — a genuine impasse prevents useful progress: an external condition, required user input, missing credentials or permissions, a persistent technical failure, or an impossible, unsafe, or contradictory objective. The goal stops but can be resumed later. Do not use `blocked` because the work is large, hard, slow, uncertain, partial, still needs validation, or needs more goal turns. +- `complete` — the objective is satisfied and any stated validation has passed. The goal ends and a completion summary is recorded. Before using this, verify the current state against the actual objective and every explicit requirement. Treat weak or indirect evidence as not complete. Do not use `complete` merely because a budget is nearly exhausted or you want to stop. +- `blocked` — a genuine impasse prevents useful progress: an external condition, required user input, missing credentials or permissions, a persistent technical failure, or an impossible, unsafe, or contradictory objective. For non-terminal blockers, do not use `blocked` the first time you hit the blocker. The same blocking condition must repeat for at least 3 consecutive goal turns before you call `blocked`, counting the original/user-triggered turn and automatic continuations. If a previously blocked goal is resumed, treat the resumed run as a fresh blocked audit. If the objective itself is impossible, unsafe, or contradictory, call `blocked` in the same turn instead of running more goal turns. Do not use `blocked` because the work is large, hard, slow, uncertain, incomplete, still needs validation, would benefit from clarification, or needs more goal turns. Once the 3-turn threshold is met and you cannot make meaningful progress without user input or an external-state change, call `blocked` instead of leaving the goal active. -Most active goal turns should not call this tool. If you complete one useful slice of work and material work remains, end the turn normally without calling UpdateGoal; the runtime will prompt you to continue in the next goal turn. Call `complete` only when all required work is done, any stated validation has passed, and there is no useful next action. Before calling `complete`, check that every required part of the objective is done, completion criteria are satisfied, requested or expected validation passed or has been reported as impossible, and no known material task remains. Do not call `complete` after only producing a plan, summary, first pass, or partial result. If you call `blocked`, you will be prompted to explain the blocker in your next message. Setting the status is the machine-readable signal; the completion summary or blocker explanation is yours to write in the following message. +Most active goal turns should not call this tool. If you complete one useful slice of work and material work remains, end the turn normally without calling UpdateGoal; the runtime will prompt you to continue in the next goal turn. Call `complete` only when all required work is done, any stated validation has passed, and there is no useful next action. Do not call `complete` after only producing a plan, summary, first pass, or partial result. Call `blocked` only after the blocked audit threshold is met. If you call `blocked`, you will be prompted to explain the blocker in your next message. Setting the status is the machine-readable signal; the completion summary or blocker explanation is yours to write in the following message. diff --git a/packages/agent-core/src/tools/builtin/goal/update-goal.ts b/packages/agent-core/src/tools/builtin/goal/update-goal.ts index 29528cf60..8c4c90481 100644 --- a/packages/agent-core/src/tools/builtin/goal/update-goal.ts +++ b/packages/agent-core/src/tools/builtin/goal/update-goal.ts @@ -25,7 +25,9 @@ export const UpdateGoalToolInputSchema = z .object({ status: z .enum(['active', 'complete', 'blocked']) - .describe('The lifecycle status to set for the current goal.'), + .describe( + 'The lifecycle status to set for the current goal. Use `blocked` for impossible, unsafe, or contradictory objectives, or after the same non-terminal blocking condition repeats for at least 3 consecutive goal turns.', + ), }) .strict(); diff --git a/packages/agent-core/test/agent/injection/goal.test.ts b/packages/agent-core/test/agent/injection/goal.test.ts index ff02efb03..c5a395005 100644 --- a/packages/agent-core/test/agent/injection/goal.test.ts +++ b/packages/agent-core/test/agent/injection/goal.test.ts @@ -176,15 +176,27 @@ describe('GoalInjector content', () => { expect(text).toContain('Goal mode is iterative'); expect(text).toContain('one bounded, useful slice of work'); expect(text).toContain('end the turn normally without calling UpdateGoal'); + expect(text).toContain('Completion audit'); + expect(text).toContain('actual objective and every explicit requirement'); + expect(text).toContain('weak or indirect evidence'); expect(text).toContain('Do not mark complete after only producing a plan'); + expect(text).toContain('budget is nearly exhausted'); }); it('reserves blocked for genuine impasses rather than ordinary unfinished work', async () => { const store = makeStore(); await store.createGoal({ objective: 'finish the migration' }); const text = (await injectOnce(store))!; + expect(text).toContain('Blocked audit'); + expect(text).toContain('do not call UpdateGoal with `blocked` the first time'); expect(text).toContain('only for a genuine impasse'); expect(text).toContain('missing credentials or permissions'); + expect(text).toContain('3 consecutive goal turns'); + expect(text).toContain('fresh blocked audit'); + expect(text).toContain('Exception: if the objective itself is impossible, unsafe, or contradictory'); + expect(text).toContain('do not run more goal turns just to satisfy the audit'); + expect(text).toContain('would benefit from clarification'); + expect(text).toContain('do not keep reporting the blocker while leaving the goal active'); expect(text).toContain('needs more goal turns'); }); diff --git a/packages/agent-core/test/harness/goal-session.test.ts b/packages/agent-core/test/harness/goal-session.test.ts index 4db52cf07..e175e66ee 100644 --- a/packages/agent-core/test/harness/goal-session.test.ts +++ b/packages/agent-core/test/harness/goal-session.test.ts @@ -152,6 +152,12 @@ describe('goal session end-to-end', () => { expect(continuationHistory).toContain('Keep the self-audit brief'); expect(continuationHistory).toContain('do not run another goal turn'); expect(continuationHistory).toContain('end the turn normally without calling UpdateGoal'); + expect(continuationHistory).toContain('Completion audit'); + expect(continuationHistory).toContain('Blocked audit'); + expect(continuationHistory).toContain('3 consecutive goal turns'); + expect(continuationHistory).toContain('fresh blocked audit'); + expect(continuationHistory).toContain('impossible, unsafe, or contradictory'); + expect(continuationHistory).toContain('do not run more goal turns just to satisfy the audit'); expect(continuationHistory).toContain('only for a genuine impasse'); // Terminal UpdateGoal asks the model for one final user-facing summary. diff --git a/packages/agent-core/test/tools/goal.test.ts b/packages/agent-core/test/tools/goal.test.ts index 048ce9b7c..a065796dc 100644 --- a/packages/agent-core/test/tools/goal.test.ts +++ b/packages/agent-core/test/tools/goal.test.ts @@ -294,6 +294,10 @@ describe('UpdateGoalTool', () => { const description = new UpdateGoalTool(fakeAgent()).description.toLowerCase(); // Reserve blocked for genuine impasses, not ordinary unfinished work. expect(description).toContain('genuine impasse'); + expect(description).toContain('3 consecutive goal turns'); + expect(description).toContain('fresh blocked audit'); + expect(description).toContain('impossible, unsafe, or contradictory'); + expect(description).toContain('same turn instead of running more goal turns'); expect(description).toContain('hard, slow'); expect(description).toContain('needs more goal turns'); // UpdateGoal also injects the completion/blocked outcome prompt, so it does @@ -301,11 +305,22 @@ describe('UpdateGoalTool', () => { expect(description).not.toContain('only records the status'); }); + it('exposes the blocked-audit rule in the status parameter schema', () => { + const statusDescription = + ((new UpdateGoalTool(fakeAgent()).parameters as { + properties: Record; + }).properties['status']?.description) ?? ''; + expect(statusDescription).toContain('3 consecutive goal turns'); + expect(statusDescription).toContain('impossible, unsafe, or contradictory objectives'); + }); + it('discourages calling UpdateGoal after a non-terminal work slice', () => { const description = new UpdateGoalTool(fakeAgent()).description; expect(description).toContain('Most active goal turns should not call this tool'); expect(description).toContain('end the turn normally without calling UpdateGoal'); - expect(description).toContain('every required part of the objective is done'); + expect(description).toContain('actual objective and every explicit requirement'); + expect(description).toContain('weak or indirect evidence'); + expect(description).toContain('budget is nearly exhausted'); }); // Keep a capturing context here to prove terminal paths no longer append a