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
This commit is contained in:
2026-05-23 22:58:09 +08:00
parent 5b68359dfc
commit 48a274685b
+26 -1
View File
@@ -77,6 +77,9 @@ export type ShouldNudgeOptions = {
maxTurns: number; maxTurns: number;
}; };
const MAX_NUDGES = 3;
const DEADLINE_WARNING_TURNS = 3;
export function shouldNudge({ noTools, text, turn, maxTurns }: ShouldNudgeOptions): boolean { export function shouldNudge({ noTools, text, turn, maxTurns }: ShouldNudgeOptions): boolean {
return !noTools && !text.trimStart().startsWith("---") && turn < maxTurns - 1; return !noTools && !text.trimStart().startsWith("---") && turn < maxTurns - 1;
} }
@@ -89,9 +92,26 @@ export async function runBuiltinLoop(
const openAiTools = options.noTools ? [] : builtinToolsToOpenAi(getBuiltinTools()); const openAiTools = options.noTools ? [] : builtinToolsToOpenAi(getBuiltinTools());
let finalText = ""; let finalText = "";
let turnCount = 0; let turnCount = 0;
let nudgeCount = 0;
let deadlineWarned = false;
for (let turn = 0; turn < options.maxTurns; turn++) { for (let turn = 0; turn < options.maxTurns; turn++) {
log("8K2M4N7P", `builtin loop turn ${turn + 1}/${options.maxTurns}`); 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( const response = await chatCompletionWithTools(
options.provider, options.provider,
messages, messages,
@@ -119,11 +139,16 @@ export async function runBuiltinLoop(
turnCount += 1; turnCount += 1;
if (shouldNudge({ noTools: options.noTools, text, turn, maxTurns: options.maxTurns })) { 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 = const nudge =
"You stopped calling tools but your response does not start with the required `---` YAML frontmatter. " + "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 `---`."; "Either continue using tools to complete your work, or output your final response starting with `---`.";
messages.push({ role: "user", content: nudge }); messages.push({ role: "user", content: nudge });
// Nudge doesn't consume turn budget (up to MAX_NUDGES)
if (nudgeCount <= MAX_NUDGES) {
turn -= 1;
}
continue; continue;
} }