Skip to main content

Event

Enum Event 

Source
pub enum Event {
Show 24 variants RunStarted { agent_def_hash: String, input: Value, labels: Option<BTreeMap<String, String>>, }, ModelCallRequested { seq: SequenceNumber, request_hash: String, request_body: Option<Value>, }, ModelCallCompleted { seq: SequenceNumber, response: Value, usage: TokenUsage, }, ToolCallRequested { seq: SequenceNumber, tool: String, input: Value, effect: Effect, idempotency_key: Option<String>, }, ToolCallCompleted { seq: SequenceNumber, output: Value, }, NowObserved { now: OffsetDateTime, }, RandomObserved { value: u64, }, Suspended { reason: String, input_schema: Value, }, Resumed { input: Value, }, BudgetExceeded { budget: Budget, observed: f64, }, RunCompleted { output: Value, }, RunFailed { error: String, }, RunAbandoned { reason: Option<String>, unresolved_write: Option<UnresolvedWrite>, }, GraphRunStarted { graph_hash: String, input: Value, labels: Option<BTreeMap<String, String>>, forked_from: Option<ForkOrigin>, }, NodeEntered { node: String, }, NodeExited { node: String, }, NodeSkipped { node: String, reason: String, }, BranchTaken { node: String, case: String, }, MapFannedOut { node: String, items: Value, }, MapIterationStarted { node: String, index: u64, child_run: String, }, MapIterationJoined { node: String, index: u64, }, FoldIterationStarted { node: String, index: u64, }, FoldIterationJoined { node: String, index: u64, }, FoldConverged { node: String, winner_index: u64, reason: String, },
}
Expand description

Everything that can happen in a run.

Adjacently tagged: each event serializes as {"kind": "...", "payload": {...}}. The tag (kind) and the content (payload) live in separate keys, which is the deliberate choice for a durable format. It never collides with a payload field (a payload could legitimately contain a field named kind), and it does not constrain payloads to be JSON objects the way internal tagging would. The wire shape is a durability contract, so it is spelled out rather than left to a default.

Variants§

§

RunStarted

A run began. Records the hash of the agent definition it ran under and the input it started with.

Fields

§agent_def_hash: String

Content hash of the agent definition (model, prompt, tools, budget) this run executed.

§input: Value

The input the run started with.

§labels: Option<BTreeMap<String, String>>

Optional operator-supplied correlation tags for the run (for example a build id or environment), set once at creation and never rewritten. BTreeMap so the wire form serializes with keys in sorted order regardless of insertion order, matching the deterministic-serialization discipline the rest of this crate holds to (see salvor_runtime::hash::canonical_json, which this field is deliberately never fed into: labels are a tag, not part of the run’s identity, and never enter agent_def_hash or request_hash).

Absent by default. #[serde(default, skip_serializing_if = "Option::is_none")] follows the identical additive contract request_body on Event::ModelCallRequested set: with no labels supplied at creation, this field is omitted from the wire form entirely, so an unlabeled run’s RunStarted serializes byte for byte as it did before this field existed. Sanity bounds (at most 16 labels, keys under 64 bytes, values under 256 bytes) are enforced where a run is created, never here: a log already on disk is trusted and replayed as recorded, whatever it holds.

§

ModelCallRequested

A model call was requested. Records the correlating sequence number and the hash of the request, so a later completion can be matched to it.

Fields

§seq: SequenceNumber

Correlates this request with its Event::ModelCallCompleted.

§request_hash: String

Content hash of the request sent to the model. This is the sole replay-correlation key for the call; it is computed the same way whether or not request_body is recorded, and the body never feeds into it.

§request_body: Option<Value>

The full model request body, verbatim, recorded only when prompt recording is opted into (per-agent record_prompts or the SALVOR_RECORD_PROMPTS default). It exists so the inspector can show the exact prompt sent.

Off by default, and for a reason: the body can hold user data and secrets. When recording is off the field is None and, thanks to skip_serializing_if, is omitted from the wire form, so the event serializes byte for byte as it did before this field existed. The body is purely informational: replay correlates on request_hash alone and ignores whatever is (or is not) recorded here, so a log captured with bodies replays identically to one captured without.

§

ModelCallCompleted

A model call completed. This is the captured nondeterministic boundary: once recorded, replay reads the response from here and never calls the model again.

Fields

§seq: SequenceNumber

Correlates this completion with its Event::ModelCallRequested.

§response: Value

The model response, stored inline.

Inline is the deliberate choice: the full response lives in the log. Large multimodal outputs may later force a content-addressed blob store, at which point this field becomes the seam, holding a blob reference instead of the response itself.

§usage: TokenUsage

Token usage reported for the call.

§

ToolCallRequested

A tool call was requested. Records intent before execution, which is what lets an unrecorded-but-attempted Effect::Write be detected on resume.

Fields

§seq: SequenceNumber

Correlates this request with its Event::ToolCallCompleted.

§tool: String

The tool’s name.

§input: Value

The typed input passed to the tool.

§effect: Effect

The tool’s declared side-effect class, which governs retry and resume behavior for a call that did not complete.

§idempotency_key: Option<String>

The idempotency key for this attempt, when the tool has one.

A Effect::Read call needs none; an Effect::Idempotent retry reuses this exact key so the provider collapses duplicates.

§

ToolCallCompleted

A tool call completed. Once recorded, replay reads the output from here and never calls the tool again, whatever its effect class.

Fields

§seq: SequenceNumber

Correlates this completion with its Event::ToolCallRequested.

§output: Value

The tool’s output.

§

NowObserved

The value ctx.now() returned, captured once while the run executed live. On replay the recorded value is returned again, bit for bit; the clock is never consulted a second time. This is how orchestration code gets to observe time without breaking the determinism constraint.

§Wire compatibility

This variant (together with Event::RandomObserved) was added after the original ten. Adding variants is a read-compatible change, so SCHEMA_VERSION stayed at 1; the constant’s docs carry the full argument.

Fields

§now: OffsetDateTime

The observed time. RFC 3339 on the wire with nanosecond precision, so the value replays exactly as recorded.

§

RandomObserved

The value ctx.random() returned, captured once while the run executed live. On replay the recorded value is returned again, bit for bit; the random source is never consulted a second time.

Fields

§value: u64

Sixty-four raw bits from the runtime’s random source.

A u64 is the deliberate representation: JSON integers carry the full 64-bit range exactly, so replay returns the identical bits. Richer values (a float in a range, a choice from a list) must be derived from these bits deterministically by the caller, never drawn fresh.

§

Suspended

The run parked durably, awaiting input (for example, human approval). Records why and the schema the resume input must satisfy.

Fields

§reason: String

Why the run suspended.

§input_schema: Value

JSON Schema the Event::Resumed input is validated against.

§

Resumed

A suspended run resumed with the given input.

Fields

§input: Value

The input supplied on resume.

§

BudgetExceeded

A declared budget was exceeded. The run suspends rather than dies, so a human can raise the limit and resume.

Fields

§budget: Budget

The budget dimension and the limit that was crossed.

§observed: f64

The observed value, interpreted in the units of budget.kind. An f64 for the same reason Budget::limit is: exact for integral token and step counts up to 2^53, and inherently fractional for cost and wall time.

§

RunCompleted

The run finished successfully with this output.

Fields

§output: Value

The run’s final output.

§

RunFailed

The run terminated with an error.

Fields

§error: String

A description of the failure.

§

RunAbandoned

The run was abandoned by an operator: deliberately retired without ever finishing or failing. A terminal event, appended by hand through the server’s abandon endpoint, never emitted by orchestration.

§Abandonment is not failure

This is a separate terminal from Event::RunFailed on purpose. A failure says the run tried to continue and could not; an abandonment says a human decided it should stop mattering (a husk that is dead forever, or a run whose noise is no longer worth carrying in the inbox). The two read differently everywhere downstream — the fold gives abandonment its own status, and the surfaces treat it as a muted resting state, never the failure ink. Keeping Event::RunFailed untouched is the point: its recorded meaning must not shift.

§Wire compatibility

Added the same read-compatible way the deterministic-context and graph events were: a new variant, so a log written before it contains none of the kind and every event in it parses to the identical value under the new build. SCHEMA_VERSION stays 1; the constant’s docs carry the full argument. Both fields are additive-optional under the identical #[serde(default, skip_serializing_if = "Option::is_none")] contract the other optional payloads hold to, so a bare abandonment (no reason, no dangling write) serializes with an empty payload object: {"kind":"RunAbandoned","payload":{}}.

Fields

§reason: Option<String>

The operator’s optional note for why the run was abandoned. Absent by default: an abandonment with no reason omits the key.

§unresolved_write: Option<UnresolvedWrite>

Set only when the abandoned run was parked at a dangling write (status NeedsReconciliation): the outstanding write intent’s position and tool, recorded as evidence.

The abandonment never claims the write question was answered. Abandoning a needs-reconciliation run is allowed precisely because this field carries the honesty forward: the write may or may not have taken effect, and the record says so by naming the intent that was left unsettled rather than pretending a completion. Absent for any run abandoned from a state with no dangling write, under the same additive-optional contract as reason.

§

GraphRunStarted

A graph run began. The head of a run that executes a graph document rather than a single agent loop.

A separate variant from Event::RunStarted, not a field on it: RunStarted requires exactly one agent_def_hash, but a graph run has many agent hashes (one per agent node) and none at its head. It records the hash of the frozen graph document and the run’s input; the node, branch, and map markers below then narrate the walk. Downstream of this crate a graph_hash is sha256:<64 lowercase hex> over the canonical graph document, but this crate treats it as an opaque string and never depends on salvor-graph to interpret it.

Fields

§graph_hash: String

Content hash of the frozen graph document this run executes. Opaque here; the runtime forms and checks it against salvor-graph.

§input: Value

The input the graph run started with.

§labels: Option<BTreeMap<String, String>>

Optional operator-supplied correlation tags, carried for parity with Event::RunStarted::labels: grouping (a build id, an environment) matters for graph runs exactly as it does for agent runs. Same additive-optional contract as that field — absent by default via #[serde(default, skip_serializing_if = "Option::is_none")], so an unlabeled graph run omits the key entirely. A tag, never part of identity: never fed into any hash. Sanity bounds are enforced where a run is created, never here; a log on disk is replayed as recorded.

§forked_from: Option<ForkOrigin>

Set only when this run is a fork of an earlier run: the recorded link back to its origin (see ForkOrigin). A fork is a new run whose log opens with the origin’s prefix rewritten under the new id, and this field at seq 0 is the durable fact that it descends from that origin. Absent by default under the same additive-optional contract, so an ordinary (non-forked) graph run omits the key.

§

NodeEntered

Execution entered a graph node. Marks the node as the run’s current position; a later Event::NodeExited closes it.

Fields

§node: String

The id of the node entered, unique within the graph document.

§

NodeExited

Execution left a graph node, having produced its output.

Fields

§node: String

The id of the node exited.

§

NodeSkipped

A graph node was skipped: reached on the walk but deliberately not run (for example, a branch case that did not fire routes past its node). A skipped node WAS reached — it is recorded precisely so a projection can tell “skipped” apart from “never reached”, which is the absence of any event naming the node.

Fields

§node: String

The id of the node skipped.

§reason: String

Why it was skipped, recorded for the audit trail.

§

BranchTaken

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 which sibling nodes were skipped.

Fields

§node: String

The id of the branch node that routed.

§case: String

The name of the case that fired, matching a branch-case name in the graph document (realized by the like-named edge). Opaque here.

§

MapFannedOut

A map node fanned out: the resolved list of items to map over was determined and recorded. Recording the items here (rather than re-resolving on replay) is what makes the fan-out deterministic — the per-iteration child ids are derived from this recorded data.

Fields

§node: String

The id of the map node.

§items: Value

The resolved list of items, one sub-run per element. Recorded verbatim so replay reproduces the same fan-out.

§

MapIterationStarted

One iteration of a map fan-out started, as a child run. The child’s id is derived deterministically from recorded data (the parent run, the node, and the index), so replay reconstructs the identical id without drawing a fresh one.

Fields

§node: String

The id of the map node this iteration belongs to.

§index: u64

The zero-based position of this iteration in the fanned-out list.

§child_run: String

The derived id of the child run executing this iteration.

§

MapIterationJoined

One iteration of a map fan-out joined back: its child run’s result was folded 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.

Fields

§node: String

The id of the map node this iteration belongs to.

§index: u64

The zero-based position of the iteration that joined.

§

FoldIterationStarted

A fold node began one bounded iteration: a single revision pass of the accumulate-and-refine loop the node models. A fold’s passes run sequentially in the one log (never as child runs, unlike a map’s iterations), so index is both the pass position and its recorded order.

Fields

§node: String

The id of the fold node this iteration belongs to.

§index: u64

The zero-based position of this pass in the fold loop.

§

FoldIterationJoined

A fold iteration joined back: its pass result was folded into the fold node’s accumulated value. Recorded in index order, exactly like Event::MapIterationJoined; because a fold’s passes are sequential, index order already is completion order.

Fields

§node: String

The id of the fold node this iteration belongs to.

§index: u64

The zero-based position of the iteration that joined.

§

FoldConverged

A fold node settled: its loop stopped and its join rule selected the winning iteration. This is the sole recorded authority for WHICH pass the fold’s output came from — the argmax of a best_by join is read from winner_index, never inferred from the iteration order — exactly as Event::BranchTaken is the sole authority for a branch’s route. reason records WHY the loop ended (its stop predicate fired, the iteration bound was reached, or a pass failed to improve), an opaque audit string like Event::NodeSkipped’s.

Fields

§node: String

The id of the fold node that settled.

§winner_index: u64

The zero-based index of the iteration whose value the join rule selected as the fold’s output.

§reason: String

Why the loop ended, recorded for the audit trail.

Trait Implementations§

Source§

impl Clone for Event

Source§

fn clone(&self) -> Event

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Event

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for Event

Source§

fn deserialize<__D>( __deserializer: __D, ) -> Result<Event, <__D as Deserializer<'de>>::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for Event

Source§

fn eq(&self, other: &Event) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Event

Source§

fn serialize<__S>( &self, __serializer: __S, ) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Event

Auto Trait Implementations§

§

impl Freeze for Event

§

impl RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl UnsafeUnpin for Event

§

impl UnwindSafe for Event

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more