Repository navigation
fix(session): end at the step cap with a toolless answer, and report empty task results - #1632
RaghwendraSingh wants to merge 1 commit into
Conversation
…empty task results - At an agent's `steps` cap the max-steps instruction was sent as a trailing assistant message with every tool still offered. The Altimate gateway rejected that request, so the session ended with no answer. It is now a user turn with no tools and toolChoice "none", the same toolless shape the answer echo, compaction and title requests use. - The cap counts model requests only. Loop iterations that ran a compaction or a subtask and continued were counted too, so capped agents stopped early. - A child task that ends on an error or without text no longer reaches the parent as an empty <task_result>: the result says why it stopped and carries the child's last written text when there is one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
👋 This PR was automatically closed by our quality checks. Common reasons:
If you believe this was a mistake, please open an issue explaining your intended contribution and a maintainer will help you. |
Issue for this PR
No GitHub issue yet.
Type of change
What does this PR do?
An agent with a
stepscap is meant to end with a text answer. On the Altimate gateway it ended with no answer instead, and a capped subagent handed its parent an empty result.1. The request at the cap failed (
session/prompt.ts).At step N the loop appended
MAX_STEPSas a trailing assistant message and still sent every tool, with notoolChoice. On the gateway that request failed withUnknownError "Could not get a response from the agent"and 0 tokens, so the session ended without an answer. In our headless runs these were the only two gateway errors in about 1,000 requests, and the only two requests sent at the cap. The answer-echo path a few lines above already notes that a conversation must end on a user turn.Fix: at the cap the instruction is a user turn, sent with no tools and
toolChoice: "none", the same toolless shape the answer echo, compaction and title requests use;llm.tsthen flattens historical tool calls, which the gateway requires for a toolless request. Ajson_schemarequest keeps its tools at the cap, because its answer is theStructuredOutputtool call.2. The cap counted iterations, not requests (
session/prompt.ts).stepalso increments on loop iterations that run a compaction or a subtask andcontinue. A capped agent that compacted twice reached its cap several requests early. The cap now uses a separatemodelStepscounter that advances only on the normal model-call path.stepis unchanged for everything else that reads it (title, telemetry, plan-mode checks).3. An errored or silent child returned an empty result (
tool/task.ts,session/task-output.ts).The parent received only the child's last text part, so a child that failed at its cap produced
<task_result>\n\n</task_result>with statuscompleted, which reads as "done, nothing found". Now, when the child produced no final text, the result says why ("Task stopped with an error before giving a final answer (UnknownError: …)" or "Task ended without a final text answer") and carries the child's last written text if there is one. The<task_result>tag itself is unchanged, sincecli/cmd/run/tool.tsparses it.Not in this PR:
task_id. Making it persist per session would also cap interactive sessions for good after N steps in total, so that belongs with a per-run budget that counts child sessions.run --max-turns: passing the remaining turns into the loop would make the last allowed turn the toolless answer, but it changes the documented--max-turnscontract (N work turns, then an abort and one toolless finalization reply, exit 1) thatbudget-finalization.test.tspins, so it is left for a separate change.How did you verify your code works?
prompt.test.tscases (the step cap asks for a final text answer …), plain and with a usage-triggered compaction, on an OpenAI-compatible mock: the last request ends with a user message carryingMAXIMUM STEPS REACHEDand declares no tools; no earlier request carries it; with a compaction the agent still gets its full step count. Both fail onmain(roleassistant; one request short with the compaction).test/tool/task-empty-result.test.ts: an errored child with earlier text, a child with no text, and a normal answer. The first two fail onmain.bun test test/session test/tool test/agent: 2,131 pass, 0 fail.test/cli: the same 2--time-budgetfailures injson-output.test.tsasmain.budget-finalization.test.ts: 46/46. Anapply_patchcase timed out once under load and passes alone (27/27).script/check-tracker-leaks.tsandanalyze.ts --markers --strictpass.Cost side effects
None added. The toolless final request sends no tool schemas, so it does not reuse the cached tools prefix; it is one request per capped session, and it replaces a request that failed and a parent turn that continued without the child's findings.
Screenshots / recordings
N/A
Checklist
🤖 Generated with Claude Code
Summary by cubic
Fixes agent sessions at the
stepscap so they end with a toolless text answer instead of failing with no answer, and makes capped or errored child tasks report why they stopped instead of returning an empty result.Sections
json_schemarequest keeps its tools at the cap because its answer is theStructuredOutputtool call.task_id; a per-run budget that counts child sessions is left for a separate change, as is adjusting--max-turnsto make the last allowed turn the toolless answer.Written for commit a674b86. Summary will update on new commits.