# 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`](./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: | Area | Files | |---|---| | Message events and queue | `nanobot/bus/events.py`, `nanobot/bus/queue.py` | | Turn orchestration | `nanobot/agent/loop.py` | | Provider/tool conversation loop | `nanobot/agent/runner.py` | | Context construction | `nanobot/agent/context.py` | | Session storage and compaction | `nanobot/session/manager.py` | | Long-term memory and Dream | `nanobot/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: - [`providers.md`](./providers.md) for practical setup; - [`configuration.md#providers`](./configuration.md#providers) for exact provider reference. ## Channels Channels translate external platforms into `InboundMessage` events and send `OutboundMessage` events back to the platform. Main files: | Area | Files | |---|---| | Base channel contract | `nanobot/channels/base.py` | | Built-in channels | `nanobot/channels/*.py` | | Discovery and lifecycle | `nanobot/channels/manager.py` | | WebSocket/WebUI channel | `nanobot/channels/websocket.py` | Channels are discovered through built-in module scanning and plugin entry points. A custom channel should follow [`channel-plugin-guide.md`](./channel-plugin-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: | Surface | Default | |---|---| | Health endpoint | `http://127.0.0.1:18790/health` | | WebUI/WebSocket | `http://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: - [`../webui/README.md`](../webui/README.md) for WebUI use and development; - [`websocket.md`](./websocket.md) for protocol details. ## Tools Tools are discovered from `nanobot/agent/tools/` and plugin entry points. Important files: | Tool area | Files | |---|---| | Tool base and schema | `nanobot/agent/tools/base.py`, `nanobot/agent/tools/schema.py` | | Discovery | `nanobot/agent/tools/registry.py` | | Shell execution | `nanobot/agent/tools/shell.py` | | Filesystem tools | `nanobot/agent/tools/filesystem.py` | | Web search/fetch | `nanobot/agent/tools/web.py` | | MCP tools | `nanobot/agent/tools/mcp.py` | | Cron | `nanobot/agent/tools/cron.py`, `nanobot/cron/` | | Image generation | `nanobot/agent/tools/image_generation.py` | | Runtime self-inspection | `nanobot/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: | Path | Default | |---|---| | Config | `~/.nanobot/config.json` | | Workspace | `~/.nanobot/workspace/` | | Sessions | `/sessions/*.jsonl` | | Memory | `/memory/` | | Cron store | `/cron/jobs.json` | | WebUI/media/log runtime data | config directory subdirectories such as `webui/`, `media/`, and `logs/` | The schema accepts both camelCase and snake_case keys, but saves config with camelCase aliases. ## Memory and Sessions Session history is the near-term conversation replay. Memory is the longer-term workspace state. | Store | File area | |---|---| | Session JSONL files | `/sessions/` | | Long-term memory | `/memory/MEMORY.md` | | Consolidation source history | `/memory/history.jsonl` | | Bootstrap identity files | `/SOUL.md`, `/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: | Boundary | Files | |---|---| | Workspace scope | `nanobot/security/workspace_access.py`, `nanobot/security/workspace_policy.py` | | Shell sandboxing | `nanobot/agent/tools/shell.py` | | SSRF/network checks | `nanobot/security/network.py`, `nanobot/agent/tools/web.py` | | PTH guard and CLI startup security | `nanobot/security/` and CLI entrypoints | | Channel access control | channel 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 | Extension | How | |---|---| | Provider | Add `ProviderSpec` in `providers/registry.py`, add schema field in `config/schema.py`, implement provider only if the generic backend is not enough | | Channel | Implement `BaseChannel`, expose an entry point, follow [`channel-plugin-guide.md`](./channel-plugin-guide.md) | | Tool | Implement a tool under `agent/tools/` or expose a plugin entry point | | MCP | Add `tools.mcpServers` config | | Skill | Add workspace skill files under `/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: | Change | Minimum useful verification | |---|---| | Provider behavior | Provider unit tests or a mocked API path; `nanobot agent -m "Hello!"` with safe config when possible | | Channel behavior | Channel tests plus `nanobot gateway` startup path | | WebUI behavior | WebUI tests/build and, for routing/settings/chat changes, browser-level verification through the gateway | | Tool behavior | Tool unit tests and an agent-run path when schema or model-facing behavior changes | | Docs | Link 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.