From 48a274685bbff0fd3ffc0c00e0e7c8906a8424bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=98=9F=E6=9C=88?= Date: Sat, 23 May 2026 22:58:09 +0800 Subject: [PATCH] fix(builtin): nudge budget + deadline warning - Nudge turns don't consume turn budget (up to MAX_NUDGES=3), prevents wasting agent work capacity on bookkeeping - Inject deadline warning when 3 turns remain, telling agent to wrap up - Agent can use status:failed to gracefully exit if it can't finish --- packages/workflow-agent-builtin/src/loop.ts | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/workflow-agent-builtin/src/loop.ts b/packages/workflow-agent-builtin/src/loop.ts index df0f411..3eef599 100644 --- a/packages/workflow-agent-builtin/src/loop.ts +++ b/packages/workflow-agent-builtin/src/loop.ts @@ -77,6 +77,9 @@ export type ShouldNudgeOptions = { maxTurns: number; }; +const MAX_NUDGES = 3; +const DEADLINE_WARNING_TURNS = 3; + export function shouldNudge({ noTools, text, turn, maxTurns }: ShouldNudgeOptions): boolean { return !noTools && !text.trimStart().startsWith("---") && turn < maxTurns - 1; } @@ -89,9 +92,26 @@ export async function runBuiltinLoop( const openAiTools = options.noTools ? [] : builtinToolsToOpenAi(getBuiltinTools()); let finalText = ""; let turnCount = 0; + let nudgeCount = 0; + let deadlineWarned = false; for (let turn = 0; turn < options.maxTurns; turn++) { log("8K2M4N7P", `builtin loop turn ${turn + 1}/${options.maxTurns}`); + + // Warn agent when approaching turn limit + const turnsRemaining = options.maxTurns - turn; + if (!options.noTools && !deadlineWarned && turnsRemaining <= DEADLINE_WARNING_TURNS) { + deadlineWarned = true; + log("4NRXW6KT", `${turnsRemaining} turns remaining, injecting deadline warning`); + messages.push({ + role: "user", + content: + `⚠️ You have ${turnsRemaining} turns remaining. ` + + "Wrap up your work and output the YAML frontmatter starting with `---`. " + + "If you cannot finish in time, output frontmatter with `status: failed` and describe what remains.", + }); + } + const response = await chatCompletionWithTools( options.provider, messages, @@ -119,11 +139,16 @@ export async function runBuiltinLoop( turnCount += 1; if (shouldNudge({ noTools: options.noTools, text, turn, maxTurns: options.maxTurns })) { - log("7FXQM2KN", "text-only turn without frontmatter, nudging LLM to continue"); + nudgeCount += 1; + log("7FXQM2KN", `text-only turn without frontmatter, nudge ${nudgeCount}/${MAX_NUDGES}`); const nudge = "You stopped calling tools but your response does not start with the required `---` YAML frontmatter. " + "Either continue using tools to complete your work, or output your final response starting with `---`."; messages.push({ role: "user", content: nudge }); + // Nudge doesn't consume turn budget (up to MAX_NUDGES) + if (nudgeCount <= MAX_NUDGES) { + turn -= 1; + } continue; }