Skip to main content

Module durable

Module durable 

Source
Expand description

Durable promise adapter for subagent spawn/await (spec-064 §P4, INV-9, FR-DE-05).

This module is a thin Layer-2 adapter. It wires the parent’s DurableContext promise lifecycle to the subagent spawn/collect path. Domain meaning (“subagent result”) lives here; the cryptographic and journaling mechanics live in zeph-durable (Layer 0).

§Scope boundary

This adapter covers the finished-child replay case only: when a parent resumes after a crash and the child already resolved its promise, try_replay_durable_subagent returns the journaled SubagentResult immediately so the call site can skip re-spawning the child (spec §1038, acceptance test item 4).

The still-running-child-on-parent-crash case is intentionally out of scope. Only the BLAKE3 hash of the resolver token is persisted (INV-9); the raw 32-byte token is Zeroizing and never stored. A crashed parent therefore cannot re-mint a valid token for an in-flight child’s promise row. This is the direct consequence of INV-9’s hash-only persistence guarantee — inventing a token-recovery path would violate INV-9. The general crash-recovery gap is declared out of v1 scope in spec §862 and §1226.

§INV-9 channel rule

The DurableResolverSeat (holding the backend handle + token) is carried through a new field on SpawnContext::durable_resolver. It is handed to the spawned background task only. It MUST NOT be accessible from the child’s tool executor or LLM surface at any point.

§Gate pattern

The gate check lives at the call site in zeph-core (where DurableConfig and the parent DurableContext are available). When durable.enabled && durable.subagent, the call site:

  1. Calls make_durable_promise to create the promise and optionally a resolver seat.
  2. On a fresh run (seat is Some) it places the seat in crate::manager::SpawnContext::durable_resolver before spawning, so the child resolves the promise on exit via resolve_durable_promise.
  3. On a resumed run (seat is None) it calls try_replay_durable_subagent against the re-derived promise. If the child already resolved, the call site replays the journaled SubagentResult and skips spawn entirely — the fix this module exists for. If the promise is still pending, the call site falls back to a plain spawn (documented “Scope boundary” gap above: a genuinely in-flight child cannot be re-attached to after a crash).

When durable.enabled && durable.subagent is false, SpawnContext::durable_resolver stays None and the plain spawn/collect path runs byte-identically to today (opt-in, zero overhead when disabled).

Structs§

DurableResolverSeat
The out-of-band resolver seat carried from parent to child background task (INV-9).
SubagentResult
The payload stored in the durable promise for a subagent’s terminal result.

Functions§

await_durable_subagent
Await a durable promise for a subagent result, with an adapter-level tracing span.
make_durable_promise
Create a durable promise in the parent’s execution and return the resolver seat for the child.
resolve_durable_promise
Called from the child’s background task after the agent loop terminates.
try_replay_durable_subagent
Non-blocking check for a resumed subagent promise’s journaled result (spec §1038).