# 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 | |---|---|---| | **Harness** | `crates/tinyagents-harness/` | provider-neutral model calls, typed tools, middleware, structured output, streaming, usage/cost, retry/limits, cache, memory, **sub-agents**, steering | | **Graph runtime** | `crates/tinyagents-graph/` | durable typed state graphs: `START`/`END`, nodes, edges, conditional routing, commands, `Send` fanout, reducers/channels, checkpoints, interrupts, **subgraphs**, child-task orchestration, streaming, observability, time travel | | **Registry** | `crates/tinyagents-registry/` | named capability catalog (models, tools, agents, graphs, routers, reducers) that `.rag` binds by name | | **Expressive `.rag`** | `crates/tinyagents-language/` | declarative, side-effect-free blueprints that compile into the same graph/harness types as hand-written Rust | | **Session** | `crates/tinyagents-session/` | durable SQLite-backed session history and a run ledger, separate from harness and graph durability | | **Tracing** | `crates/tinyagents-tracing/` | shared, feature-gated tracing macros used by the other crates | There is no facade crate: a consumer depends directly on the crates it needs. See [Quick Start](Quick-Start) for the dependency snippet and feature flags, and [Capabilities](Capabilities) for a full index of what each crate exposes. ## Dependency direction ```text tinyagents-tracing ^ | tinyagents-harness --------+ ^ | | v tinyagents-language tinyagents-session ^ | tinyagents-graph ^ | tinyagents-registry ``` `tinyagents-tracing` has no internal dependencies and compiles to no-op macros unless the `tracing` feature is enabled. `tinyagents-harness` depends only on tracing. `tinyagents-language` depends on harness (it compiles a `.rag` blueprint into harness/graph types). `tinyagents-graph` depends on harness, language, and tracing. `tinyagents-registry` depends on graph, harness, and language, since it is the layer that binds a `.rag` blueprint's capability references against real, registered Rust values. `tinyagents-session` depends only on harness (for its `sqlite` feature) and tracing; 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. Both `tinyagents-graph` and `tinyagents-registry` also depend on a vendored, provider-neutral inference crate, `tinyinference` (`vendor/tinyinference/crates/tinyinference`), and `tinyagents-harness` depends on a vendored tool-schema crate, `tinytools` (`vendor/tinytools/crates/tinytools`). ## Request flow ```text +-------------+ +-------------------------------------------+ | 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. Optionally, a workflow can be authored as a `.rag` blueprint instead of Rust: the blueprint compiles (lexer, parser, compiler) into a `Blueprint`, which the registry binds by name into the same `GraphBuilder`/`CompiledGraph` and harness types a hand-written graph would use. See [Expressive Language `.rag`](Expressive-Language-RAG). ## 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_harness::subagent` (`SubAgent`, `SubAgentSession`, `SubAgentTool`) wraps a whole harness agent as an ordinary `Tool`. A parent agent calling a sub-agent is just a tool call; the child run gets its own `root_run_id`/`parent_run_id` and its usage and cost roll up to the parent. See [Harness](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](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 - sub-agents (`tinyagents_harness::subagent`): an agent exposed as a tool, run as a child at depth `+1`, capped by a run-limit policy and reported through sub-agent lifecycle events - deterministic model and tool test doubles (`tinyagents_harness::testkit`) See [Harness](Harness). ## Graph runtime The graph owns deterministic state movement: - typed state, reducers/channels, and updates - nodes, static and conditional edges, commands, `Send` fanout - checkpoints, interrupts, time travel, topology export, run status, observability - child-task orchestration (`tinyagents_graph::orchestration`): typed task controls (spawn/await/cancel/status/list/steer, and similar) exposed as harness tools, so an orchestrator agent manages concurrent child tasks by a stable task id - subgraphs (`tinyagents_graph::subgraph`): a node embeds another `CompiledGraph` See [Graph Runtime](Graph-Runtime). ## Registry The registry owns named capabilities, letting Rust code and `.rag` blueprints refer to a model, tool, agent, graph, or router by a stable name instead of a process global. Entries describe capabilities well enough that a `.rag` blueprint's references can be validated and bound before it ever runs. See [Registry](Registry). ## Expressive language `.rag` `.rag` is a declarative, side-effect-free blueprint format. It cannot embed arbitrary code; it can only reference capabilities by name, which the compiler binds and validates against a registry. Pipeline: ```text .rag source -> lexer -> parser -> compiler -> Blueprint -> bind_capabilities (registry) -> build_graph -> run to END ``` See [Expressive Language `.rag`](Expressive-Language-RAG). ## 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 - `.rag` describes workflow source, and lowers into the same graph/harness types a hand-written Rust caller would build directly No layer bypasses another layer's policy, observability, or validation boundary: a `.rag`-authored blueprint, a sub-agent call, and a subgraph all pass through the same registry binding, run-limit, and event machinery as a top-level, hand-written run.