đ 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-aibut the command you run is alwaysnanobot. Requires Python ⼠3.11.
1. Quick Start
Get a working AI agent in three commands.
Step 1 â Install
pip
pip install nanobot-ai
uv (fast)
uv tool install nanobot-ai
From source
git clone https://github.com/HKUDS/nanobot.git
cd nanobot
pip install -e .
Tip
Install from PyPI or
uvfor 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
nanobot onboard
â 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:
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 is recommended for global users because it can reach almost any model with one key.
{
"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 onboardalready filled it with defaults you want to keep.
Step 4 â Chat
nanobot agent
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:
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":
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 needspython-olm, which has no Windows wheel. Use macOS, Linux, or WSL2 for the Matrix channel.
Updating
# 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:
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 agentruns its own local agent. It does not attach to or proxy through a runningnanobot gatewayâ they are independent processes that share the same config and workspace.
4. Everyday Usage
4.1 Interactive terminal chat
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 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 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 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 |
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 |
| IMAP/SMTP credentials | No | |
| 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:
nanobot gateway
5.1 Telegram (recommended)
- Open Telegram, message @BotFather, send
/newbot, and copy the token. - Configure:
{
"channels": {
"telegram": {
"enabled": true,
"token": "YOUR_BOT_TOKEN",
"allowFrom": ["YOUR_USER_ID"]
}
}
}
- Run
nanobot gatewayand message your bot.
Tip
Find your numeric User ID with
@userinfobotand 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
{
"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.
groupPolicycontrols group behavior:"mention"(default â reply only when @mentioned) or"open"(reply to everything). DMs always respond for users inallowFrom.
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:
{
"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:
{
"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 addcardkit: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.
# Terminal 1 â link your device (scan the QR with WhatsApp â Linked Devices)
nanobot channels login whatsapp
# Terminal 2 â run the service
nanobot gateway
{
"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.
{
"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
consentGrantedmust betrueto allow mailbox access â it's a deliberate safety gate. Set"autoReplyEnabled": falseif you only want nanobot to read/analyze mail without sending automatic replies. For Gmail, create a dedicated account and an App Password.
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
allowFromdenies everyone. Set["*"]to allow all users, or list specific user IDs. If you don't setallowFrom, unapproved users who DM the bot get a pairing 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": nullfor 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)
{
"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 |
{
"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.
{
"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:
/model deep # switch to your "deep" preset for future turns
/model default # return to agents.defaults.*
Note
/modelswitching is runtime-only â it never rewritesconfig.json, and an in-progress turn keeps the model it started with.defaultis always available and meansagents.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:
{
"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 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.
{
"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.
- An un-approved user DMs the bot on any channel.
- The bot replies with a pairing code like
ABCD-EFGH(valid 10 minutes) and tells them to forward it to you. - You approve it:
/pairing approve ABCD-EFGH
- 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
/pairingto see who's waiting and/pairing revoke <user_id>to remove someone later.
7.2 Allow everyone (open mode)
{ "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:
{ "channels": { "websocket": { "enabled": true } } }
2. Start the gateway:
nanobot gateway
3. Open it: visit 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.1to0.0.0.0and 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
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.
## 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.mdfor 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:
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:
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
pip install "nanobot-ai[api]"
nanobot serve # binds 127.0.0.1:8900 by default
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"}'
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
usermessage. Passsession_idto isolate conversations, setstream=truefor SSE streaming, and attach files (images, PDF, Word, Excel, PowerPoint) via base64 ormultipart/form-dataup 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:
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.mdcoherent. Every change is version-controlled.
Inspect and steer memory from chat:
/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.
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:
{ "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.
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 |