Skip to content

Fix escalation outcomes in the coding loop and render carried guidance fields - #169

Draft
ddbaron wants to merge 2 commits into
Agent-Field:mainfrom
ddbaron:fm/swe-af-upstream-u1-r1
Draft

ddbaron wants to merge 2 commits into
Agent-Field:mainfrom
ddbaron:fm/swe-af-upstream-u1-r1

Conversation

@ddbaron

@ddbaron ddbaron commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • What changed

    • Default-path blocking reviews are fix iterations. _run_default_path maps blocking=true to action="fix" instead of "block", and the fix feedback leads with the blocking findings. The default-path stuck window now also counts three fix passes on the same blocking finding.
    • Each issue-advisor retry runs its own coding-loop window. Iteration checkpoints are keyed per advisor round (<issue>.r<k>.json; round 0 keeps the bare issue name so in-flight crash resume is unchanged). A window that runs zero iterations returns FAILED_UNRECOVERABLE. The last blocking state is read from the recorded iteration history instead of an unbound review variable.
    • Every COMPLETED_WITH_DEBT records at least one debt item from the last review's blocking and should-fix findings, and the coding loop's debt is merged with the advisor's instead of overwritten.
    • SplitIssueSpec gets an optional guidance field. A child that omits it inherits the parent issue's guidance, so a flagged parent's children stay on the flagged path.
    • The coder prompt renders description, retry_context, approach_changes, retry_diagnosis, debt_notes and needs_new_tests. The reviewer prompt renders estimated_scope and needs_new_tests. The QA prompt renders testing_guidance.
    • The same changes are mirrored 1:1 in the Go port: go/internal/coding/loop.go, go/internal/coding/artifacts.go, the advisor loop and split gate under go/internal/dag/, go/internal/schemas/execution.go, and the coder, reviewer and QA builders under go/internal/prompts/coding/. The coding golden fixtures are regenerated from the Python prompt output.
    • Two docs/ARCHITECTURE.md sentences now describe the bounded fix pass and keep the existing meaning of "block": reserved for security, crash and data-loss concerns.
  • Why it changed

    • The reviewer prompt defines blocking as "MUST be fixed before merge", but the default path treated it as a terminal failure after one iteration. In a recorded build, every default-path block was followed by an issue-advisor call in the same iteration, so advisor work stood in for fix passes.
    • Advisor retries resumed an exhausted iteration counter. A retry could run zero coder iterations and still return COMPLETED_WITH_DEBT with an empty debt list, while the last review was blocking. The build log has Coding loop exhausted (non-blocking) ... accepting with debt in the same second as an advisor retry_approach, followed by Debt gate: 1 issues accepted with debt, total debt items: 0.
    • The pipeline already writes retry_context, approach_changes, retry_diagnosis, debt_notes, estimated_scope, needs_new_tests and testing_guidance, but the prompts dropped them. That contradicts the architecture document's statement that debt reaches downstream coders and "Nothing is silently dropped".
    • Split children dropped the parent's guidance, so a flagged parent's children ran the default path.

Validation

  • make check (pytest plus compileall) on the branch: 2229 passed, 1 skipped.
  • Relevant manual test performed:
    • Python baseline on upstream/main in the same shell: 2210 passed, 1 skipped. The branch adds 19 tests and changes no existing test count otherwise.
    • go test ./... under go/. The changed packages pass. Three unrelated packages fail identically on upstream/main in the same shell: internal/furrow disk-budget tests and internal/roles/planning provider default, both environment dependent.
    • go test -race ./internal/coding/ ./internal/dag/ ./internal/issue/ ./internal/prompts/coding/ ./internal/schemas/ passes.
    • The two model-free reproduction scripts from the issue run against the branch: the default-path block gets three fix iterations before escalation, and each advisor retry runs a full five-iteration window instead of zero.

Behavior Impact

  • No behavior change
  • Backward compatible behavior change
  • Breaking change (explain below)

This is a default-on bug fix with no flag. The iteration ceiling per issue becomes (max_advisor_invocations + 1) * max_coding_iterations coder iterations. With the defaults (max_coding_iterations=5, max_advisor_invocations=2) that is at most 15 coder iterations for one issue, with at most 2 advisor calls. The previous code shared one counter across rounds, so its worst case was 5 coder iterations but a retry could run zero. The new ceiling is still bounded by the two existing config values, and the default-path fix pass makes advisor rounds rarer.

Checkpoint names change only for advisor rounds after the first; round 0 keeps the old file name, so crash resume during a run still works. SplitIssueSpec.guidance is an optional additive field; no API change.

Notes

The prompt text that changed:

  • Coder system prompt: "If the issue says no new tests are needed, add no test files; run the build and the existing test suite only."
  • Coder task prompt: a Description line; a New tests needed line for false ("do NOT create or extend test files for this issue. Run the build and the existing test suite only, and report their results.") and for true ("add tests only for the behaviors this issue changes."); an Upstream Debt Notes block; a Retry Context (from Issue Advisor) block with the diagnosis, new approach and approach changes; and the task step "Write or update tests per the Testing Strategy/guidance, unless the issue says no new tests are needed."
  • Reviewer system prompt: "Your review depth is guided by the sprint planner's review_focus and by the issue's estimated_scope, both provided in the task prompt." plus "When the task prompt says the issue needs no new tests, judge whether the existing suite still covers the behavior this issue changes, and do not require new tests or new test files."
  • Reviewer task prompt: a Scope and Test Expectations (from sprint planner) block with Estimated scope, and New tests needed for false ("do not require new tests and do not block on missing test coverage") and true.
  • QA task prompt: a Testing Guidance (from sprint planner) line.

Refs #168, which describes the problem and includes the two reproduction scripts and the production evidence. The alternative of a separate reviewer field for "another coder pass cannot fix this", or keeping a terminal fast-fail for security and data loss, is left for the maintainer to decide. This PR implements the fix pass bounded by the existing stuck window and max_coding_iterations.

The two commits are:

  1. Fix the coding loop's escalation outcomes (loop mapping, stuck window, per-round checkpoints, zero-iteration guard, debt guarantees, split guidance, architecture text).
  2. Render carried guidance fields in the coder, reviewer and QA prompts (Python and Go prompt rendering, goldens, tests).

On the default path a reviewer blocking finding ended the issue immediately:
the loop returned FAILED_UNRECOVERABLE and the issue advisor ran in place of a
fix iteration. The reviewer uses "blocking" to mean "must be fixed before
merge", and the flagged path's synthesizer already treats it as a fix. The
default path now does the same, with the blocking findings leading the fix
feedback. Block stays reserved for security, crash and data-loss concerns; an
issue escalates when the same blocking finding survives the three-iteration
stuck window or the iteration budget runs out.

Advisor retries no longer share an exhausted iteration counter. Iteration
checkpoints are keyed per advisor round (<issue>.r<k>.json; round 0 keeps the
bare issue name so in-flight resume is unchanged), a window that runs zero
iterations returns FAILED_UNRECOVERABLE instead of a completion, and the last
blocking state comes from the recorded iteration history instead of an unbound
review variable. Every completed-with-debt outcome records at least one debt
item built from the last review's blocking and should-fix findings, and the
coding loop's debt is merged with the advisor's rather than overwritten.

Split children inherit the parent issue's guidance when the advisor omits it,
so a flagged parent's children stay on the flagged path.

The same behaviour is mirrored in the Go port, with tests for each case. The
two ARCHITECTURE.md sentences that described default-path blocking as a
separate terminal outcome now describe the bounded fix pass.
The pipeline already carries per-issue guidance and escalation context, but the
prompts dropped most of it.

The coder now sees the issue description, the issue advisor's retry context,
approach changes and failure diagnosis, upstream debt notes, and
needs_new_tests. When needs_new_tests is false it is told to add no test files
and to run the build and the existing suite only.

The reviewer sees estimated_scope and needs_new_tests. For an issue that needs
no new tests it validates that the existing suite still covers the changed
behavior and does not require or block on new test coverage.

QA sees the sprint planner's testing_guidance.

The Go prompt mirrors are updated 1:1 and the coding golden fixtures are
regenerated from the Python prompt output. Go tests cover the same rendered
fields as the Python tests.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant