Architecture
Robota is a collection of TypeScript packages, not one framework you adopt whole. Each package owns
one concern, and packages are stacked in layers so that a package only ever depends on the layers
beneath it. This page explains what the layers are, why they are separate, and how a request moves
through them. The repository-level summary is in ARCHITECTURE.md; each
package's contract is in its docs/SPEC.md.
Why the layers exist
- Use as little as you need.
@robota-sdk/agent-coreruns an agent on its own. You add the session runtime, the assembly layer, a transport or a UI only when you need them, and you install only the provider packages you actually call. - Swap parts without touching the core. Providers, tools, transports and user interfaces plug into contracts that the lower layers define. Adding a vendor or a protocol is a new package, not an edit to the engine.
- Keep implementations independent. Shared types live in contract-only packages
(
agent-interface, one subpath per family). A transport can talk to a session through those contracts without depending on the package that builds sessions. - Keep product opinions at the edge. Library packages stay neutral: they read no ambient config
and pick no file locations on their own. The
robotaCLI is one application built on top of them, and it is where product decisions (which settings files to read, which commands to offer) live.
The layers
Arrows point from a package to what it depends on. The diagram groups packages; the tables below list the exact members.
Foundation — agent-core
agent-core contains the ConversationAgent agent class (also exported as Robota) and its
execution loop (model call, tool calls, repeat), the provider abstraction (IAIProvider,
AbstractAIProvider), the tool registry (FunctionTool, ToolRegistry), the plugin base class
(AbstractPlugin), permission evaluation, hook execution, context-window accounting, model
metadata, and the typed error classes.
It has no dependency on any other Robota package. Everything else registers with it through its
abstract contracts, so it can be used alone and nothing can form a dependency cycle through it.
Its main entry is platform-neutral — it runs in a browser, a Web Worker, Node, Deno and Bun — and
everything that needs a Node host is on the separate @robota-sdk/agent-core/node entry point, which
a Node application passes in (hook executors, the process environment).
Contracts — agent-interface
| Package | Contracts it owns |
|---|---|
agent-interface/session | Interactive sessions, interaction channels, events, turns, session records |
agent-interface/command | Commands, command results, plugin adapters, capability descriptors |
agent-interface/execution | Background tasks, job groups, subagent jobs, execution workspaces |
agent-interface/transport | Transport adapters (ITransportAdapter), admission, access tokens, external events |
agent-interface/session-mobility | Peer messaging and session handoff between sessions and devices |
agent-interface/analytics | Usage snapshots, per-source totals, run-trace timelines |
agent-interface/tui | Terminal command interactions (pickers, confirmations, missing-arg prompts) |
This package holds type declarations and a few pure accessors over them, and does no I/O. A
subpath depends only on agent-core and on lower subpaths, never on an implementation. That is what
lets, for example, every transport accept a session without depending on agent-framework.
Capabilities — providers, tools, MCP, plugins
| Package | What it provides |
|---|---|
agent-provider/anthropic | Anthropic Claude |
agent-provider/openai | OpenAI |
agent-provider/deepseek, /qwen, /gemma | DeepSeek, Qwen, and Gemma-family models behind OpenAI-compatible endpoints |
agent-provider/gemini | Google Gemini |
agent-provider/bytedance | ByteDance (ModelArk) video generation |
agent-provider/openai-compatible | The shared OpenAI-compatible protocol layer that openai, deepseek, qwen and gemma build on |
agent-tools | Tool factories (createZodFunctionTool, createFunctionTool) and the built-in tools |
agent-mcp | Model Context Protocol client: server definitions, activation, OAuth sign-in |
agent-plugin | Eight ready-made plugins (logging, usage, limits, webhooks, and more) |
agent-os | Node host primitives, one subpath each: rooted file access (file-authority), a nonblocking operation lock (node-lock), process-tree termination (process) |
agent-keyring | Key custody and public-key sealing for secure conversations (node:crypto only) |
agent-session-analytics | Session-log timing and usage analysis, reports and OTLP export |
agent-telemetry-otel | OpenTelemetry spans and metrics for agent runs and traces, GenAI or agent.* names |
agent-remote-pairing | Pairing and channel binding for peer-to-peer connections (no Robota dependencies) |
agent-roundtable | Coordinates independent agents and people in sequential or parallel conversations (no Robota dependencies) |
agent-organization | Canonical JSON and generic organization budget contracts (no Robota dependencies) |
product-config | Explicit product configuration contracts and their host loading (product-config/node) |
agent-provider is a leaf over agent-core with one subpath per vendor and no root export; the vendor
SDKs are optional peers, so an application installs only the SDKs of the providers it imports.
agent-plugin depends only on agent-core; plugins attach to the engine and never reach into the
layers above it.
Runtime — agent-session, agent-executor
agent-session wraps a ConversationAgent in a Session: tool calls pass through permission checks
and hooks, context usage is tracked, the conversation is compacted when it grows too large, and
records can be persisted through a store port. It receives its provider and tools already built;
it never constructs them. Its main entry is platform-neutral; the file-backed store and log sink are
on @robota-sdk/agent-session/node.
agent-executor provides background-task lifecycles (queueing, cancellation, snapshots) and the
subagent job ports.
agent-roundtable-robota runs a Session or a ConversationAgent as a participant, or as the
selector of the next speaker, in an agent-roundtable conversation.
Assembly — agent-framework
agent-framework puts the lower layers together into a ready-to-use session. Its entry points are
InteractiveSession, createQuery() and createAgentRuntime(). It also owns config and context
loading, workspace trust, the command registry, skills, subagent assembly, and the headless and
programmatic runners.
It is provider-neutral — you construct the provider and pass it in — and it contains no React or
Ink. Its main entry is platform-neutral and takes the platform as an injected host; a Node
application builds one with createNodeFrameworkHost from @robota-sdk/agent-framework/node. It
reads only the files and directories its host passes to it: without an explicit decision it loads no
user settings, no project instructions and no project skills. See Using the SDK.
Composition — defaults, presets, commands, packs
| Package | Role |
|---|---|
agent-provider/builtin | createDefaultProviderDefinitions() — the built-in chat provider definitions |
agent-tools/defaults | createDefaultTools() — the default tool set, handed to the Node host |
agent-product/command | The slash-command modules (/help, /compact, /permissions, and the rest) |
agent-product/preset | Named bundles of session options (persona, model, permission posture) |
agent-product/capability-pack | ICapabilityPack and mergeCapabilityPacks() for adding tools, commands, agents |
agent-product/coding | createCodingPack() — the coding tools, commands and subagents as one pack |
agent-product | assembleProduct() — builds a product from a declarative profile |
agent-subagent-runner | Optional runner that executes subagents in child processes |
These packages are meant to be imported where an application is put together (its "composition root"), not by libraries in the middle of the stack, so a library that only needs the mechanisms never drags in these defaults.
Transports
| Package | Protocol |
|---|---|
agent-transport | Carrier-neutral wire messages, decoders and delivery helpers; ./client has the browser-safe decoders, ./node the access-token verification |
agent-transport/http | HTTP, built on Hono |
agent-transport/ws | WebSocket |
agent-transport/mcp | Exposes a session as an MCP server (stdio and Streamable HTTP) |
agent-transport/acp | Serves a session to an editor as an ACP agent over stdio |
agent-transport/webrtc | Peer-to-peer WebRTC data channels (remote control, device mesh) |
agent-transport/link | Owner link to a local runtime, admitted by proof |
A transport is a thin adapter: it turns protocol messages into session calls (submit, abort,
commands, permission answers) and sends session events back. Transports implement
ITransportAdapter from agent-interface/transport and depend on the session contracts, not on
agent-framework. agent-framework itself provides the non-interactive runner (text, json or
stream-json output) and the TransportRegistry that starts and stops several transports
together. See Deployment.
Surfaces
| Package | What it is |
|---|---|
agent-ui-terminal | The terminal UI (React + Ink), including renderApp and TuiInteractionChannel |
agent-cli | The robota command — a maintained agent interface built from the packages above |
agent-ui-web | GUI components, a session reducer over the transport wire protocol and a WebSocket client |
agent-gui-web (internal) | The GUI web app served by robota --serve --open and loaded by the desktop app |
agent-cli bundles the Robota packages it uses into its own build, so installing it does not
install the SDK packages separately. The desktop app (apps/agent-app) is an Electron shell: it
starts or reuses the workspace's robota daemon, connects to it over loopback, and loads
agent-gui-web.
React and Ink appear only in these surface packages: Ink in agent-ui-terminal (and agent-cli,
which bundles it), React in the terminal and web UI packages. Every other package is plain
TypeScript.
Internal packages
Four workspace packages are private and not published: agent-gui-web (above), agent-local-host
(Node host authority for owner-protected local runtime state), agent-organization-host (an
operator-owned organization broker with durable budgets) and agent-provider-replay (a provider that
replays a recorded session log, for offline tests). The CLI bundles the separately maintained
@robota-sdk/workflows library for its /workflows command; it is not a workspace package.
Dependency rules
These rules keep the layering real rather than aspirational (the full list is in
ARCHITECTURE.md):
- Dependencies point one way, down the layers. There are no cycles.
agent-coredepends on no other Robota package.- An
agent-interfacesubpath depends only onagent-coreand lower subpaths. - Plugin packages depend only on
agent-core. agent-frameworkandagent-corenever depend on a transport or UI package.- No package re-exports another package wholesale; you import a symbol from the package that owns it.
How a turn flows
InteractiveSession records everything that happens in one ordered history of IHistoryEntry
items. An entry's category is 'chat' for user and assistant messages and 'event' for things
such as tool starts and ends. The same history drives display and persistence.
User input
→ InteractiveSession.submit()
→ history: IHistoryEntry (category 'chat', user message)
→ Session.run() → ConversationAgent.run()
→ the provider receives only the chat entries, converted to messages
→ streaming reply → text_delta events; tool calls → tool_start / tool_end events
→ history: assistant message (category 'chat') and tool entries (category 'event')
→ context_update, then complete (or interrupted / error)
→ every attached client (terminal, web GUI, HTTP, WebSocket, MCP) updates from the events
The provider never sees event entries; the session filters the history to chat messages before each model call. Background tasks are tracked beside the session as task snapshots and append-only transcripts, so a long-running task's output does not rewrite the session record on every chunk.
Session delegates to focused components: PermissionEnforcer wraps each tool with permission
checks and hooks, ContextWindowTracker tracks token usage and the auto-compaction threshold, and
CompactionOrchestrator summarizes the conversation when it is compacted. See
Context Management.
Plugins
agent-core defines AbstractPlugin; @robota-sdk/agent-plugin provides eight implementations.
A plugin overrides the lifecycle hooks it cares about, such as beforeRun, afterRun,
beforeProviderCall, afterProviderCall and onError. A failing hook is logged and does not fail
the run. The hook list and examples are in Building Agents.
Design patterns
| Pattern | Where | Purpose |
|---|---|---|
| Facade | Robota, Session, InteractiveSession | One entry point over many internal parts |
| Decorator | PermissionEnforcer.wrapTools() | Adds permission checks around each tool |
| Strategy | IAIProvider, ISessionLogger | Swappable implementations |
| Factory | createQuery(), createAgentRuntime(), createZodFunctionTool() | Build configured objects |
| Null Object | SilentLogger, DefaultEventService | Safe do-nothing defaults |
| Registry | ToolRegistry, CommandRegistry, TransportRegistry | One place to look up tools, commands, transports |
| Composition | InteractiveSession → Session → Robota | Delegation instead of inheritance |