Skip to content

fix(subagent,core): replay journaled result on durable subagent resume#6014

Merged
bug-ops merged 1 commit into
mainfrom
fix/5944-durable-resume-replay
Jul 11, 2026
Merged

fix(subagent,core): replay journaled result on durable subagent resume#6014
bug-ops merged 1 commit into
mainfrom
fix/5944-durable-resume-replay

Conversation

@bug-ops

@bug-ops bug-ops commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • zeph-subagent's durable-promise adapter (durable.rs) implemented the "finished-child replay" case documented since feat(sub-agents): durable promise for subagent spawn/await resume (P4) #4954, but the only production call site in zeph-core discarded the resumed promise unconditionally and always re-spawned the sub-agent, duplicating LLM calls and any side-effecting tool calls (git operations, GitHub issue/PR creation, file writes) a finished child had already performed.
  • maybe_make_durable_seat is replaced by resolve_durable_spawn_gate (DurableSpawnGate::{Fresh, Replayed, None}). On a resumed run it performs a non-blocking check via the new zeph_subagent::try_replay_durable_subagent (wrapping DurableContext::take_resolved_promise, now pub) for whether the child already resolved before the crash.
  • handle_agent_background / handle_agent_spawn_foreground skip mgr.spawn(...) entirely when the promise already resolved, feeding the journaled SubagentResult through the normal completion-notification path instead.
  • A still-pending resumed promise (child's fate unknown after a crash — resolver token unrecoverable per INV-9) falls back to the pre-existing spawn behavior, matching the documented v1 scope boundary, and now emits a tracing::warn! so the residual duplicate-spawn window is observable rather than silent. Tracked as a known v1 limitation in durable sub-agent resume: still-pending promise falls back to spawn, may duplicate a genuinely in-flight child #6010.
  • specs/064-durable-execution/spec.md §888 updated to record that the v1 LocalBackend integration falls back to spawn rather than re-parking, and why.

Closes #5944.

Test plan

  • cargo +nightly fmt --check
  • cargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warnings
  • cargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins (12695/12695 passing, includes 3 new end-to-end regression tests exercising handle_agent_background/handle_agent_spawn_foreground through the replay branch and the still-pending fallback, using two independently-constructed Agent instances over a shared on-disk sqlite journal to simulate a real parent-process restart)
  • RUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links" cargo doc --no-deps --workspace --features "desktop,ide,server,chat,pdf,scheduler"
  • cargo test --doc -p zeph-subagent -p zeph-durable -p zeph-core
  • Adversarial critique (rust-critic): verdict significant → 1 gap (silent duplicate-spawn fallback path) fixed with a warning log + follow-up issue durable sub-agent resume: still-pending promise falls back to spawn, may duplicate a genuinely in-flight child #6010; core replay/fallback design explicitly approved
  • Code review (rust-code-reviewer): verdict approved, all check-suite gates independently re-verified

@github-actions github-actions Bot added bug Something isn't working size/XL Extra large PR (500+ lines) documentation Improvements or additions to documentation rust Rust code changes core zeph-core crate and removed size/XL Extra large PR (500+ lines) labels Jul 11, 2026
@bug-ops
bug-ops enabled auto-merge (squash) July 11, 2026 12:50
@bug-ops
bug-ops force-pushed the fix/5944-durable-resume-replay branch from 00d0b7c to 7bd3484 Compare July 11, 2026 12:53
@github-actions github-actions Bot added the size/XL Extra large PR (500+ lines) label Jul 11, 2026
Restarting a crashed parent whose sub-agent had already finished and
resolved its durable promise previously discarded that promise and
spawned a brand-new child from scratch, duplicating LLM calls and any
side-effecting tool calls the finished child had already performed.

maybe_make_durable_seat is replaced by resolve_durable_spawn_gate,
which on a resumed run performs a non-blocking check
(zeph_durable::DurableContext::take_resolved_promise, exposed via the
new zeph_subagent::try_replay_durable_subagent) for whether the child
already resolved its promise before the crash. If so,
handle_agent_background and handle_agent_spawn_foreground skip
mgr.spawn(...) entirely and feed the journaled SubagentResult through
the normal completion path instead.

A still-pending resumed promise falls back to the pre-existing spawn
behavior, matching the documented v1 scope boundary, and now logs a
warning so the residual duplicate-spawn window is observable rather
than silent (tracked as a known v1 limitation in #6010).

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

Labels

bug Something isn't working core zeph-core crate documentation Improvements or additions to documentation rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zeph-subagent durable resume always re-spawns the child instead of replaying the journaled result (violates #4954 AC)

1 participant