TinyAgents is a durable agent and graph harness for Rust. It is a typed, checkpointed runtime where language models call models, agents call agents, graphs run graphs, and a model can author, compile, and run the very workflow it is standing inside — all as inspectable, checkpointed, policy-checked Rust.
Most agent frameworks stuff everything into one ever-growing context window and hope the model copes. TinyAgents takes the other stance: a long task is an external environment that gets decomposed, and the runtime is re-entrant, so a model can recurse over pieces of it instead of swallowing the whole thing at once. The concrete surfaces:
- Sub-agents (agents calling agents). A harness agent is exposed as a tool
to another agent, so orchestration is literally a model calling a model
(
SubAgent,SubAgentSession,SubAgentTool). - Recursion policy + depth tracking. The runtime tracks
root_run_id/parent_run_id, enforces a recursion limit, and rolls child runs' events, usage, and cost up to the parent as first-class observable runs. - Graphs that run graphs. A node can embed another compiled graph, so a whole compiled workflow can appear as a single step inside another one.
- Self-authoring (the deepest recursion). A model can emit a
.ragblueprint that compiles through the same registry-bound compiler path as a human-authored file, then runs on the same runtime the model is already executing in. The harness can describe and re-enter itself.
One language, one runtime: .rag blueprints lower into the exact same graph +
harness types as hand-written Rust — a language whose programs are the
runtime that interprets them.
Not in this crate, by design: the scripted CodeAct/REPL loop — an embedded
interpreter (Rhai, Python, JavaScript) executing model-written code cells whose
only host surface is capability calls. That is a host concern. TinyAgents gives
it everything it needs (the capability registry, the harness, typed
SessionId/CellId/CallId, and the repl_agent node kind for binding a
host-provided scripted node by name) without pulling an interpreter into your
dependency graph.
- Harness — provider-neutral model calls, typed tools, middleware, structured output, streaming, usage/cost accounting, retries and limits, response caching, memory/embeddings, summarization, steering, and a testkit.
- Graph runtime — LangGraph-style durable, typed state graphs:
START/END, nodes, edges, conditional routing, commands,Sendfanout, reducers/channels, checkpoints, interrupts, subgraphs, streaming, topology export, and time travel. - Registry — a named capability catalog (models, tools, agents, graphs,
stores, middleware, policy) that
.ragbinds by name. .ragexpressive language — a declarative, side-effect-free blueprint format that compiles (lexer → parser → compiler) into the runtime; the safe boundary for agent-authored plans.- Recursion & sub-agents — agents-as-tools, subgraphs, depth tracking, and a recursion policy so deep call trees stay bounded and observable.
- Durability & checkpoints — resume long runs, replay history, and travel back in time across superstep boundaries.
- Provider-neutral — one interface across hosted and local providers; swap models without rewriting workflows.
- Observability — normalized events, usage, and cost that roll up across recursive child runs.
- Structured output & streaming — typed responses and incremental token streams at the harness boundary.
+-----------------------+
| .rag blueprint |
| declarative workflow |
+-----------+-----------+
|
| compile / lower (by name)
v
+-------------+ +-------------------------------------------+
| Application |------->| Capability Registry |
| Rust code | | models | tools | agents | graphs | policy |
+------+------+ +---------------------+---------------------+
| |
| v
| +-------------------------------------------+
+------------->| Durable Graph Runtime |
| typed state | nodes | edges | checkpoints |
+---------------------+---------------------+
|
v
+-------------------------------------------+
| Agent Harness |
| prompts | tools | middleware | usage/cost |
+----+--------------------------+-----------+
| |
v v
+------------------+ +------------------+
| Model Providers | | Typed Tools |
| OpenAI/Anthropic | | local functions |
| Ollama/etc. | | external systems |
+------------------+ +------------------+
The recursion loop — agents call agents, and graphs run graphs:
+-------+
| START |
+---+---+
|
v
+-------------+ a sub-agent is just a tool,
| Agent Node | and a tool may itself be a
+------+------+ whole compiled graph...
|
+------+-------------------------+
| | |
needs tool calls sub-agent done
| | |
v v v
+-----------+ +---------------+ +-----+
| Tool Node | | SubAgent / | | END |
+-----+-----+ | Subgraph Node | +-----+
| +-------+-------+
| | depth +1, recursion policy,
| | child run rolls up usage/cost
+-- loops back --+--- re-enters the runtime ---+
to Agent Node (graph -> subgraph -> graph)
Add TinyAgents to your project:
[dependencies]
tinyagents = "1.5"The OpenAI (and OpenAI-compatible) provider is compiled in by default; the
build stays offline unless you actually make a call. Two optional Cargo
features gate heavier dependencies: sqlite (embedded SQLite checkpointer)
and tools (the builtin generic tool family).
To explore locally:
git clone git@github.com:tinyhumansai/tinyagents.git
cd tinyagents
cargo run --example basic_graphOpenAI-backed examples need an API key:
export OPENAI_API_KEY=...
cargo run --example openai_chatBound individual tool calls by installing shared timeout settings on the
harness. Tools return ToolTimeout::Inherit by default; they may instead opt
out with Unbounded or request a clamped explicit Millis budget:
use tinyagents::{AgentHarness, ToolTimeoutSettings};
let mut harness: AgentHarness<()> = AgentHarness::new();
harness.with_tool_timeout_settings(ToolTimeoutSettings::new(
120_000, // inherited default
1_000, // minimum explicit budget
3_600_000,
5_000, // scheduling grace for explicit budgets
));A per-tool deadline produces a recoverable tool-error message and the agent loop continues, so the model can retry or choose another tool. The run's wall-clock deadline remains the outer hard abort. Clones of the settings share their inherited value, allowing a host to update it without rebuilding a harness.
Export durable harness observations to Langfuse with the embedded client:
use tinyagents::{LangfuseClient, LangfuseTraceConfig};
let client = LangfuseClient::proxy("https://api.tinyhumans.ai", backend_jwt)?;
client
.send_observations(
LangfuseTraceConfig {
user_id: Some("user_123".to_string()),
session_id: Some("thread_abc".to_string()),
..Default::default()
},
&observations,
)
.await?;LangfuseClient::proxy sends to the backend
/telemetry/langfuse/ingestion endpoint with bearer auth. Use
LangfuseClient::direct(langfuse_url, public_key, secret_key) when an
application is allowed to talk to Langfuse directly.
Graph runs export the same way through GraphLangfuseExporter, which reuses the
harness LangfuseClient transport and turns supersteps and nodes into timed
spans (failures promoted to ERROR), with per-node tool health telemetry
attached to the trace:
use tinyagents::{GraphLangfuseExporter, LangfuseClient, LangfuseTraceConfig};
let exporter = GraphLangfuseExporter::new(LangfuseClient::from_env()?);
let observations = journal.read_from(run_id, 0).await?;
exporter
.send_observations(LangfuseTraceConfig::default(), &observations)
.await?;Because a graph run and the agent runs its nodes spawn share the same
root_run_id — the default Langfuse traceId for both exporters — exporting a
graph run and its child agents lands every step, node, model generation, and
tool call under one trace for full end-to-end observability.
All live in examples/:
basic_graph— a minimal typed state graph:START, nodes, edges,END.complex_graph— conditional routing, fanout, and richer topology.durable_graph— checkpoints, resume, and time-travel over supersteps.resilient_graph— node-level retry over transient failures, plus a resumable failure checkpoint thatretryrestarts after an outage clears.agent_loop_tools— the agent ↔ tool loop the harness runs.orchestrator_subagents— recursion in action: an orchestrator agent that calls sub-agents as tools, with depth tracking and rolled-up usage.openai_self_blueprint— the deepest recursion: a model authors a.ragblueprint that is compiled and run on the same runtime.rag_blueprint— load and run a declarative.ragworkflow.goals_and_todos— a durableThreadGoaldriving aTaskBoardkanban on one thread.subconscious_loop— an offline, testable autonomous closed-loop harness (seeexamples/subconscious_loop/README.md).openai_chat— a single provider-backed chat turn.openai_tools— tool calling against a hosted model.openai_structured— typed structured output.openai_graph_agent— a provider-backed agent driven inside a graph.
OpenAI-backed examples require OPENAI_API_KEY at run time.
Contributors working directly in the repository should also read the checked-in
architecture specification under docs/spec/README.md.
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo build --all-targets
cargo testcargo test never touches the network. To check the providers you hold keys
for — a chat call, a streaming call, and a tool call each, reported as a
provider | PASS/FAIL(reason) | latency(ms) table:
cp providers.env.example providers.env # fill in the keys you have; blank => skipped
PROVIDER_MATRIX=1 cargo test --test live_provider_matrix -- --nocaptureDialling is opt-in through PROVIDER_MATRIX=1, so a bare cargo test stays
offline even with a fully configured providers.env.
providers.env is gitignored — never commit real keys. See
src/harness/providers/openai/README.md
for the configuration format.
TinyAgents welcomes focused contributions that improve the graph runtime,
harness contracts, the registry, the .rag language, provider
adapters, tests, examples, and documentation.
Read CONTRIBUTING.md before opening a pull request.
TinyAgents is licensed under GPL-3.0-only.
Built by TinyHumans for the Rust agent ecosystem.
