pub struct ReplayCursor { /* private fields */ }Expand description
The pure replay/live cursor over one run’s event log.
Construct it with the recorded log (possibly empty, for a fresh run) and
drive it with one request per orchestration step. Each request returns an
Outcome: Outcome::Replayed while history covers the step,
Outcome::Live from the first uncovered step onward. See the module
docs for the full protocol and how RunCtx wraps it.
Implementations§
Source§impl ReplayCursor
impl ReplayCursor
Sourcepub fn new(log: Vec<EventEnvelope>) -> Result<ReplayCursor, ReplayError>
pub fn new(log: Vec<EventEnvelope>) -> Result<ReplayCursor, ReplayError>
Builds a cursor over a run’s recorded log.
An empty log is a fresh run: every request will be live. A non-empty
log must be well formed: it starts with a run head (Event::RunStarted
for an agent run or Event::GraphRunStarted for a graph run) at
position 0, all envelopes share one run id, positions are contiguous,
and nothing follows a terminal event.
§Errors
Returns ReplayError::MalformedLog when the log violates any of
those shape rules.
Sourcepub fn run_id(&self) -> Option<RunId>
pub fn run_id(&self) -> Option<RunId>
The run id the log carries, or None for a fresh (empty) log.
Sourcepub fn is_replaying(&self) -> bool
pub fn is_replaying(&self) -> bool
Whether recorded history remains to be consumed.
true means the next request will be answered from the log (or fail
as a divergence); false means the cursor has handed off to live
mode.
Sourcepub fn is_finished(&self) -> bool
pub fn is_finished(&self) -> bool
Whether a terminal event (Event::RunCompleted or
Event::RunFailed) has been consumed or emitted.
Sourcepub fn next_seq(&self) -> SequenceNumber
pub fn next_seq(&self) -> SequenceNumber
The position the next consumed or emitted event occupies.
Sourcepub fn begin(
&mut self,
agent_def_hash: &str,
labels: Option<BTreeMap<String, String>>,
) -> Result<Outcome<Value, BeginPermit<'_>>, ReplayError>
pub fn begin( &mut self, agent_def_hash: &str, labels: Option<BTreeMap<String, String>>, ) -> Result<Outcome<Value, BeginPermit<'_>>, ReplayError>
Requests the start of the run.
Replayed: verifies agent_def_hash against the recorded
Event::RunStarted (a changed agent definition must not silently
resume an old run) and returns the recorded input. Live: returns a
BeginPermit the caller redeems with the run’s input.
labels is the optional correlation tags to stamp on a genuinely
fresh Event::RunStarted; see the field’s docs. It plays no part in
replay: a recorded RunStarted is matched on agent_def_hash alone,
and whatever labels the caller passes here is ignored on that path,
exactly as ReplayCursor::model_call’s request_body is ignored
when replaying. Bounds on labels are not checked here (see the
field’s docs for why); a caller enforces them before calling.
§Errors
ReplayError::Divergence on a hash mismatch, a different recorded
event, or a request after the run already ended.
Sourcepub fn begin_graph(
&mut self,
graph_hash: &str,
labels: Option<BTreeMap<String, String>>,
forked_from: Option<ForkOrigin>,
) -> Result<Outcome<Value, GraphBeginPermit<'_>>, ReplayError>
pub fn begin_graph( &mut self, graph_hash: &str, labels: Option<BTreeMap<String, String>>, forked_from: Option<ForkOrigin>, ) -> Result<Outcome<Value, GraphBeginPermit<'_>>, ReplayError>
Requests the start of a graph run.
The graph-run counterpart of begin: the log
opens with Event::GraphRunStarted rather than Event::RunStarted,
because a graph run coordinates many agent hashes and has none at its
head. Replayed: verifies graph_hash against the recorded head (a
changed graph document must not silently resume an old run) and returns
the recorded input. Live: returns a GraphBeginPermit the caller
redeems with the run’s input.
labels and forked_from land on a genuinely fresh
Event::GraphRunStarted; they play no part in replay, exactly as
begin’s labels do. Bounds on labels are not
checked here; a caller enforces them before calling.
§Errors
ReplayError::Divergence on a hash mismatch, a different recorded
event, or a request after the run already ended.
Sourcepub fn node_entered(
&mut self,
node: &str,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn node_entered( &mut self, node: &str, ) -> Result<Outcome<(), Emitted>, ReplayError>
Requests entry into a graph node.
Replayed: matches the recorded Event::NodeEntered for node. Live:
returns the event to persist. A graph node’s own events (an agent loop’s
model calls, a tool call) are recorded between this and the matching
node_exited.
§Errors
ReplayError::Divergence on a node-id mismatch, a different recorded
event, or a request after the run already ended.
Sourcepub fn node_exited(
&mut self,
node: &str,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn node_exited( &mut self, node: &str, ) -> Result<Outcome<(), Emitted>, ReplayError>
Requests exit from a graph node, having produced its output.
Replayed: matches the recorded Event::NodeExited for node. Live:
returns the event to persist. The counterpart of
node_entered.
§Errors
ReplayError::Divergence on a node-id mismatch, a different recorded
event, or a request after the run already ended.
Sourcepub fn node_skipped(
&mut self,
node: &str,
reason: &str,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn node_skipped( &mut self, node: &str, reason: &str, ) -> Result<Outcome<(), Emitted>, ReplayError>
Records (or replays) that a graph node was skipped: reached on the walk
but deliberately not run (a branch routed past it). No
node_entered precedes a skip; the skip is
the sole marker for the node.
Replayed: matches the recorded Event::NodeSkipped for node (reason
included, so a skip that recorded one reason live cannot replay under
another). Live: returns the event to persist. The reason a caller
passes must be a pure function of the document and recorded values, like
every other emitted payload, so it reproduces on replay.
§Errors
ReplayError::Divergence on a node-id or reason mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn branch_taken(
&mut self,
node: &str,
case: &str,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn branch_taken( &mut self, node: &str, case: &str, ) -> Result<Outcome<(), Emitted>, ReplayError>
Records (or replays) that a branch node routed: the named case fired.
This is the sole recorded authority for which way a branch went; the
executed path is read from these events, never inferred from skips.
Recorded between the branch’s node_entered
and node_exited. Replayed: matches the
recorded Event::BranchTaken for node and case (a branch that fired
one case live cannot replay having fired another). Live: returns the event
to persist. The chosen case must be a deterministic function of recorded
values (a pure expression over the routed value, or a decision recomputed
from a replayed model reply), so replay reproduces the same route.
§Errors
ReplayError::Divergence on a node-id or case mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn map_fanned_out(
&mut self,
node: &str,
items: &Value,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn map_fanned_out( &mut self, node: &str, items: &Value, ) -> Result<Outcome<(), Emitted>, ReplayError>
Records (or replays) that a map node fanned out over a resolved item list.
Recorded between the map node’s node_entered
and its per-iteration markers. Replayed: matches the recorded
Event::MapFannedOut for node and items (the resolved list is part of
the recorded fact, so a fan-out that recorded one list live cannot replay
under another). Live: returns the event to persist. The items a caller
passes must be a deterministic function of recorded values (the over
reference resolved against the recorded routed value) so replay reproduces
the identical fan-out, which is what makes the derived per-iteration child
ids reproducible.
§Errors
ReplayError::Divergence on a node-id or item-list mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn map_iteration_started(
&mut self,
node: &str,
index: u64,
child_run: &str,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn map_iteration_started( &mut self, node: &str, index: u64, child_run: &str, ) -> Result<Outcome<(), Emitted>, ReplayError>
Records (or replays) that one iteration of a map fan-out started, as a child
run with the derived id child_run.
Replayed: matches the recorded Event::MapIterationStarted on its
STRUCTURAL position — node and index — and the RECORDED child_run
wins, exactly as the recorded input wins in begin.
The passed child_run is compared only loosely (it is derived data), for a
deliberate reason: the id is sha256: over the parent run id, the node, and
the index, and a FORK replays the origin’s prefix under a NEW run id, so a
caller re-deriving the id there produces a different value than the origin
recorded. Trusting the recorded id keeps a fork’s inherited map markers
replayable while node/index still pin the structural position. Live:
returns the event to persist with the caller’s derived child_run.
§Errors
ReplayError::Divergence on a node-id or index mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn map_iteration_joined(
&mut self,
node: &str,
index: u64,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn map_iteration_joined( &mut self, node: &str, index: u64, ) -> Result<Outcome<(), Emitted>, ReplayError>
Records (or replays) that one iteration of a map fan-out joined back into the map node’s output.
Joins are recorded in index order, never completion order, so the
concurrency of the fan-out never influences the parent log’s byte sequence.
Replayed: matches the recorded Event::MapIterationJoined for node and
index. Live: returns the event to persist.
§Errors
ReplayError::Divergence on a node-id or index mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn now(
&mut self,
) -> Result<Outcome<OffsetDateTime, NowPermit<'_>>, ReplayError>
pub fn now( &mut self, ) -> Result<Outcome<OffsetDateTime, NowPermit<'_>>, ReplayError>
Requests the current time.
Replayed: returns the recorded instant, bit for bit. Live: returns a
NowPermit; the caller reads a real clock and redeems the permit
with the reading.
§Errors
ReplayError::Divergence when the log records a different kind of
event here, or the run already ended.
Sourcepub fn random(&mut self) -> Result<Outcome<u64, RandomPermit<'_>>, ReplayError>
pub fn random(&mut self) -> Result<Outcome<u64, RandomPermit<'_>>, ReplayError>
Requests random bits.
Replayed: returns the recorded bits, exactly. Live: returns a
RandomPermit; the caller draws from a real random source and
redeems the permit with the draw.
§Errors
ReplayError::Divergence when the log records a different kind of
event here, or the run already ended.
Sourcepub fn model_call(
&mut self,
request_hash: &str,
request_body: Option<Value>,
) -> Result<Outcome<ModelReply, ModelCallPermit<'_>>, ReplayError>
pub fn model_call( &mut self, request_hash: &str, request_body: Option<Value>, ) -> Result<Outcome<ModelReply, ModelCallPermit<'_>>, ReplayError>
Requests a model call identified by its request hash.
Replayed: matches the recorded intent/completion pair and returns the
recorded ModelReply. A recorded intent with no completion (the
process died mid-call) hands back a live ModelCallPermit with no
intent event: re-issuing an unanswered model request is safe, and the
fresh completion correlates to the recorded intent. Live: the permit
carries the intent event to persist before calling the provider.
request_body is the optional full request to record on the intent,
off by default (see the field on Event::ModelCallRequested). It is
stored only on a genuinely fresh live intent and is ignored everywhere
else: correlation keys on request_hash alone, so passing a body never
changes which recorded call matches or what replays.
§Errors
ReplayError::Divergence on a hash mismatch or a different recorded
event; ReplayError::MalformedLog when the event after the intent
is not its completion.
Sourcepub fn tool_call(
&mut self,
tool: &str,
input: &Value,
effect: Effect,
idempotency_key: Option<&str>,
) -> Result<Outcome<Value, ToolCallPermit<'_>>, ReplayError>
pub fn tool_call( &mut self, tool: &str, input: &Value, effect: Effect, idempotency_key: Option<&str>, ) -> Result<Outcome<Value, ToolCallPermit<'_>>, ReplayError>
Requests a tool call.
Replayed: matches the recorded intent/completion pair (tool, input, effect, and idempotency key must all be equal) and returns the recorded output. A recorded intent with no completion is the effect-table case:
Effect::ReadandEffect::Idempotenthand back a liveToolCallPermitto re-execute; the permit surfaces the recorded idempotency key so the retry reuses it.Effect::Writefails withReplayError::NeedsReconciliation. The write may have landed; only a human may decide.
Live: the permit carries the intent event, which the caller must persist before executing the tool. That write-ahead ordering is what makes a crash between intent and completion detectable.
§Errors
ReplayError::Divergence on any payload mismatch or a different
recorded event; ReplayError::NeedsReconciliation for a dangling
write intent; ReplayError::MalformedLog when the event after the
intent is not its completion.
Sourcepub fn suspend(
&mut self,
reason: &str,
input_schema: &Value,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn suspend( &mut self, reason: &str, input_schema: &Value, ) -> Result<Outcome<(), Emitted>, ReplayError>
Requests suspension of the run.
Replayed: matches the recorded Event::Suspended. Live: returns the
event to persist. Either way, follow with ReplayCursor::await_resume
to obtain the resume input (or learn the run is still parked).
§Errors
ReplayError::Divergence on a payload mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn await_resume(
&mut self,
) -> Result<Outcome<Value, Parked<'_>>, ReplayError>
pub fn await_resume( &mut self, ) -> Result<Outcome<Value, Parked<'_>>, ReplayError>
Requests the input a parked run was resumed with.
Replayed: returns the recorded Event::Resumed input. Live: the
resume has not happened yet; the Parked value can be redeemed with
the input once a human provides it, or simply dropped, in which case
the run stays parked and the process may exit.
§Errors
ReplayError::Divergence when the log records a different kind of
event here, or the run already ended.
Sourcepub fn budget_exceeded(
&mut self,
budget: Budget,
observed: f64,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn budget_exceeded( &mut self, budget: Budget, observed: f64, ) -> Result<Outcome<(), Emitted>, ReplayError>
Reports a budget crossing, recorded so replay parks at the same point.
This is a runtime request, not an orchestration one: the budget gate
between events calls it when a declared limit is crossed. Checks must
be computed from replayed data (recorded token usage, recorded
timestamps), so a crossing that fired live is recomputed identically
on replay and matched here. Follow with
ReplayCursor::await_resume, exactly like a suspension.
§Errors
ReplayError::Divergence on a payload mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn complete_run(
&mut self,
output: &Value,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn complete_run( &mut self, output: &Value, ) -> Result<Outcome<(), Emitted>, ReplayError>
Requests successful completion of the run.
Replayed: verifies the produced output equals the recorded one (a deterministic orchestration over replayed values must reproduce its own final answer). Live: returns the terminal event to persist. After either, every further request is a divergence.
§Errors
ReplayError::Divergence on an output mismatch, a different
recorded event, or a request after the run already ended.
Sourcepub fn fail_run(
&mut self,
error: &str,
) -> Result<Outcome<(), Emitted>, ReplayError>
pub fn fail_run( &mut self, error: &str, ) -> Result<Outcome<(), Emitted>, ReplayError>
Requests failure of the run.
The failing counterpart of ReplayCursor::complete_run, with the
same replay/live/terminal behavior.
§Errors
ReplayError::Divergence on an error-message mismatch, a different
recorded event, or a request after the run already ended.