Skip to content

feat(roadmap): every step repairs before it gives up, in bounded rounds #3053

Closed
3 of 7 issues completed

Description

@dan-petty

What happened

A step that fails stops the item: one more turn after a ruff or mypy error, then "all your work is lost". Examples: #2175 stopped on its own scratch file; XML tool-call parse errors (#2953) end a step; a lost test ends a step. The agent has no way to try another approach.

What should happen

  • A step runs in attempts. On a failure the agent gets the failure class, the evidence (command output, trace) and a diagnosis hint, and tries again. After round 1 the strategy changes: more context from the repo map, a smaller step (split the group), a different model tier (D4).
  • The number of rounds, tokens and time per step and per item are limits in config. Each attempt is in the trace. Only an exhausted budget, or a failure class that no attempt can fix (a false premise), stops the item.

Acceptance criteria

  • A replay with a failing check passes on the second attempt and the trace shows both.
  • The rounds, tokens and time limits are settings with defaults, and each stop names the limit that ended it.
  • A failure of class plan defect or premise false stops at once with the evidence (F1).
  • No attempt repeats a previous failed action unchanged (a test with a stub model).

Related: tracker for this set (filed with it), #2908 (issue workflow), #2867 (draft PR evaluator), #3005 (task kind), #3010 to #3015 (plan gates), #2919 (stuck-item metrics), #2953 (Ollama parser).

Delivery

This issue is delivered in parts. Each part has its own change folder. The pull request of the last part to merge closes this issue.

Alternatives check of 2026-11-04: partly.
Tool: pydantic/pydantic-ai (https://github.com/pydantic/pydantic-ai), MIT, last activity 2026-11-04, maintained.

Proposed design

Problem statement

A step that fails stops the whole item after one more turn. The agent cannot try another approach. The item loses the work of the step. This change gives each step bounded repair rounds. Each round carries the failure class, the evidence and a diagnosis hint. The rounds, token and time limits are settings, and each stop names the limit that ended it.

Acceptance criteria

Requirement: Repair on the second attempt

A step SHALL retry a failed check within its round budget and pass on the second attempt.

Scenario: Failing check passes on the second attempt

  • WHEN A replay runs a step whose first attempt fails a check and whose second attempt passes.
  • THEN The step succeeds and the trace holds one entry for each attempt with its class and evidence.

Requirement: Limits are settings with defaults

The round, token and time limits of a step and of an item SHALL be settings with defaults.

Scenario: Token budget ends a step

  • WHEN A step uses more input tokens than roadmap.implement.step_token_budget.
  • THEN The step stops and the stop comment names that setting and its value.

Requirement: Fatal classes stop at once

A failure of class plan defect or premise false SHALL stop the item at once with its evidence.

Scenario: A false premise stops before a retry

  • WHEN The first attempt of a step fails with class premise false.
  • THEN The item stops with the evidence and the trace holds no second attempt.

Requirement: No repeated failed action

A repair attempt MUST NOT repeat a previous failed action unchanged.

Scenario: A stub model repeats its tool call

  • WHEN A stub model sends the same tool call with the same arguments as a failed earlier attempt.
  • THEN The executor refuses the call and returns the earlier failure and its diagnosis hint to the model.

Requirement: Strategy change between rounds

Each attempt after a failure SHALL change the strategy of the step, as the round and the failure class decide.

Scenario: The second attempt widens the context

  • WHEN The first attempt of a step fails with class test.
  • THEN The second attempt carries more repo-map context and the trace records the strategy of each round.

Requirement: Parser failure gets a smaller edit

A parser failure SHALL add one message that asks for a smaller edit, and a second parser failure MUST stop the step.

Scenario: Two parser errors stop the step

  • WHEN Two attempts of one step fail with class parser.
  • THEN The step stops with class parser and the work of the group stays.

Requirement: Moved HEAD is reset

A step whose failure says the agent moved HEAD SHALL reset the branch to the start commit with the changes kept.

Scenario: The agent commits during a step

  • WHEN A step fails with class ci_infra and the text The agent moved HEAD.
  • THEN The job runs git reset --soft and the next attempt starts from the start commit with the changes in the worktree.

Requirement: No-op group is a plan defect

A task group that changes no file SHALL be recorded as class plan defect and returned for a smaller replacement.

Scenario: One no-op group and one real group

  • WHEN A step runs one group that changes no file and one group that changes a file.
  • THEN The trace records class plan defect for the no-op group only and the next round asks for a smaller replacement of that group.

Requirement: One-tier ladder names its setting

A change_tier action on a ladder with one tier SHALL name roadmap.implement.ladder in the stop comment.

Scenario: Tier change with one tier configured

  • WHEN A step reaches the tier round and roadmap.implement.ladder holds one tier.
  • THEN The stop comment names roadmap.implement.ladder and its value.

Existing Tool Considered

pydantic/pydantic-ai (MIT, last activity 2026-11-04, maintained) covers bounded attempts, failure feedback with ModelRetry, usage budgets with a named limit, and a message history that keeps each attempt. It lacks a failure-class taxonomy, a strategy change between rounds, per-step and per-item round and time budgets with a named stop, and a guard against a repeated failed action. The project builds these missing parts: about 260000 tokens to build, about 120000 tokens to keep and fix in the first three months, and about 4 working days.

Key questions

  • Which failure classes does a step distinguish, and which of them stop the item at once?: A step records one class for each failure: plan defect, premise false, parser, test, typecheck, lint, commit and ci_infra. Class plan defect and class premise false stop the item at once with the evidence. The other classes get repair rounds. (best practice) [sources: src/devops_cli/roadmap/failure.py, tests/roadmap/test_roadmap_failure.py]
    • Chosen: Class from the failing check and the failure text. The check name gives test, typecheck and lint; the failure text gives plan defect, premise false, parser and ci_infra.
    • Rejected: Two classes only, fatal and repairable. The strategy change and the stop message need the finer class.
    • Rejected: Class from a model call. A model call adds cost and gives a different answer on equal input.
  • What are the default round, token and time limits for a step and for an item?: roadmap.implement.repair_rounds defaults to 3 attempts for each step. roadmap.implement.step_token_budget defaults to 300000 input tokens. roadmap.implement.item_token_budget defaults to 2000000 input tokens. roadmap.implement.step_time_limit defaults to 30 minutes. roadmap.implement.item_time_limit defaults to 4 hours. A person can change each setting in .github/roadmap.toml. (best practice) [sources: src/devops_cli/config/defaults.py, src/devops_cli/config/settings.py, src/devops_cli/config/constants.py]
    • Chosen: 3 rounds, 300000 tokens and 30 minutes for each step. The 39 stops in the evaluation cost about 45000 input tokens each; 300000 allows repairs and still stops a runaway step.
    • Rejected: One repair turn, as today. One turn cannot change the strategy and fails the second-attempt criterion.
    • Rejected: Rounds only, with no token or time limit. A step that repeats a large prompt can exhaust the item budget with no named stop.
  • How does the strategy change after the first failed attempt?: Attempt 1 runs the step as planned. A failed attempt gives the next attempt the class, the evidence and a diagnosis hint. Attempt 2 adds context from the repo map and, when the failure names a group or the inputs exceed the step budget, asks for a smaller group. Attempt 3 changes the model tier when roadmap.implement.ladder holds more than one tier; otherwise the stop names that setting. (best practice) [sources: src/devops_cli/roadmap/implement_step.py, tests/roadmap/test_roadmap_implement_step_repair.py]
    • Chosen: A fixed ladder: context, then smaller group, then tier. It is predictable, it changes the input in each round, and a stop names the round that ended.
    • Rejected: Class-driven strategy only. Two failures of the same class would repeat the same strategy.
    • Rejected: Tier change first. A tier change costs more and does not fix a group that is too large.
  • How does the loop stop an attempt from repeating a failed action unchanged?: The step keeps a digest of each failed tool call: the tool name and its normalized arguments. The executor refuses a call whose digest is in the set. It returns the earlier failure and its diagnosis hint to the model as a new turn. (best practice) [sources: src/devops_cli/roadmap/runner_tools.py, tests/roadmap/test_roadmap_runner_tools.py]
    • Chosen: A digest guard in the tool executor. The executor sees every call and can refuse one before it runs.
    • Rejected: A prompt instruction only. A model can ignore the instruction, and the stub-model test needs a hard refusal.
    • Rejected: ModelRetry alone. ModelRetry returns a message, but it does not record the failed actions of earlier rounds.
  • What does a parser failure class do in the repair loop?: The failure is deterministic for one conversation. The next attempt carries one message that asks for a smaller edit. A second parser failure stops the step with class parser, and the work of the group stays. (best practice) [sources: src/devops_cli/roadmap/implement_step.py, tests/roadmap/test_roadmap_implement_step.py]
    • Chosen: One smaller-edit message, then stop. A resend of the same conversation cannot change a deterministic parse error.
    • Rejected: Resend the same conversation. _turn does this today, and the upstream report says the error repeats.
    • Rejected: Change the model tier on the first parser error. A tier change is the third round, and the parse error needs a smaller edit first.
  • What repair does a step get when the failure says the agent moved HEAD?: The job runs git reset --soft to move the branch back to the start commit and keeps the changes in the worktree. The next attempt runs the checks on that result. (best practice) [sources: src/devops_cli/roadmap/implement_step.py, src/devops_cli/roadmap/runner_tools.py, tests/roadmap/test_roadmap_implement_shell_policy.py]
    • Chosen: git reset --soft to the start commit. It keeps the work and puts the branch where the checks expect it.
    • Rejected: Refuse git commands in the step shell. Refused commands are the largest stop class, and feat(roadmap): run the implement agent shell in a real sandbox #2695 records the sandbox decision.
    • Rejected: Discard the work of the step. The work is valid; only the branch position is wrong.
  • What happens when a task group changes no file?: The step records class plan defect for the group and returns it for a smaller replacement within the round budget. The stop names the group. The owner decision of 2026-11-02 applies: a plan defect stops at once unless the bounded amendment covers it. (best practice) [sources: src/devops_cli/roadmap/implement_step.py, tests/roadmap/test_roadmap_implement_step.py]
    • Chosen: Plan defect with a smaller replacement inside the budget. It follows the owner decision and keeps the work of the group.
    • Rejected: Stop the item at once. A group that is too large is not a false premise, and the bounded amendment covers it.
    • Rejected: Mark the group ticked. The plan did not do its work, and a tick would hide it.
  • What does each attempt record, and what does a stop name?: The trace holds one entry for each attempt with its class, its evidence and the strategy of the round. A stop names the limit that ended it and its value, for example roadmap.implement.repair_rounds=3. (best practice) [sources: tests/roadmap/test_roadmap_implement_trace.py, src/devops_cli/roadmap/failure.py]
    • Chosen: One trace entry for each attempt; the stop names the limit and its value. The replay test can check both attempts, and a person sees the setting to change.
    • Rejected: One trace entry for the step. The second-attempt criterion needs both attempts in the trace.
    • Rejected: The stop names the class only. The criterion requires the limit that ended it.

Fit

Fits in one pull request.

Suspected block

None.

Dependencies

None.

Activity

  1. added
    type/featureNew user-facing functionality or capability addition
    scope/roadmapThe roadmap jobs: intake, refine, stage, implement and the Service that runs them
    status/newStatus New: the item is not ready yet
    value/highValue High: the delivery of the item matters much
    effort/highEffort High: much work, in one pull request
    on Nov 1, 2026
  2. dan-petty commented on Nov 1, 2026

    @dan-petty
    OwnerAuthor

    Intake placed this item to the backlog: intake puts every new item in the backlog. A person plans the milestones.

    • Type: type/feature. The feat prefix of the title gives the type.
    • Scope: scope/roadmap. The (roadmap) scope of the title gives the scope.
    • Priority: P2-Medium. It prevents loss of completed work on transient failures, but the pipeline still completes single-try steps today.
    • Value: High. Without it every transient failure discards all prior work in a step, which blocks most non-trivial items.
    • Effort: High. It needs a failure-class taxonomy, per-round strategy change, configurable budgets, trace recording, and a stub-model test.
  3. added
    priority/p2-mediumStandard priority feature work and architectural improvements
    on Nov 1, 2026
  4. dan-petty commented on Nov 1, 2026

    @dan-petty
    OwnerAuthor

    Live evidence (2026-11-01, 13 stops): every failure ended the item at once. Causes that a second round could have fixed with the information the step already had:

  5. dan-petty commented on Nov 1, 2026

    @dan-petty
    OwnerAuthor

    The owner asks (2026-11-01) that the job identify and handle partial implementations, to fix them or iterate on them, instead of discarding them and blocking the issue. The issue #3098 holds the design: keep the pushed work, classify the state, continue from the branch with a repair step for the class, block last. It builds on this issue (repair rounds), #3054 (keep work and resume) and #3065 (failure classes).

  6. devops-cli-service commented on Nov 2, 2026

    @devops-cli-service
    Contributor

    Refine cannot set this item to Ready at a588893 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.

    • Which failure classes stop the item immediately without further attempts? (missing verified source)
    • What is D4, the model tier used for strategy escalation? (missing verified source)
  7. dan-petty commented on Nov 2, 2026

    @dan-petty
    OwnerAuthor

    Decision of the owner (2026-11-02), recorded to settle a conflict with plan #3057 (PR #3184):

    Acceptance criterion 3 of this issue reads: "A failure of class plan defect or premise false stops at once with the evidence (F1)". Read it this way:

    • premise false stops at once with the evidence, and the item returns to New (as the plan for feat(roadmap): the implement agent can amend the plan when the plan is wrong #3057 also says). No attempt can fix a false premise.
    • plan defect stops at once with the evidence unless the bounded plan amendment of feat(roadmap): the implement agent can amend the plan when the plan is wrong #3057 covers it. The amendment may only change the text of an unticked group in tasks.md (a path to a tracked file with the same name, a command that keeps the same paths, a task text) and add a bullet under ## Amendments. A defect that would change a spec requirement, ## Criteria, proposal.md, design.md or a ticked task is not covered: it stops with a question for a person.
    • The repair rounds of this issue do not repeat an amendment: an amendment is one repair, counted in the same round and token limits.

    Why: 34 of 37 plans reviewed had defects, and most were a wrong path, a missing task or a wrong verify command. Stopping at once on each one wastes the work of the plan. The class plan defect that no amendment covers still stops at once, so no threshold is lowered.

    Related: #3057, #3098, PR #3184.


    Generated by Claude Code

  8. devops-cli-service commented on Nov 2, 2026

    @devops-cli-service
    Contributor

    Refine cannot set this item to Ready at 3f3103a 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.

    • The proposal has no problem statement, no acceptance criterion with its requirement, scenario, WHEN and THEN, or no answer to the existing-tool question. State the problem, the requirements with their scenarios, and the open tool that covers the need or the cost of building it.
  9. devops-cli-service commented on Nov 4, 2026

    @devops-cli-service
    Contributor

    Alternatives check: partly

    Tool: pydantic/pydantic-ai (https://github.com/pydantic/pydantic-ai)

    • License: MIT
    • Last activity: 2026-11-04
    • Maintained: yes

    Covers:

    • Bounded attempts: per-agent and per-run retry budgets for tool calls and output (retries={'tools': N, 'output': M}, default 1), settable in code and per run.
    • Failure feedback to the model: a tool or output validator raising ModelRetry returns the failure and its message to the agent for the next attempt, and the retry keeps its own tool_call_id in the message history.
    • Configurable usage budgets with defaults: UsageLimits caps requests (default 50), input/output tokens, tool calls and cost, plus per_request_input_tokens_limit.
    • Each stop names the limit: UsageLimitExceeded reports which limit was exceeded and its value, and UnexpectedModelBehavior marks an exhausted retry budget.
    • Attempts stay in the run's message history, and a failed run's messages can be recovered and continued instead of discarded.
    • Model fallback and durable-execution integrations give a place to escalate a model tier or persist a run across rounds.

    Lacks:

    • No failure-class taxonomy: nothing like plan defect or premise false that stops the item at once, and no classification of the failure into classes for the next round.
    • No strategy change between rounds: it cannot split a step into a smaller group, widen repo-map context, or pick a model tier as an escalation policy (fallback models exist, but no per-round escalation rule).
    • No per-step or per-item round/time budget with a named stop: the limits are per run and cover requests/tokens/cost, not wall-clock time or repair rounds of a step.
    • No guard that forbids an attempt from repeating a previously failed action unchanged; that would have to be built on top.
    • It is an agent framework, not a repair loop: the diagnosis hint, evidence bundle and stop/continue policy per failure class would be custom code around it.

    Searches run:

    • open source library agent step retry with failure diagnosis and bounded attempts trace
    • LangGraph node retry policy checkpointer durable execution agent workflow
    • Pydantic AI usage limits token budget request limit retries output validator
    • coding agent harness retry loop failure classification repair attempt open source project

    Sources:

    Checked on 2026-11-04. A person decides what this means for the item. The check changes no label and closes no issue.

  10. 8 remaining items

  11. dan-petty commented on Nov 6, 2026

    @dan-petty
    OwnerAuthor

    Answer from project standards, under the owner's instruction of 2026-11-05.

    Question: this issue is Ready and its wait for #3054 ended. It has 9 requirements. What does one pull request deliver?

    Answer: part 1 merged as #3568 (budgets, named stops, attempt events). The other parts have issues #3569, #3570, #3571, #3575, #3576 and #3577. The pull request of this issue delivers the first part that gives value alone: the class parser (the first input of the evaluation of 2026-11-04). The body of this issue now lists what each part delivers and which issue holds each moved criterion (AGENTS.md rule 1, and the Partial work rule of the brief). Issue #3571 holds the same work and closes as delivered when the pull request merges.

    Question: does a second parser failure stop the step when the ladder holds more than one tier?

    Answer: the turn ends with no resend, and the existing rule for a class with the action change_tier applies. The step moves to the next tier when the ladder holds one, and stops with class parser when it does not. The order of the tier change belongs to the strategy ladder of #3569, so this part does not change it. Rule: ACTION_BY_CLASS in src/devops_cli/roadmap/failure.py, and the key question "What does a parser failure class do in the repair loop?" of this issue.

    Question: is the smaller-edit message sent once for each turn or once for each step?

    Answer: once for each step. The count is in ToolCallLog, which lives for all turns of a step. Source: requirement "Parser failure gets a smaller edit" of this issue ("a second parser failure MUST stop the step").

    Premise check: ollama/ollama pull request 18571 says the failure is deterministic for a given conversation and comes from a long file-write call, so a resend with the same text cannot help. A test with a scripted model shows the changed request.

    What a person must decide: nothing for this part. Not verified: that a real model sends a smaller call after the message. Issue #3553 still holds the question why the gateway fallbacks did not serve the three calls.

  12. added
    status/in-progressActively being designed or implemented by developer or agent
    and removed
    status/readyStatus Ready: problem, acceptance criteria and an answer to each key question
    on Nov 6, 2026
  13. removed
    status/in-progressActively being designed or implemented by developer or agent
    owner/agentOwner Agent: an agentic coder works on the item
    on Nov 6, 2026
  14. added a commit that references this issue on Nov 6, 2026
  15. devops-cli-service commented on Nov 6, 2026

    @devops-cli-service
    Contributor
  16. devops-cli-service commented on Nov 6, 2026

    @devops-cli-service
    Contributor

    Live since 2026-11-06 06:16 UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort/highEffort High: much work, in one pull requestpriority/p2-mediumStandard priority feature work and architectural improvementsscope/roadmapThe roadmap jobs: intake, refine, stage, implement and the Service that runs themsource/agentFiled by an agenttype/featureNew user-facing functionality or capability additionvalue/highValue High: the delivery of the item matters much

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions