Skip to main content

EngineError

Enum EngineError 

Source
pub enum EngineError {
Show 14 variants MapOverNotAList { node: String, over: String, }, UnsupportedMapBody { node: String, detail: String, }, NoBranchCaseMatched { node: String, }, BranchDecisionUnmatched { node: String, reply: String, cases: Vec<String>, }, UnsupportedFoldBody { node: String, detail: String, }, FoldNoComparableCandidate { node: String, reference: String, }, FoldBoundExceeded { node: String, bound: u32, }, UnknownAgent { node: String, agent_hash: String, }, UnknownTool { node: String, tool: String, }, MalformedGraph { detail: String, }, ToolFailed { node: String, message: String, }, ApprovalSchemaViolation { node: String, violations: Vec<ApprovalViolation>, }, GraphEncode(Error), Runtime(RuntimeError),
}
Expand description

Why a graph drive could not continue.

Variants§

§

MapOverNotAList

A map node’s over reference did not resolve to a JSON array against the routed value (it was missing, or resolved to a non-array value). A map can only fan out over a list, so the engine refuses deterministically rather than guessing. Returned before the map’s NodeEntered is recorded, so nothing lands in the log past the refusal, and it reproduces on replay: the same recorded routed value re-resolves to the same non-list.

Fields

§node: String

The id of the map node.

§over: String

The over reference that failed to resolve to a list.

§

UnsupportedMapBody

A map node’s body is a form that is not executable: an embedded subgraph (per-item sub-walks need their own log per iteration to keep node ids unambiguous, which is not implemented yet), or a node body that names a node whose kind cannot be a per-item worker (only agent and tool bodies run). Returned before the map’s NodeEntered is recorded, so nothing lands in the log past the refusal. The document layer still validates these as legal graphs; only the engine declines to run them.

Fields

§node: String

The id of the map node.

§detail: String

What about the body is not supported.

§

NoBranchCaseMatched

An expression branch reached with no case whose condition evaluated true. The author declared the cases exhaustively or the graph cannot proceed; the engine refuses deterministically rather than guessing a route. Returned before the branch’s NodeEntered is recorded, so nothing lands in the log past the refusal, and the refusal reproduces on replay (the same routed value re-evaluates to the same no-match).

Fields

§node: String

The id of the branch node.

§

BranchDecisionUnmatched

A model-decision branch’s agent produced a reply that is not one of the branch’s case names. Unlike the other refusals this arrives after the branch’s NodeEntered and the decision agent’s own events are recorded (the model had to run to produce the reply); it still reproduces on replay, because the reply is decoded from the recorded model completion.

Fields

§node: String

The id of the branch node.

§reply: String

The agent’s reply, trimmed, that named no case.

§cases: Vec<String>

The branch’s case names, in author order.

§

UnsupportedFoldBody

A fold node’s body is a form that is not executable: an embedded subgraph (per-pass sub-walks need their own log per pass to keep node ids unambiguous, which is not implemented yet, exactly as for a map), or a node body that names a node whose kind cannot be a per-pass worker (only agent and tool bodies run). Returned before the fold’s NodeEntered is recorded, so nothing lands in the log past the refusal. The document layer still validates these as legal graphs; only the engine declines to run them.

Fields

§node: String

The id of the fold node.

§detail: String

What about the body is not supported.

§

FoldNoComparableCandidate

A fold node’s best_by join found no pass it could choose between: the reference resolved on no pass, or resolved only to values the expression language does not order (anything but a number or a string). An argmax with no candidate has no answer, so the engine refuses rather than falling back to a pass no rule chose. Unlike the body refusal this arrives after the fold’s NodeEntered and its passes are recorded (the passes had to run to be chosen among), but before FoldConverged: no winner and no reason land in the log for a convergence that did not happen. It reproduces on replay, because the argmax reads the recorded pass outputs.

Fields

§node: String

The id of the fold node.

§reference: String

The best_by reference that named nothing comparable.

§

FoldBoundExceeded

A fold node that declares on_bound: fail ran every pass its max_iterations bound allows and stop_when never held. For such a fold the predicate is a REQUIREMENT rather than an early exit: the loop converged on nothing, so the node produces no value and the join is never consulted.

Recorded state at the refusal: the passes and their joins are all in the log, because they really happened and a replay must reproduce them. What does not land is the convergence: this is returned exactly where FoldConverged would have been recorded, so no FoldConverged and no NodeExited are written, mirroring EngineError::FoldNoComparableCandidate. It reproduces on replay, because the pass count and the predicate’s verdict are both pure functions of the recorded pass outputs.

Fields

§node: String

The id of the fold node.

§bound: u32

The max_iterations bound the loop reached.

§

UnknownAgent

An agent node referenced an agent hash the resolver could not supply.

Fields

§node: String

The id of the agent node.

§agent_hash: String

The unresolved agent definition hash.

§

UnknownTool

A tool node named a tool the resolver could not supply.

Fields

§node: String

The id of the tool node.

§tool: String

The unresolved tool name.

§

MalformedGraph

The graph’s edges do not form a well-formed DAG (a cycle, or an edge referencing a node that is not in the document). The document validator rejects both at submit; the engine re-checks defensively so a walk is never attempted over a malformed topology.

Fields

§detail: String

What was wrong with the topology.

§

ToolFailed

A tool node’s call failed after exhausting its retry policy. The full failure is already recorded in the log’s ToolCallCompleted; this carries the message so the caller sees why the graph stopped.

Fields

§node: String

The id of the tool node that failed.

§message: String

The recorded failure message.

§

ApprovalSchemaViolation

A gate node was resumed with an input that does not satisfy the gate’s declared approval_schema. Returned from the accept edge: after the gate’s Suspended has been replayed and BEFORE await_resume can append a Resumed, so the refusal appends nothing and leaves the run parked exactly where it was, ready for a conforming approval. It is therefore not reachable on replay at all: a recorded Resumed is history and is fed to the gate untouched. See crate::approval.

Fields

§node: String

The id of the gate node the run is parked at.

§violations: Vec<ApprovalViolation>

Every way the input failed the schema, in a stable order.

§

GraphEncode(Error)

The graph document could not be serialized to compute its hash. A graph is plain data, so this does not arise in practice; it exists to keep the hashing edge honest rather than panicking on a serde_json error.

§

Runtime(RuntimeError)

A RunCtx operation surfaced a runtime error (replay divergence, a dangling write needing reconciliation, a live provider failure, a store failure). Passed through unchanged.

Implementations§

Source§

impl EngineError

Source

pub fn is_permanent(&self) -> bool

Whether this refusal is PERMANENT: a pure function of the frozen graph document and the recorded log, so the same drive re-fails identically forever and no retry, registration, or live call can change the answer. false means TRANSIENT: the refusal depends on the environment, on how the drive was invoked, or on a live call, so a retry (possibly with different flags) can succeed.

A graph driver records a terminal RunFailed for a permanent refusal so a dead run stops reading as running, and leaves a transient one recoverable exactly as it was. Because a wrong true kills a run that would have come back and a wrong false costs only an operator’s re-drive, an arguable variant is classified TRANSIENT.

The match is exhaustive with no wildcard arm on purpose: a new variant does not compile until someone decides which side it falls on.

§The table

PERMANENT:

  • MapOverNotAList: the over reference and the routed value are both recorded, so the resolve re-runs to the same non-list every time.
  • UnsupportedMapBody / UnsupportedFoldBody: the body form is a field of the frozen document. No retry makes a subgraph body run; only a NEW document (a new run) does.
  • NoBranchCaseMatched: the cases are the document’s and the routed value is recorded, so the same no-match reproduces exactly.
  • BranchDecisionUnmatched: the reply is decoded from a RECORDED model completion, never re-requested, so the mapping re-fails on replay. The model is not asked again, which is what separates this from a live provider failure.
  • FoldNoComparableCandidate: the argmax reads the recorded pass outputs; nothing in the log can become comparable later.
  • FoldBoundExceeded: the bound and on_bound are the document’s, and the passes that failed the predicate are recorded. The loop cannot be given more passes without changing the document.
  • MalformedGraph: a property of the document alone (a cycle, a dangling body reference, a bound below one). The supplied document is pinned to the run by the recorded graph_hash, so “supply a fixed one” is not a retry of THIS run; it is a new run.

TRANSIENT:

  • UnknownAgent / UnknownTool: the document names a hash or a name; whether it RESOLVES is a fact about this invocation’s resolvers, which is registration, not meaning. Registering the agent on the server, or passing the missing --agent file, makes the same log drive on. Killing the run for a forgotten flag would be the exact mistake this split exists to avoid.
  • ToolFailed: a live call failed after its retry policy. The tool is the outside world; a resume can reach a world that answers. (A recorded failure does replay, but re-driving is the operator’s decision to make, not the engine’s to foreclose.)
  • ApprovalSchemaViolation: NOT permanent, and the clearest case of it. The refusal is about an input that has not been recorded and never will be; the run is still parked at its gate, and a conforming approval can arrive at any moment. This variant is not even reachable on replay.
  • GraphEncode: arguable, so transient. It is a serializer edge rather than a statement about the document’s meaning, and it is raised before begin_graph writes the run head, so there is no run for a terminal to belong to. Classifying it permanent would invite appending RunFailed onto a log with no GraphRunStarted.
  • Runtime: everything the RunCtx surfaces (a store failure, a provider failure, a replay divergence, a dangling write needing reconciliation). Store and provider failures are plainly retryable; a divergence or a reconciliation refusal is the operator’s to resolve, and resolve exists precisely so such a run continues. None of it is the engine’s to declare dead.

Trait Implementations§

Source§

impl Debug for EngineError

Source§

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

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

impl Display for EngineError

Source§

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

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

impl Error for EngineError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<RuntimeError> for EngineError

Source§

fn from(source: RuntimeError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

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> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> 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> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

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

Source§

type Error = !

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