pub struct DurableContext { /* private fields */ }Expand description
The &self durable execution context handed to a consumer’s program.
Construct it with DurableContext::new from a shared backend (the read path) and a
JournalWriterHandle (the write path) — both bound to the same durable.db. A fresh
execution opens with is_resume = false; a resumed one with is_resume = true, which activates
the ReplayCursor.
Implementations§
Source§impl DurableContext
impl DurableContext
Sourcepub fn new(
execution_id: ExecutionId,
kind: ExecutionKind,
is_resume: bool,
backend: Arc<DurableBackendEnum>,
writer: JournalWriterHandle,
config: &DurableConfig,
) -> Self
pub fn new( execution_id: ExecutionId, kind: ExecutionKind, is_resume: bool, backend: Arc<DurableBackendEnum>, writer: JournalWriterHandle, config: &DurableConfig, ) -> Self
Build a context for an execution.
backend is the shared read path (the ReplayCursor reads segments through it) and must
be the same durable.db instance the writer commits to. is_resume activates replay:
pass true when reopening an execution that already has journal entries (as
LocalBackend::open_execution reports), false for a
brand-new execution.
Sourcepub fn execution_id(&self) -> ExecutionId
pub fn execution_id(&self) -> ExecutionId
The execution this context drives.
Sourcepub fn kind(&self) -> ExecutionKind
pub fn kind(&self) -> ExecutionKind
The execution’s category.
Sourcepub async fn step<T, F, Fut>(
&self,
desc: StepDescriptor,
op: F,
) -> Result<T, DurableError>where
T: Serialize + DeserializeOwned + Send,
F: FnOnce(StepHandle) -> Fut + Send,
Fut: Future<Output = Result<T, StepError>> + Send,
pub async fn step<T, F, Fut>(
&self,
desc: StepDescriptor,
op: F,
) -> Result<T, DurableError>where
T: Serialize + DeserializeOwned + Send,
F: FnOnce(StepHandle) -> Fut + Send,
Fut: Future<Output = Result<T, StepError>> + Send,
Run a durable step, returning just its value.
On a fresh execution the operation op runs and its result is journaled; on replay the
journaled result is returned and op is never invoked (INV-10). The closure receives a
StepHandle carrying the step’s IdempotencyKey for boundary deduplication.
Use step_recorded when the live/replayed distinction or the
step id matters.
§Errors
DurableError::StepFailedifopreturns an error on a fresh run.DurableError::ReplayDivergenceif the journaled step at this position has a different structural fingerprint (INV-3).DurableError::AmbiguousEffectfor anOnAmbiguous::Failguarded step caught in the ambiguous window.DurableError::Serialize/DurableError::Decodeon a payload codec failure, or a storage error from the journal.
§Examples
use zeph_durable::StepDescriptor;
let lines: usize = ctx
.step(StepDescriptor::idempotent("count", b"tool:count".to_vec()), |_handle| async {
Ok(42)
})
.await?;
assert_eq!(lines, 42);Sourcepub async fn step_recorded<T, F, Fut>(
&self,
desc: StepDescriptor,
op: F,
) -> Result<DurableStep<T>, DurableError>where
T: Serialize + DeserializeOwned + Send,
F: FnOnce(StepHandle) -> Fut + Send,
Fut: Future<Output = Result<T, StepError>> + Send,
pub async fn step_recorded<T, F, Fut>(
&self,
desc: StepDescriptor,
op: F,
) -> Result<DurableStep<T>, DurableError>where
T: Serialize + DeserializeOwned + Send,
F: FnOnce(StepHandle) -> Fut + Send,
Fut: Future<Output = Result<T, StepError>> + Send,
Run a durable step, returning the full DurableStep record (id, key, and outcome).
§Errors
Identical to step.
Sourcepub fn parallel(&self) -> ParallelScope<'_>
pub fn parallel(&self) -> ParallelScope<'_>
Open a ParallelScope whose children receive contiguous, eagerly-assigned step ids.
Construct the children synchronously (e.g. with a Vec or .map(...).collect()), then drive
them with join_all/try_join_all; their ids are fixed by construction order and are stable
across replay regardless of completion order (INV-2).
Sourcepub async fn promise<T>(&self) -> Result<DurablePromise<T>, DurableError>
pub async fn promise<T>(&self) -> Result<DurablePromise<T>, DurableError>
Create a durable promise resolved out of band by an operator or A2A reply (FR-DE-05).
The promise occupies a deterministic program position, so a resumed execution re-derives the
same PromiseId and re-attaches to the pending row rather than minting an orphan. A fresh
promise carries its 32-byte resolver token — hand it to the resolving channel via
DurablePromise::resolver_token; a resumed promise carries none (the original token was
delivered before the crash). Await the result with await_promise.
§Errors
DurableError::StepCapExceededif the promise would exceed the per-execution step cap.- A storage error if the promise row cannot be read or inserted.
Sourcepub async fn claim_promise_notification(
&self,
id: PromiseId,
) -> Result<bool, DurableError>
pub async fn claim_promise_notification( &self, id: PromiseId, ) -> Result<bool, DurableError>
Claim the one-time out-of-band notification for a replayed promise result.
Returns true for the first caller (fire the side effects) and false on every later call
(suppress — already claimed). The claim is a single conditional UPDATE on the promise’s
notified_at column keyed by its PromiseId; it does not allocate a durable step id, so
— unlike wrapping the notice in step — it has zero interaction with the INV-2
step-id counter and can never cause a DurableError::ReplayDivergence, regardless of how many
times the parent restarts (#6027).
Because PromiseId::derive is deterministic across runs (a resumed execution re-derives the
same id at the same program position), the claim converges on one row for the fresh run and
every replay.
§Errors
Returns DurableError::Storage on a database error.
Sourcepub async fn await_promise<T: DeserializeOwned>(
&self,
promise: DurablePromise<T>,
) -> Result<T, DurableError>
pub async fn await_promise<T: DeserializeOwned>( &self, promise: DurablePromise<T>, ) -> Result<T, DurableError>
Await a durable promise’s resolved value, parking until it is resolved.
Returns immediately if the promise is already resolved (the common replay case). Otherwise it
parks on an in-process notify keyed by the promise id and falls back to a database poll every
promise_poll_interval_secs; above max_parked_promises concurrent waiters it polls without
parking. A resolution committed by DurableHandle::resolve
wakes the waiter at once.
§Errors
DurableError::UnknownPromiseif the promise row is missing (e.g. pruned).- A decode/integrity error if the resolved payload cannot be opened into
T.
Sourcepub async fn take_resolved_promise<T: DeserializeOwned>(
&self,
id: PromiseId,
) -> Result<Option<T>, DurableError>
pub async fn take_resolved_promise<T: DeserializeOwned>( &self, id: PromiseId, ) -> Result<Option<T>, DurableError>
Read a promise’s state and, if resolved, open and decode its value — without parking.
Unlike await_promise, this performs a single backend read and
returns immediately regardless of resolution state: Ok(None) means the promise row
exists but has not been resolved yet. Callers on an interactive path (e.g. a resumed
parent deciding whether to replay a journaled subagent result or spawn a fresh one) use
this to avoid an unbounded park on a promise that may never resolve (INV-9: a resumed
promise’s resolver token is unrecoverable, so nothing can ever resolve it if the
original resolver is gone).
§Errors
DurableError::UnknownPromiseif the promise row is missing (e.g. pruned).- A decode/integrity error if the resolved payload cannot be opened into
T.
Sourcepub async fn sleep_until(&self, due: SystemTime) -> Result<(), DurableError>
pub async fn sleep_until(&self, due: SystemTime) -> Result<(), DurableError>
Durably sleep until due, surviving a process restart (FR-DE-06).
Arms a durable_timers row at a deterministic position (so a resume re-attaches to it),
then parks until the instant arrives — firing the timer itself when due, or returning at once
if a restart finds it already fired or past due. The
DurableTimerService, when running, fires due timers and wakes
the waiter; without it, this loop still makes progress on its own.
§Errors
DurableError::StepCapExceededif the timer would exceed the per-execution step cap.- A storage error if the timer cannot be armed or its state read.
Sourcepub fn resolver_handle(&self) -> DurableHandle
pub fn resolver_handle(&self) -> DurableHandle
Build an out-of-band DurableHandle over this context’s backend.
The handle is the operator/A2A resolution surface; it MUST NOT be exposed to an LLM tool
(INV-9). It shares the same backend, so a resolution it commits wakes an
await_promise parked on the same process at once.
Sourcepub async fn finalize(
&self,
status: ExecutionStatus,
) -> Result<(), DurableError>
pub async fn finalize( &self, status: ExecutionStatus, ) -> Result<(), DurableError>
Transition this execution to a terminal status (FR-DE / retention section).
Consumers call this on the two production terminal transitions the journal itself never
observes: Completed when the unit of work this execution represents finishes
successfully, and Failed on an unrecoverable (non-retryable) error. Aborted is reserved
for the replay-divergence guard (DurableError::ReplayDivergence) and is set internally,
not by consumers.
Idempotent: calling this more than once, or racing it against an internal Aborted
transition, is safe — only the first call to observe the execution as running applies
(see crate::journal::Journal::finalize). The retention sweep only reclaims a finalized execution after
its configured TTL, so a finalized-then-reopened execution (e.g. a resumed conversation)
automatically un-finalizes back to running on the next DurableContext::new with
is_resume = true, protecting it from a stale finalized_at.
§Errors
Returns a storage error if the transition cannot be committed.
§Examples
use zeph_durable::ExecutionStatus;
ctx.finalize(ExecutionStatus::Completed).await?;Sourcepub async fn drain_background(&self)
pub async fn drain_background(&self)
Await any in-flight background checkpoint folds — a turn-boundary / test barrier.
The soft step-cap fold runs on a spawned task so it never blocks step dispatch; call this at a turn boundary to ensure the journal is compacted before the next phase observes it.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for DurableContext
impl !RefUnwindSafe for DurableContext
impl !UnwindSafe for DurableContext
impl Send for DurableContext
impl Sync for DurableContext
impl Unpin for DurableContext
impl UnsafeUnpin for DurableContext
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more