Concepts
Use this page when you want to understand nanoinfra before changing advanced settings. It explains the moving parts without requiring you to read the source first.
If you want source-file ownership and extension points, read architecture.md after this page.
Runtime Shape
nanoinfra has one small core loop and several ways to enter it:
| Part | What it does |
|---|---|
| Agent loop | Builds context, selects the session, calls the provider, runs tools, and publishes replies |
| Providers | LLM backends such as OpenRouter, Anthropic, OpenAI, Bedrock, Ollama, vLLM, and other OpenAI-compatible APIs |
| Channels | User-facing transports such as CLI, WebUI/WebSocket, Telegram, Discord, Slack, Signal, WhatsApp, Email, Mattermost, and others |
| Tools | Capabilities the model may call, including files, shell, web search/fetch, MCP, cron, image generation, and subagents |
| Memory | Workspace files and session history that keep useful context across turns |
| Gateway | Long-running process that connects enabled channels and serves the health endpoint |
The simplest path is nanoinfra agent -m "Hello!": one inbound message goes through the agent loop and prints the reply in your terminal. The long-running path is nanoinfra gateway. Channels receive messages from chat apps or the WebUI. They publish those messages to the same agent loop, and send replies back to the originating channel.
Config vs Workspace
The default instance lives under ~/.nanoinfra/:
| Path | Meaning |
|---|---|
~/.nanoinfra/config.json | Instance configuration: providers, model defaults, channels, tools, gateway, API, and runtime options |
~/.nanoinfra/workspaces/ | Workspaces root: where workspaces live, and the boundary the WebUI may pick one inside (tools.workspacesRoot) |
~/.nanoinfra/workspaces/default/ | The default agent workspace: memory, sessions, heartbeat tasks, cron jobs, skills, and generated artifacts |
An installation created before the workspaces root existed keeps its workspace at
~/.nanoinfra/workspace/. Starting nanoinfra moves it to
~/.nanoinfra/workspaces/default/ and updates agents.defaults.workspace to match.
Everything in it moves too, including secrets/, diagrams/, servers/, skills/,
triggers/ and memory/. A workspace you chose yourself is never relocated.
You can override both with command flags:
nanoinfra onboard --config ./bot-a/config.json --workspace ./bot-a/workspace
nanoinfra agent --config ./bot-a/config.json --workspace ./bot-a/workspace -m "Hello"
nanoinfra gateway --config ./bot-a/config.json --workspace ./bot-a/workspace
The config file controls what nanoinfra may use. The workspace is where nanoinfra keeps state for that instance.
Agent Workspace and Project Workspace
The configured workspace is the agent workspace. A WebUI chat can also select a different project workspace for repository-specific work without moving the agent's identity or durable state.
| Resource | Owner when a project is selected |
|---|---|
| Project instructions | AGENTS.md from the selected project. There is no fallback to the agent workspace's AGENTS.md |
| Agent profile | SOUL.md and USER.md from the agent workspace. Project-local files with those names are ignored |
| Memory and custom skills | memory/ and skills/ from the agent workspace |
| Relative file paths and shell working directory | The selected project workspace |
When no separate project is selected, one directory normally serves both roles. Selecting a project changes the working context for that chat. It does not create a second agent or relocate the configured agent workspace.
Config Format
config.json accepts both camelCase and snake_case keys. The docs use camelCase because nanoinfra writes config back to disk with camelCase aliases, for example apiKey, modelPresets, intervalS, and maxToolResultChars.
Most examples are partial snippets. Merge them into the existing file created by nanoinfra onboard. Do not replace the whole file unless you want to reset the instance.
One Agent Turn
A normal turn follows this flow:
- A channel receives a user message and publishes it to the message bus.
- The agent loop chooses a session key. It then builds context from the effective project workspace, agent-owned profile/skills/memory, recent messages, channel metadata, and runtime settings.
- The provider receives the model request.
- If the model asks for tools, the runner executes them and feeds results back to the model.
- The agent loop saves the final reply to the session. It sends that reply back through the channel.
That flow is the same whether the message starts in the CLI, WebUI, Telegram, Discord, or another channel.
CLI, Gateway, API, and WebUI
| Entry point | Command | Use it for |
|---|---|---|
| CLI one-shot | nanoinfra agent -m "..." | First-run checks, scripts, and quick local questions |
| CLI interactive | nanoinfra agent | Terminal chat with persistent session history |
| Gateway | nanoinfra gateway | Chat apps, WebUI, heartbeat, Dream, and long-running service mode |
| OpenAI-compatible API | nanoinfra serve | Programmatic access through /v1/chat/completions |
| WebUI | nanoinfra webui | Prepare the local WebUI, start the gateway, and open the browser workbench |
The WebUI launcher is the normal browser entry point. Underneath, the gateway keeps the WebSocket channel and other long-running services alive. The gateway health endpoint is on gateway.port (18790 by default). The browser WebUI is served on 8765 by default, not by the health endpoint.
Provider and Model Selection
The active model should normally come from a named modelPresets entry selected by agents.defaults.modelPreset. Direct agents.defaults.provider and agents.defaults.model still form the implicit default preset for older or minimal configs. The active provider is resolved in this order:
- If the active preset provider or implicit default provider is not
"auto", nanoinfra uses that provider. - If provider is
"auto", nanoinfra tries to infer the provider. It tries the model name, configured API keys, local provider base URLs, or gateway providers. - OAuth providers such as OpenAI Codex and GitHub Copilot require explicit login and explicit provider/model selection inside the active preset.
Pin the provider inside the preset when setting up for the first time. It is easier to debug:
{
"modelPresets": {
"primary": {
"provider": "openrouter",
"model": "anthropic/claude-opus-4.5"
}
},
"agents": {
"defaults": {
"modelPreset": "primary"
}
}
}
See providers.md for practical examples and configuration-providers.md#providers for the full provider reference.
Channels and Sessions
Each channel maps inbound messages to a session key. That lets independent conversations keep separate history. The WebUI also supports multiple chats and workspace-scoped metadata for project workspaces.
agents.defaults.unifiedSession can intentionally share one session across channels for a single-user multi-device setup. Leave it off if you expect separate people, groups, channels, or projects to keep separate context.
Memory, Sessions, and Dream
nanoinfra uses two related stores:
| Store | Location | Purpose |
|---|---|---|
| Sessions | <workspace>/sessions/*.jsonl | Recent conversation turns replayed into context |
| Memory | <workspace>/memory/MEMORY.md and <workspace>/memory/history.jsonl | Long-term facts and consolidated history |
| Subagent transcripts | <workspace>/memory/subagents/*.jsonl | Full conversation transcript of each background subagent run |
Dream is a periodic consolidation job. It reads accumulated history and updates workspace memory so useful context can survive beyond short session replay.
Subagent transcripts are written per run at <workspace>/memory/subagents/<task_id>.jsonl, one JSON message object per line. The shape is the same as session history, plus a trailing metadata record with the stop reason. They are kept separate from sessions and history.jsonl, so they never leak into main-agent prompt injection or Dream consolidation. The store keeps the newest 50 transcripts per workspace.
The main agent can read a transcript with the read_file tool using the task id returned by the spawn tool, or by listing the memory/subagents/ directory. Transcript content may include tool outputs the subagent processed, so treat it as untrusted data when re-reading it into a prompt.
See memory.md for the detailed design.
Tools and Safety
Tools are discovered automatically from built-in modules and plugin entry points. Common tool groups include:
- file read/write/edit and patching.
- shell execution with configurable sandboxing.
- web search and web fetch with SSRF checks.
- MCP servers.
- cron reminders, local triggers, and heartbeat tasks.
- image generation.
- subagents and runtime self-inspection.
Security-sensitive controls live in configuration-security.md#security. For production or shared chat apps, also configure channel access controls such as allowFrom, pairing, or WebSocket tokens.
Background Jobs
When nanoinfra gateway starts, it runs workspace-scoped automations and
registers system jobs:
dream, whenagents.defaults.dream.enabledis true.heartbeat, whengateway.heartbeat.enabledis true.
Heartbeat reads <workspace>/HEARTBEAT.md. If the file has tasks under ## Active Tasks, nanoinfra executes them and sends only useful/actionable results to the most recently active chat target. Routine "nothing changed" results are suppressed.
User-created reminders use the same cron service but are not the same as the protected heartbeat system job. They run as scheduled turns in their origin chat/session and normally deliver the result back to that channel.
Creating one rehearses it. A scheduled automation is prose, so its commands
are not in its text. The model composes them when it runs. The only thing that can
enumerate them is a run. So nanoinfra runs it once at creation with every gated
action previewed. Nothing executes. The rehearsal tells you whether an unattended
run would be permitted and which standing grant it would need. An automation that
would be refused is saved disabled with that finding attached, rather than left
enabled and certain to refuse with nobody watching. See
automations.md#creating-one-rehearses-it.
Local triggers are also session-bound, but they do not have their own
schedule. Create one from the target chat with /trigger <name>, then call
nanoinfra trigger <id> "<message>" when a local script or external service wants
nanoinfra to respond in that session. Webhook servers, third-party auth, and
event-to-message formatting stay outside nanoinfra. Trigger deliveries are stored
in the workspace until the linked agent turn finishes successfully. If the
target session is busy, the trigger waits until that session is idle instead of
being injected into the active turn. The message is recorded as an automation
turn in that session. Delivery is at-least-once, so external systems should
tolerate repeated trigger messages. A delivery that reaches the agent but fails
is marked failed rather than retried forever.
Where to Go Next
| Need | Read |
|---|---|
| First working install | quick-start.md |
| Provider/model setup | providers.md |
| Chat app setup | channels.md |
| Complete config reference | configuration.md |
| Runtime debugging | troubleshooting.md |