ri-agent-graph
Graph-based agent orchestration for Rust — a LangGraph-inspired execution engine with checkpointing, parallel fan-out/fan-in, interrupt/resume, retry policies, and cryptographic execution receipts.
What it gives you
- Deterministic graph execution — define nodes as computational steps, edges as control flow, and execute with typed state flowing through the graph
- 8 node types —
llm,router,join,parallel,passthrough,state_transform,subgraph,human_approval - Parallel fan-out/fan-in with configurable join policies:
collect_array,merge_objects,first_non_null,all_success,quorum - Superstep execution loop — dispatch → execute → checkpoint → advance, with automatic retry and cancellation
- Checkpointing & interrupt/resume — SQLite-backed persistence with atomic transactions, crash recovery, and checkpoint mismatch detection
- Cryptographic receipts — HMAC-SHA256 authenticated
GraphExecutionReceiptV1with step-level digests and budget counters - Event streaming — node lifecycle events, token streaming, state snapshots via
StreamExt - Retry policies — per-node retry with configurable backoff, max retries, and predicate filters
- stack-ids integration —
TraceCtx,AttemptId,TrialIdat every layer for distributed tracing - Zero-cost abstractions — generic over user-defined state
S, no heap allocation beyond what your nodes require
Installation
Or in Cargo.toml:
[]
= "0.2"
Feature flags
| Flag | Default | Description |
|---|---|---|
checkpointing |
✅ on | SQLite-backed persistence via rusqlite |
To run without persistence:
= { = "0.2", = false }
Quick start
use *;
async
Core concepts
Graph & state model
The public API centers on three types:
AgentGraph<S>— immutable graph definition: nodes + edges + reducers. Built with the builder pattern and validated at.build().AgentState— key-value state (serde_json::Value) flowing through execution. Thread-safe viaArc<RwLock<>>.GraphExecutor<S>— the runtime engine. Wraps a graph and optional checkpoint store. Drives the superstep loop.
State is typed but flows as serde_json::Value internally, enabling heterogeneous workflows where different nodes operate on different state keys.
Superstep execution loop
1. Dispatch → Route edges from current frontier to target nodes
2. Execute → Run all target nodes (parallel via JoinSet for fan-out)
3. Checkpoint → Save attempt outcomes to SQLite (if enabled)
4. Advance → Set new frontier; halt if END sentinel reached
5. Repeat → Guarded by max_iterations; retry on failure with policy
State management
// Set/get typed values
state.set.await?;
state.set.await?;
let name: String = state.get.await?;
let count: i32 = state.get.await?;
// Optional access
let maybe: = state.get_opt.await?;
// Check existence
if state.contains.await?
// List all keys
let keys: = state.keys.await;
// Remove a key
state.remove.await?;
// Snapshot & restore
let snapshot = state.snapshot.await;
state.restore.await?;
State limits
let graph = builder
.with_state_limits
.build?;
Node types
| Type | Description | Status |
|---|---|---|
llm |
Invoke an LLM via Payload trait. Response merged via reducer. |
✅ |
router |
Conditional branching. Evaluates a predicate to select next edges dynamically. | ✅ |
join |
Fan-in synchronization. Waits for all parallel branches, merges state. | ✅ |
parallel |
Fan-out dispatch. Engine's JoinSet handles real concurrent execution. |
✅ |
passthrough |
No-op pass. Useful for fan-out distribution points between coordinator and workers. | ✅ |
state_transform |
10 declarative state mutations: set, copy, delete, increment, append, merge, merge_object, select, compare, format. |
✅ |
subgraph |
Reference another registered graph as a composable sub-workflow. | ✅ |
human_approval |
HITL gate. Emits InterruptError; resumes via checkpoint injection. |
✅ |
Router example
use ;
use json;
let graph = builder
.add_node
.add_node
.add_node
.add_node
.add_edge
.add_router
.add_edge
.add_edge
.add_edge
.build?;
Parallel fan-out with join
let graph = builder
.add_node
.add_node
.add_node
.add_node
.add_node
.add_node
.add_edge
.add_edge
.add_edge
.add_edge
.add_edge
.add_edge
.add_edge
.add_edge
.with_reducers
.build?;
Reducers
When parallel branches write to the same state key, a reducer resolves the conflict:
use Reducer;
new
.append_to // Concatenate arrays
.merge_into // Deep-merge objects
.with // Numeric addition
.with
.with_fn;
Checkpointing & interrupt/resume
use SqliteCheckpointStore;
let store = open.await?;
let executor = new
.with_checkpoint_store;
match executor.execute_with_interrupt.await
Retry on failure
use RetryPolicy;
let graph = builder
.add_node
.with_retry_policy
.build?;
Execution receipts
Every run produces a GraphExecutionReceiptV1:
Each StepExecutionReceiptV1:
node_id— which node executedattempt— attempt number (0-based)duration_ms— wall-clock durationinput_digest/output_digest— state hashes before/aftererror— error details if the node failedtrace_ctx/attempt_id/trial_id— fromstack-ids
Error handling
All fallible operations return Result<T, AgentGraphError>.
Event streaming
use StreamExt;
let executor = new;
let mut stream = executor.execute_stream.await?;
while let Some = stream.next.await
Ecosystem
| Crate | Description | Version |
|---|---|---|
| ri-agent-graph | Core graph execution engine (this crate) | v0.2.1 |
| agent-graph-mcp | MCP server — 25 typed tools for graph lifecycle, execution, approval, templates | v0.2.2 |
| stack-ids | Shared identity, scope, and trace primitives | v0.1.3 |
| llm-pipeline | Reusable LLM node payloads (Ollama, prompt templating, parsing) | v0.2.0 |
Comparison
| Feature | ri-agent-graph | LangGraph (Python) | LangGraph (JS) |
|---|---|---|---|
| Language | Rust | Python | TypeScript |
| Parallel fan-out | ✅ JoinSet | ✅ | ✅ |
| Checkpointing | ✅ SQLite | ✅ Postgres/SQLite | ✅ Postgres/SQLite |
| Interrupt/resume | ✅ Deterministic | ✅ Full | ✅ Full |
| Retry policies | ✅ Per-node | ✅ Per-node | ✅ Per-node |
| Event streaming | ✅ StreamExt | ✅ | ✅ |
| Cryptographic receipts | ✅ HMAC-SHA256 | ❌ | ❌ |
| MCP protocol server | ✅ Built-in | ❌ | ❌ |
| Zero-copy state | ✅ serde_json::Value | ❌ Python dict | ❌ JS object |
Claim boundaries
- Graph execution semantics only — this crate does not include LLM provider clients, prompt templating, or response parsing. Those belong in
llm-pipelineor your application layer. - Receipts prove structural execution — they carry cryptographic digests of the local execution trace only. They do not prove that an external LLM call occurred or what any provider's internal state was.
- Interrupt/resume is deterministic local — supports linear chains of deterministic
passthroughandstate_transformnodes with SQLite-bound state. It does not support resuming across LLM calls, network I/O, or external tool invocations. - Parallelism is best-effort — uses Tokio's
JoinSet. Unordered parallel writes to the same state key are rejected unless an explicitReduceris declared.
Verification
Roadmap
- Typed state extractors (derive macro for
StateExtract) - Graph visualization (Mermaid/DOT export from
graph_inspect) - Streaming LLM token passthrough to event stream
- Distributed checkpoint backends (PostgreSQL, S3)
- Subgraph composition with isolated state namespaces
- WebAssembly target (
wasm-bindgen, no_std without checkpointing) - Generic replay for non-deterministic node types
License
MIT — see LICENSE-MIT.
Built by RecursiveIntell — an applied R&D studio building local-first AI infrastructure.