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
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.
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).
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
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.
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
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.
UnknownAgent
An agent node referenced an agent hash the resolver could not supply.
Fields
UnknownTool
A tool node named a tool the resolver could not supply.
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.
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
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
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
impl EngineError
Sourcepub fn is_permanent(&self) -> bool
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: theoverreference 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 asubgraphbody 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 andon_boundare 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 recordedgraph_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--agentfile, 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 beforebegin_graphwrites the run head, so there is no run for a terminal to belong to. Classifying it permanent would invite appendingRunFailedonto a log with noGraphRunStarted.Runtime: everything theRunCtxsurfaces (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, andresolveexists precisely so such a run continues. None of it is the engine’s to declare dead.
Trait Implementations§
Source§impl Debug for EngineError
impl Debug for EngineError
Source§impl Display for EngineError
impl Display for EngineError
Source§impl Error for EngineError
impl Error for EngineError
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()