Repository navigation
Description
Problem or Motivation
Three stops (#2554, #2579 and #3318) had class parser (XML syntax error ... unexpected EOF) and cost 1.77M input tokens. The upstream report ollama/ollama PR 18571 says the failure is deterministic for one conversation. _turn sends the same conversation again with no new text (CONST_ROADMAP_IMPLEMENT_MODEL_RESENDS is 1), so the resend cannot help. Split from #3053.
Proposed Solution & Architectural Design
- On class
parser, the resend carries one message that asks for a smaller edit. - A second
parserfailure stops the step with classparser. The work of the group stays aswip:commits. - Acceptance: a stub model that fails twice with a parser error stops the step after one smaller-edit message; the message is in the second request.
Target Subsystem
src/devops_cli/roadmap/implement_step.py (_run_agent, _resend_after).
Existing Tool Considered
No open tool covers a smaller-edit message for a parser error of a provider. The project builds it: about 30000 tokens to build, 2000 each quarter to keep, 6000 to fix one miss, about 2 hours.
Source: #3053
Proposed design
Problem statement
A parser error of a provider is deterministic for one conversation. The step resends a failed request with one message that asks for a smaller edit. A second parser failure must stop the step with class parser and keep the wip: commits. The tree at this commit still moves a parser failure to the next tier, so the step can continue instead of stopping.
Acceptance criteria
Requirement: Parser resend asks for a smaller edit
On a parser failure of a model call, the step SHALL send the failed request again with one message that asks for a smaller edit.
Scenario: The second request holds the smaller-edit message
- WHEN the stub model fails the first request with a parser error and answers the second request
- THEN the second request ends with the
implement_parser_smaller_edittext, and the step made two requests
Requirement: A second parser failure stops the step
When the request that carries the smaller-edit message fails with a parser error, the step SHALL stop with class parser and move no tier.
Scenario: Two parser failures stop the step on the first tier
- WHEN the stub model fails both requests with a parser error and the ladder names two tiers
- THEN the outcome is stopped with
failure_classparser, the step made two requests, andtiersnames only the first tier
Requirement: A parser stop keeps the wip commits
A step that stops with class parser SHALL keep the wip: commits of its group and discard only the work after the last one.
Scenario: The checkout is at the last wip commit
- WHEN the agent ticked a task whose verification passed and the stub model then failed with a parser error twice
- THEN the checkout is at the last
wip:commit, and the stop reports the discarded diff
Requirement: Other in-stream failures keep the plain resend
A failure that is not of class parser SHALL send the request again with the same messages and no new text.
Scenario: An in-stream 400 that is not a parser error adds no message
- WHEN the stub model fails the first request with an in-stream 400 that the classifier does not read as
parser - THEN the second request holds the same messages as the first, and no smaller-edit text
Existing Tool Considered
No alternatives check has run. No open, maintained tool or library covers a smaller-edit message for a provider parser error. The project builds it: about 30,000 tokens to build, 2,000 each quarter to keep, 6,000 to fix one miss, about 2 hours. The tree already holds the message and the resend, so the remaining change is small.
Key questions
- Which failure class gets the smaller-edit message?: Only class
parser._run_turnsets the class with_run_class, which callsclassify_failureon the reason. The parser rule comes before the provider rule, because an Ollama that cannot parse a tool call answers with HTTP 400._run_agentreads the class inrun.failure_class. [sources: src/devops_cli/roadmap/implement_step.py, src/devops_cli/roadmap/failure.py] - How does the smaller-edit message reach the second request?:
_ask_smaller_editappends oneUserPromptPartwithMESSAGES.roadmap.implement_parser_smaller_editto the lastModelRequestof the resend conversation._run_agentthen calls_run_turnwithNone, so the second request ends with that text. (best practice) [sources: src/devops_cli/roadmap/implement_step.py]- Chosen: Append a
UserPromptPartin_ask_smaller_edit. It adds one part to the request that_resend_afterreturns, and_run_turnalready resumes a conversation. - Rejected: Pass the text as the
messageof_run_turn. It starts a new user turn, not a resend of the failed request. - Rejected: Append a whole
ModelRequestto the conversation. It builds a message by hand and puts the text outside the failed request.
- Chosen: Append a
- Where does the text of the smaller-edit message live?: In
MESSAGES.roadmap.implement_parser_smaller_edit, besideimplement_repair_turn. It asks for one file or one small part of a file in each tool call, and it is bounded. (best practice) [sources: src/devops_cli/lang/en/messages.py, src/devops_cli/roadmap/implement_step.py]- Chosen: Add
implement_parser_smaller_edittoMESSAGES.roadmap. The step keeps its texts inMESSAGES.roadmap, and the text says only what a parser failure needs. - Rejected: Reuse
implement_repair_turn. It names a failed check and the paths of a repair turn, and a parser failure has neither.
- Chosen: Add
- How many sends does one parser failure get, and do they count in the limits?: One.
CONST_ROADMAP_IMPLEMENT_MODEL_RESENDSstays 1._resend_afteradds the failed request and the replies before it to the usage of the conversation, so the request limit counts every send. [sources: src/devops_cli/config/constants.py, src/devops_cli/roadmap/implement_step.py] - Does a parser failure try the next tier of the ladder?: No. A second parser failure must stop the step with class
parserand must not run the next tier._moves_tiermust return false for a parser failure, even whenprovider_erroris set, because the parser failure is deterministic for one conversation and another tier would get the same conversation. (best practice) [sources: src/devops_cli/roadmap/failure.py, src/devops_cli/roadmap/implement_step.py, openspec/changes/3060-a-model-ladder-for-retries-with/design.md]- Chosen: Stop on
parserand move no tier. The item states the stop, and the work of the group stays aswip:commits. This overrides theCHANGE_TIERdefault of feat(roadmap): a model ladder for retries, with the tier chosen per attempt #3060 for this class. - Rejected: Keep
change_tierforparser. A tier move continues the step with the same conversation, which fails the same way.
- Chosen: Stop on
- What delivers this item, and what happens when that pull request merges?: The item delivers with its own pull request. The pull request changes
_moves_tierso a parser failure moves no tier, and adds the regression tests. The tree at this commit already holds_ask_smaller_editandimplement_parser_smaller_edit. When the pull request merges, it closes feat(roadmap): a parser failure gets one smaller-edit message #3571, and no separate close is needed. (best practice) [sources: src/devops_cli/roadmap/implement_step.py, src/devops_cli/lang/en/messages.py]- Chosen: Deliver with its own pull request that stops the tier move. The smaller-edit resend is in the tree, but
_moves_tierstill moves a parser failure to the next tier. - Rejected: Wait on feat(roadmap): every step repairs before it gives up, in bounded rounds #3053 and close as delivered. The tree already holds the feat(roadmap): every step repairs before it gives up, in bounded rounds #3053 behavior, and the remaining
_moves_tierdefect needs a change.
- Chosen: Deliver with its own pull request that stops the tier move. The smaller-edit resend is in the tree, but
Fit
Fits in one pull request.
Suspected block
None.
Dependencies
None.
Activity
dan-petty commentedon Nov 5, 2026
Intake placed this item to the backlog: intake puts every new item in the backlog. A person plans the milestones.
featprefix of the title gives the type.(roadmap)scope of the title gives the scope.It reduces token waste and improves step reliability but does not block a release.It saves tokens and makes the implement step recover from one parser miss.The body states about 2 hours of work in one file with two functions.Both handle parser failures in the implement step, but #2922 asks to show the cause of the stop while the candidate asks to change the resend to a smaller-edit message.feat(roadmap): a step stops at its budgets and records attempts (#3580)
dan-petty commentedon Nov 6, 2026
The work of this issue is built on the branch of #3053 (change
3053-parser-smaller-edit): the resend after aparserfailure carries one message that asks for a smaller edit, and the second parser failure of a step stops it with classparser. The pull request closes #3053, not this issue. Close this issue as delivered when that pull request merges.devops-cli-service commentedon Nov 6, 2026
Refine cannot set this item to Ready at 29309f0 until a person answers the questions below. Reply in a comment or edit the body, and the next refine round looks at the item again.
devops-cli-service commentedon Nov 6, 2026
Status set to Ready at 8714474 (see proposed design in issue body).
dan-petty commentedon Nov 6, 2026
Checked on origin/main c714551. The item cannot be built as written, and most of it exists.
Delivered already (PR 3737, change 3053-parser-smaller-edit):
_run_agentand_ask_smaller_editsend the request again with MESSAGES.roadmap.implement_parser_smaller_edit on class parser. A second parser failure on a tier ends the turn with class parser. The wip: commits stay. Tests: tests/roadmap/test_roadmap_implement_step_parser.py. Three of the four requirements in the refined design hold now.The one open part is "_moves_tier must return false for parser". Its reason is false. The refined design says a tier move "continues the step with the same conversation, which fails the same way". The code does not do that.
run_stepbuilds a new agent for each tier withbuild_agent(tier_model, ...)and starts it with_turn(agent, tier_model, log, inputs.message), with no conversation. A new tier is a new conversation on another model. The upstream report (ollama PR 18571) says the failure is deterministic for one conversation, not for every model.The change would also undo a settled decision.
ACTION_BY_CLASSmaps parser to CHANGE_TIER, and the design of change 3060 says why: a model family with a tool-call parser bug repeats the same error, so a tier move fixes a model defect and spends no repair round. Change 3053 kept this on purpose ("the ladder decides after that"), and docs/roadmap/implement.md states it. The default ladder holds one tier, so the default step already stops with class parser after the one smaller-edit message.To follow the issue, the code must make parser stop on every ladder. That removes the tier move that #3060 added for the failure it was made for, and it makes ACTION_BY_CLASS and the stop-comment lines (
failure_lines) disagree with the step. I wrote no code. If the owner wants a stop on parser for every ladder, the work is to change the action of parser in ACTION_BY_CLASS and to decide what the job does after the stop. That is a decision for a new issue.dan-petty commentedon Nov 6, 2026
Closed as not planned: #3053 (PR #3737) already delivers it, and the one open part would undo a decision. Checked on origin/main f1130d3 on 2026-11-06.
src/devops_cli/roadmap/implement_step.py:_ask_smaller_editaddsMESSAGES.roadmap.implement_parser_smaller_editfor the classparser, and a second parser failure on a tier ends the turn with that class._moves_tiermust return false forparser. Inrun_stepeach tier builds a new agent and starts a new conversation, and it resetslog.parser_failures. The upstream report says the failure is deterministic for one conversation, not for every model.failure.pymapsPARSERtoCHANGE_TIERon purpose (change 3060). Following the issue would makeACTION_BY_CLASSdisagree with the step.If a person wants a parser failure to stop the step on every ladder, file a new issue for that decision.