TinyAgents is a small, provider-neutral agent harness for Rust, plus a durable
typed state-graph runtime. It takes its shape from
LangChain (models, tools, middleware, structured
output, streaming, usage/cost) and
LangGraph (START/END, nodes,
conditional edges, channels/reducers, checkpoints, interrupts, subgraphs, time
travel) — rebuilt as ordinary, typed Rust with no hidden magic.
It is for Rust services that need to call models and tools in a loop, want that loop to be resumable and inspectable, and would rather not carry a Python runtime or a framework's DSL to get there.
TinyAgents is a Cargo workspace, not one crate. Depend on the pieces you need:
tinyagents-harness— provider-neutral model calls, typed tools, middleware, structured output, streaming, usage/cost accounting, retries, caching, and memory. Features:sqlite,tools,multimodal,tracing.tinyagents-graph— a LangGraph-style durable, typed state graph:START/END, nodes, conditional edges,Sendfanout, reducers/channels, checkpoints, interrupts, subgraphs, and time travel. Features:sqlite,tracing.tinyagents-language— the.ragblueprint format: a declarative, side-effect-free workflow description that lexes, parses, and compiles into the same graph and harness types as hand-written Rust.tinyagents-registry— a named capability catalog (models, tools, agents, graphs, routers) that.ragand application code bind against by name, plus an offline model price/capability catalog.tinyagents-session— a SQLite-backed store for session history, messages, tool calls, cost, and run lineage.tinyagents-tracing— thetracingmacros the other crates gate behind theirtracingfeature. Compiled out by default.tinyagents-integration-tests— cross-crate tests and the runnable examples referenced below (not published, workspace-internal).
None of the crates are published to crates.io (publish = false in every
Cargo.toml), so add them as git or path dependencies:
[dependencies]
tinyagents-harness = { git = "https://github.com/tinyhumansai/tinyagents", package = "tinyagents-harness" }
tinyagents-graph = { git = "https://github.com/tinyhumansai/tinyagents", package = "tinyagents-graph" }
tinyagents-language = { git = "https://github.com/tinyhumansai/tinyagents", package = "tinyagents-language" }
tinyagents-registry = { git = "https://github.com/tinyhumansai/tinyagents", package = "tinyagents-registry" }
# The code samples below build `Message` and provider types directly from
# TinyInference, the message/model crate TinyAgents is built on. It is a
# separate git dependency, not re-exported by the crates above.
tinyinference = { git = "https://github.com/tinyhumansai/tinyinference", package = "tinyinference" }A minimal typed graph — a whole-state agent/tool loop (trimmed from
examples/basic_graph.rs):
use tinyagents_graph::*;
use tinyinference::message::Message;
#[derive(Clone, Debug)]
struct AgentState {
messages: Vec<Message>,
needs_tool: bool,
}
let graph = GraphBuilder::<AgentState, AgentState>::overwrite()
.add_node("agent", |mut state: AgentState, _ctx: NodeContext| async move {
state.messages.push(Message::assistant("checking the local tool"));
Ok(NodeResult::Update(state))
})
.add_node("tool", |mut state: AgentState, _ctx: NodeContext| async move {
state.messages.push(Message::tool("echo", "tool result"));
state.needs_tool = false;
Ok(NodeResult::Update(state))
})
.set_entry("agent")
.add_conditional_edges(
"agent",
|state: &AgentState| if state.needs_tool { "tool".to_string() } else { "done".to_string() },
[("tool", "tool"), ("done", END)],
)
.add_edge("tool", "agent")
.compile()?;
let run = graph.run(AgentState { messages: vec![], needs_tool: true }).await?;Run it for real:
git clone git@github.com:tinyhumansai/tinyagents.git
cd tinyagents
cargo run -p tinyagents-integration-tests --example basic_graphA one-shot model call through the harness (export OPENAI_API_KEY=... then
cargo run -p tinyagents-integration-tests --example openai_chat):
use std::sync::Arc;
use tinyagents_harness::runtime::AgentHarness;
use tinyinference::message::Message;
use tinyinference::providers::openai::OpenAiModel;
let model = OpenAiModel::from_env()?;
let mut harness: AgentHarness<()> = AgentHarness::new();
harness.register_model("openai", Arc::new(model)).set_default_model("openai");
let run = harness
.invoke_default(&(), vec![Message::user("What is a Rust trait?")])
.await?;
println!("{}", run.text().unwrap_or_default());tinyagents-graph is a durable, typed state graph modeled on LangGraph:
START/END markers, nodes, static and conditional edges, Command-based
routing, Send fanout, reducers over named channels, checkpointing (with an
optional sqlite backend), interrupts, streaming events, topology export, and
replay/time travel across superstep boundaries. A node can embed another
compiled graph as a subgraph, so a whole workflow can appear as a single step
inside a larger one.
tinyagents-harness runs the model/tool agent loop: provider-neutral model
calls, typed tool definitions, middleware, structured output, streaming,
usage and cost accounting, retries and limits, response caching, memory, and
a testkit for exercising the loop without a live provider. An agent can be
wrapped as a tool and handed to another agent (SubAgent /
SubAgentSession / SubAgentTool), which is how multi-agent orchestration
is composed — plain function composition, not a distinct execution mode.
tinyagents-registry is a name-addressable catalog of models, tools, agents,
graphs, and routers. .rag blueprints and application code both resolve
capabilities by name against it rather than holding direct handles, which is
what lets a blueprint be validated against exactly the capabilities a host
chose to register.
tinyagents-language implements .rag: a declarative, side-effect-free
format for describing a graph's state channels, nodes, routes, and named
capability references. It compiles through a fixed pipeline —
source -> lexer -> tokens -> parser -> AST -> compiler -> Blueprint — into
the same tinyagents-graph and tinyagents-harness types produced by
hand-written Rust. It can only reference capabilities by name; it has no way
to embed arbitrary code, so a blueprint is bound and validated against a
registry before it runs. See
examples/rag_blueprint.rs.
Every provider speaks the OpenAI Chat Completions wire format, so one adapter
reaches all of them; only the base URL and model differ. Built-in presets:
OpenAI, Anthropic (via its OpenAI-compatible endpoint), DeepSeek, Groq, xAI,
OpenRouter, Together, Mistral, and Ollama (local). Any other OpenAI-compatible
endpoint works by base URL — see providers.env.example
for the full list and configuration format.
All live in
crates/tinyagents-integration-tests/examples/:
basic_graph,complex_graph,durable_graph,resilient_graph— a minimal typed graph, then conditional routing/fanout, checkpoint/resume/time-travel, and node-level retry.agent_loop_tools— the agent/tool loop the harness runs.orchestrator_subagents— an orchestrator agent that resolves and calls sub-agents by name from the registry.rag_blueprint— parse and compile a.ragworkflow, then bind it against a registry.openai_self_blueprint— a model emits a.ragblueprint that is compiled and run.goals_and_todos— a durable goal driving a task-board kanban on one thread.openai_chat,openai_tools,openai_structured,openai_graph_agent— provider-backed chat, tool calling, structured output, and a graph-driven agent (all needOPENAI_API_KEY).subconscious_loop— an offline, testable autonomous closed-loop harness (see its own README).
docs/spec/README.md— architecture specification.- Wiki — Harness, Graph Runtime, Registry, Expressive Language, Providers, Quick Start, Examples, Development.
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo build --workspace --all-targets
cargo test --workspacecargo 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 -p tinyagents-integration-tests --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.
Read CONTRIBUTING.md before opening a pull request.
TinyAgents is licensed under GPL-3.0-only.
