Skip to content

fix(agent-runtime): isolate delegate mutation recovery and failure results - #1446

Merged
vastsa merged 3 commits into
vastsa:mainfrom
AR307:codex/pr-delegate-mutation-recovery
Oct 7, 2026
Merged

vastsa merged 3 commits into
vastsa:mainfrom
AR307:codex/pr-delegate-mutation-recovery

Conversation

@AR307

@AR307 AR307 commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Concurrent Task delegates currently share the parent's mutation recovery keys.
Failures from two writers editing the same path can terminate one writer before
its third failure and set the parent's terminal error. A delegate that wrote
introductory text before exhausting recovery can also return completed, even
though its editing work failed. Both failures reproduce on upstream 0.17.0
with a controlled local provider and no fork-specific features.

Scope retry counts and per-code graces to the executing parent or delegate run.
Carry exhaustion into that delegate's failed Task result, preserve its earlier
report and resumable chain, and release its recovery state when it settles.
A new parent prompt resets only the parent's budget. The existing three-failure
limit, permission checks and cancellation precedence remain unchanged.

The shared mutation descriptions are extracted to a small module so the runtime
entry point shrinks despite the added ownership handling.

Reproduction and validation:

  • node scripts/e2e-subagent-edit-isolation.mjs: real Runtime, pi Agent and
    SubagentRun over local HTTP/SSE. On upstream, A terminates after only two
    failed Edits as B spends the shared budget. After the fix, A fails on its own
    third attempt, B completes after two failures, and the parent has no mutation
    error. Resuming A succeeds.
  • --single: upstream reports A as completed and reports
    MUTATION_RETRY_BUDGET_EXHAUSTED on the parent. The fixed result is failed
    on A, without the parent error; resume succeeds.
  • --patch: the same isolation and resume flow passes for shell patch commands.
  • Runtime and subagent regression suites: 327 passed, including parent restart
    with a still-running delegate under default and explicit permissions.
  • Agent-runtime TypeScript build: passed.

All three provider-boundary scenarios and the 327 regressions were also run on
this standalone PR branch. Only provider responses and the host tool boundary
are controlled fixtures; no paid provider or user profile is used.

Related work: #1193 classifies output-token truncation, #1394 preserves resume after a parent error, and #283 publishes settlement updates. This change fixes per-delegate mutation recovery ownership and exhaustion classification.

Validation candidate: 7a03669b0f0c661f75c26e7b463b0a7f50dd4272; upstream main: 72b5e826cb7a9928467091ccf745aa9b225eeb04.

AR307 and others added 3 commits October 7, 2026 14:46
…sults

Concurrent delegates reused parent recovery keys, allowing one writer to spend another's retry budget and fail the parent. Scope recovery to each executing agent and carry exhaustion into the Task result so earlier prose cannot disguise a failed run as completed.

Keep failed chains resumable and preserve permission and cancellation behavior. Controlled provider tests cover parallel edits, shell patches and resume.
The summary is outside the delegate recovery fix and duplicates the shared root path added by other ready maintenance PRs. Drop it so the PR stays scoped and later conflict resolution covers only the E2E plan entries.
The latest main contains the session index and UTF-16 fixes. Refresh this PR and preserve both E2E scenarios in the combined delivery document.

# Conflicts:
#	docs/spec/06-delivery/04-e2e-test-plan.md

@muzimu217 muzimu217 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified locally on head 7a03669b0: packages/agent-runtime runtime.test.ts passes 287/287 (including the expanded coverage), full pnpm build:js clean.

The scoping mechanism matches the described fix:

  • delegateMutationTerminations keys terminal-failure state by toolCall.id, so concurrent delegates no longer share — and can no longer prematurely exhaust — the parent's recovery budget; entries are released when a delegate settles and the map is cleared on a new parent prompt, which keeps the "three failures" semantics per run instead of per session.
  • Carrying exhaustion into the delegate's own failed Task result while preserving its earlier report and resumable chain fixes the completed-despite-failed-editing case the body describes, and the honest-retry exemption set (EDIT_TAG_MISMATCH / EDIT_TAG_UNKNOWN / EDIT_LINES_UNSEEN) is consistent with the line-anchored edit contract's §9.3 reveal semantics.
  • Both failure modes say they reproduce on unmodified 0.17.0, and scripts/e2e-subagent-edit-isolation.mjs covers the isolation end-to-end — the right regression shape for a concurrency bug.

Non-blocking notes: the mutationTerminationAdvice strings are a nice touch (they point the model at the specific recovery action per error code); worth watching that the advice set stays in sync with the recoverable-codes set as both evolve.

Nothing blocking from a correctness standpoint; fix scope fits the current contribution window.

@vastsa
vastsa merged commit 7a1c0c7 into vastsa:main Oct 7, 2026
5 checks passed
@AR307
AR307 deleted the codex/pr-delegate-mutation-recovery branch October 7, 2026 08:46
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.

3 participants