From 6f55348d38e0f9210413b25a862e30ea794bf193 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Fri, 7 Aug 2026 17:14:56 +0200 Subject: [PATCH] feat: add 50% step budget warning before exhaustion Nudge the agent at 50% of step budget to encourage taking action rather than looping on research. The warning explains that action tools (file_write, file_edit, shell) reset the counter. --- agent/loop.go | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/agent/loop.go b/agent/loop.go index f6366d5..f102280 100644 --- a/agent/loop.go +++ b/agent/loop.go @@ -667,17 +667,24 @@ func run(ctx context.Context, cfg agentConfig, state state) error { } } - // Soft step-budget guardrail: when MaxSteps is set and the budget is - // exhausted, nudge the model to wrap up and exit cleanly. This is not - // a hard abort — the model gets one final turn without tools to emit - // a summary or hand-off message. - if cfg.MaxSteps > 0 && step >= cfg.MaxSteps-1 { - emit(cfg, Event{Role: "maxsteps", Content: fmt.Sprintf("%d", step+1)}) - state.update(backend.Message{ - Role: "user", - Content: fmt.Sprintf("[system: step budget exhausted (%d/%d steps used). Stop calling toolsrv. Summarize what you have done and what remains, then stop.]", step+1, cfg.MaxSteps), - }, nil) - break + // Step budget warnings: nudge the agent at 50% and when exhausted. + // These only fire for research-heavy loops — action tools (file_write, + // file_edit, shell) reset the counter, so active progress never triggers. + if cfg.MaxSteps > 0 { + halfBudget := cfg.MaxSteps / 2 + if step == halfBudget { + state.update(backend.Message{ + Role: "user", + Content: fmt.Sprintf("[system: you have used %d/%d research steps without taking action. Consider making progress — write code, edit files, or run commands. Action tools reset this counter.]", step, cfg.MaxSteps), + }, nil) + } else if step >= cfg.MaxSteps-1 { + emit(cfg, Event{Role: "maxsteps", Content: fmt.Sprintf("%d", step+1)}) + state.update(backend.Message{ + Role: "user", + Content: fmt.Sprintf("[system: step budget exhausted (%d/%d steps used). Stop calling tools. Summarize what you have done and what remains, then stop.]", step+1, cfg.MaxSteps), + }, nil) + break + } } // Periodic task state re-injection: every N tool rounds, surface the