# nanobot User Guide

🐈 **nanobot** is an open-source, ultra-lightweight agent runtime for people who want to own their AI agent stack. It gives you a small, readable core plus the practical pieces for real long-running agents: a WebUI, chat channels, tools, memory, MCP, model routing, image generation, and deployment — all driven by a single `~/.nanobot/config.json` file.

This guide shows you how to install nanobot, get a working agent running in about two minutes, connect it to your favorite chat apps, and use its day-to-day features. Written from the source code of nanobot **v0.2.1**. For architecture, the complete configuration reference, security hardening, and developer internals, see the companion **Admin Guide**.

> [!NOTE]
> nanobot installs from PyPI as the package **`nanobot-ai`** but the command you run is always **`nanobot`**. Requires **Python ≥ 3.11**.

---

## 1. Quick Start

Get a working AI agent in three commands.

### Step 1 — Install

=== "pip"

    ```bash
    pip install nanobot-ai
    ```

=== "uv (fast)"

    ```bash
    uv tool install nanobot-ai
    ```

=== "From source"

    ```bash
    git clone https://github.com/HKUDS/nanobot.git
    cd nanobot
    pip install -e .
    ```

> [!TIP]
> Install from **PyPI or `uv`** for the most stable day-to-day experience. Install **from source** if you want the newest features and experiments — they land there first.

### Step 2 — Initialize

```bash
nanobot onboard
```

```text
✓ Created ~/.nanobot/config.json
✓ Created ~/.nanobot/workspace/
```

This creates your config and workspace under `~/.nanobot/`. Add `--wizard` to walk through an interactive setup that helps you pick a provider and model:

```bash
nanobot onboard --wizard
```

### Step 3 — Configure your key and model

Open `~/.nanobot/config.json` and set just **two things** — a provider API key and a model. Everything else has sensible defaults. [OpenRouter](https://openrouter.ai/keys) is recommended for global users because it can reach almost any model with one key.

```json
{
  "providers": {
    "openrouter": {
      "apiKey": "sk-or-v1-xxx"
    }
  },
  "agents": {
    "defaults": {
      "provider": "openrouter",
      "model": "anthropic/claude-opus-4-6"
    }
  }
}
```

> [!IMPORTANT]
> Add or merge these blocks into your existing config — don't replace the whole file. `nanobot onboard` already filled it with defaults you want to keep.

### Step 4 — Chat

```bash
nanobot agent
```

```text
nanobot 🐈  (type /help for commands, /quit to exit)
you ›
```

That's it — you have a working AI agent. Send a one-off message instead of entering interactive mode with `-m`:

```bash
nanobot agent -m "What time is it in Tokyo?"
```

---

## 2. Installation in Detail

### Requirements

| Requirement | Detail |
|-------------|--------|
| **Python** | 3.11 or newer |
| **OS** | Linux, macOS, Windows (Windows is explicitly supported) |
| **Node.js** | Only needed for the **WhatsApp** channel (≥ 18) |
| **An LLM API key** | From any supported provider — or a local model (Ollama, LM Studio, vLLM, etc.) with no key |

### Optional features (extras)

Some channels and capabilities need extra dependencies. Install them with pip "extras":

```bash
pip install "nanobot-ai[matrix]"     # Matrix channel
pip install "nanobot-ai[discord]"    # Discord channel
pip install "nanobot-ai[weixin]"     # WeChat (Weixin) channel
pip install "nanobot-ai[wecom]"      # WeCom channel
pip install "nanobot-ai[msteams]"    # Microsoft Teams channel
pip install "nanobot-ai[api]"        # OpenAI-compatible API server
pip install "nanobot-ai[azure]"      # Azure OpenAI (Entra ID auth)
pip install "nanobot-ai[pdf]"        # Faster/richer PDF reading (PyMuPDF)
```

> [!NOTE]
> **Matrix is not supported on Windows.** Its `matrix-nio[e2e]` dependency needs `python-olm`, which has no Windows wheel. Use macOS, Linux, or WSL2 for the Matrix channel.

### Updating

```bash
# pip
pip install -U nanobot-ai
nanobot --version

# uv
uv tool upgrade nanobot-ai
nanobot --version
```

> [!WARNING]
> **If you use WhatsApp**, the local bridge is **not** rebuilt automatically when you upgrade. After updating, rebuild it:
> ```bash
> rm -rf ~/.nanobot/bridge
> nanobot channels login whatsapp
> ```

---

## 3. The Three Ways to Run nanobot

nanobot is one program with several front doors. Pick the one that fits what you're doing.

| Command | What it starts | Use it when |
|---------|----------------|-------------|
| `nanobot agent` | A local interactive (or one-shot) chat in your terminal | You want to talk to the agent directly, test config, or script it |
| `nanobot gateway` | The long-running service that connects all your enabled chat channels **and** serves the WebUI | You want nanobot online 24/7 reachable from Telegram, Discord, the browser, etc. |
| `nanobot serve` | A minimal OpenAI-compatible HTTP API | You want to call nanobot from your own code/tools as if it were OpenAI |

> [!IMPORTANT]
> `nanobot agent` runs its **own** local agent. It does **not** attach to or proxy through a running `nanobot gateway` — they are independent processes that share the same config and workspace.

---

## 4. Everyday Usage

### 4.1 Interactive terminal chat

```bash
nanobot agent
```

Useful flags:

| Flag | Effect |
|------|--------|
| `-m "..."` | Send one message and exit (non-interactive) |
| `--no-markdown` | Show plain-text replies instead of rendered markdown |
| `--logs` | Show runtime logs during the chat |
| `-w <workspace>` | Use a specific workspace directory |
| `-c <config>` | Use a specific config file |

Exit interactive mode with any of: `exit`, `quit`, `/exit`, `/quit`, `:q`, or `Ctrl+D`.

### 4.2 In-chat slash commands

These work everywhere — the terminal, the WebUI, and every chat channel. They control the live agent, not the service.

| Command | Description |
|---------|-------------|
| `/new` | Stop the current task and start a fresh conversation |
| `/stop` | Stop the current task |
| `/restart` | Restart the bot |
| `/status` | Show bot status |
| `/model` | Show the current model and available presets |
| `/model <preset>` | Switch the runtime model preset for future turns |
| `/dream` | Run Dream memory consolidation now |
| `/dream-log` | Show the latest Dream memory change |
| `/dream-log <sha>` | Show a specific Dream memory change |
| `/dream-restore` | List recent Dream memory versions |
| `/dream-restore <sha>` | Restore memory to the state before a change |
| `/skill` | List enabled skills and their descriptions |
| `/pairing` | List pending pairing requests |
| `/pairing approve <code>` | Approve a pairing code |
| `/pairing deny <code>` | Deny a pending pairing request |
| `/pairing revoke <user_id>` | Revoke an approved user on the current channel |
| `/help` | Show available in-chat commands |

> [!TIP]
> Slash commands are different from the **shell CLI** (`nanobot agent`, `nanobot gateway`…). The shell CLI manages the *service*; slash commands operate on the *live conversation*. You can also fire a slash command from the terminal: `nanobot agent -m "/status"`.

### 4.3 What the agent can do — the tool kit

nanobot's agent decides *on its own* which tools to call to complete your request. You never call tools directly; you just describe what you want. These are the built-in capabilities:

| Tool | What it lets the agent do |
|------|---------------------------|
| `read_file`, `write_file`, `edit_file`, `list_dir` | Read, create, modify, and browse files in its workspace |
| `apply_patch` | Apply a structured multi-file patch |
| `exec` | Run shell commands (sandboxable — see Admin Guide) |
| `web_search` | Search the web (DuckDuckGo by default, no key needed) |
| `web_fetch` | Fetch a web page and read it as Markdown |
| `spawn` | Spin up a subagent to work on a sub-task in parallel |
| `long_task` / `goal` | Pursue a long-running objective across many turns |
| `cron` | Schedule its own reminders and recurring tasks |
| `message` | Proactively send a message to one of your chat channels |
| `generate_image` | Generate or edit images (when enabled) |
| `my` | Inspect and tune its own runtime state (model, context window, iteration budget) |
| `cli_apps` | Drive installed command-line apps |
| MCP tools | Any tool exposed by an external [MCP](#69-mcp-connect-external-tools) server you connect |

> [!NOTE]
> Web tools (`web_search` + `web_fetch`) are **on by default** and work out of the box with no API key. See [Web search](#62-web-search) to switch to Brave, Tavily, Kagi, and others.

### 4.4 Talking to your agent in chat apps

The real power of nanobot is reaching your agent from the messaging apps you already use. Enable a channel in config, run `nanobot gateway`, and message your bot. See [Section 5](#5-connect-chat-apps) for per-platform setup.

---

## 5. Connect Chat Apps

nanobot can front your agent across many platforms at once from a single `nanobot gateway` process. Enable each channel under `channels` in `~/.nanobot/config.json`.

| Channel | What you need | Public IP needed? |
|---------|---------------|-------------------|
| **Telegram** | Bot token from @BotFather | No (long-poll) / optional webhook |
| **Discord** | Bot token + Message Content intent | No |
| **WhatsApp** | QR code scan (`nanobot channels login whatsapp`) | No |
| **WeChat (Weixin)** | QR code scan (`nanobot channels login weixin`) | No |
| **Feishu / Lark** | App ID + App Secret | No (WebSocket) |
| **DingTalk** | App Key + App Secret | No (Stream mode) |
| **Slack** | Bot token + App-Level token | No (Socket mode) |
| **Matrix** | Homeserver URL + access token (or password) | No |
| **Email** | IMAP/SMTP credentials | No |
| **QQ** | App ID + App Secret | No |
| **Napcat (QQ)** | Forward-WebSocket URL + access token | No |
| **WeCom (企业微信)** | Bot ID + Bot Secret | No |
| **Microsoft Teams** | App ID + Password + public HTTPS endpoint | **Yes** (webhook) |
| **Mochat** | Claw token (auto-setup available) | No |
| **Signal** | signal-cli daemon + phone number | No |
| **WebSocket / WebUI** | Nothing (built-in) | No |

After enabling any channel, start the service:

```bash
nanobot gateway
```

### 5.1 Telegram (recommended)

1. Open Telegram, message **@BotFather**, send `/newbot`, and copy the token.
2. Configure:

```json
{
  "channels": {
    "telegram": {
      "enabled": true,
      "token": "YOUR_BOT_TOKEN",
      "allowFrom": ["YOUR_USER_ID"]
    }
  }
}
```

3. Run `nanobot gateway` and message your bot.

> [!TIP]
> Find your numeric **User ID** with `@userinfobot` and paste it **without** the `@`. Telegram uses long polling by default — no public IP required. A webhook mode is available (see Admin Guide).

### 5.2 Discord

```json
{
  "channels": {
    "discord": {
      "enabled": true,
      "token": "YOUR_BOT_TOKEN",
      "allowFrom": ["YOUR_USER_ID"],
      "groupPolicy": "mention",
      "streaming": true
    }
  }
}
```

Enable the **MESSAGE CONTENT INTENT** in the Discord developer portal, invite the bot with `Send Messages` + `Read Message History` permissions, then run `nanobot gateway`.

> `groupPolicy` controls group behavior: `"mention"` (default — reply only when @mentioned) or `"open"` (reply to everything). DMs always respond for users in `allowFrom`.

### 5.3 Slack

Uses **Socket Mode** — no public URL needed. Create a Slack app, turn on Socket Mode, add the bot scopes (`chat:write`, `app_mentions:read`, `files:read`, `files:write`, channel histories), subscribe to `message.im` / `app_mention` events, then:

```json
{
  "channels": {
    "slack": {
      "enabled": true,
      "botToken": "xoxb-...",
      "appToken": "xapp-...",
      "allowFrom": ["YOUR_SLACK_USER_ID"],
      "groupPolicy": "mention"
    }
  }
}
```

### 5.4 Feishu / Lark

Uses a WebSocket long connection — no public IP. Create a Feishu app, enable the **Bot** capability, add the `im:message` permissions (plus `cardkit:card:write` for streaming replies), subscribe to `im.message.receive_v1` in **Long Connection** mode, then:

```json
{
  "channels": {
    "feishu": {
      "enabled": true,
      "appId": "cli_xxx",
      "appSecret": "xxx",
      "allowFrom": ["ou_YOUR_OPEN_ID"],
      "streaming": true,
      "domain": "feishu"
    }
  }
}
```

> Set `"domain": "lark"` for international Lark. If you can't add `cardkit:card:write`, set `"streaming": false` — replies still work, just without token-by-token streaming.

### 5.5 WhatsApp

Requires **Node.js ≥ 18**. nanobot ships a local bridge (built on Baileys) that links to your phone via QR code.

```bash
# Terminal 1 — link your device (scan the QR with WhatsApp → Linked Devices)
nanobot channels login whatsapp

# Terminal 2 — run the service
nanobot gateway
```

```json
{
  "channels": {
    "whatsapp": {
      "enabled": true,
      "allowFrom": ["+1234567890"]
    }
  }
}
```

### 5.6 Email

Give nanobot its own mailbox; it polls **IMAP** and replies via **SMTP** like a personal email assistant.

```json
{
  "channels": {
    "email": {
      "enabled": true,
      "consentGranted": true,
      "imapHost": "imap.gmail.com",
      "imapPort": 993,
      "imapUsername": "[email removed]",
      "imapPassword": "your-app-password",
      "smtpHost": "smtp.gmail.com",
      "smtpPort": 587,
      "smtpUsername": "[email removed]",
      "smtpPassword": "your-app-password",
      "fromAddress": "[email removed]",
      "allowFrom": ["[email removed]"]
    }
  }
}
```

> [!CAUTION]
> `consentGranted` **must be `true`** to allow mailbox access — it's a deliberate safety gate. Set `"autoReplyEnabled": false` if you only want nanobot to read/analyze mail without sending automatic replies. For Gmail, create a dedicated account and an [App Password](https://myaccount.google.com/apppasswords).

### 5.7 Other channels

DingTalk (Stream mode), QQ / Napcat, WeChat (Weixin), WeCom, Matrix (Element, with E2EE), Microsoft Teams, Mochat, and Signal are all supported with the same `channels.<name>` pattern. Each one's exact fields and setup steps are in the Admin Guide's **Channel Reference**.

> [!IMPORTANT]
> **Access control matters.** Since v0.1.4.post4, an empty `allowFrom` **denies everyone**. Set `["*"]` to allow all users, or list specific user IDs. If you don't set `allowFrom`, unapproved users who DM the bot get a [pairing](#71-pairing-granting-access) code instead — a safe default.

---

## 6. Models, Providers, and Web Tools

### 6.1 Choosing a provider and model

nanobot speaks to ~40 LLM providers, plus any OpenAI-compatible endpoint. You set a key under `providers.<name>` and a model under `agents.defaults`. The model is written as `provider-prefix/model-name` for gateways like OpenRouter, or just the bare model name for a pinned provider.

Popular choices:

| Provider | `provider` value | Notes |
|----------|------------------|-------|
| OpenRouter | `openrouter` | One key, access to nearly every model (recommended) |
| Anthropic (Claude) | `anthropic` | Direct Claude access |
| OpenAI | `openai` | GPT models + Whisper voice transcription |
| Google Gemini | `gemini` | Direct Gemini |
| DeepSeek | `deepseek` | Direct DeepSeek |
| Groq | `groq` | Fast inference + free Whisper transcription (default) |
| AWS Bedrock | `bedrock` | Claude/Nova/Llama via the Converse API |
| Azure OpenAI | `azure_openai` | API key or Microsoft Entra ID auth |
| Local — Ollama | `ollama` | No key; `apiBase: "http://localhost:11434"` |
| Local — LM Studio | `lm_studio` | No key; OpenAI-compatible local server |
| Local — vLLM | `vllm` | No key; any OpenAI-compatible server |
| Any OpenAI-compatible API | `custom` | Point `apiBase` at your endpoint |

> [!TIP]
> **Running fully local?** Use Ollama, LM Studio, vLLM, OVMS, or Atomic Chat. Set `"apiKey": null` for servers that don't need authentication. The full provider table, OAuth providers (OpenAI Codex, GitHub Copilot), and per-provider quirks are in the Admin Guide.

#### Local model example (Ollama)

```json
{
  "providers": {
    "ollama": { "apiBase": "http://localhost:11434" }
  },
  "agents": {
    "defaults": { "provider": "ollama", "model": "llama3.2" }
  }
}
```

### 6.2 Web search

Web search is on by default using **DuckDuckGo** (zero config, no key). To use a different backend, configure `tools.web.search`:

| Provider | Free? | Config |
|----------|-------|--------|
| `duckduckgo` (default) | Yes | none |
| `searxng` (self-hosted) | Yes | `baseUrl` |
| `jina` | Free tier (10M tokens) | `apiKey` |
| `brave` | No | `apiKey` |
| `tavily` | No | `apiKey` |
| `kagi` | No | `apiKey` |
| `olostep` | No | `apiKey` |
| `volcengine` | Monthly quota | `apiKey` |

```json
{
  "tools": {
    "web": {
      "search": { "provider": "brave", "apiKey": "${BRAVE_API_KEY}" }
    }
  }
}
```

To disable all web tools entirely, set `tools.web.enable` to `false`.

### 6.3 Voice messages

Voice notes from Telegram and WhatsApp are **automatically transcribed** with Whisper. By default the free **Groq** tier is used; switch to OpenAI with `"transcriptionProvider": "openai"` under `channels`, and optionally hint the language with `"transcriptionLanguage": "en"`.

### 6.4 Switching models on the fly with presets

A **model preset** is a named, complete model configuration you can switch to at runtime with `/model <preset>` — without editing config.

```json
{
  "modelPresets": {
    "fast": { "model": "openai/gpt-4.1-mini", "provider": "openai", "reasoningEffort": "low" },
    "deep": { "model": "anthropic/claude-opus-4-5", "provider": "anthropic", "reasoningEffort": "high" }
  },
  "agents": {
    "defaults": { "modelPreset": "fast", "fallbackModels": ["deep"] }
  }
}
```

Then in chat:

```text
/model deep      # switch to your "deep" preset for future turns
/model default   # return to agents.defaults.*
```

> [!NOTE]
> `/model` switching is **runtime-only** — it never rewrites `config.json`, and an in-progress turn keeps the model it started with. `default` is always available and means `agents.defaults.*`.

### 6.5 Automatic model fallback

`agents.defaults.fallbackModels` is an ordered failover chain. If your primary provider returns a retryable error (timeout, 5xx, 429 rate limit, overload, quota exhaustion) **before any answer has started streaming**, nanobot transparently retries with the next candidate. It does *not* fall back on auth errors, content refusals, or malformed requests.

### 6.6 Image generation

nanobot can generate and edit images through the `generate_image` tool. It's **off by default** — enable it and pick a provider:

```json
{
  "tools": {
    "imageGeneration": {
      "enabled": true,
      "provider": "openrouter",
      "model": "openai/gpt-5.4-image-2"
    }
  }
}
```

In the WebUI composer, click **Image Generation**, choose an aspect ratio (`1:1`, `16:9`, `9:16`, `4:3`, `3:4`, or `Auto`), describe the image, and iterate ("make it warmer", "try a 16:9 version"). Supported providers include OpenRouter, MiniMax, Gemini, StepFun, Zhipu, AIHubMix, Ollama, and any OpenAI-compatible `custom` endpoint. Generated artifacts are stored under `~/.nanobot/media/generated/`. Full per-provider details: Admin Guide.

### 6.7 MCP — connect external tools

nanobot is an [MCP](https://modelcontextprotocol.io/) client. Connect external tool servers and the agent uses them as native tools — no code changes. The config format is compatible with Claude Desktop / Cursor, so you can paste server configs straight from any MCP README.

```json
{
  "tools": {
    "mcpServers": {
      "filesystem": {
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
      },
      "my-remote-mcp": {
        "url": "https://example.com/mcp/",
        "headers": { "Authorization": "Bearer xxxxx" }
      }
    }
  }
}
```

Both **stdio** (`command` + `args`) and **HTTP** (`url` + optional `headers`) transports are supported. Tools are discovered and registered automatically on startup.

---

## 7. Access Control and Pairing

### 7.1 Pairing — granting access

Pairing lets people get access to your bot through a simple code exchange — no config editing.

1. An un-approved user DMs the bot on any channel.
2. The bot replies with a pairing code like `ABCD-EFGH` (valid 10 minutes) and tells them to forward it to you.
3. You approve it:

```text
/pairing approve ABCD-EFGH
```

4. They can now chat normally.

> [!NOTE]
> Pairing only works in **DMs** — unapproved users in group chats are silently ignored. This also covers an already-approved user connecting from a *new* channel (e.g. they were on Telegram, now setting up Discord). Use `/pairing` to see who's waiting and `/pairing revoke <user_id>` to remove someone later.

### 7.2 Allow everyone (open mode)

```json
{ "channels": { "telegram": { "enabled": true, "allowFrom": ["*"] } } }
```

---

## 8. The WebUI

The WebUI ships **inside the published wheel** — no extra build step. It's a chat workbench in your browser with Thought/response timelines, live file-edit activity, model and context controls, and image generation.

**1. Enable the WebSocket channel:**

```json
{ "channels": { "websocket": { "enabled": true } } }
```

**2. Start the gateway:**

```bash
nanobot gateway
```

**3. Open it:** visit [`http://127.0.0.1:8765`](http://127.0.0.1:8765).

> [!TIP]
> To reach the WebUI from another device on your LAN — or from a Docker container — you must change the bind address from the default `127.0.0.1` to `0.0.0.0` and protect it with a token. See the Admin Guide's deployment and security sections.

---

## 9. Workflows & Recipes

### 9.1 Run a 24/7 personal assistant in Telegram

```bash
pip install nanobot-ai
nanobot onboard
# edit ~/.nanobot/config.json: add provider key + model + telegram block with allowFrom
nanobot gateway
```

Now message your bot from anywhere. Keep it online permanently with a [systemd / launchd service](#) (Admin Guide → Deployment).

### 9.2 Give the agent recurring tasks (Heartbeat)

The gateway wakes every 30 minutes and checks `~/.nanobot/workspace/HEARTBEAT.md`. Any task under `## Active Tasks` gets executed, and results are delivered to your most recently active chat.

```markdown
## Active Tasks

- [ ] Check the weather forecast and send me a summary
- [ ] Scan my inbox for urgent emails
```

> [!NOTE]
> The gateway must be running and you must have chatted with the bot at least once (so it knows which channel to deliver to). You can also just *ask* the agent to "add a periodic task" and it will edit `HEARTBEAT.md` for you.

### 9.3 Pursue a long-term goal

Ask the agent to hold a sustained objective and it uses its `goal` / `long_task` tools to keep working across many turns, showing visible multi-step progress. Example: *"Track this stock all day and message me if it drops more than 3%."*

### 9.4 Use nanobot from your own code (Python SDK)

No CLI, no gateway — just a library:

```python
import asyncio
from nanobot import Nanobot

async def main():
    bot = Nanobot.from_config()                 # reuses ~/.nanobot/config.json
    result = await bot.run("What time is it in Tokyo?")
    print(result.content)

asyncio.run(main())
```

Isolate conversations with `session_key`, target a specific workspace, and attach observability hooks:

```python
bot = Nanobot.from_config(workspace="/my/project")
await bot.run("hi", session_key="user-alice")
```

The full hook lifecycle (`before_iteration`, `on_stream`, `before_execute_tools`, `finalize_content`, …) is documented in the Admin Guide.

### 9.5 Call nanobot like the OpenAI API

```bash
pip install "nanobot-ai[api]"
nanobot serve            # binds 127.0.0.1:8900 by default
```

```bash
curl http://127.0.0.1:8900/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"hi"}],"session_id":"my-session"}'
```

```python
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8900/v1", api_key="dummy")
resp = client.chat.completions.create(
    model="MiniMax-M2.7",
    messages=[{"role": "user", "content": "hi"}],
    extra_body={"session_id": "my-session"},
)
print(resp.choices[0].message.content)
```

> [!NOTE]
> Each request must contain exactly **one** `user` message. Pass `session_id` to isolate conversations, set `stream=true` for SSE streaming, and attach files (images, PDF, Word, Excel, PowerPoint) via base64 or `multipart/form-data` up to 10 MB each. Endpoints: `GET /health`, `GET /v1/models`, `POST /v1/chat/completions`.

### 9.6 Run multiple isolated bots

Point `--config` at separate config files to run independent instances (e.g. one Telegram bot and one Discord bot) with their own workspaces, memory, and cron jobs:

```bash
nanobot onboard --config ~/.nanobot-telegram/config.json --workspace ~/.nanobot-telegram/workspace
nanobot gateway --config ~/.nanobot-telegram/config.json
nanobot gateway --config ~/.nanobot-discord/config.json --port 18792
```

> Each instance must use a different port if they run at the same time.

---

## 10. How nanobot Remembers (Memory)

nanobot keeps memory in layers so conversations stay fast in the moment but reflective over time. You don't have to manage any of it, but you can inspect and steer it.

| File (in your workspace) | Role |
|--------------------------|------|
| `SOUL.md` | The bot's long-term voice and communication style |
| `USER.md` | Stable knowledge about you and your preferences |
| `memory/MEMORY.md` | Durable project facts and decisions |
| `memory/history.jsonl` | Append-only archive of compressed past turns |

Two stages keep it tidy:

- **Consolidator** — when a conversation grows large, it summarizes the oldest safe slice and appends it to `history.jsonl`.
- **Dream** — a slower, scheduled pass that reads new history plus your durable files and makes the *smallest honest edit* to keep `SOUL.md` / `USER.md` / `MEMORY.md` coherent. Every change is version-controlled.

Inspect and steer memory from chat:

```text
/dream                 # consolidate now
/dream-log             # see the latest memory change
/dream-restore         # list recent versions
/dream-restore <sha>   # roll memory back to before a change
```

> [!TIP]
> Because Dream changes are versioned, memory is *auditable* — you can always see what changed and restore a previous state.

---

## 11. Troubleshooting

### Error: `Environment variable 'NAME' referenced in config is not set`

**Cause:** Your `config.json` uses a `${NAME}` placeholder but that environment variable isn't set in the gateway's environment. nanobot fails fast at startup rather than running with a missing secret.
**Solution:** Export the variable before launching (`export NAME=...`), use a `--env-file` with Docker, or put the literal value in config.
**Prevention:** `${VAR}` is **not** shell-style default syntax — there is no fallback. Make sure every referenced variable exists in the process environment.

### Error: `Permission denied` on `~/.nanobot` (Docker)

**Cause:** The container runs as user `nanobot` (UID 1000) but your mounted `~/.nanobot` is owned by a different UID.
**Solution:** `sudo chown -R 1000:1000 ~/.nanobot`, or run with `--user $(id -u):$(id -g)` (Podman: `--userns=keep-id`).
**Prevention:** Always mount your host config to `/home/nanobot/.nanobot`, not `/root/.nanobot`.

### The WebUI / a port isn't reachable from another device or Docker

**Cause:** The gateway and WebSocket channel default to `host: "127.0.0.1"` (loopback only). Docker `-p` forwarding and LAN clients cannot reach loopback.
**Solution:** Set both `gateway.host` and `channels.websocket.host` to `"0.0.0.0"` in config — and add a `token` or `tokenIssueSecret`, because the WebSocket channel refuses to start on `0.0.0.0` without one.
**Prevention:** Treat `0.0.0.0` as "exposed" and always pair it with authentication.

### Unapproved users can't talk to the bot / everyone is denied

**Cause:** Since v0.1.4.post4, an empty `allowFrom` denies **all** users by default.
**Solution:** Set `"allowFrom": ["*"]` to allow everyone, or list specific user IDs. Or leave it unset and approve people via [pairing](#71-pairing-granting-access).
**Prevention:** Decide per channel whether you want open access, an allow-list, or pairing-only.

### `generate_image` is not available

**Cause:** Image generation is disabled by default.
**Solution:** Set `tools.imageGeneration.enabled` to `true`, configure a provider, and restart the gateway.

### Missing API key error for image generation

**Cause:** No `providers.<provider>.apiKey`, or a `${VAR}` reference that the gateway process can't see.
**Solution:** Set the provider key; if using `${VAR_NAME}`, confirm the variable is visible to the gateway process.

### WhatsApp stopped working after upgrading nanobot

**Cause:** The local WhatsApp bridge is not rebuilt on upgrade.
**Solution:** `rm -rf ~/.nanobot/bridge && nanobot channels login whatsapp`.

### Matrix channel fails to start on Windows

**Cause:** `matrix-nio[e2e]` (via `python-olm`) has no Windows wheel and is skipped on Windows.
**Solution:** Use macOS, Linux, or WSL2 for the Matrix channel.

### Feishu replies aren't streaming

**Cause:** The Feishu app lacks the `cardkit:card:write` permission.
**Solution:** Add the permission and publish a new app version, **or** set `"streaming": false` under `channels.feishu` to use normal cards.

### The agent isn't searching the web

**Cause:** Web tools are disabled (`tools.web.enable: false`), or the configured search provider has no/invalid key.
**Solution:** Ensure `tools.web.enable` is `true`; the default `duckduckgo` provider needs no key. For paid providers, verify the API key.

### Web fetch fails on Cloudflare / proof-of-work pages

**Cause:** The default Jina Reader path can be blocked by anti-bot challenges.
**Solution:** Set a random user agent and disable Jina Reader:
```json
{ "tools": { "web": { "userAgent": "Not-A-Browser", "fetch": { "useJinaReader": false } } } }
```

### Replies fail to deliver: `Failed to send to {channel} after N attempts`

**Cause:** Persistent delivery failure — invalid token, revoked access, or an unreachable channel. nanobot retries (default 3 attempts with backoff) before giving up.
**Solution:** Check the channel's token/credentials and that the platform is reachable. Watch logs for this line to spot the failing channel.

---

## 12. FAQ

**Q: What's the difference between `nanobot agent`, `gateway`, and `serve`?**
`agent` is a local terminal chat (one-shot or interactive). `gateway` is the always-on service that connects your chat channels and serves the WebUI. `serve` exposes a minimal OpenAI-compatible HTTP API. They're independent processes that share config + workspace.

**Q: Do I need an API key?**
For hosted models, yes. For local models (Ollama, LM Studio, vLLM, OVMS, Atomic Chat) you can set `"apiKey": null` and pay nothing.

**Q: Where is my data stored?**
Everything lives under `~/.nanobot/` — `config.json`, your `workspace/` (memory, sessions, skills), `media/`, and channel auth state. Chat history is stored locally, not in any cloud.

**Q: Can one bot run on many chat apps at once?**
Yes. Enable as many channels as you like; a single `nanobot gateway` serves all of them. Enable `agents.defaults.unifiedSession` if you want them to share one conversation across devices.

**Q: How do I switch models without restarting?**
Define `modelPresets`, then use `/model <preset>` in chat. It's runtime-only and doesn't touch your config file.

**Q: How do I keep my API keys out of `config.json`?**
Use `${VAR_NAME}` references anywhere in config; nanobot resolves them from environment variables at startup and never writes the resolved value back to disk.

**Q: Is it safe to let the agent run shell commands?**
The `exec` tool blocks obviously dangerous patterns, but for real isolation enable the bubblewrap sandbox (`tools.exec.sandbox: "bwrap"`, Linux only) and `tools.restrictToWorkspace`. See the Admin Guide's security section before exposing the agent to untrusted input.

**Q: Can I add my own chat channel or tool?**
Yes — channels and tools are discovered via Python entry points. See the Admin Guide's extension guide (and `docs/channel-plugin-guide.md`).

**Q: How do I run multiple bots on one machine?**
Use `--config` (and optionally `--workspace`) to point each instance at its own files, with a different port per instance.

**Q: Does nanobot support voice?**
Yes — voice messages on Telegram and WhatsApp are auto-transcribed with Whisper (Groq free tier by default).

**Q: What is "Dream"?**
A scheduled memory-consolidation pass that distills your conversation history into durable, versioned long-term memory files. Trigger it manually with `/dream`.

**Q: Where's the official documentation?**
Repo docs ship in `docs/` and the stable docs live at [nanobot.wiki](https://nanobot.wiki/docs/latest/getting-started/nanobot-overview).

---

## 13. Quick Reference

### Shell commands

| Command | Purpose |
|---------|---------|
| `nanobot onboard [--wizard]` | Initialize/refresh config + workspace |
| `nanobot agent [-m "..."]` | Local chat (interactive or one-shot) |
| `nanobot gateway` | Start the channel + WebUI service |
| `nanobot serve` | Start the OpenAI-compatible API |
| `nanobot status` | Show status |
| `nanobot provider login <name>` | OAuth login (OpenAI Codex, GitHub Copilot) |
| `nanobot channels login <channel>` | Interactive channel login (QR, etc.) |
| `nanobot channels status` | Show channel status |
| `nanobot --version` | Show installed version |

### Common flags

| Flag | Applies to | Effect |
|------|-----------|--------|
| `-c, --config <path>` | most commands | Use a specific config file |
| `-w, --workspace <path>` | `agent`, `gateway`, `onboard` | Use a specific workspace |
| `-m "..."` | `agent` | One-shot message |
| `--no-markdown` | `agent` | Plain-text replies |
| `--logs` | `agent` | Show runtime logs |
| `--port <n>` | `gateway` | Override the gateway port |

### Key files & ports

| Item | Default |
|------|---------|
| Config | `~/.nanobot/config.json` |
| Workspace | `~/.nanobot/workspace/` |
| Media / generated images | `~/.nanobot/media/` |
| Gateway health endpoint | `127.0.0.1:18790` (`GET /health`) |
| WebUI / WebSocket | `127.0.0.1:8765` |
| OpenAI-compatible API | `127.0.0.1:8900` |

---

_Generated June 2026 from HKUDS/nanobot at 6a0368b. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
