Architecture - tinyhumansai/tinyagents GitHub Wiki
Architecture
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.
This page shows how the crates layer together.
The crates
| Crate | Path | Owns |
|---|---|---|
| Definition | crates/tinyagents-definition/ |
the host-owned agent definition vocabulary: identity, description, declared model/tools/delegates, and a read-only catalogue seam |
| Harness | crates/tinyagents-harness/ |
provider-neutral model calls, typed tools, middleware, structured output, streaming, usage/cost, retry/limits, failover, cache, steering, typed terminal outcomes |
| Tasks | crates/tinyagents-tasks/ |
graph-independent detached-task machinery: task model, TaskStore (in-memory and JSONL), DetachedTaskRegistry, restart reconciliation, the durable completion router, and the orchestrate_* control tools |
| Graph runtime | crates/tinyagents-graph/ |
durable typed state graphs: START/END, nodes, edges, conditional routing, commands, Send fanout, reducers/channels, checkpoints, interrupts, subgraphs, streaming, observability, time travel; tinyagents_graph::orchestration is a re-export shim over Tasks |
| Registry | crates/tinyagents-registry/ |
named capability catalog (models, tools, agents, graphs, routers) plus an offline model price/capability catalog and the model router |
| Session | crates/tinyagents-session/ |
durable SQLite-backed session history, JSONL transcripts and a run ledger, separate from harness and graph durability |
| Runtime | crates/tinyagents-runtime/ |
host-neutral stateful sessions over the harness and the transcript storage seam |
| Orchestration | crates/tinyagents-orchestration/ |
host-neutral subagent invocation and lifecycle (SubagentDriver, spawn admission, job registry), plus teams and workflows |
| Live | crates/tinyagents-live/ |
live (realtime voice) agent sessions that run a model's tool calls through the harness's own admission and middleware pipeline |
| Integration tests | crates/tinyagents-integration-tests/ |
cross-crate tests and runnable examples; not a library dependency |
There is no facade crate: a consumer depends directly on the crates it needs. See Quick Start for the dependency snippet and feature flags, and Capabilities for a full index of what each crate exposes.
Dependency direction
definition <- harness <- { tasks, registry, live }
harness + tasks <- { graph, session }
harness + session <- runtime
graph + tasks + harness + session + runtime <- orchestration
Read A <- B as "B depends on A". The list below is the exact set of library
dependencies from each crate's Cargo.toml ([dependencies], not
dev-dependencies):
tinyagents-definitionhas no internal dependencies.tinyagents-livedepends on harness.tinyagents-harnessdepends ontinyagents-definition.tinyagents-tasksdepends on harness (task tools are ordinary harness tools) and does not depend on the graph engine.tinyagents-graphdepends on harness and tasks.tinyagents-registrydepends on definition and harness.tinyagents-sessiondepends on harness (with itssqlitefeature) and tasks.tinyagents-runtimedepends on harness and session.tinyagents-orchestrationdepends on graph, tasks, harness, session, and runtime; none of those depend back on it.
Nothing in harness reads from session, and session is not required to run a harness or a graph at all — it is a separate persistence domain for history and search.
The crates also depend on vendored libraries: tinyinference-llm
(vendor/tinyinference/crates/tinyinference-llm, messages and provider types)
and tinytools (vendor/tinytools/crates/tinytools, the shared Tool trait);
the harness additionally uses tinyinference-core and
tinyinference-embeddings.
Request flow
+-------------+ +-------------------------------------------+
| Application |------->| Capability Registry |
| Rust code | | models | tools | agents | graphs | routers |
+------+------+ +---------------------+---------------------+
| |
| v
| +-------------------------------------------+
+------------->| Durable Graph Runtime |
| typed state | nodes | edges | checkpoints |
+---------------------+---------------------+
|
v
+-------------------------------------------+
| Agent Harness |
| prompts | tools | middleware | usage/cost |
+----+--------------------------+-----------+
| |
v v
+------------------+ +------------------+
| Model Providers | | Typed Tools |
| OpenAI and | | local functions |
| compatible hosts | | external systems |
+------------------+ +------------------+
A graph node can call into the harness to run a model or a tool. The harness does not depend on the graph: it runs standalone as a single agent loop, or as one node's implementation inside a larger graph.
Composition: agents calling agents, graphs embedding graphs
Two composition features let larger systems be built out of the same typed pieces, rather than a separate execution model:
- Sub-agents.
tinyagents_orchestration::subagent(SubAgent,SubAgentSession,SubAgentTool,SubagentDriver) wraps a whole harness agent as an ordinaryTool. A parent agent calling a sub-agent is just a tool call; the child run gets its ownroot_run_id/parent_run_idand its usage and cost roll up to the parent.SubagentDriveris the single engine for an agent step, so team members and workflow agent children run through it too. See Harness. - Subgraphs.
tinyagents_graph::subgraph(adapter_subgraph_node,shared_subgraph_node) embeds one compiled graph as a single node inside another. The embedded graph's checkpoints are namespaced under the embedding node id, so durability composes the same way state does. See Graph Runtime.
tinyagents_graph::recursion (RecursionPolicy, RecursionStack,
RunTree) tracks parent/child run lineage for both of the above and enforces
a depth limit, so a chain of sub-agents or nested subgraphs stays bounded and
observable rather than growing without a cap. It is a safety and
observability mechanism, not a distinct architectural layer.
Harness
The harness owns nondeterministic agent work:
- model request construction and provider adapter calls
- tool registration and execution
- middleware hooks, structured output strategy, streaming deltas
- provider error normalization, usage and cost records
- retries, limits, cancellation, cache, memory/embeddings
- a typed
TerminalOutcomefor why a run ended, per-turn events (TurnStarted/TurnCompleted/MessageAppended), steering, and nested tool calls (off by default) - deterministic model and tool test doubles (
tinyagents_harness::testkit)
See Harness.
Graph runtime
The graph owns deterministic state movement:
- typed state, reducers/channels, and updates
- nodes, static and conditional edges, commands,
Sendfanout - checkpoints, interrupts, time travel, topology export, run status, observability
- subgraphs (
tinyagents_graph::subgraph): a node embeds anotherCompiledGraph
Child-task orchestration (typed orchestrate_* task controls, task stores,
the detached-task registry, restart reconciliation, and the durable
completion router) lives in tinyagents-tasks, not in the graph engine;
tinyagents_graph::orchestration only re-exports it.
See Graph Runtime.
Tasks and orchestration
tinyagents-tasks owns the durable bookkeeping for detached
child work: TaskStore (InMemoryTaskStore, JsonlTaskStore),
TaskStoreRegistry, DetachedTaskRegistry, reconcile_orphaned_tasks,
SteeringRegistry/OrchestrationTool, and the completions module
(CompletionRouter, CompletionStore, NotifyMode, CompletionFormatter).
tinyagents-orchestration builds subagents, teams and workflows on top of it:
SubAgentJobRegistry is backed by DetachedTaskRegistry. See
Graph Runtime and
Harness.
Registry
The registry owns named capabilities, letting Rust code refer to a model, tool, agent, graph, or router by a stable name instead of a process global, and carries the offline model price/capability catalog. See Registry.
Design rule
Each crate keeps its contract narrow:
- the harness calls providers and tools
- the graph moves typed state through explicit topology
- the registry binds names to capabilities
- tasks own durable child-task bookkeeping, and orchestration drives subagents,
teams and workflows through one
SubagentDriver
No layer bypasses another layer's policy, observability, or validation boundary: a sub-agent call and a subgraph both pass through the same run-limit and event machinery as a top-level run.