-
Notifications
You must be signed in to change notification settings - Fork 0
Examples
The repository keeps runnable examples under
examples/. Run every command from the repository root.
The catalog below is an annotated tour of every example, ordered from the simplest local graph to the most deeply recursive — an agent that authors and runs its own workflow. For each example: what it shows, how to run it, and which recursive-language-model (RLM) concept it demonstrates.
Examples whose name starts with openai_ (plus orchestrator_subagents) call a
real hosted provider and require the openai Cargo feature and an API key:
export OPENAI_API_KEY=... # or copy .env.example to .env
cargo run --example <name>The default build is offline: basic_graph, complex_graph,
durable_graph, agent_loop_tools, rag_blueprint, and subconscious_loop
run with no key and no feature flag.
cargo run --example basic_graphA minimal durable graph: a whole-state agent/tool loop. A GraphBuilder over
Update == State (the overwrite reducer, so each node returns the full next
state) wires an agent node and a tool node. Conditional edges route agent -> tool while needs_tool is set, otherwise agent -> END; tool clears the flag
and loops back to agent.
Recursive concept: the base case. A typed state graph is a loop — a model node and a tool node calling each other until a condition ends the run. This is the smallest unit the deeper examples nest and compose.
cargo run --example durable_graphDurable execution with partial updates, a reducer, and checkpointing. State
is a Counter; each node returns a small i64 partial update, and a
ClosureStateReducer folds updates into the running counter while appending an
audit-log line. The graph runs on a thread backed by an InMemoryCheckpointer,
and the example lists the checkpoints written at each superstep boundary.
Recursive concept: durability/observability of a run. Checkpoints make each superstep an inspectable value — the persistence foundation that lets a parent pause, resume, and reason about a child computation.
cargo run --example complex_graphA nested subgraph embedded inside a parallel fan-out / join. dispatch
fans out (via Command::goto) into two branches that run concurrently. One
branch (branch_sub) is a subgraph embedded with adapter_subgraph_node; its
child itself embeds a grandchild via shared_subgraph_node, so a value flows
up through two subgraph levels (0 -> +5 -> +1 = 6). The reducer deterministically
joins the branches before join finalizes.
Recursive concept: graphs that run graphs. A node embeds another compiled graph, which embeds another — a subgraph inside a subgraph — the structural form of recursion in the graph runtime.
cargo run --example agent_loop_toolsThe harness agent loop end-to-end with a real tool and no network. A
ScriptedModel is scripted to first request a tool call, then (after seeing the
tool result) produce a final answer. A small CalculatorTool (add) is
registered so the loop has something to run. This is the canonical
model→tool→model loop that every higher example builds on.
Recursive concept: the harness agent loop — the substrate. A sub-agent is just this loop invoked one level deeper, so understanding it is the key to the recursive examples.
cargo run --example openai_chatThe simplest hosted happy path: register an OpenAiModel in an AgentHarness,
set it as the default model, run one question through the default agent loop, and
print the answer plus token usage.
Recursive concept: the leaf call. A single provider-neutral model invocation — the atom that recursion is built from.
cargo run --example openai_toolsThe full tool-calling loop against a real provider. A local get_weather Tool
is registered alongside an OpenAiModel; a question triggers the tool, the
harness runs it locally, feeds the result back, and the model produces the final
answer.
Recursive concept: model → tool → model over the network. The tool boundary here is exactly the boundary a sub-agent later occupies.
cargo run --example openai_structuredSchema-constrained output. RunPolicy::default_response_format is set to a
ResponseFormat::json_schema describing a {sentiment, score} object; the
harness attaches it to every request and extracts the parsed JSON into the
run's structured response.
Recursive concept: typed channels between levels. Structured output is how a parent reliably reads a child's result as data — the typed wire an orchestrator needs to route on a sub-agent's answer.
cargo run --example openai_graph_agentA durable graph whose node drives a real OpenAI-backed harness. An AgentHarness
is wrapped in an Arc, captured in a graph-node closure, and the graph runs
START -> agent -> END: the node calls the harness (which talks to OpenAI),
stores the answer in graph state, and ends.
Recursive concept: the two runtimes compose. The durable graph runtime and the harness agent loop nest cleanly — a graph node is an agent — which is what makes graph→agent→graph recursion possible.
cargo run --example orchestrator_subagentsFlagship registry showcase: an orchestrator that designs which sub-agents to
call at runtime. Several specialized SubAgents (researcher, coder,
summarizer) — each an OpenAiModel with a distinct system prompt — are wrapped
as SubAgentTools and registered by name in a CapabilityRegistry. The flow:
- Register named capabilities.
- Discover the available names + descriptions back out of the registry (nothing hard-coded into the planner).
- Design — an orchestrator agent, given the task plus the discovered menu, decides via structured output which sub-agents to invoke.
-
Bind at runtime — each chosen name is resolved with
CapabilityRegistry::tool, the sub-agents run in parallel (join_all), and their results are composed into a final answer.
Recursive concept: agents calling agents. A model decides which other agents to run, then runs them as tools — orchestration is one model invoking models, with capabilities named, discovered, and bound at runtime rather than wired in statically.
cargo run --example rag_blueprintCompiles the spec's support_agent .rag blueprint. The example parses the
expressive-language source into a Program, compiles it into a Blueprint,
prints the node/edge/route structure, then bind_capabilities resolves the
blueprint's referenced model and tools against a CapabilityResolver.
Recursive concept: programs as runtime values. A declarative .rag
workflow lowers — lexer → parser → compiler — into the same graph/harness runtime
as hand-written Rust. This is the human-authored half of self-authoring, and the
safe boundary the next example lets a model write into.
cargo run --example openai_self_blueprintThe deepest recursion: the agent authors its own graph. OpenAI is asked to
emit only .rag source for a small agent graph (given the grammar plus a
worked example), the .rag text is extracted from the reply, then run through the
same safe pipeline as a human-authored blueprint: parse_str -> compile ->
print the Blueprint -> bind_capabilities against a CapabilityResolver
allowlist (the policy gate — only allowlisted models/tools pass) -> build_graph
with a NodeFactory -> run to END. The model never executes code; it only
produces declarative source that a Rust-side factory materializes, and the
capability allowlist is the safety boundary. Parse/compile failures print the
diagnostic and the offending source instead of panicking.
Recursive concept: self-authoring — a model emits a blueprint that compiles through the same registry-bound compiler path and runs on the same runtime the model is already executing in. The harness describes and re-enters itself, with the capability allowlist as the policy gate.
cargo run --example subconscious_loopA multi-file example (under examples/subconscious_loop/) that maps a
LangGraph-style autonomous closed-loop harness with a dedicated subconscious
layer onto the typed graph runtime, kept fully deterministic so it runs offline
and under cargo test. The graph models three cognitive tiers — a quick layer
(frontend_agent turns channel payloads into macro instructions, then compiles
the final response), a reasoning layer (agent_execution does mock retrieval
from long-term memory, simulates sub-agent execution, extracts semantic traces,
emits a sequential world-state diff, and decides whether to escalate), and a
subconscious layer (subconscious_eval consumes gated world summaries and
emits a short steering directive, then resets the trigger). A
summarization_gate compresses diffs and a context_manager_hook evicts
semantic history into a mock vector store when context_utilization crosses a
threshold. Nodes return a StatePatch merged through a ClosureStateReducer
rather than overwriting state. The matching integration tests live in
tests/e2e_subconscious_loop_example.rs.
Recursive concept: a runtime that steers itself. A bounded loop feeds its own compressed world-state back into a subconscious node that emits steering for the next cycle — recursion as a self-regulating control loop over durable graph state, all without a network call.
cargo run --example goals_and_todosWires the two per-thread productivity primitives — a durable goal
(graph::goals) and a kanban task board (graph::todos) — together on one
thread and one shared InMemoryStore, and lets the goal drive the board. A
ThreadGoal ("Ship the v2 release") is the completion contract with a token
budget; a TaskBoard holds three cards; a goal_gate_node forms a self-driving
loop where each iteration advances the board by one kanban transition (Todo →
InProgress → Done) and completes the goal once every card is done. The gate keeps
looping while the goal is Active and under budget, accounting each iteration's
token usage, and routes to END when the goal completes. Prints the board
markdown and final goal status.
Recursive concept: intent that carries itself forward. Durable per-thread
state (the goal) steers a bounded graph loop that mutates other durable state
(the board), with the graph's recursion_limit and the goal's budget as the two
stop conditions — continuation without a heartbeat. See
Goals and Todos for the full design.
Copy-pasteable snippets for the newest harness/graph/registry surfaces; each mirrors its cited test.
Resolution normally skips ModelStatus::Retired models; set allow_retired to
opt a retired model back in. (See src/harness/model/test.rs
registry_skips_retired_models_across_every_resolution_path.)
use tinyagents::harness::model::{ModelRegistry, ModelSelection};
// Without allow_retired this returns None; with it the retired model resolves.
let binding = registry.resolve(ModelSelection {
requested: Some("gpt-legacy".into()),
allow_retired: true,
..ModelSelection::default()
});Emits no events; unresolved selections simply return None.
Turn a model's call to an unregistered tool into a recoverable tool-error
message instead of aborting the run. (See
tests/e2e_unknown_tool_policy.rs::return_tool_error_preserves_original_arguments.)
use tinyagents::harness::runtime::{RunPolicy, UnknownToolPolicy};
harness.with_policy(RunPolicy {
unknown_tool: UnknownToolPolicy::ReturnToolError,
..RunPolicy::default()
});
// The next run injects an "unknown tool `..`" message and keeps going.Emits AgentEvent::UnknownToolCall { requested_name, arguments, recovery, .. }
(here recovery == "tool_error", with the original arguments preserved).
Prepare an isolated workspace, enforce that every tool path stays inside it, then
clean up — threading the descriptor into a run so tools inherit the sandbox.
(See src/harness/workspace/test.rs.)
use std::path::Path;
use tinyagents::harness::context::{RunConfig, RunContext};
use tinyagents::harness::events::EventSink;
use tinyagents::harness::workspace::{
SharedRootWorkspace, cleanup_workspace, prepare_workspace,
};
let events = EventSink::new();
let provider = SharedRootWorkspace::new("/work");
let ws = prepare_workspace(&provider, &events, "run-7", Some("worker")).await?;
ws.enforce(Path::new("/work/out.txt"), &events)?; // Err for paths outside the root
let ctx = RunContext::new(RunConfig::new("run-7"), ()).with_workspace(ws.clone());
cleanup_workspace(&provider, &events, &ws).await?;Emits workspace.prepared and workspace.cleanup; a blocked path emits
workspace.violation and fails closed.
Gate tool exposure and execution: sandboxed tools require a sandboxed workspace,
approval-gated tools require an explicit grant, and oversized results are
truncated. (See src/harness/middleware/library/test.rs
tool_policy_requires_sandbox_for_sandboxed_tool,
tool_policy_truncates_oversized_results.)
use tinyagents::harness::middleware::ToolPolicyMiddleware;
let mw = ToolPolicyMiddleware::new(policies) // HashMap<String, ToolPolicy>
.require_sandbox(true)
.require_approval(["deploy"])
.enforce_result_bytes(true);No dedicated event: blocked calls surface as TinyAgentsError::Validation, and
oversized results are truncated in place with a max_result_bytes note attached.
inheriting composes a parent allow/deny with a child's so a sub-agent can only
narrow, never widen, the exposed tools. (See
src/harness/middleware/library/test.rs
contextual_selection_inheriting_narrows_never_widens,
contextual_selection_emits_exposure_event.)
use tinyagents::harness::middleware::ContextualToolSelectionMiddleware;
// parent allow ∩ child allow, then union of both deny lists.
let mw = ContextualToolSelectionMiddleware::inheriting(
Some(["a", "b", "c"]), // parent allow
["c"], // parent deny
Some(["b", "c", "d"]), // child allow
Vec::<String>::new(), // child deny
);Emits AgentEvent::ToolsFiltered { excluded, remaining, .. } before the model
call.
Request a control outcome from middleware; the loop honors it at the checkpoint
after the model call, keeping the highest-precedence request. (See
tests/e2e_control_and_steer.rs, src/harness/context/test.rs
request_control_keeps_highest_precedence.)
use tinyagents::harness::context::MiddlewareControl;
// From inside a middleware's after_model, on `ctx: &mut RunContext`:
ctx.request_control(MiddlewareControl::StopWithFinal("stopped".into()));
// A stronger Interrupt would not be downgraded by a later StopWithFinal.StopWithFinal ends the run (no more tools) and still emits run.completed
plus control.applied; Interrupt surfaces as TinyAgentsError::Interrupted.
Preflight reserves against estimated input tokens (blocking oversized calls) and
reconciles against actual usage; a separate cap bounds cache-read tokens. (See
src/harness/middleware/library/test.rs
budget_preflight_reserves_and_reconciles,
budget_enforces_cached_input_token_limit.)
use tinyagents::harness::middleware::{BudgetLimits, BudgetMiddleware};
let mw = BudgetMiddleware::new(BudgetLimits {
max_input_tokens: Some(5),
max_cached_input_tokens: Some(10),
..BudgetLimits::default()
});Emits AgentEvent::BudgetReserved on preflight and BudgetReconciled { actual_input_tokens, .. }
after the call; exhaustion emits BudgetExceeded { blocked: true, .. } and errors
with TinyAgentsError::LimitExceeded.
Seed a sink with a stream id so (stream_id, offset) re-mints identical event
ids across restarts/replays. (See src/harness/events/test.rs
stream_id_prefix_makes_event_ids_stable_and_collision_free.)
use tinyagents::harness::events::EventSink;
let sink = EventSink::with_stream_id("run-42");
let record = sink.emit(tinyagents::harness::events::AgentEvent::StateUpdate);
assert_eq!(record.id.as_str(), "run-42-evt-0"); // stable across restartIds are "<stream_id>-evt-<offset>"; default sinks get process-unique prefixes.
Run a fallible async closure over items with bounded concurrency, per-item and
total timeouts, and a cancellation token — results stay in input order. (See
src/graph/parallel/test.rs.)
use std::time::Duration;
use tinyagents::harness::cancel::CancellationToken;
use tinyagents::{ParallelOptions, map_reduce};
let token = CancellationToken::new();
let out = map_reduce(
vec![1u64, 2, 3],
ParallelOptions::default()
.with_item_timeout(Duration::from_millis(50))
.with_total_timeout(Duration::from_secs(5))
.with_cancellation(token),
|_index, n| async move { Ok::<_, tinyagents::TinyAgentsError>(n * 10) },
)
.await?;
let values: Vec<u64> = out.into_successes();No events; a total timeout yields TinyAgentsError::Timeout, a cancelled token TinyAgentsError::Cancelled.
The orchestrate_list tool filters spawned tasks by kind and a creation-time
window. (See src/graph/orchestration/test.rs
list_tool_honors_created_window_and_kind.)
use serde_json::json;
use tinyagents::graph::orchestration::{OrchestrationTool, OrchestrationToolKind};
use tinyagents::harness::tool::{Tool, ToolCall};
let list = OrchestrationTool::new(OrchestrationToolKind::List, store);
let result = list
.call(&(), ToolCall::new("l1", "orchestrate_list",
json!({ "kind": "sub_agent", "created_after_ms": 0 })))
.await?;
// result.raw is a JSON array of matching task snapshots.No events; the tool result's raw holds the filtered task list.
Snapshot the registry (components + aliases) for audit/UI, and run integrity
diagnostics. (See src/registry/capability/test.rs snapshot_enumerates_aliases,
diagnostics_flag_name_reused_across_kinds.)
use tinyagents::registry::ComponentKind;
let snapshot = registry.snapshot();
for a in &snapshot.aliases {
println!("{:?} {} -> {}", a.kind, a.alias, a.canonical);
}
let components = snapshot.by_kind(ComponentKind::Model);
let diagnostics = registry.diagnostics(); // e.g. a name reused across kindsNo events; snapshot serializes for audit logs and diagnostics returns any
integrity warnings.
Reusable contract suites prove every TaskStore/Checkpointer backend behaves
interchangeably, including concurrent access and replay-after-restart. (See
tests/conformance.rs.)
use tinyagents::graph::orchestration::{InMemoryTaskStore, JsonlTaskStore};
use tinyagents::graph::testkit::conformance::{
checkpointer_concurrent_contract, taskstore_concurrent_contract, taskstore_replay_contract,
};
taskstore_concurrent_contract(std::sync::Arc::new(InMemoryTaskStore::new()));
taskstore_replay_contract(|| JsonlTaskStore::open(&path).unwrap());No events; each helper panics on a contract violation, so a passing call is the assertion.
- Harness — the agent loop, sub-agents, steering, and the surfaces these examples drive.
- Graph Runtime — subgraphs, reducers, and checkpointing.
-
Providers — configuring
OpenAiModeland compatible hosts.
Recursive language-model (RLM) harness for Rust.
Getting started
Concepts
Modules
Providers
Contributing