Expand description
The graph document format: the Graph envelope, the six node kinds, the
edges that connect them, and the small payload types a node carries.
Everything here is pure data. No type reads the clock, draws randomness, or performs IO. That purity is deliberate: this crate is a leaf that the CLI, the control plane, and (later) a wasm dashboard projection all parse graph documents with, so it must drag in no runtime and no host dependency.
§Two postures, one format
A graph is a CONTROL document, not a data payload. A silently dropped field
could drop a gate or an unenforced budget, so parsing is STRICT: every
struct and the node enum carry #[serde(deny_unknown_fields)], and a stray
key is rejected rather than ignored. That is the opposite posture from the
event log, which stays forward-tolerant because it carries recorded data.
The one concession the two share is the additive schema_version
discipline (see SCHEMA_VERSION): a graph that was recorded under an
older build must still parse and validate under a newer one. Strict in,
additive-tolerant out.
§The optional node display name, and why it hashes unlike an agent’s
Every node payload carries an optional name: a short, purely
presentational label (“Approve the draft”) an author can hang on a node so
a rendered graph reads by intent instead of by id. Bounds mirror the
precedent set by the agent definition’s own name
(salvor_cli::agent_config::MAX_NAME_LEN): at most 64 CHARACTERS
(chars().count(), not bytes), and, when set, not empty or all
whitespace. [crate::validate] enforces both, node-precise.
An agent’s name is deliberately excluded from its agent_def_hash: an
agent is a long-lived identity that a run keeps replaying under the same
hash while an operator relabels it, so a rename must not mint a new
identity (see salvor_runtime::Agent::def_hash). A graph document has no
such identity to protect — it IS its hash, the whole reason POST /v1/graphs stores it content-addressed. So a node’s name gets NO
special treatment: it is an ordinary field on the payload struct, present
on the wire exactly when set (skip_serializing_if = "Option::is_none"),
and folds into the canonical JSON salvor_engine::graph_hash hashes like
any other field. Renaming a node is therefore authoring a new document
version, by design, the same way changing a prompt or an over
reference is.
Structs§
- Agent
Node - An
agentnode: a full agent loop referenced by content hash. - Branch
Case - One case of a
BranchNode: a name and the condition that selects it. - Branch
Node - A
branchnode: routes on a typed output. - Edge
- A directed edge: a typed payload flows from one node to another.
- Fold
Node - A
foldnode: bounded iteration that accumulates across passes. - Gate
Node - A
gatenode: human approval that suspends the run. - Graph
- A graph document: the control document authored once, submitted, hashed into a run, and then frozen. It carries the schema version, the set of nodes (each with a stable string id), and the edges that connect them.
- MapNode
- A
mapnode: fan-out a sub-run per element of a typed list, with a concurrency cap. - Tool
Node - A
toolnode: one direct tool invocation.
Enums§
- Branch
Condition - How a
BranchCaseis selected. Modeled as data; not evaluated in this crate. - Fold
Body - The body a
FoldNoderuns each pass. Adjacently tagged, mirroringMapBody, so adding a third form later stays additive. Anodebody names an existing node by id (checked for existence during validation); asubgraphbody embeds a wholeGraphand is deferred exactly as the map’s subgraph body is. - Fold
Join - How a
FoldNodefolds its passes into the single value it produces. - MapBody
- The body a
MapNodemaps each element through. - Node
- One node in a graph: exactly one of the six kinds the runtime knows how to execute.
Constants§
- SCHEMA_
VERSION - The schema version stamped onto every graph document.