Back to Nanobot

Architecture

docs/architecture.md

0.3.09.3 KB
Original Source

Architecture

This page maps nanobot's runtime behavior to source files. Use it when you are debugging internals, reviewing a PR, adding a provider/channel/tool, or trying to understand where a user-visible behavior comes from.

For the product-level mental model, read concepts.md first.

Core Flow

mermaid
flowchart LR
    Channel["Channel
CLI, WebUI, chat apps"] --> Bus["MessageBus
InboundMessage"]
    Bus --> Loop["AgentLoop
session, workspace, context"]
    Loop --> Runner["AgentRunner
provider/tool loop"]
    Runner --> Provider["Provider
LLM backend"]
    Provider --> Runner
    Runner --> Tools["Tools
files, shell, web, MCP, cron"]
    Tools --> Runner
    Runner --> Loop
    Loop --> Outbound["MessageBus
OutboundMessage"]
    Outbound --> Channel

    Loop -. reads/writes .-> State["Session, memory,
hooks, skills, templates"]

Main files:

AreaFiles
Message events and queuenanobot/bus/events.py, nanobot/bus/queue.py
Turn orchestrationnanobot/agent/loop.py
Provider/tool conversation loopnanobot/agent/runner.py
Context constructionnanobot/agent/context.py
Session storage and compactionnanobot/session/manager.py
Long-term memory and Dreamnanobot/agent/memory.py

Agent Loop vs Agent Runner

AgentLoop owns the channel-facing turn:

  • receives inbound messages;
  • determines the effective session and workspace scope;
  • builds context;
  • wires hooks, progress, and channel metadata;
  • publishes outbound messages.

AgentRunner owns the model-facing loop:

  • sends messages to the selected provider;
  • handles streaming deltas and reasoning blocks;
  • executes tool calls;
  • feeds tool results back into the model;
  • stops when a final answer is produced or runtime limits are hit.

Keep this split in mind when debugging. If a problem is about channel routing, session keys, workspace selection, or outbound delivery, start in agent/loop.py. If it is about provider calls, tool calls, streaming, or iteration limits, start in agent/runner.py.

Providers

Provider metadata is centralized in nanobot/providers/registry.py. Configuration fields live in nanobot/config/schema.py.

Provider selection uses:

  • explicit agents.defaults.provider or preset provider;
  • provider registry keywords;
  • API key prefixes and API base URL hints;
  • local provider fallback when apiBase is configured;
  • gateway fallback for providers that can route many model families.

Provider implementations live in nanobot/providers/. Most hosted providers use the OpenAI-compatible implementation, while Anthropic, Azure OpenAI, AWS Bedrock, OpenAI Codex, and GitHub Copilot have specialized paths.

Useful docs:

Channels

Channels translate external platforms into InboundMessage events and send OutboundMessage events back to the platform.

Main files:

AreaFiles
Base channel contractnanobot/channels/base.py
Channel packagesnanobot/channels/<channel>/
Discovery and lifecyclenanobot/channels/manager.py
WebSocket/WebUI channelnanobot/channels/websocket/

Channels are discovered by scanning self-contained packages under nanobot/channels/. Add a channel by contributing one package that follows channel-package-guide.md.

WebUI and Gateway

nanobot gateway starts:

  • enabled chat channels;
  • the WebSocket channel when configured;
  • workspace-scoped cron service;
  • system jobs such as Dream and heartbeat;
  • the health endpoint on gateway.port.

The packaged WebUI is served by the WebSocket channel, not the health endpoint:

SurfaceDefault
Health endpointhttp://127.0.0.1:18790/health
WebUI/WebSockethttp://127.0.0.1:8765

WebUI source lives in webui/. The production build is written to nanobot/web/dist/ and bundled into the wheel.

Useful docs:

Tools

Tools are discovered from nanobot/agent/tools/ and plugin entry points.

Important files:

Tool areaFiles
Tool base and schemananobot/agent/tools/base.py, nanobot/agent/tools/schema.py
Discoverynanobot/agent/tools/registry.py
Shell executionnanobot/agent/tools/shell.py
Filesystem toolsnanobot/agent/tools/filesystem.py
Web search/fetchnanobot/agent/tools/web.py
MCP toolsnanobot/agent/tools/mcp.py
Cronnanobot/agent/tools/cron.py, nanobot/cron/
Image generationnanobot/agent/tools/image_generation.py
Runtime self-inspectionnanobot/agent/tools/self.py

Tool behavior is part of the model contract. Keep user-visible tool names, schemas, and error messages stable unless a change is intentional.

Config and Paths

The config schema lives in nanobot/config/schema.py. Loading and saving live in nanobot/config/loader.py. Runtime path helpers live in nanobot/config/paths.py.

Defaults:

PathDefault
Config~/.nanobot/config.json
Workspace~/.nanobot/workspace/
Sessions<workspace>/sessions/*.jsonl
Memory<workspace>/memory/
Cron store<workspace>/cron/jobs.json
WebUI/media/log runtime dataconfig directory subdirectories such as webui/, media/, and logs/

The schema accepts both camelCase and snake_case keys, but saves config with camelCase aliases.

Agent-Owned State vs Effective Project Context

Runtime code distinguishes the configured agent workspace from the effective project workspace carried by a session scope. They are often the same path, but a WebUI chat may select a separate project:

ConcernPath owner
Sessions, SOUL.md, USER.md, memory, and custom skillsConfigured agent workspace
Project AGENTS.md, relative tool paths, and shell working directoryEffective project workspace
Workspace access mode and project metadataSession workspace scope

ContextBuilder combines project instructions with agent-owned profile and memory. Filesystem and search tools use the project as their ordinary boundary and receive only capability-specific read access to built-in/agent skills and the exact agent history file. Keep those cross-root capabilities read-only and explicit; do not treat the entire agent workspace as an allowed root.

Memory and Sessions

Session history is the near-term conversation replay. Memory is the longer-term workspace state.

StoreFile area
Session JSONL files<workspace>/sessions/
Long-term memory<workspace>/memory/MEMORY.md
Consolidation source history<workspace>/memory/history.jsonl
Bootstrap identity files<workspace>/SOUL.md, <workspace>/USER.md, templates under nanobot/templates/

Dream is implemented in nanobot/agent/memory.py and scheduled by the runtime when enabled.

Security Boundaries

Security-sensitive code paths include:

BoundaryFiles
Workspace scopenanobot/security/workspace_access.py, nanobot/security/workspace_policy.py
Shell sandboxingnanobot/agent/tools/shell.py
SSRF/network checksnanobot/security/network.py, nanobot/agent/tools/web.py
PTH guard and CLI startup securitynanobot/security/ and CLI entrypoints
Channel access controlchannel config in nanobot/channels/*.py

When changing tools, channels, file access, WebUI workspace behavior, or network fetching, treat security as part of the functional behavior and update docs if the user-facing boundary changes.

Extension Points

ExtensionHow
ProviderAdd ProviderSpec in providers/registry.py, add schema field in config/schema.py, implement provider only if the generic backend is not enough
ChannelExport a ChannelPlugin descriptor, keep its runtime and optional setup surfaces in one package, and follow channel-package-guide.md
ToolImplement a tool under agent/tools/ or expose a plugin entry point
MCPAdd tools.mcpServers config
SkillAdd workspace skill files under <workspace>/skills/ or built-in skills under nanobot/skills/

Prefer existing registry/discovery patterns over ad hoc wiring.

Testing and Verification

Common checks:

bash
pytest tests/test_openai_api.py::test_function -v
ruff check nanobot/
cd webui && bun run test
cd webui && bun run build

Choose tests based on the changed surface:

ChangeMinimum useful verification
Provider behaviorProvider unit tests or a mocked API path; nanobot agent -m "Hello!" with safe config when possible
Channel behaviorChannel tests plus nanobot gateway startup path
WebUI behaviorWebUI tests/build and, for routing/settings/chat changes, browser-level verification through the gateway
Tool behaviorTool unit tests and an agent-run path when schema or model-facing behavior changes
DocsLink checks, command accuracy against CLI/schema, and git diff --check

For user-facing flows, prefer at least one verification path through the public surface the user actually touches: CLI command, HTTP endpoint, WebSocket/WebUI, chat channel, or packaged import.