diff --git a/README.md b/README.md index 140846ac..2d785f1d 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,21 @@ +![cover-v5-optimized](./images/GitHub_README.png) +
- nanobot -

nanobot: Ultra-Lightweight Personal AI Agent

PyPI Downloads Python License - Docs + + follow on X(Twitter) + Docs Feishu WeChat Discord

-๐Ÿˆ **nanobot** is an **ultra-lightweight** personal AI agent inspired by [OpenClaw](https://github.com/openclaw/openclaw). - -โšก๏ธ Delivers core agent functionality with **99% fewer lines of code**. - -๐Ÿ“ Real-time line count: run `bash core_agent_lines.sh` to verify anytime. +๐Ÿˆ **nanobot** is an open-source and ultra-lightweight personal AI agent in the spirit of [OpenClaw](https://github.com/openclaw/openclaw), [Claude Code](https://www.anthropic.com/claude-code), and [Codex](https://www.openai.com/codex/). It keeps the core agent loop small and readable while still supporting chat channels, memory, MCP, an OpenAI-compatible API, and practical deployment paths, so you can go from local setup to a long-running personal agent with minimal overhead. ## ๐Ÿ“ข News @@ -100,46 +98,94 @@ -> ๐Ÿˆ nanobot is for educational, research, and technical exchange purposes only. It is unrelated to crypto and does not involve any official token or coin. -## Key Features of nanobot: +## ๐Ÿ’ก Key Features of nanobot -๐Ÿชถ **Ultra-Lightweight**: A lightweight implementation built for stable, long-running AI agents. +- **Ultra-lightweight**: stable long-running agent behavior with a small, readable core. +- **Research-ready**: the codebase is intentionally simple enough to study, modify, and extend. +- **Practical**: chat channels, API, memory, MCP, and deployment paths are already built in. +- **Hackable**: you can start fast, then go deeper through repo docs instead of a monolithic landing page. -๐Ÿ”ฌ **Research-Ready**: Clean, readable code that's easy to understand, modify, and extend for research. +## ๐Ÿ“ฆ Install -โšก๏ธ **Lightning Fast**: Minimal footprint means faster startup, lower resource usage, and quicker iterations. +> [!IMPORTANT] +> If you want the newest features and experiments, install from source. If you want the most stable day-to-day experience, install from PyPI or with `uv`. -๐Ÿ’Ž **Easy-to-Use**: One-click to deploy and you're ready to go. +**Install from source** + +```bash +git clone https://github.com/HKUDS/nanobot.git +cd nanobot +pip install -e . +``` + +**Install with `uv`** + +```bash +uv tool install nanobot-ai +``` + +**Install from PyPI** + +```bash +pip install nanobot-ai +``` + +## ๐Ÿš€ Quick Start + +**1. Initialize** + +```bash +nanobot onboard +``` + +**2. Configure** (`~/.nanobot/config.json`) + +Configure these **two parts** in your config (other options have defaults). Add or merge the following blocks into your existing config instead of replacing the whole file. + +*Set your API key* (e.g. [OpenRouter](https://openrouter.ai/keys), recommended for global users): + +```json +{ + "providers": { + "openrouter": { + "apiKey": "sk-or-v1-xxx" + } + } +} +``` + +*Set your model* (optionally pin a provider โ€” defaults to auto-detection): + +```json +{ + "agents": { + "defaults": { + "provider": "openrouter", + "model": "anthropic/claude-opus-4-6" + } + } +} +``` + +**3. Chat** + +```bash +nanobot agent +``` + + +- Want different LLM providers, web search, MCP, security settings, or more config options? See [`Configuration`](./docs/configuration.md) +- Want to run nanobot in chat apps like Telegram, Discord, WeChat or Feishu? See [`Chat Apps`](./docs/chat-apps.md) +- Want Docker or Linux service deployment? See [`Deployment`](./docs/deployment.md) ## ๐Ÿ—๏ธ Architecture

- nanobot architecture + nanobot architecture

-## Table of Contents - -- [News](#-news) -- [Key Features](#key-features-of-nanobot) -- [Architecture](#๏ธ-architecture) -- [Features](#-features) -- [Install](#-install) -- [Quick Start](#-quick-start) -- [Chat Apps](#-chat-apps) -- [Agent Social Network](#-agent-social-network) -- [Configuration](#๏ธ-configuration) -- [Multiple Instances](#-multiple-instances) -- [Memory](#-memory) -- [CLI Reference](#-cli-reference) -- [In-Chat Commands](#-in-chat-commands) -- [Python SDK](#-python-sdk) -- [OpenAI-Compatible API](#-openai-compatible-api) -- [Docker](#-docker) -- [Linux Service](#-linux-service) -- [Project Structure](#-project-structure) -- [Contribute & Roadmap](#-contribute--roadmap) -- [Star History](#-star-history) +๐Ÿˆ nanobot stays lightweight by centering everything around a small agent loop: messages come in from chat apps, the LLM decides when tools are needed, and memory or skills are pulled in only as context instead of becoming a heavy orchestration layer. That keeps the core path readable and easy to extend, while still letting you add channels, tools, memory, and deployment options without turning the system into a monolith. ## โœจ Features @@ -164,2061 +210,14 @@ -## ๐Ÿ“ฆ Install +## ๐Ÿ“š Docs -> [!IMPORTANT] -> This README may describe features that are available first in the latest source code. -> If you want the newest features and experiments, install from source. -> If you want the most stable day-to-day experience, install from PyPI or with `uv`. +Browse the [repo docs](./docs/README.md) for the latest features and GitHub development version, or visit [nanobot.wiki](https://nanobot.wiki/docs/latest/getting-started/nanobot-overview) for the stable release documentation. -**Install from source** (latest features, experimental changes may land here first; recommended for development) - -```bash -git clone https://github.com/HKUDS/nanobot.git -cd nanobot -pip install -e . -``` - -**Install with [uv](https://github.com/astral-sh/uv)** (stable release, fast) - -```bash -uv tool install nanobot-ai -``` - -**Install from PyPI** (stable release) - -```bash -pip install nanobot-ai -``` - -### Update to latest version - -**PyPI / pip** - -```bash -pip install -U nanobot-ai -nanobot --version -``` - -**uv** - -```bash -uv tool upgrade nanobot-ai -nanobot --version -``` - -**Using WhatsApp?** Rebuild the local bridge after upgrading: - -```bash -rm -rf ~/.nanobot/bridge -nanobot channels login whatsapp -``` - -## ๐Ÿš€ Quick Start - -> [!TIP] -> Set your API key in `~/.nanobot/config.json`. -> Get API keys: [OpenRouter](https://openrouter.ai/keys) (Global) -> -> For other LLM providers, please see the [Providers](#providers) section. -> -> For web search capability setup, please see [Web Search](#web-search). - -**1. Initialize** - -```bash -nanobot onboard -``` - -Use `nanobot onboard --wizard` if you want the interactive setup wizard. - -**2. Configure** (`~/.nanobot/config.json`) - -Configure these **two parts** in your config (other options have defaults). - -*Set your API key* (e.g. OpenRouter, recommended for global users): -```json -{ - "providers": { - "openrouter": { - "apiKey": "sk-or-v1-xxx" - } - } -} -``` - -*Set your model* (optionally pin a provider โ€” defaults to auto-detection): -```json -{ - "agents": { - "defaults": { - "model": "anthropic/claude-opus-4-5", - "provider": "openrouter" - } - } -} -``` - -**3. Chat** - -```bash -nanobot agent -``` - -That's it! You have a working AI agent in 2 minutes. - -## ๐Ÿ’ฌ Chat Apps - -Connect nanobot to your favorite chat platform. Want to build your own? See the [Channel Plugin Guide](./docs/CHANNEL_PLUGIN_GUIDE.md). - -| Channel | What you need | -|---------|---------------| -| **Telegram** | Bot token from @BotFather | -| **Discord** | Bot token + Message Content intent | -| **WhatsApp** | QR code scan (`nanobot channels login whatsapp`) | -| **WeChat (Weixin)** | QR code scan (`nanobot channels login weixin`) | -| **Feishu** | App ID + App Secret | -| **DingTalk** | App Key + App Secret | -| **Slack** | Bot token + App-Level token | -| **Matrix** | Homeserver URL + Access token | -| **Email** | IMAP/SMTP credentials | -| **QQ** | App ID + App Secret | -| **Wecom** | Bot ID + Bot Secret | -| **Microsoft Teams** | App ID + App Password + public HTTPS endpoint | -| **Mochat** | Claw token (auto-setup available) | - -
-Telegram (Recommended) - -**1. Create a bot** -- Open Telegram, search `@BotFather` -- Send `/newbot`, follow prompts -- Copy the token - -**2. Configure** - -```json -{ - "channels": { - "telegram": { - "enabled": true, - "token": "YOUR_BOT_TOKEN", - "allowFrom": ["YOUR_USER_ID"] - } - } -} -``` - -> You can find your **User ID** in Telegram settings. It is shown as `@yourUserId`. -> Copy this value **without the `@` symbol** and paste it into the config file. - - -**3. Run** - -```bash -nanobot gateway -``` - -
- -
-Mochat (Claw IM) - -Uses **Socket.IO WebSocket** by default, with HTTP polling fallback. - -**1. Ask nanobot to set up Mochat for you** - -Simply send this message to nanobot (replace `xxx@xxx` with your real email): - -``` -Read https://raw.githubusercontent.com/HKUDS/MoChat/refs/heads/main/skills/nanobot/skill.md and register on MoChat. My Email account is xxx@xxx Bind me as your owner and DM me on MoChat. -``` - -nanobot will automatically register, configure `~/.nanobot/config.json`, and connect to Mochat. - -**2. Restart gateway** - -```bash -nanobot gateway -``` - -That's it โ€” nanobot handles the rest! - -
- -
-Manual configuration (advanced) - -If you prefer to configure manually, add the following to `~/.nanobot/config.json`: - -> Keep `claw_token` private. It should only be sent in `X-Claw-Token` header to your Mochat API endpoint. - -```json -{ - "channels": { - "mochat": { - "enabled": true, - "base_url": "https://mochat.io", - "socket_url": "https://mochat.io", - "socket_path": "/socket.io", - "claw_token": "claw_xxx", - "agent_user_id": "6982abcdef", - "sessions": ["*"], - "panels": ["*"], - "reply_delay_mode": "non-mention", - "reply_delay_ms": 120000 - } - } -} -``` - - - -
- -
- -
-Discord - -**1. Create a bot** -- Go to https://discord.com/developers/applications -- Create an application โ†’ Bot โ†’ Add Bot -- Copy the bot token - -**2. Enable intents** -- In the Bot settings, enable **MESSAGE CONTENT INTENT** -- (Optional) Enable **SERVER MEMBERS INTENT** if you plan to use allow lists based on member data - -**3. Get your User ID** -- Discord Settings โ†’ Advanced โ†’ enable **Developer Mode** -- Right-click your avatar โ†’ **Copy User ID** - -**4. Configure** - -```json -{ - "channels": { - "discord": { - "enabled": true, - "token": "YOUR_BOT_TOKEN", - "allowFrom": ["YOUR_USER_ID"], - "allowChannels": [], - "groupPolicy": "mention", - "streaming": true - } - } -} -``` - -> `groupPolicy` controls how the bot responds in group channels: -> - `"mention"` (default) โ€” Only respond when @mentioned -> - `"open"` โ€” Respond to all messages -> DMs always respond when the sender is in `allowFrom`. -> - If you set group policy to open create new threads as private threads and then @ the bot into it. Otherwise the thread itself and the channel in which you spawned it will spawn a bot session. -> `allowChannels` restricts the bot to specific Discord channel IDs. Empty (default) means respond in every channel the bot can see. Example: `["1234567890", "0987654321"]`. The filter applies after `allowFrom`, so both must pass. -> `streaming` defaults to `true`. Disable it only if you explicitly want non-streaming replies. - -**5. Invite the bot** -- OAuth2 โ†’ URL Generator -- Scopes: `bot` -- Bot Permissions: `Send Messages`, `Read Message History` -- Open the generated invite URL and add the bot to your server - -**6. Run** - -```bash -nanobot gateway -``` - -
- -
-Matrix (Element) - -Install Matrix dependencies first: - -```bash -pip install nanobot-ai[matrix] -``` - -> [!NOTE] -> Matrix is not supported on Windows. `matrix-nio[e2e]` depends on -> `python-olm`, which has no pre-built Windows wheel and is skipped by the -> `matrix` extra on `sys_platform == 'win32'`. The command above will still -> succeed on Windows but without `matrix-nio` installed, so enabling the -> Matrix channel will fail at startup. Use macOS, Linux, or WSL2. - -**1. Create/choose a Matrix account** - -- Create or reuse a Matrix account on your homeserver (for example `matrix.org`). -- Confirm you can log in with Element. - -**2. Get credentials** - -- You need: - - `userId` (example: `@nanobot:matrix.org`) - - `password` - -(Note: `accessToken` and `deviceId` are still supported for legacy reasons, but -for reliable encryption, password login is recommended instead. If the -`password` is provided, `accessToken` and `deviceId` will be ignored.) - -**3. Configure** - -```json -{ - "channels": { - "matrix": { - "enabled": true, - "homeserver": "https://matrix.org", - "userId": "@nanobot:matrix.org", - "password": "mypasswordhere", - "e2eeEnabled": true, - "allowFrom": ["@your_user:matrix.org"], - "groupPolicy": "open", - "groupAllowFrom": [], - "allowRoomMentions": false, - "maxMediaBytes": 20971520 - } - } -} -``` - -> Keep a persistent `matrix-store` โ€” encrypted session state is lost if these change across restarts. - -| Option | Description | -|--------|-------------| -| `allowFrom` | User IDs allowed to interact. Empty denies all; use `["*"]` to allow everyone. | -| `groupPolicy` | `open` (default), `mention`, or `allowlist`. | -| `groupAllowFrom` | Room allowlist (used when policy is `allowlist`). | -| `allowRoomMentions` | Accept `@room` mentions in mention mode. | -| `e2eeEnabled` | E2EE support (default `true`). Set `false` for plaintext-only. | -| `maxMediaBytes` | Max attachment size (default `20MB`). Set `0` to block all media. | - - - - -**4. Run** - -```bash -nanobot gateway -``` - -
- -
-WhatsApp - -Requires **Node.js โ‰ฅ18**. - -**1. Link device** - -```bash -nanobot channels login whatsapp -# Scan QR with WhatsApp โ†’ Settings โ†’ Linked Devices -``` - -**2. Configure** - -```json -{ - "channels": { - "whatsapp": { - "enabled": true, - "allowFrom": ["+1234567890"] - } - } -} -``` - -**3. Run** (two terminals) - -```bash -# Terminal 1 -nanobot channels login whatsapp - -# Terminal 2 -nanobot gateway -``` - -> WhatsApp bridge updates are not applied automatically for existing installations. -> After upgrading nanobot, rebuild the local bridge with: -> `rm -rf ~/.nanobot/bridge && nanobot channels login whatsapp` - -
- -
-Feishu - -Uses **WebSocket** long connection โ€” no public IP required. - -**1. Create a Feishu bot** -- Visit [Feishu Open Platform](https://open.feishu.cn/app) -- Create a new app โ†’ Enable **Bot** capability -- **Permissions**: - - `im:message` (send messages) and `im:message.p2p_msg:readonly` (receive messages) - - **Streaming replies** (default in nanobot): add **`cardkit:card:write`** (often labeled **Create and update cards** in the Feishu developer console). Required for CardKit entities and streamed assistant text. Older apps may not have it yet โ€” open **Permission management**, enable the scope, then **publish** a new app version if the console requires it. - - If you **cannot** add `cardkit:card:write`, set `"streaming": false` under `channels.feishu` (see below). The bot still works; replies use normal interactive cards without token-by-token streaming. -- **Events**: Add `im.message.receive_v1` (receive messages) - - Select **Long Connection** mode (requires running nanobot first to establish connection) -- Get **App ID** and **App Secret** from "Credentials & Basic Info" -- Publish the app - -**2. Configure** - -```json -{ - "channels": { - "feishu": { - "enabled": true, - "appId": "cli_xxx", - "appSecret": "xxx", - "encryptKey": "", - "verificationToken": "", - "allowFrom": ["ou_YOUR_OPEN_ID"], - "groupPolicy": "mention", - "reactEmoji": "OnIt", - "doneEmoji": "DONE", - "toolHintPrefix": "๐Ÿ”ง", - "streaming": true, - "domain": "feishu" - } - } -} -``` - -> `streaming` defaults to `true`. Use `false` if your app does not have **`cardkit:card:write`** (see permissions above). -> `encryptKey` and `verificationToken` are optional for Long Connection mode. -> `allowFrom`: Add your open_id (find it in nanobot logs when you message the bot). Use `["*"]` to allow all users. -> `groupPolicy`: `"mention"` (default โ€” respond only when @mentioned), `"open"` (respond to all group messages). Private chats always respond. -> `reactEmoji`: Emoji for "processing" status (default: `OnIt`). See [available emojis](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce). -> `doneEmoji`: Optional emoji for "completed" status (e.g., `DONE`, `OK`, `HEART`). When set, bot adds this reaction after removing `reactEmoji`. -> `toolHintPrefix`: Prefix for inline tool hints in streaming cards (default: `๐Ÿ”ง`). -> `domain`: `"feishu"` (default) for China (open.feishu.cn), `"lark"` for international Lark (open.larksuite.com). - -**3. Run** - -```bash -nanobot gateway -``` - -> [!TIP] -> Feishu uses WebSocket to receive messages โ€” no webhook or public IP needed! - -
- -
-QQ (QQๅ•่Š) - -Uses **botpy SDK** with WebSocket โ€” no public IP required. Currently supports **private messages only**. - -**1. Register & create bot** -- Visit [QQ Open Platform](https://q.qq.com) โ†’ Register as a developer (personal or enterprise) -- Create a new bot application -- Go to **ๅผ€ๅ‘่ฎพ็ฝฎ (Developer Settings)** โ†’ copy **AppID** and **AppSecret** - -**2. Set up sandbox for testing** -- In the bot management console, find **ๆฒ™็ฎฑ้…็ฝฎ (Sandbox Config)** -- Under **ๅœจๆถˆๆฏๅˆ—่กจ้…็ฝฎ**, click **ๆทปๅŠ ๆˆๅ‘˜** and add your own QQ number -- Once added, scan the bot's QR code with mobile QQ โ†’ open the bot profile โ†’ tap "ๅ‘ๆถˆๆฏ" to start chatting - -**3. Configure** - -> - `allowFrom`: Add your openid (find it in nanobot logs when you message the bot). Use `["*"]` for public access. -> - `msgFormat`: Optional. Use `"plain"` (default) for maximum compatibility with legacy QQ clients, or `"markdown"` for richer formatting on newer clients. -> - For production: submit a review in the bot console and publish. See [QQ Bot Docs](https://bot.q.qq.com/wiki/) for the full publishing flow. - -```json -{ - "channels": { - "qq": { - "enabled": true, - "appId": "YOUR_APP_ID", - "secret": "YOUR_APP_SECRET", - "allowFrom": ["YOUR_OPENID"], - "msgFormat": "plain" - } - } -} -``` - -**4. Run** - -```bash -nanobot gateway -``` - -Now send a message to the bot from QQ โ€” it should respond! - -
- -
-DingTalk (้’‰้’‰) - -Uses **Stream Mode** โ€” no public IP required. - -**1. Create a DingTalk bot** -- Visit [DingTalk Open Platform](https://open-dev.dingtalk.com/) -- Create a new app -> Add **Robot** capability -- **Configuration**: - - Toggle **Stream Mode** ON -- **Permissions**: Add necessary permissions for sending messages -- Get **AppKey** (Client ID) and **AppSecret** (Client Secret) from "Credentials" -- Publish the app - -**2. Configure** - -```json -{ - "channels": { - "dingtalk": { - "enabled": true, - "clientId": "YOUR_APP_KEY", - "clientSecret": "YOUR_APP_SECRET", - "allowFrom": ["YOUR_STAFF_ID"] - } - } -} -``` - -> `allowFrom`: Add your staff ID. Use `["*"]` to allow all users. - -**3. Run** - -```bash -nanobot gateway -``` - -
- -
-Slack - -Uses **Socket Mode** โ€” no public URL required. - -**1. Create a Slack app** -- Go to [Slack API](https://api.slack.com/apps) โ†’ **Create New App** โ†’ "From scratch" -- Pick a name and select your workspace - -**2. Configure the app** -- **Socket Mode**: Toggle ON โ†’ Generate an **App-Level Token** with `connections:write` scope โ†’ copy it (`xapp-...`) -- **OAuth & Permissions**: Add bot scopes: `chat:write`, `reactions:write`, `app_mentions:read` -- **Event Subscriptions**: Toggle ON โ†’ Subscribe to bot events: `message.im`, `message.channels`, `app_mention` โ†’ Save Changes -- **App Home**: Scroll to **Show Tabs** โ†’ Enable **Messages Tab** โ†’ Check **"Allow users to send Slash commands and messages from the messages tab"** -- **Install App**: Click **Install to Workspace** โ†’ Authorize โ†’ copy the **Bot Token** (`xoxb-...`) - -**3. Configure nanobot** - -```json -{ - "channels": { - "slack": { - "enabled": true, - "botToken": "xoxb-...", - "appToken": "xapp-...", - "allowFrom": ["YOUR_SLACK_USER_ID"], - "groupPolicy": "mention" - } - } -} -``` - -**4. Run** - -```bash -nanobot gateway -``` - -DM the bot directly or @mention it in a channel โ€” it should respond! - -> [!TIP] -> - `groupPolicy`: `"mention"` (default โ€” respond only when @mentioned), `"open"` (respond to all channel messages), or `"allowlist"` (restrict to specific channels). -> - DM policy defaults to open. Set `"dm": {"enabled": false}` to disable DMs. - -
- -
-Email - -Give nanobot its own email account. It polls **IMAP** for incoming mail and replies via **SMTP** โ€” like a personal email assistant. - -**1. Get credentials (Gmail example)** -- Create a dedicated Gmail account for your bot (e.g. `my-nanobot@gmail.com`) -- Enable 2-Step Verification โ†’ Create an [App Password](https://myaccount.google.com/apppasswords) -- Use this app password for both IMAP and SMTP - -**2. Configure** - -> - `consentGranted` must be `true` to allow mailbox access. This is a safety gate โ€” set `false` to fully disable. -> - `allowFrom`: Add your email address. Use `["*"]` to accept emails from anyone. -> - `smtpUseTls` and `smtpUseSsl` default to `true` / `false` respectively, which is correct for Gmail (port 587 + STARTTLS). No need to set them explicitly. -> - Set `"autoReplyEnabled": false` if you only want to read/analyze emails without sending automatic replies. -> - `allowedAttachmentTypes`: Save inbound attachments matching these MIME types โ€” `["*"]` for all, e.g. `["application/pdf", "image/*"]` (default `[]` = disabled). -> - `maxAttachmentSize`: Max size per attachment in bytes (default `2000000` / 2MB). -> - `maxAttachmentsPerEmail`: Max attachments to save per email (default `5`). - -```json -{ - "channels": { - "email": { - "enabled": true, - "consentGranted": true, - "imapHost": "imap.gmail.com", - "imapPort": 993, - "imapUsername": "my-nanobot@gmail.com", - "imapPassword": "your-app-password", - "smtpHost": "smtp.gmail.com", - "smtpPort": 587, - "smtpUsername": "my-nanobot@gmail.com", - "smtpPassword": "your-app-password", - "fromAddress": "my-nanobot@gmail.com", - "allowFrom": ["your-real-email@gmail.com"], - "allowedAttachmentTypes": ["application/pdf", "image/*"] - } - } -} -``` - - -**3. Run** - -```bash -nanobot gateway -``` - -
- -
-WeChat (ๅพฎไฟก / Weixin) - -Uses **HTTP long-poll** with QR-code login via the ilinkai personal WeChat API. No local WeChat desktop client is required. - -**1. Install with WeChat support** - -```bash -pip install "nanobot-ai[weixin]" -``` - -**2. Configure** - -```json -{ - "channels": { - "weixin": { - "enabled": true, - "allowFrom": ["YOUR_WECHAT_USER_ID"] - } - } -} -``` - -> - `allowFrom`: Add the sender ID you see in nanobot logs for your WeChat account. Use `["*"]` to allow all users. -> - `token`: Optional. If omitted, log in interactively and nanobot will save the token for you. -> - `routeTag`: Optional. When your upstream Weixin deployment requires request routing, nanobot will send it as the `SKRouteTag` header. -> - `stateDir`: Optional. Defaults to nanobot's runtime directory for Weixin state. -> - `pollTimeout`: Optional long-poll timeout in seconds. - -**3. Login** - -```bash -nanobot channels login weixin -``` - -Use `--force` to re-authenticate and ignore any saved token: - -```bash -nanobot channels login weixin --force -``` - -**4. Run** - -```bash -nanobot gateway -``` - -
- -
-Wecom (ไผไธšๅพฎไฟก) - -> Here we use [wecom-aibot-sdk-python](https://github.com/chengyongru/wecom_aibot_sdk) (community Python version of the official [@wecom/aibot-node-sdk](https://www.npmjs.com/package/@wecom/aibot-node-sdk)). -> -> Uses **WebSocket** long connection โ€” no public IP required. - -**1. Install the optional dependency** - -```bash -pip install nanobot-ai[wecom] -``` - -**2. Create a WeCom AI Bot** - -Go to the WeCom admin console โ†’ Intelligent Robot โ†’ Create Robot โ†’ select **API mode** with **long connection**. Copy the Bot ID and Secret. - -**3. Configure** - -```json -{ - "channels": { - "wecom": { - "enabled": true, - "botId": "your_bot_id", - "secret": "your_bot_secret", - "allowFrom": ["your_id"] - } - } -} -``` - -**4. Run** - -```bash -nanobot gateway -``` - -
- -
-Microsoft Teams (MVP โ€” DM only) - -> Direct-message text in/out, tenant-aware OAuth, conversation reference persistence. -> Uses a public HTTPS webhook โ€” no WebSocket; you need a tunnel or reverse proxy. - -**1. Install the optional dependency** - -```bash -pip install nanobot-ai[msteams] -``` - -**2. Create a Teams / Azure bot app registration** - -Create or reuse a Microsoft Teams / Azure bot app registration. Set the bot messaging endpoint to a public HTTPS URL ending in `/api/messages`. - -**3. Configure** - -```json -{ - "channels": { - "msteams": { - "enabled": true, - "appId": "YOUR_APP_ID", - "appPassword": "YOUR_APP_SECRET", - "tenantId": "YOUR_TENANT_ID", - "host": "0.0.0.0", - "port": 3978, - "path": "/api/messages", - "allowFrom": ["*"], - "replyInThread": true, - "mentionOnlyResponse": "Hi โ€” what can I help with?", - "validateInboundAuth": true - } - } -} -``` - -> - `replyInThread: true` replies to the triggering Teams activity when a stored `activity_id` is available. -> - `mentionOnlyResponse` controls what Nanobot receives when a user sends only a bot mention (`Nanobot`). Set to `""` to ignore mention-only messages. -> - `validateInboundAuth: true` enables inbound Bot Framework bearer-token validation (signature, issuer, audience, lifetime, `serviceUrl`). This is the safe default for public deployments. Only set it to `false` for local development or tightly controlled testing. - -**4. Run** - -```bash -nanobot gateway -``` - -
- -## ๐ŸŒ Agent Social Network - -๐Ÿˆ nanobot is capable of linking to the agent social network (agent community). **Just send one message and your nanobot joins automatically!** - -| Platform | How to Join (send this message to your bot) | -|----------|-------------| -| [**Moltbook**](https://www.moltbook.com/) | `Read https://moltbook.com/skill.md and follow the instructions to join Moltbook` | -| [**ClawdChat**](https://clawdchat.ai/) | `Read https://clawdchat.ai/skill.md and follow the instructions to join ClawdChat` | - -Simply send the command above to your nanobot (via CLI or any chat channel), and it will handle the rest. - -## โš™๏ธ Configuration - -Config file: `~/.nanobot/config.json` - -> [!NOTE] -> If your config file is older than the current schema, you can refresh it without overwriting your existing values: -> run `nanobot onboard`, then answer `N` when asked whether to overwrite the config. -> nanobot will merge in missing default fields and keep your current settings. - -### Environment Variables for Secrets - -Instead of storing secrets directly in `config.json`, you can use `${VAR_NAME}` references that are resolved from environment variables at startup: - -```json -{ - "channels": { - "telegram": { "token": "${TELEGRAM_TOKEN}" }, - "email": { - "imapPassword": "${IMAP_PASSWORD}", - "smtpPassword": "${SMTP_PASSWORD}" - } - }, - "providers": { - "groq": { "apiKey": "${GROQ_API_KEY}" } - } -} -``` - -For **systemd** deployments, use `EnvironmentFile=` in the service unit to load variables from a file that only the deploying user can read: - -```ini -# /etc/systemd/system/nanobot.service (excerpt) -[Service] -EnvironmentFile=/home/youruser/nanobot_secrets.env -User=nanobot -ExecStart=... -``` - -```bash -# /home/youruser/nanobot_secrets.env (mode 600, owned by youruser) -TELEGRAM_TOKEN=your-token-here -IMAP_PASSWORD=your-password-here -``` - -### Providers - -> [!TIP] -> - **Voice transcription**: Voice messages (Telegram, WhatsApp) are automatically transcribed using Whisper. By default Groq is used (free tier). Set `"transcriptionProvider": "openai"` under `channels` to use OpenAI Whisper instead โ€” the API key is picked from the matching provider config. -> - **MiniMax Coding Plan**: Exclusive discount links for the nanobot community: [Overseas](https://platform.minimax.io/subscribe/coding-plan?code=9txpdXw04g&source=link) ยท [Mainland China](https://platform.minimaxi.com/subscribe/token-plan?code=GILTJpMTqZ&source=link) -> - **MiniMax (Mainland China)**: If your API key is from MiniMax's mainland China platform (minimaxi.com), set `"apiBase": "https://api.minimaxi.com/v1"` in your minimax provider config. -> - **MiniMax thinking mode**: Use `providers.minimaxAnthropic` when you want `reasoningEffort` / thinking mode. MiniMax exposes that capability through its Anthropic-compatible endpoint, so nanobot keeps it as a separate provider instead of guessing MiniMax-specific thinking parameters on the generic OpenAI-compatible `minimax` endpoint. It uses the same `MINIMAX_API_KEY`. Default Anthropic-compatible base URL: `https://api.minimax.io/anthropic`; for mainland China use `https://api.minimaxi.com/anthropic`. -> - **VolcEngine / BytePlus Coding Plan**: Use dedicated providers `volcengineCodingPlan` or `byteplusCodingPlan` instead of the pay-per-use `volcengine` / `byteplus` providers. -> - **Zhipu Coding Plan**: If you're on Zhipu's coding plan, set `"apiBase": "https://open.bigmodel.cn/api/coding/paas/v4"` in your zhipu provider config. -> - **Alibaba Cloud BaiLian**: If you're using Alibaba Cloud BaiLian's OpenAI-compatible endpoint, set `"apiBase": "https://dashscope.aliyuncs.com/compatible-mode/v1"` in your dashscope provider config. -> - **Step Fun (Mainland China)**: If your API key is from Step Fun's mainland China platform (stepfun.com), set `"apiBase": "https://api.stepfun.com/v1"` in your stepfun provider config. - -| Provider | Purpose | Get API Key | -|----------|---------|-------------| -| `custom` | Any OpenAI-compatible endpoint | โ€” | -| `openrouter` | LLM (recommended, access to all models) | [openrouter.ai](https://openrouter.ai) | -| `volcengine` | LLM (VolcEngine, pay-per-use) | [Coding Plan](https://www.volcengine.com/activity/codingplan?utm_campaign=nanobot&utm_content=nanobot&utm_medium=devrel&utm_source=OWO&utm_term=nanobot) ยท [volcengine.com](https://www.volcengine.com) | -| `byteplus` | LLM (VolcEngine international, pay-per-use) | [Coding Plan](https://www.byteplus.com/en/activity/codingplan?utm_campaign=nanobot&utm_content=nanobot&utm_medium=devrel&utm_source=OWO&utm_term=nanobot) ยท [byteplus.com](https://www.byteplus.com) | -| `anthropic` | LLM (Claude direct) | [console.anthropic.com](https://console.anthropic.com) | -| `azure_openai` | LLM (Azure OpenAI) | [portal.azure.com](https://portal.azure.com) | -| `openai` | LLM + Voice transcription (Whisper) | [platform.openai.com](https://platform.openai.com) | -| `deepseek` | LLM (DeepSeek direct) | [platform.deepseek.com](https://platform.deepseek.com) | -| `groq` | LLM + Voice transcription (Whisper, default) | [console.groq.com](https://console.groq.com) | -| `minimax` | LLM (MiniMax direct) | [platform.minimaxi.com](https://platform.minimaxi.com) | -| `minimax_anthropic` | LLM (MiniMax Anthropic-compatible endpoint, thinking mode) | [platform.minimaxi.com](https://platform.minimaxi.com) | -| `gemini` | LLM (Gemini direct) | [aistudio.google.com](https://aistudio.google.com) | -| `aihubmix` | LLM (API gateway, access to all models) | [aihubmix.com](https://aihubmix.com) | -| `siliconflow` | LLM (SiliconFlow/็ก…ๅŸบๆตๅŠจ) | [siliconflow.cn](https://siliconflow.cn) | -| `dashscope` | LLM (Qwen) | [dashscope.console.aliyun.com](https://dashscope.console.aliyun.com) | -| `moonshot` | LLM (Moonshot/Kimi) | [platform.moonshot.cn](https://platform.moonshot.cn) | -| `zhipu` | LLM (Zhipu GLM) | [open.bigmodel.cn](https://open.bigmodel.cn) | -| `mimo` | LLM (MiMo) | [platform.xiaomimimo.com](https://platform.xiaomimimo.com) | -| `ollama` | LLM (local, Ollama) | โ€” | -| `lm_studio` | LLM (local, LM Studio) | โ€” | -| `mistral` | LLM | [docs.mistral.ai](https://docs.mistral.ai/) | -| `stepfun` | LLM (Step Fun/้˜ถ่ทƒๆ˜Ÿ่พฐ) | [platform.stepfun.com](https://platform.stepfun.com) | -| `ovms` | LLM (local, OpenVINO Model Server) | [docs.openvino.ai](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html) | -| `vllm` | LLM (local, any OpenAI-compatible server) | โ€” | -| `openai_codex` | LLM (Codex, OAuth) | `nanobot provider login openai-codex` | -| `github_copilot` | LLM (GitHub Copilot, OAuth) | `nanobot provider login github-copilot` | -| `qianfan` | LLM (Baidu Qianfan) | [cloud.baidu.com](https://cloud.baidu.com/doc/qianfan/s/Hmh4suq26) | - - -
-OpenAI Codex (OAuth) - -Codex uses OAuth instead of API keys. Requires a ChatGPT Plus or Pro account. -No `providers.openaiCodex` block is needed in `config.json`; `nanobot provider login` stores the OAuth session outside config. - -**1. Login:** -```bash -nanobot provider login openai-codex -``` - -**2. Set model** (merge into `~/.nanobot/config.json`): -```json -{ - "agents": { - "defaults": { - "model": "openai-codex/gpt-5.1-codex" - } - } -} -``` - -**3. Chat:** -```bash -nanobot agent -m "Hello!" - -# Target a specific workspace/config locally -nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello!" - -# One-off workspace override on top of that config -nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -m "Hello!" -``` - -> Docker users: use `docker run -it` for interactive OAuth login. - -
- - -
-GitHub Copilot (OAuth) - -GitHub Copilot uses OAuth instead of API keys. Requires a [GitHub account with a plan](https://github.com/features/copilot/plans) configured. -No `providers.githubCopilot` block is needed in `config.json`; `nanobot provider login` stores the OAuth session outside config. - -**1. Login:** -```bash -nanobot provider login github-copilot -``` - -**2. Set model** (merge into `~/.nanobot/config.json`): -```json -{ - "agents": { - "defaults": { - "model": "github-copilot/gpt-4.1" - } - } -} -``` - -**3. Chat:** -```bash -nanobot agent -m "Hello!" - -# Target a specific workspace/config locally -nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello!" - -# One-off workspace override on top of that config -nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -m "Hello!" -``` - -> Docker users: use `docker run -it` for interactive OAuth login. - -
- -
-Custom Provider (Any OpenAI-compatible API) - -Connects directly to any OpenAI-compatible endpoint โ€” llama.cpp, Together AI, Fireworks, Azure OpenAI, or any self-hosted server. Model name is passed as-is. - -```json -{ - "providers": { - "custom": { - "apiKey": "your-api-key", - "apiBase": "https://api.your-provider.com/v1" - } - }, - "agents": { - "defaults": { - "model": "your-model-name" - } - } -} -``` - -> For local servers that don't require authentication, set `apiKey` to `null`. -> -> `custom` is the right choice for providers that expose an OpenAI-compatible **chat completions** API. It does **not** force third-party endpoints onto the OpenAI/Azure **Responses API**. -> -> If your proxy or gateway is specifically Responses-API-compatible, use the `azure_openai` provider shape instead and point `apiBase` at that endpoint: -> -> ```json -> { -> "providers": { -> "azure_openai": { -> "apiKey": "your-api-key", -> "apiBase": "https://api.your-provider.com", -> "defaultModel": "your-model-name" -> } -> }, -> "agents": { -> "defaults": { -> "provider": "azure_openai", -> "model": "your-model-name" -> } -> } -> } -> ``` -> -> In short: **chat-completions-compatible endpoint โ†’ `custom`**; **Responses-compatible endpoint โ†’ `azure_openai`**. - -
- -
-Ollama (local) - -Run a local model with Ollama, then add to config: - -**1. Start Ollama** (example): -```bash -ollama run llama3.2 -``` - -**2. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): -```json -{ - "providers": { - "ollama": { - "apiBase": "http://localhost:11434" - } - }, - "agents": { - "defaults": { - "provider": "ollama", - "model": "llama3.2" - } - } -} -``` - -> `provider: "auto"` also works when `providers.ollama.apiBase` is configured, but setting `"provider": "ollama"` is the clearest option. - -
- -
-LM Studio (local) - -[LM Studio](https://lmstudio.ai/) provides a local OpenAI-compatible server for running LLMs. Download models through the LM Studio UI, then start the local server. - -**1. Start LM Studio server:** -- Launch LM Studio -- Go to the "Local Server" tab -- Load a model (e.g., Llama, Mistral, Qwen) -- Click "Start Server" (default port: 1234) - -**2. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): -```json -{ - "providers": { - "lm_studio": { - "apiKey": null, - "apiBase": "http://localhost:1234/v1" - } - }, - "agents": { - "defaults": { - "provider": "lm_studio", - "model": "local-model" - } - } -} -``` - -> **Note:** Set `apiKey` to `null` for LM Studio since it runs locally and doesn't require authentication. The model name should match what's shown in the LM Studio UI. -> `provider: "auto"` also works when `providers.lm_studio.apiBase` is configured, but setting `"provider": "lm_studio"` is the clearest option. - -
- -
-OpenVINO Model Server (local / OpenAI-compatible) - -Run LLMs locally on Intel GPUs using [OpenVINO Model Server](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html). OVMS exposes an OpenAI-compatible API at `/v3`. - -> Requires Docker and an Intel GPU with driver access (`/dev/dri`). - -**1. Pull the model** (example): - -```bash -mkdir -p ov/models && cd ov - -docker run -d \ - --rm \ - --user $(id -u):$(id -g) \ - -v $(pwd)/models:/models \ - openvino/model_server:latest-gpu \ - --pull \ - --model_name openai/gpt-oss-20b \ - --model_repository_path /models \ - --source_model OpenVINO/gpt-oss-20b-int4-ov \ - --task text_generation \ - --tool_parser gptoss \ - --reasoning_parser gptoss \ - --enable_prefix_caching true \ - --target_device GPU -``` - -> This downloads the model weights. Wait for the container to finish before proceeding. - -**2. Start the server** (example): - -```bash -docker run -d \ - --rm \ - --name ovms \ - --user $(id -u):$(id -g) \ - -p 8000:8000 \ - -v $(pwd)/models:/models \ - --device /dev/dri \ - --group-add=$(stat -c "%g" /dev/dri/render* | head -n 1) \ - openvino/model_server:latest-gpu \ - --rest_port 8000 \ - --model_name openai/gpt-oss-20b \ - --model_repository_path /models \ - --source_model OpenVINO/gpt-oss-20b-int4-ov \ - --task text_generation \ - --tool_parser gptoss \ - --reasoning_parser gptoss \ - --enable_prefix_caching true \ - --target_device GPU -``` - -**3. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): - -```json -{ - "providers": { - "ovms": { - "apiBase": "http://localhost:8000/v3" - } - }, - "agents": { - "defaults": { - "provider": "ovms", - "model": "openai/gpt-oss-20b" - } - } -} -``` - -> OVMS is a local server โ€” no API key required. Supports tool calling (`--tool_parser gptoss`), reasoning (`--reasoning_parser gptoss`), and streaming. -> See the [official OVMS docs](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html) for more details. -
- -
-vLLM (local / OpenAI-compatible) - -Run your own model with vLLM or any OpenAI-compatible server, then add to config: - -**1. Start the server** (example): -```bash -vllm serve meta-llama/Llama-3.1-8B-Instruct --port 8000 -``` - -**2. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): - -*Provider (set API key to null for local servers):* -```json -{ - "providers": { - "vllm": { - "apiKey": null, - "apiBase": "http://localhost:8000/v1" - } - } -} -``` - -*Model:* -```json -{ - "agents": { - "defaults": { - "model": "meta-llama/Llama-3.1-8B-Instruct" - } - } -} -``` - -
- -
-Adding a New Provider (Developer Guide) - -nanobot uses a **Provider Registry** (`nanobot/providers/registry.py`) as the single source of truth. -Adding a new provider only takes **2 steps** โ€” no if-elif chains to touch. - -**Step 1.** Add a `ProviderSpec` entry to `PROVIDERS` in `nanobot/providers/registry.py`: - -```python -ProviderSpec( - name="myprovider", # config field name - keywords=("myprovider", "mymodel"), # model-name keywords for auto-matching - env_key="MYPROVIDER_API_KEY", # env var name - display_name="My Provider", # shown in `nanobot status` - default_api_base="https://api.myprovider.com/v1", # OpenAI-compatible endpoint -) -``` - -**Step 2.** Add a field to `ProvidersConfig` in `nanobot/config/schema.py`: - -```python -class ProvidersConfig(BaseModel): - ... - myprovider: ProviderConfig = ProviderConfig() -``` - -That's it! Environment variables, model routing, config matching, and `nanobot status` display will all work automatically. - -**Common `ProviderSpec` options:** - -| Field | Description | Example | -|-------|-------------|---------| -| `default_api_base` | OpenAI-compatible base URL | `"https://api.deepseek.com"` | -| `env_extras` | Additional env vars to set | `(("ZHIPUAI_API_KEY", "{api_key}"),)` | -| `model_overrides` | Per-model parameter overrides | `(("kimi-k2.5", {"temperature": 1.0}),)` | -| `is_gateway` | Can route any model (like OpenRouter) | `True` | -| `detect_by_key_prefix` | Detect gateway by API key prefix | `"sk-or-"` | -| `detect_by_base_keyword` | Detect gateway by API base URL | `"openrouter"` | -| `strip_model_prefix` | Strip provider prefix before sending to gateway | `True` (for AiHubMix) | -| `supports_max_completion_tokens` | Use `max_completion_tokens` instead of `max_tokens`; required for providers that reject both being set simultaneously (e.g. VolcEngine) | `True` | - -
- -### Channel Settings - -Global settings that apply to all channels. Configure under the `channels` section in `~/.nanobot/config.json`: - -```json -{ - "channels": { - "sendProgress": true, - "sendToolHints": false, - "sendMaxRetries": 3, - "transcriptionProvider": "groq", - "telegram": { ... } - } -} -``` - -| Setting | Default | Description | -|---------|---------|-------------| -| `sendProgress` | `true` | Stream agent's text progress to the channel | -| `sendToolHints` | `false` | Stream tool-call hints (e.g. `read_file("โ€ฆ")`) | -| `sendMaxRetries` | `3` | Max delivery attempts per outbound message, including the initial send (0-10 configured, minimum 1 actual attempt) | -| `transcriptionProvider` | `"groq"` | Voice transcription backend: `"groq"` (free tier, default) or `"openai"`. API key is auto-resolved from the matching provider config. | - -#### Retry Behavior - -Retry is intentionally simple. - -When a channel `send()` raises, nanobot retries at the channel-manager layer. By default, `channels.sendMaxRetries` is `3`, and that count includes the initial send. - -- **Attempt 1**: Send immediately -- **Attempt 2**: Retry after `1s` -- **Attempt 3**: Retry after `2s` -- **Higher retry budgets**: Backoff continues as `1s`, `2s`, `4s`, then stays capped at `4s` -- **Transient failures**: Network hiccups and temporary API limits often recover on the next attempt -- **Permanent failures**: Invalid tokens, revoked access, or banned channels will exhaust the retry budget and fail cleanly - -> [!NOTE] -> This design is deliberate: channel implementations should raise on delivery failure, and the channel manager owns the shared retry policy. -> -> Some channels may still apply small API-specific retries internally. For example, Telegram separately retries timeout and flood-control errors before surfacing a final failure to the manager. -> -> If a channel is completely unreachable, nanobot cannot notify the user through that same channel. Watch logs for `Failed to send to {channel} after N attempts` to spot persistent delivery failures. - -### Web Search - -> [!TIP] -> Use `proxy` in `tools.web` to route all web requests (search + fetch) through a proxy: -> ```json -> { "tools": { "web": { "proxy": "http://127.0.0.1:7890" } } } -> ``` - -nanobot supports multiple web search providers. Configure in `~/.nanobot/config.json` under `tools.web.search`. - -By default, web tools are enabled and web search uses `duckduckgo`, so search works out of the box without an API key. - -If you want to disable all built-in web tools entirely, set `tools.web.enable` to `false`. This removes both `web_search` and `web_fetch` from the tool list sent to the LLM. - -If you need to allow trusted private ranges such as Tailscale / CGNAT addresses, you can explicitly exempt them from SSRF blocking with `tools.ssrfWhitelist`: - -```json -{ - "tools": { - "ssrfWhitelist": ["100.64.0.0/10"] - } -} -``` - -| Provider | Config fields | Env var fallback | Free | -|----------|--------------|------------------|------| -| `brave` | `apiKey` | `BRAVE_API_KEY` | No | -| `tavily` | `apiKey` | `TAVILY_API_KEY` | No | -| `jina` | `apiKey` | `JINA_API_KEY` | Free tier (10M tokens) | -| `kagi` | `apiKey` | `KAGI_API_KEY` | No | -| `searxng` | `baseUrl` | `SEARXNG_BASE_URL` | Yes (self-hosted) | -| `duckduckgo` (default) | โ€” | โ€” | Yes | - -**Disable all built-in web tools:** -```json -{ - "tools": { - "web": { - "enable": false - } - } -} -``` - -**Brave:** -```json -{ - "tools": { - "web": { - "search": { - "provider": "brave", - "apiKey": "BSA..." - } - } - } -} -``` - -**Tavily:** -```json -{ - "tools": { - "web": { - "search": { - "provider": "tavily", - "apiKey": "tvly-..." - } - } - } -} -``` - -**Jina** (free tier with 10M tokens): -```json -{ - "tools": { - "web": { - "search": { - "provider": "jina", - "apiKey": "jina_..." - } - } - } -} -``` - -**Kagi:** -```json -{ - "tools": { - "web": { - "search": { - "provider": "kagi", - "apiKey": "your-kagi-api-key" - } - } - } -} -``` - -**SearXNG** (self-hosted, no API key needed): -```json -{ - "tools": { - "web": { - "search": { - "provider": "searxng", - "baseUrl": "https://searx.example" - } - } - } -} -``` - -**DuckDuckGo** (zero config): -```json -{ - "tools": { - "web": { - "search": { - "provider": "duckduckgo" - } - } - } -} -``` - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `enable` | boolean | `true` | Enable or disable all built-in web tools (`web_search` + `web_fetch`) | -| `proxy` | string or null | `null` | Proxy for all web requests, for example `http://127.0.0.1:7890` | - -#### `tools.web.search` - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `provider` | string | `"duckduckgo"` | Search backend: `brave`, `tavily`, `jina`, `searxng`, `duckduckgo` | -| `apiKey` | string | `""` | API key for Brave or Tavily | -| `baseUrl` | string | `""` | Base URL for SearXNG | -| `maxResults` | integer | `5` | Results per search (1โ€“10) | - -### MCP (Model Context Protocol) - -> [!TIP] -> The config format is compatible with Claude Desktop / Cursor. You can copy MCP server configs directly from any MCP server's README. - -nanobot supports [MCP](https://modelcontextprotocol.io/) โ€” connect external tool servers and use them as native agent tools. - -Add MCP servers to your `config.json`: - -```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" - } - } - } - } -} -``` - -Two transport modes are supported: - -| Mode | Config | Example | -|------|--------|---------| -| **Stdio** | `command` + `args` | Local process via `npx` / `uvx` | -| **HTTP** | `url` + `headers` (optional) | Remote endpoint (`https://mcp.example.com/sse`) | - -Use `toolTimeout` to override the default 30s per-call timeout for slow servers: - -```json -{ - "tools": { - "mcpServers": { - "my-slow-server": { - "url": "https://example.com/mcp/", - "toolTimeout": 120 - } - } - } -} -``` - -Use `enabledTools` to register only a subset of tools from an MCP server: - -```json -{ - "tools": { - "mcpServers": { - "filesystem": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"], - "enabledTools": ["read_file", "mcp_filesystem_write_file"] - } - } - } -} -``` - -`enabledTools` accepts either the raw MCP tool name (for example `read_file`) or the wrapped nanobot tool name (for example `mcp_filesystem_write_file`). - -- Omit `enabledTools`, or set it to `["*"]`, to register all tools. -- Set `enabledTools` to `[]` to register no tools from that server. -- Set `enabledTools` to a non-empty list of names to register only that subset. - -MCP tools are automatically discovered and registered on startup. The LLM can use them alongside built-in tools โ€” no extra configuration needed. - - - - -### Security - -> [!TIP] -> For production deployments, set `"restrictToWorkspace": true` and `"tools.exec.sandbox": "bwrap"` in your config to sandbox the agent. -> In `v0.1.4.post3` and earlier, an empty `allowFrom` allowed all senders. Since `v0.1.4.post4`, empty `allowFrom` denies all access by default. To allow all senders, set `"allowFrom": ["*"]`. - -| Option | Default | Description | -|--------|---------|-------------| -| `tools.restrictToWorkspace` | `false` | When `true`, restricts **all** agent tools (shell, file read/write/edit, list) to the workspace directory. Prevents path traversal and out-of-scope access. | -| `tools.exec.sandbox` | `""` | Sandbox backend for shell commands. Set to `"bwrap"` to wrap exec calls in a [bubblewrap](https://github.com/containers/bubblewrap) sandbox โ€” the process can only see the workspace (read-write) and media directory (read-only); config files and API keys are hidden. Automatically enables `restrictToWorkspace` for file tools. **Linux only** โ€” requires `bwrap` installed (`apt install bubblewrap`; pre-installed in the Docker image). Not available on macOS or Windows (bwrap depends on Linux kernel namespaces). | -| `tools.exec.enable` | `true` | When `false`, the shell `exec` tool is not registered at all. Use this to completely disable shell command execution. | -| `tools.exec.pathAppend` | `""` | Extra directories to append to `PATH` when running shell commands (e.g. `/usr/sbin` for `ufw`). | -| `channels.*.allowFrom` | `[]` (deny all) | Whitelist of user IDs. Empty denies all; use `["*"]` to allow everyone. | - -**Docker security**: The official Docker image runs as a non-root user (`nanobot`, UID 1000) with bubblewrap pre-installed. When using `docker-compose.yml`, the container drops all Linux capabilities except `SYS_ADMIN` (required for bwrap's namespace isolation). - - -### Auto Compact - -When a user is idle for longer than a configured threshold, nanobot **proactively** compresses the older part of the session context into a summary while keeping a recent legal suffix of live messages. This reduces token cost and first-token latency when the user returns โ€” instead of re-processing a long stale context with an expired KV cache, the model receives a compact summary, the most recent live context, and fresh input. - -```json -{ - "agents": { - "defaults": { - "idleCompactAfterMinutes": 15 - } - } -} -``` - -| Option | Default | Description | -|--------|---------|-------------| -| `agents.defaults.idleCompactAfterMinutes` | `0` (disabled) | Minutes of idle time before auto-compaction starts. Set to `0` to disable. Recommended: `15` โ€” close to a typical LLM KV cache expiry window, so stale sessions get compacted before the user returns. | - -`sessionTtlMinutes` remains accepted as a legacy alias for backward compatibility, but `idleCompactAfterMinutes` is the preferred config key going forward. - -How it works: -1. **Idle detection**: On each idle tick (~1 s), checks all sessions for expiration. -2. **Background compaction**: Idle sessions summarize the older live prefix via LLM and keep the most recent legal suffix (currently 8 messages). -3. **Summary injection**: When the user returns, the summary is injected as runtime context (one-shot, not persisted) alongside the retained recent suffix. -4. **Restart-safe resume**: The summary is also mirrored into session metadata so it can still be recovered after a process restart. - -> [!NOTE] -> Mental model: "summarize older context, keep the freshest live turns, **and overwrite the session file with the compact form.**" It is not a full `session.clear()`, but it is a write โ€” not a soft cursor move. -> -> Concretely, auto compact rewrites `sessions/.jsonl` in place: older messages (including their structured `tool_calls` / `tool_call_id` / `reasoning_content`) are replaced by just the retained recent suffix (currently 8 messages), while the archived prefix is preserved only as a plain-text summary appended to `memory/history.jsonl` (or a `[RAW] ...` flattened dump if LLM summarization fails). The original structured JSON of those turns is no longer recoverable from the session file. -> -> This differs from the **token-driven soft consolidation** that fires when a prompt exceeds the context budget: that path only advances an internal `last_consolidated` cursor and leaves the session file untouched, so the raw tool-call trail stays on disk and can still be replayed or audited. If you rely on that trail for debugging or auditing, leave `idleCompactAfterMinutes` at the default `0` and let only the token-driven path run. - -### Timezone - -Time is context. Context should be precise. - -By default, nanobot uses `UTC` for runtime time context. If you want the agent to think in your local time, set `agents.defaults.timezone` to a valid [IANA timezone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones): - -```json -{ - "agents": { - "defaults": { - "timezone": "Asia/Shanghai" - } - } -} -``` - -This affects runtime time strings shown to the model, such as runtime context and heartbeat prompts. It also becomes the default timezone for cron schedules when a cron expression omits `tz`, and for one-shot `at` times when the ISO datetime has no explicit offset. - -Common examples: `UTC`, `America/New_York`, `America/Los_Angeles`, `Europe/London`, `Europe/Berlin`, `Asia/Tokyo`, `Asia/Shanghai`, `Asia/Singapore`, `Australia/Sydney`. - -> Need another timezone? Browse the full [IANA Time Zone Database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). - -### Unified Session - -By default, each channel ร— chat ID combination gets its own session. If you use nanobot across multiple channels (e.g. Telegram + Discord + CLI) and want them to share the same conversation, enable `unifiedSession`: - -```json -{ - "agents": { - "defaults": { - "unifiedSession": true - } - } -} -``` - -When enabled, all incoming messages โ€” regardless of which channel they arrive on โ€” are routed into a single shared session. Switching from Telegram to Discord (or any other channel) continues the same conversation seamlessly. - -| Behavior | `false` (default) | `true` | -|----------|-------------------|--------| -| Session key | `channel:chat_id` | `unified:default` | -| Cross-channel continuity | No | Yes | -| `/new` clears | Current channel session | Shared session | -| `/stop` finds tasks | By channel session | By shared session | -| Existing `session_key_override` (e.g. Telegram thread) | Respected | Still respected โ€” not overwritten | - -> This is designed for single-user, multi-device setups. It is **off by default** โ€” existing users see zero behavior change. - -### Disabled Skills - -nanobot ships with built-in skills, and your workspace can also define custom skills under `skills/`. If you want to hide specific skills from the agent, set `agents.defaults.disabledSkills` to a list of skill directory names: - -```json -{ - "agents": { - "defaults": { - "disabledSkills": ["github", "weather"] - } - } -} -``` - -Disabled skills are excluded from the main agent's skill summary, from always-on skill injection, and from subagent skill summaries. This is useful when some bundled skills are unnecessary for your deployment or should not be exposed to end users. - -| Option | Default | Description | -|--------|---------|-------------| -| `agents.defaults.disabledSkills` | `[]` | List of skill directory names to exclude from loading. Applies to both built-in skills and workspace skills. | - -## ๐Ÿงฉ Multiple Instances - -Run multiple nanobot instances simultaneously with separate configs and runtime data. Use `--config` as the main entrypoint. Optionally pass `--workspace` during `onboard` when you want to initialize or update the saved workspace for a specific instance. - -### Quick Start - -If you want each instance to have its own dedicated workspace from the start, pass both `--config` and `--workspace` during onboarding. - -**Initialize instances:** - -```bash -# Create separate instance configs and workspaces -nanobot onboard --config ~/.nanobot-telegram/config.json --workspace ~/.nanobot-telegram/workspace -nanobot onboard --config ~/.nanobot-discord/config.json --workspace ~/.nanobot-discord/workspace -nanobot onboard --config ~/.nanobot-feishu/config.json --workspace ~/.nanobot-feishu/workspace -``` - -**Configure each instance:** - -Edit `~/.nanobot-telegram/config.json`, `~/.nanobot-discord/config.json`, etc. with different channel settings. The workspace you passed during `onboard` is saved into each config as that instance's default workspace. - -**Run instances:** - -```bash -# Instance A - Telegram bot -nanobot gateway --config ~/.nanobot-telegram/config.json - -# Instance B - Discord bot -nanobot gateway --config ~/.nanobot-discord/config.json - -# Instance C - Feishu bot with custom port -nanobot gateway --config ~/.nanobot-feishu/config.json --port 18792 -``` - -### Path Resolution - -When using `--config`, nanobot derives its runtime data directory from the config file location. The workspace still comes from `agents.defaults.workspace` unless you override it with `--workspace`. - -To open a CLI session against one of these instances locally: - -```bash -nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello from Telegram instance" -nanobot agent -c ~/.nanobot-discord/config.json -m "Hello from Discord instance" - -# Optional one-off workspace override -nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -``` - -> `nanobot agent` starts a local CLI agent using the selected workspace/config. It does not attach to or proxy through an already running `nanobot gateway` process. - -| Component | Resolved From | Example | -|-----------|---------------|---------| -| **Config** | `--config` path | `~/.nanobot-A/config.json` | -| **Workspace** | `--workspace` or config | `~/.nanobot-A/workspace/` | -| **Cron Jobs** | config directory | `~/.nanobot-A/cron/` | -| **Media / runtime state** | config directory | `~/.nanobot-A/media/` | - -### How It Works - -- `--config` selects which config file to load -- By default, the workspace comes from `agents.defaults.workspace` in that config -- If you pass `--workspace`, it overrides the workspace from the config file - -### Minimal Setup - -1. Copy your base config into a new instance directory. -2. Set a different `agents.defaults.workspace` for that instance. -3. Start the instance with `--config`. - -Example config: - -```json -{ - "agents": { - "defaults": { - "workspace": "~/.nanobot-telegram/workspace", - "model": "anthropic/claude-sonnet-4-6" - } - }, - "channels": { - "telegram": { - "enabled": true, - "token": "YOUR_TELEGRAM_BOT_TOKEN" - } - }, - "gateway": { - "host": "127.0.0.1", - "port": 18790 - } -} -``` - -Start separate instances: - -```bash -nanobot gateway --config ~/.nanobot-telegram/config.json -nanobot gateway --config ~/.nanobot-discord/config.json -``` - -Each gateway instance also exposes a lightweight HTTP health endpoint on -`gateway.host:gateway.port`. By default, the gateway binds to `127.0.0.1`, -so the endpoint stays local unless you explicitly set `gateway.host` to a -public or LAN-facing address. - -- `GET /health` returns `{"status":"ok"}` -- Other paths return `404` - -Override workspace for one-off runs when needed: - -```bash -nanobot gateway --config ~/.nanobot-telegram/config.json --workspace /tmp/nanobot-telegram-test -``` - -### Common Use Cases - -- Run separate bots for Telegram, Discord, Feishu, and other platforms -- Keep testing and production instances isolated -- Use different models or providers for different teams -- Serve multiple tenants with separate configs and runtime data - -### Notes - -- Each instance must use a different port if they run at the same time -- Use a different workspace per instance if you want isolated memory, sessions, and skills -- `--workspace` overrides the workspace defined in the config file -- Cron jobs and runtime media/state are derived from the config directory - -## ๐Ÿง  Memory - -nanobot uses a layered memory system designed to stay light in the moment and durable over -time. - -- `memory/history.jsonl` stores append-only summarized history -- `SOUL.md`, `USER.md`, and `memory/MEMORY.md` store long-term knowledge managed by Dream -- `Dream` can also promote repeated workflows into reusable workspace skills under `skills/` -- `Dream` runs on a schedule and can also be triggered manually -- memory changes can be inspected and restored with built-in commands - -If you want the full design, see [docs/MEMORY.md](docs/MEMORY.md). - -## ๐Ÿ’ป CLI Reference - -| Command | Description | -|---------|-------------| -| `nanobot onboard` | Initialize config & workspace at `~/.nanobot/` | -| `nanobot onboard --wizard` | Launch the interactive onboarding wizard | -| `nanobot onboard -c -w ` | Initialize or refresh a specific instance config and workspace | -| `nanobot agent -m "..."` | Chat with the agent | -| `nanobot agent -w ` | Chat against a specific workspace | -| `nanobot agent -w -c ` | Chat against a specific workspace/config | -| `nanobot agent` | Interactive chat mode | -| `nanobot agent --no-markdown` | Show plain-text replies | -| `nanobot agent --logs` | Show runtime logs during chat | -| `nanobot serve` | Start the OpenAI-compatible API | -| `nanobot gateway` | Start the gateway | -| `nanobot status` | Show status | -| `nanobot provider login openai-codex` | OAuth login for providers | -| `nanobot channels login ` | Authenticate a channel interactively | -| `nanobot channels status` | Show channel status | - -Interactive mode exits: `exit`, `quit`, `/exit`, `/quit`, `:q`, or `Ctrl+D`. - -## ๐Ÿ’ฌ In-Chat Commands - -These commands work inside chat channels and interactive agent sessions: - -| Command | Description | -|---------|-------------| -| `/new` | Start a new conversation | -| `/stop` | Stop the current task | -| `/restart` | Restart the bot | -| `/status` | Show bot status | -| `/dream` | Run Dream memory consolidation now | -| `/dream-log` | Show the latest Dream memory change | -| `/dream-log ` | Show a specific Dream memory change | -| `/dream-restore` | List recent Dream memory versions | -| `/dream-restore ` | Restore memory to the state before a specific change | -| `/help` | Show available in-chat commands | - -
-Heartbeat (Periodic Tasks) - -The gateway wakes up every 30 minutes and checks `HEARTBEAT.md` in your workspace (`~/.nanobot/workspace/HEARTBEAT.md`). If the file has tasks, the agent executes them and delivers results to your most recently active chat channel. - -**Setup:** edit `~/.nanobot/workspace/HEARTBEAT.md` (created automatically by `nanobot onboard`): - -```markdown -## Periodic Tasks - -- [ ] Check weather forecast and send a summary -- [ ] Scan inbox for urgent emails -``` - -The agent can also manage this file itself โ€” ask it to "add a periodic task" and it will update `HEARTBEAT.md` for you. - -> **Note:** The gateway must be running (`nanobot gateway`) and you must have chatted with the bot at least once so it knows which channel to deliver to. - -
- -## ๐Ÿ Python SDK - -Use nanobot as a library โ€” no CLI, no gateway, just Python: - -```python -from nanobot import Nanobot - -bot = Nanobot.from_config() -result = await bot.run("Summarize the README") -print(result.content) -``` - -Each call carries a `session_key` for conversation isolation โ€” different keys get independent history: - -```python -await bot.run("hi", session_key="user-alice") -await bot.run("hi", session_key="task-42") -``` - -Add lifecycle hooks to observe or customize the agent: - -```python -from nanobot.agent import AgentHook, AgentHookContext - -class AuditHook(AgentHook): - async def before_execute_tools(self, ctx: AgentHookContext) -> None: - for tc in ctx.tool_calls: - print(f"[tool] {tc.name}") - -result = await bot.run("Hello", hooks=[AuditHook()]) -``` - -See [docs/PYTHON_SDK.md](docs/PYTHON_SDK.md) for the full SDK reference. - -## ๐Ÿ”Œ OpenAI-Compatible API - -nanobot can expose a minimal OpenAI-compatible endpoint for local integrations: - -```bash -pip install "nanobot-ai[api]" -nanobot serve -``` - -By default, the API binds to `127.0.0.1:8900`. You can change this in `config.json`. - -### Behavior - -- Session isolation: pass `"session_id"` in the request body to isolate conversations; omit for a shared default session (`api:default`) -- Single-message input: each request must contain exactly one `user` message -- Fixed model: omit `model`, or pass the same model shown by `/v1/models` -- Streaming: set `stream=true` to receive Server-Sent Events (`text/event-stream`) with OpenAI-compatible delta chunks, terminated by `data: [DONE]`; omit or set `stream=false` for a single JSON response -- **File uploads**: supports images, PDF, Word (.docx), Excel (.xlsx), PowerPoint (.pptx) via JSON base64 or `multipart/form-data` (max 10MB per file) -- API requests run in the synthetic `api` channel, so the `message` tool does **not** automatically deliver to Telegram/Discord/etc. To proactively send to another chat, call `message` with an explicit `channel` and `chat_id` for an enabled channel. - -Example tool call for cross-channel delivery from an API session: - -```json -{ - "content": "Build finished successfully.", - "channel": "telegram", - "chat_id": "123456789" -} -``` - -If `channel` points to a channel that is not enabled in your config, nanobot will queue the outbound event but no platform delivery will occur. - -### Endpoints - -- `GET /health` -- `GET /v1/models` -- `POST /v1/chat/completions` - -### curl - -```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" - }' -``` - -### File Upload (JSON base64) - -Send images inline using the OpenAI multimodal content format: - -```bash -curl http://127.0.0.1:8900/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "messages": [{"role": "user", "content": [ - {"type": "text", "text": "Describe this image"}, - {"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBOR..."}} - ]}] - }' -``` - -### File Upload (multipart/form-data) - -Upload any supported file type (images, PDF, Word, Excel, PPT) via multipart: - -```bash -# Single file -curl http://127.0.0.1:8900/v1/chat/completions \ - -F "message=Summarize this report" \ - -F "files=@report.docx" - -# Multiple files with session isolation -curl http://127.0.0.1:8900/v1/chat/completions \ - -F "message=Compare these files" \ - -F "files=@chart.png" \ - -F "files=@data.xlsx" \ - -F "session_id=my-session" -``` - -Supported file types: -- **Images**: PNG, JPEG, GIF, WebP (sent to AI as base64 for vision analysis) -- **Documents**: PDF, Word (.docx), Excel (.xlsx), PowerPoint (.pptx) (text extracted and sent to AI) -- **Text**: TXT, Markdown, CSV, JSON, etc. (read directly) - -### Python (`requests`) - -```python -import requests - -resp = requests.post( - "http://127.0.0.1:8900/v1/chat/completions", - json={ - "messages": [{"role": "user", "content": "hi"}], - "session_id": "my-session", # optional: isolate conversation - }, - timeout=120, -) -resp.raise_for_status() -print(resp.json()["choices"][0]["message"]["content"]) -``` - -### Python (`openai`) - -```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"}, # optional: isolate conversation -) -print(resp.choices[0].message.content) -``` - -## ๐Ÿณ Docker - -> [!TIP] -> The `-v ~/.nanobot:/home/nanobot/.nanobot` flag mounts your local config directory into the container, so your config and workspace persist across container restarts. -> The container runs as user `nanobot` (UID 1000). If you get **Permission denied**, fix ownership on the host first: `sudo chown -R 1000:1000 ~/.nanobot`, or pass `--user $(id -u):$(id -g)` to match your host UID. Podman users can use `--userns=keep-id` instead. - -### Docker Compose - -```bash -docker compose run --rm nanobot-cli onboard # first-time setup -vim ~/.nanobot/config.json # add API keys -docker compose up -d nanobot-gateway # start gateway -``` - -```bash -docker compose run --rm nanobot-cli agent -m "Hello!" # run CLI -docker compose logs -f nanobot-gateway # view logs -docker compose down # stop -``` - -### Docker - -```bash -# Build the image -docker build -t nanobot . - -# Initialize config (first time only) -docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot onboard - -# Edit config on host to add API keys -vim ~/.nanobot/config.json - -# Run gateway (connects to enabled channels, e.g. Telegram/Discord/Mochat) -docker run -v ~/.nanobot:/home/nanobot/.nanobot -p 18790:18790 nanobot gateway - -# Or run a single command -docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot agent -m "Hello!" -docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot status -``` - -## ๐Ÿง Linux Service - -Run the gateway as a systemd user service so it starts automatically and restarts on failure. - -**1. Find the nanobot binary path:** - -```bash -which nanobot # e.g. /home/user/.local/bin/nanobot -``` - -**2. Create the service file** at `~/.config/systemd/user/nanobot-gateway.service` (replace `ExecStart` path if needed): - -```ini -[Unit] -Description=Nanobot Gateway -After=network.target - -[Service] -Type=simple -ExecStart=%h/.local/bin/nanobot gateway -Restart=always -RestartSec=10 -NoNewPrivileges=yes -ProtectSystem=strict -ReadWritePaths=%h - -[Install] -WantedBy=default.target -``` - -**3. Enable and start:** - -```bash -systemctl --user daemon-reload -systemctl --user enable --now nanobot-gateway -``` - -**Common operations:** - -```bash -systemctl --user status nanobot-gateway # check status -systemctl --user restart nanobot-gateway # restart after config changes -journalctl --user -u nanobot-gateway -f # follow logs -``` - -If you edit the `.service` file itself, run `systemctl --user daemon-reload` before restarting. - -> **Note:** User services only run while you are logged in. To keep the gateway running after logout, enable lingering: -> -> ```bash -> loginctl enable-linger $USER -> ``` - -## ๐Ÿ“ Project Structure - -``` -nanobot/ -โ”œโ”€โ”€ agent/ # ๐Ÿง  Core agent logic -โ”‚ โ”œโ”€โ”€ loop.py # Agent loop (LLM โ†” tool execution) -โ”‚ โ”œโ”€โ”€ context.py # Prompt builder -โ”‚ โ”œโ”€โ”€ memory.py # Persistent memory -โ”‚ โ”œโ”€โ”€ skills.py # Skills loader -โ”‚ โ”œโ”€โ”€ subagent.py # Background task execution -โ”‚ โ””โ”€โ”€ tools/ # Built-in tools (incl. spawn) -โ”œโ”€โ”€ skills/ # ๐ŸŽฏ Bundled skills (github, weather, tmux...) -โ”œโ”€โ”€ channels/ # ๐Ÿ“ฑ Chat channel integrations (supports plugins) -โ”œโ”€โ”€ bus/ # ๐ŸšŒ Message routing -โ”œโ”€โ”€ cron/ # โฐ Scheduled tasks -โ”œโ”€โ”€ heartbeat/ # ๐Ÿ’“ Proactive wake-up -โ”œโ”€โ”€ providers/ # ๐Ÿค– LLM providers (OpenRouter, etc.) -โ”œโ”€โ”€ session/ # ๐Ÿ’ฌ Conversation sessions -โ”œโ”€โ”€ config/ # โš™๏ธ Configuration -โ””โ”€โ”€ cli/ # ๐Ÿ–ฅ๏ธ Commands -``` +- Talk to your nanobot with familiar chat apps: [`Chat Apps`](./docs/chat-apps.md) +- Configure providers, web search, MCP, and runtime behavior: [`Configuration`](./docs/configuration.md) +- Integrate nanobot with local tools and automations: [`OpenAI-Compatible API`](./docs/openai-api.md) ยท [`Python SDK`](./docs/python-sdk.md) +- Run nanobot with Docker or as a Linux service: [`Deployment`](./docs/deployment.md) ## ๐Ÿค Contribute & Roadmap @@ -2235,11 +234,11 @@ PRs welcome! The codebase is intentionally small and readable. ๐Ÿค— **Roadmap** โ€” Pick an item and [open a PR](https://github.com/HKUDS/nanobot/pulls)! -- [ ] **Multi-modal** โ€” See and hear (images, voice, video) -- [ ] **Long-term memory** โ€” Never forget important context -- [ ] **Better reasoning** โ€” Multi-step planning and reflection -- [ ] **More integrations** โ€” Calendar and more -- [ ] **Self-improvement** โ€” Learn from feedback and mistakes +- **Multi-modal** โ€” See and hear (images, voice, video) +- **Long-term memory** โ€” Never forget important context +- **Better reasoning** โ€” Multi-step planning and reflection +- **More integrations** โ€” Calendar and more +- **Self-improvement** โ€” Learn from feedback and mistakes ### Contributors @@ -2263,9 +262,4 @@ PRs welcome! The codebase is intentionally small and readable. ๐Ÿค—

Thanks for visiting โœจ nanobot!

Views -

- - -

- nanobot is for educational, research, and technical exchange purposes only -

+

\ No newline at end of file diff --git a/docs/PYTHON_SDK.md b/docs/PYTHON_SDK.md deleted file mode 100644 index 2b51055a..00000000 --- a/docs/PYTHON_SDK.md +++ /dev/null @@ -1,138 +0,0 @@ -# Python SDK - -> **Note:** This interface is currently an experiment in the latest source code version and is planned to officially ship in `v0.1.5`. - -Use nanobot programmatically โ€” load config, run the agent, get results. - -## Quick Start - -```python -import asyncio -from nanobot import Nanobot - -async def main(): - bot = Nanobot.from_config() - result = await bot.run("What time is it in Tokyo?") - print(result.content) - -asyncio.run(main()) -``` - -## API - -### `Nanobot.from_config(config_path?, *, workspace?)` - -Create a `Nanobot` from a config file. - -| Param | Type | Default | Description | -|-------|------|---------|-------------| -| `config_path` | `str \| Path \| None` | `None` | Path to `config.json`. Defaults to `~/.nanobot/config.json`. | -| `workspace` | `str \| Path \| None` | `None` | Override workspace directory from config. | - -Raises `FileNotFoundError` if an explicit path doesn't exist. - -### `await bot.run(message, *, session_key?, hooks?)` - -Run the agent once. Returns a `RunResult`. - -| Param | Type | Default | Description | -|-------|------|---------|-------------| -| `message` | `str` | *(required)* | The user message to process. | -| `session_key` | `str` | `"sdk:default"` | Session identifier for conversation isolation. Different keys get independent history. | -| `hooks` | `list[AgentHook] \| None` | `None` | Lifecycle hooks for this run only. | - -```python -# Isolated sessions โ€” each user gets independent conversation history -await bot.run("hi", session_key="user-alice") -await bot.run("hi", session_key="user-bob") -``` - -### `RunResult` - -| Field | Type | Description | -|-------|------|-------------| -| `content` | `str` | The agent's final text response. | -| `tools_used` | `list[str]` | Tool names invoked during the run. | -| `messages` | `list[dict]` | Raw message history (for debugging). | - -## Hooks - -Hooks let you observe or modify the agent loop without touching internals. - -Subclass `AgentHook` and override any method: - -| Method | When | -|--------|------| -| `before_iteration(ctx)` | Before each LLM call | -| `on_stream(ctx, delta)` | On each streamed token | -| `on_stream_end(ctx)` | When streaming finishes | -| `before_execute_tools(ctx)` | Before tool execution (inspect `ctx.tool_calls`) | -| `after_iteration(ctx, response)` | After each LLM response | -| `finalize_content(ctx, content)` | Transform final output text | - -### Example: Audit Hook - -```python -from nanobot.agent import AgentHook, AgentHookContext - -class AuditHook(AgentHook): - def __init__(self): - self.calls = [] - - async def before_execute_tools(self, ctx: AgentHookContext) -> None: - for tc in ctx.tool_calls: - self.calls.append(tc.name) - print(f"[audit] {tc.name}({tc.arguments})") - -hook = AuditHook() -result = await bot.run("List files in /tmp", hooks=[hook]) -print(f"Tools used: {hook.calls}") -``` - -### Composing Hooks - -Pass multiple hooks โ€” they run in order, errors in one don't block others: - -```python -result = await bot.run("hi", hooks=[AuditHook(), MetricsHook()]) -``` - -Under the hood this uses `CompositeHook` for fan-out with error isolation. - -### `finalize_content` Pipeline - -Unlike the async methods (fan-out), `finalize_content` is a pipeline โ€” each hook's output feeds the next: - -```python -class Censor(AgentHook): - def finalize_content(self, ctx, content): - return content.replace("secret", "***") if content else content -``` - -## Full Example - -```python -import asyncio -from nanobot import Nanobot -from nanobot.agent import AgentHook, AgentHookContext - -class TimingHook(AgentHook): - async def before_iteration(self, ctx: AgentHookContext) -> None: - import time - ctx.metadata["_t0"] = time.time() - - async def after_iteration(self, ctx, response) -> None: - import time - elapsed = time.time() - ctx.metadata.get("_t0", 0) - print(f"[timing] iteration took {elapsed:.2f}s") - -async def main(): - bot = Nanobot.from_config(workspace="/my/project") - result = await bot.run( - "Explain the main function", - hooks=[TimingHook()], - ) - print(result.content) - -asyncio.run(main()) -``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..5158f802 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,30 @@ +# nanobot Docs + +For the latest documentation, visit [nanobot.wiki](https://nanobot.wiki/docs/0.1.5/getting-started/nanobot-overview). + +The pages in this directory track the current repository and may move faster than the published website. + +## Core Docs + +| Topic | Repo docs | +|---|---| +| Install and quick start | [`quick-start.md`](./quick-start.md) | +| Chat apps | [`chat-apps.md`](./chat-apps.md) | +| Agent social network | [`agent-social-network.md`](./agent-social-network.md) | +| Configuration | [`configuration.md`](./configuration.md) | +| Multiple instances | [`multiple-instances.md`](./multiple-instances.md) | +| CLI reference | [`cli-reference.md`](./cli-reference.md) | +| In-chat commands | [`chat-commands.md`](./chat-commands.md) | +| OpenAI-compatible API | [`openai-api.md`](./openai-api.md) | +| Deployment | [`deployment.md`](./deployment.md) | + +## Advanced Docs + +| Topic | Repo docs | +|---|---| +| Memory | [`memory.md`](./memory.md) | +| Python SDK | [`python-sdk.md`](./python-sdk.md) | +| Channel plugin guide | [`channel-plugin-guide.md`](./channel-plugin-guide.md) | +| WebSocket channel | [`websocket.md`](./websocket.md) | +| Custom tools | [`my-tool.md`](./my-tool.md) | + diff --git a/docs/agent-social-network.md b/docs/agent-social-network.md new file mode 100644 index 00000000..74579b8b --- /dev/null +++ b/docs/agent-social-network.md @@ -0,0 +1,10 @@ +# Agent Social Network + +๐Ÿˆ nanobot is capable of linking to the agent social network (agent community). **Just send one message and your nanobot joins automatically!** + +| Platform | How to Join (send this message to your bot) | +|----------|-------------| +| [**Moltbook**](https://www.moltbook.com/) | `Read https://moltbook.com/skill.md and follow the instructions to join Moltbook` | +| [**ClawdChat**](https://clawdchat.ai/) | `Read https://clawdchat.ai/skill.md and follow the instructions to join ClawdChat` | + +Simply send the command above to your nanobot (via CLI or any chat channel), and it will handle the rest. diff --git a/docs/CHANNEL_PLUGIN_GUIDE.md b/docs/channel-plugin-guide.md similarity index 99% rename from docs/CHANNEL_PLUGIN_GUIDE.md rename to docs/channel-plugin-guide.md index 12a41685..d37a9288 100644 --- a/docs/CHANNEL_PLUGIN_GUIDE.md +++ b/docs/channel-plugin-guide.md @@ -19,7 +19,7 @@ We'll build a minimal webhook channel that receives messages via HTTP POST and s ### Project Structure -``` +```text nanobot-channel-webhook/ โ”œโ”€โ”€ nanobot_channel_webhook/ โ”‚ โ”œโ”€โ”€ __init__.py # re-export WebhookChannel diff --git a/docs/chat-apps.md b/docs/chat-apps.md new file mode 100644 index 00000000..9332bdc0 --- /dev/null +++ b/docs/chat-apps.md @@ -0,0 +1,661 @@ +# Chat Apps + +Connect nanobot to your favorite chat platform. Want to build your own? See the [Channel Plugin Guide](./channel-plugin-guide.md). + +| Channel | What you need | +|---------|---------------| +| **Telegram** | Bot token from @BotFather | +| **Discord** | Bot token + Message Content intent | +| **WhatsApp** | QR code scan (`nanobot channels login whatsapp`) | +| **WeChat (Weixin)** | QR code scan (`nanobot channels login weixin`) | +| **Feishu** | App ID + App Secret | +| **DingTalk** | App Key + App Secret | +| **Slack** | Bot token + App-Level token | +| **Matrix** | Homeserver URL + Access token | +| **Email** | IMAP/SMTP credentials | +| **QQ** | App ID + App Secret | +| **Wecom** | Bot ID + Bot Secret | +| **Microsoft Teams** | App ID + App Password + public HTTPS endpoint | +| **Mochat** | Claw token (auto-setup available) | + +
+Telegram (Recommended) + +**1. Create a bot** +- Open Telegram, search `@BotFather` +- Send `/newbot`, follow prompts +- Copy the token + +**2. Configure** + +```json +{ + "channels": { + "telegram": { + "enabled": true, + "token": "YOUR_BOT_TOKEN", + "allowFrom": ["YOUR_USER_ID"] + } + } +} +``` + +> You can find your **User ID** in Telegram settings. It is shown as `@yourUserId`. +> Copy this value **without the `@` symbol** and paste it into the config file. + + +**3. Run** + +```bash +nanobot gateway +``` + +
+ +
+Mochat (Claw IM) + +Uses **Socket.IO WebSocket** by default, with HTTP polling fallback. + +**1. Ask nanobot to set up Mochat for you** + +Simply send this message to nanobot (replace `xxx@xxx` with your real email): + +``` +Read https://raw.githubusercontent.com/HKUDS/MoChat/refs/heads/main/skills/nanobot/skill.md and register on MoChat. My Email account is xxx@xxx Bind me as your owner and DM me on MoChat. +``` + +nanobot will automatically register, configure `~/.nanobot/config.json`, and connect to Mochat. + +**2. Restart gateway** + +```bash +nanobot gateway +``` + +That's it โ€” nanobot handles the rest! + +
+ +
+Manual configuration (advanced) + +If you prefer to configure manually, add the following to `~/.nanobot/config.json`: + +> Keep `claw_token` private. It should only be sent in `X-Claw-Token` header to your Mochat API endpoint. + +```json +{ + "channels": { + "mochat": { + "enabled": true, + "base_url": "https://mochat.io", + "socket_url": "https://mochat.io", + "socket_path": "/socket.io", + "claw_token": "claw_xxx", + "agent_user_id": "6982abcdef", + "sessions": ["*"], + "panels": ["*"], + "reply_delay_mode": "non-mention", + "reply_delay_ms": 120000 + } + } +} +``` + + + +
+ +
+ +
+Discord + +**1. Create a bot** +- Go to https://discord.com/developers/applications +- Create an application โ†’ Bot โ†’ Add Bot +- Copy the bot token + +**2. Enable intents** +- In the Bot settings, enable **MESSAGE CONTENT INTENT** +- (Optional) Enable **SERVER MEMBERS INTENT** if you plan to use allow lists based on member data + +**3. Get your User ID** +- Discord Settings โ†’ Advanced โ†’ enable **Developer Mode** +- Right-click your avatar โ†’ **Copy User ID** + +**4. Configure** + +```json +{ + "channels": { + "discord": { + "enabled": true, + "token": "YOUR_BOT_TOKEN", + "allowFrom": ["YOUR_USER_ID"], + "allowChannels": [], + "groupPolicy": "mention", + "streaming": true + } + } +} +``` + +> `groupPolicy` controls how the bot responds in group channels: +> - `"mention"` (default) โ€” Only respond when @mentioned +> - `"open"` โ€” Respond to all messages +> DMs always respond when the sender is in `allowFrom`. +> - If you set group policy to open create new threads as private threads and then @ the bot into it. Otherwise the thread itself and the channel in which you spawned it will spawn a bot session. +> `allowChannels` restricts the bot to specific Discord channel IDs. Empty (default) means respond in every channel the bot can see. Example: `["1234567890", "0987654321"]`. The filter applies after `allowFrom`, so both must pass. +> `streaming` defaults to `true`. Disable it only if you explicitly want non-streaming replies. + +**5. Invite the bot** +- OAuth2 โ†’ URL Generator +- Scopes: `bot` +- Bot Permissions: `Send Messages`, `Read Message History` +- Open the generated invite URL and add the bot to your server + +**6. Run** + +```bash +nanobot gateway +``` + +
+ +
+Matrix (Element) + +Install Matrix dependencies first: + +```bash +pip install nanobot-ai[matrix] +``` + +> [!NOTE] +> Matrix is not supported on Windows. `matrix-nio[e2e]` depends on +> `python-olm`, which has no pre-built Windows wheel and is skipped by the +> `matrix` extra on `sys_platform == 'win32'`. The command above will still +> succeed on Windows but without `matrix-nio` installed, so enabling the +> Matrix channel will fail at startup. Use macOS, Linux, or WSL2. + +**1. Create/choose a Matrix account** + +- Create or reuse a Matrix account on your homeserver (for example `matrix.org`). +- Confirm you can log in with Element. + +**2. Get credentials** + +- You need: + - `userId` (example: `@nanobot:matrix.org`) + - `password` + +(Note: `accessToken` and `deviceId` are still supported for legacy reasons, but +for reliable encryption, password login is recommended instead. If the +`password` is provided, `accessToken` and `deviceId` will be ignored.) + +**3. Configure** + +```json +{ + "channels": { + "matrix": { + "enabled": true, + "homeserver": "https://matrix.org", + "userId": "@nanobot:matrix.org", + "password": "mypasswordhere", + "e2eeEnabled": true, + "allowFrom": ["@your_user:matrix.org"], + "groupPolicy": "open", + "groupAllowFrom": [], + "allowRoomMentions": false, + "maxMediaBytes": 20971520 + } + } +} +``` + +> Keep a persistent `matrix-store` โ€” encrypted session state is lost if these change across restarts. + +| Option | Description | +|--------|-------------| +| `allowFrom` | User IDs allowed to interact. Empty denies all; use `["*"]` to allow everyone. | +| `groupPolicy` | `open` (default), `mention`, or `allowlist`. | +| `groupAllowFrom` | Room allowlist (used when policy is `allowlist`). | +| `allowRoomMentions` | Accept `@room` mentions in mention mode. | +| `e2eeEnabled` | E2EE support (default `true`). Set `false` for plaintext-only. | +| `maxMediaBytes` | Max attachment size (default `20MB`). Set `0` to block all media. | + + + + +**4. Run** + +```bash +nanobot gateway +``` + +
+ +
+WhatsApp + +Requires **Node.js โ‰ฅ18**. + +**1. Link device** + +```bash +nanobot channels login whatsapp +# Scan QR with WhatsApp โ†’ Settings โ†’ Linked Devices +``` + +**2. Configure** + +```json +{ + "channels": { + "whatsapp": { + "enabled": true, + "allowFrom": ["+1234567890"] + } + } +} +``` + +**3. Run** (two terminals) + +```bash +# Terminal 1 +nanobot channels login whatsapp + +# Terminal 2 +nanobot gateway +``` + +> WhatsApp bridge updates are not applied automatically for existing installations. +> After upgrading nanobot, rebuild the local bridge with: +> `rm -rf ~/.nanobot/bridge && nanobot channels login whatsapp` + +
+ +
+Feishu + +Uses **WebSocket** long connection โ€” no public IP required. + +**1. Create a Feishu bot** +- Visit [Feishu Open Platform](https://open.feishu.cn/app) +- Create a new app โ†’ Enable **Bot** capability +- **Permissions**: + - `im:message` (send messages) and `im:message.p2p_msg:readonly` (receive messages) + - **Streaming replies** (default in nanobot): add **`cardkit:card:write`** (often labeled **Create and update cards** in the Feishu developer console). Required for CardKit entities and streamed assistant text. Older apps may not have it yet โ€” open **Permission management**, enable the scope, then **publish** a new app version if the console requires it. + - If you **cannot** add `cardkit:card:write`, set `"streaming": false` under `channels.feishu` (see below). The bot still works; replies use normal interactive cards without token-by-token streaming. +- **Events**: Add `im.message.receive_v1` (receive messages) + - Select **Long Connection** mode (requires running nanobot first to establish connection) +- Get **App ID** and **App Secret** from "Credentials & Basic Info" +- Publish the app + +**2. Configure** + +```json +{ + "channels": { + "feishu": { + "enabled": true, + "appId": "cli_xxx", + "appSecret": "xxx", + "encryptKey": "", + "verificationToken": "", + "allowFrom": ["ou_YOUR_OPEN_ID"], + "groupPolicy": "mention", + "reactEmoji": "OnIt", + "doneEmoji": "DONE", + "toolHintPrefix": "๐Ÿ”ง", + "streaming": true, + "domain": "feishu" + } + } +} +``` + +> `streaming` defaults to `true`. Use `false` if your app does not have **`cardkit:card:write`** (see permissions above). +> `encryptKey` and `verificationToken` are optional for Long Connection mode. +> `allowFrom`: Add your open_id (find it in nanobot logs when you message the bot). Use `["*"]` to allow all users. +> `groupPolicy`: `"mention"` (default โ€” respond only when @mentioned), `"open"` (respond to all group messages). Private chats always respond. +> `reactEmoji`: Emoji for "processing" status (default: `OnIt`). See [available emojis](https://open.larkoffice.com/document/server-docs/im-v1/message-reaction/emojis-introduce). +> `doneEmoji`: Optional emoji for "completed" status (e.g., `DONE`, `OK`, `HEART`). When set, bot adds this reaction after removing `reactEmoji`. +> `toolHintPrefix`: Prefix for inline tool hints in streaming cards (default: `๐Ÿ”ง`). +> `domain`: `"feishu"` (default) for China (open.feishu.cn), `"lark"` for international Lark (open.larksuite.com). + +**3. Run** + +```bash +nanobot gateway +``` + +> [!TIP] +> Feishu uses WebSocket to receive messages โ€” no webhook or public IP needed! + +
+ +
+QQ (QQๅ•่Š) + +Uses **botpy SDK** with WebSocket โ€” no public IP required. Currently supports **private messages only**. + +**1. Register & create bot** +- Visit [QQ Open Platform](https://q.qq.com) โ†’ Register as a developer (personal or enterprise) +- Create a new bot application +- Go to **ๅผ€ๅ‘่ฎพ็ฝฎ (Developer Settings)** โ†’ copy **AppID** and **AppSecret** + +**2. Set up sandbox for testing** +- In the bot management console, find **ๆฒ™็ฎฑ้…็ฝฎ (Sandbox Config)** +- Under **ๅœจๆถˆๆฏๅˆ—่กจ้…็ฝฎ**, click **ๆทปๅŠ ๆˆๅ‘˜** and add your own QQ number +- Once added, scan the bot's QR code with mobile QQ โ†’ open the bot profile โ†’ tap "ๅ‘ๆถˆๆฏ" to start chatting + +**3. Configure** + +> - `allowFrom`: Add your openid (find it in nanobot logs when you message the bot). Use `["*"]` for public access. +> - `msgFormat`: Optional. Use `"plain"` (default) for maximum compatibility with legacy QQ clients, or `"markdown"` for richer formatting on newer clients. +> - For production: submit a review in the bot console and publish. See [QQ Bot Docs](https://bot.q.qq.com/wiki/) for the full publishing flow. + +```json +{ + "channels": { + "qq": { + "enabled": true, + "appId": "YOUR_APP_ID", + "secret": "YOUR_APP_SECRET", + "allowFrom": ["YOUR_OPENID"], + "msgFormat": "plain" + } + } +} +``` + +**4. Run** + +```bash +nanobot gateway +``` + +Now send a message to the bot from QQ โ€” it should respond! + +
+ +
+DingTalk (้’‰้’‰) + +Uses **Stream Mode** โ€” no public IP required. + +**1. Create a DingTalk bot** +- Visit [DingTalk Open Platform](https://open-dev.dingtalk.com/) +- Create a new app -> Add **Robot** capability +- **Configuration**: + - Toggle **Stream Mode** ON +- **Permissions**: Add necessary permissions for sending messages +- Get **AppKey** (Client ID) and **AppSecret** (Client Secret) from "Credentials" +- Publish the app + +**2. Configure** + +```json +{ + "channels": { + "dingtalk": { + "enabled": true, + "clientId": "YOUR_APP_KEY", + "clientSecret": "YOUR_APP_SECRET", + "allowFrom": ["YOUR_STAFF_ID"] + } + } +} +``` + +> `allowFrom`: Add your staff ID. Use `["*"]` to allow all users. + +**3. Run** + +```bash +nanobot gateway +``` + +
+ +
+Slack + +Uses **Socket Mode** โ€” no public URL required. + +**1. Create a Slack app** +- Go to [Slack API](https://api.slack.com/apps) โ†’ **Create New App** โ†’ "From scratch" +- Pick a name and select your workspace + +**2. Configure the app** +- **Socket Mode**: Toggle ON โ†’ Generate an **App-Level Token** with `connections:write` scope โ†’ copy it (`xapp-...`) +- **OAuth & Permissions**: Add bot scopes: `chat:write`, `reactions:write`, `app_mentions:read` +- **Event Subscriptions**: Toggle ON โ†’ Subscribe to bot events: `message.im`, `message.channels`, `app_mention` โ†’ Save Changes +- **App Home**: Scroll to **Show Tabs** โ†’ Enable **Messages Tab** โ†’ Check **"Allow users to send Slash commands and messages from the messages tab"** +- **Install App**: Click **Install to Workspace** โ†’ Authorize โ†’ copy the **Bot Token** (`xoxb-...`) + +**3. Configure nanobot** + +```json +{ + "channels": { + "slack": { + "enabled": true, + "botToken": "xoxb-...", + "appToken": "xapp-...", + "allowFrom": ["YOUR_SLACK_USER_ID"], + "groupPolicy": "mention" + } + } +} +``` + +**4. Run** + +```bash +nanobot gateway +``` + +DM the bot directly or @mention it in a channel โ€” it should respond! + +> [!TIP] +> - `groupPolicy`: `"mention"` (default โ€” respond only when @mentioned), `"open"` (respond to all channel messages), or `"allowlist"` (restrict to specific channels). +> - DM policy defaults to open. Set `"dm": {"enabled": false}` to disable DMs. + +
+ +
+Email + +Give nanobot its own email account. It polls **IMAP** for incoming mail and replies via **SMTP** โ€” like a personal email assistant. + +**1. Get credentials (Gmail example)** +- Create a dedicated Gmail account for your bot (e.g. `my-nanobot@gmail.com`) +- Enable 2-Step Verification โ†’ Create an [App Password](https://myaccount.google.com/apppasswords) +- Use this app password for both IMAP and SMTP + +**2. Configure** + +> - `consentGranted` must be `true` to allow mailbox access. This is a safety gate โ€” set `false` to fully disable. +> - `allowFrom`: Add your email address. Use `["*"]` to accept emails from anyone. +> - `smtpUseTls` and `smtpUseSsl` default to `true` / `false` respectively, which is correct for Gmail (port 587 + STARTTLS). No need to set them explicitly. +> - Set `"autoReplyEnabled": false` if you only want to read/analyze emails without sending automatic replies. +> - `allowedAttachmentTypes`: Save inbound attachments matching these MIME types โ€” `["*"]` for all, e.g. `["application/pdf", "image/*"]` (default `[]` = disabled). +> - `maxAttachmentSize`: Max size per attachment in bytes (default `2000000` / 2MB). +> - `maxAttachmentsPerEmail`: Max attachments to save per email (default `5`). + +```json +{ + "channels": { + "email": { + "enabled": true, + "consentGranted": true, + "imapHost": "imap.gmail.com", + "imapPort": 993, + "imapUsername": "my-nanobot@gmail.com", + "imapPassword": "your-app-password", + "smtpHost": "smtp.gmail.com", + "smtpPort": 587, + "smtpUsername": "my-nanobot@gmail.com", + "smtpPassword": "your-app-password", + "fromAddress": "my-nanobot@gmail.com", + "allowFrom": ["your-real-email@gmail.com"], + "allowedAttachmentTypes": ["application/pdf", "image/*"] + } + } +} +``` + + +**3. Run** + +```bash +nanobot gateway +``` + +
+ +
+WeChat (ๅพฎไฟก / Weixin) + +Uses **HTTP long-poll** with QR-code login via the ilinkai personal WeChat API. No local WeChat desktop client is required. + +**1. Install with WeChat support** + +```bash +pip install "nanobot-ai[weixin]" +``` + +**2. Configure** + +```json +{ + "channels": { + "weixin": { + "enabled": true, + "allowFrom": ["YOUR_WECHAT_USER_ID"] + } + } +} +``` + +> - `allowFrom`: Add the sender ID you see in nanobot logs for your WeChat account. Use `["*"]` to allow all users. +> - `token`: Optional. If omitted, log in interactively and nanobot will save the token for you. +> - `routeTag`: Optional. When your upstream Weixin deployment requires request routing, nanobot will send it as the `SKRouteTag` header. +> - `stateDir`: Optional. Defaults to nanobot's runtime directory for Weixin state. +> - `pollTimeout`: Optional long-poll timeout in seconds. + +**3. Login** + +```bash +nanobot channels login weixin +``` + +Use `--force` to re-authenticate and ignore any saved token: + +```bash +nanobot channels login weixin --force +``` + +**4. Run** + +```bash +nanobot gateway +``` + +
+ +
+Wecom (ไผไธšๅพฎไฟก) + +> Here we use [wecom-aibot-sdk-python](https://github.com/chengyongru/wecom_aibot_sdk) (community Python version of the official [@wecom/aibot-node-sdk](https://www.npmjs.com/package/@wecom/aibot-node-sdk)). +> +> Uses **WebSocket** long connection โ€” no public IP required. + +**1. Install the optional dependency** + +```bash +pip install nanobot-ai[wecom] +``` + +**2. Create a WeCom AI Bot** + +Go to the WeCom admin console โ†’ Intelligent Robot โ†’ Create Robot โ†’ select **API mode** with **long connection**. Copy the Bot ID and Secret. + +**3. Configure** + +```json +{ + "channels": { + "wecom": { + "enabled": true, + "botId": "your_bot_id", + "secret": "your_bot_secret", + "allowFrom": ["your_id"] + } + } +} +``` + +**4. Run** + +```bash +nanobot gateway +``` + +
+ +
+Microsoft Teams (MVP โ€” DM only) + +> Direct-message text in/out, tenant-aware OAuth, conversation reference persistence. +> Uses a public HTTPS webhook โ€” no WebSocket; you need a tunnel or reverse proxy. + +**1. Install the optional dependency** + +```bash +pip install nanobot-ai[msteams] +``` + +**2. Create a Teams / Azure bot app registration** + +Create or reuse a Microsoft Teams / Azure bot app registration. Set the bot messaging endpoint to a public HTTPS URL ending in `/api/messages`. + +**3. Configure** + +```json +{ + "channels": { + "msteams": { + "enabled": true, + "appId": "YOUR_APP_ID", + "appPassword": "YOUR_APP_SECRET", + "tenantId": "YOUR_TENANT_ID", + "host": "0.0.0.0", + "port": 3978, + "path": "/api/messages", + "allowFrom": ["*"], + "replyInThread": true, + "mentionOnlyResponse": "Hi โ€” what can I help with?", + "validateInboundAuth": true + } + } +} +``` + +> - `replyInThread: true` replies to the triggering Teams activity when a stored `activity_id` is available. +> - `mentionOnlyResponse` controls what Nanobot receives when a user sends only a bot mention (`Nanobot`). Set to `""` to ignore mention-only messages. +> - `validateInboundAuth: true` enables inbound Bot Framework bearer-token validation (signature, issuer, audience, lifetime, `serviceUrl`). This is the safe default for public deployments. Only set it to `false` for local development or tightly controlled testing. + +**4. Run** + +```bash +nanobot gateway +``` + +
\ No newline at end of file diff --git a/docs/chat-commands.md b/docs/chat-commands.md new file mode 100644 index 00000000..72707e76 --- /dev/null +++ b/docs/chat-commands.md @@ -0,0 +1,33 @@ +# In-Chat Commands + +These commands work inside chat channels and interactive agent sessions: + +| Command | Description | +|---------|-------------| +| `/new` | Start a new conversation | +| `/stop` | Stop the current task | +| `/restart` | Restart the bot | +| `/status` | Show bot status | +| `/dream` | Run Dream memory consolidation now | +| `/dream-log` | Show the latest Dream memory change | +| `/dream-log ` | Show a specific Dream memory change | +| `/dream-restore` | List recent Dream memory versions | +| `/dream-restore ` | Restore memory to the state before a specific change | +| `/help` | Show available in-chat commands | + +## Periodic Tasks + +The gateway wakes up every 30 minutes and checks `HEARTBEAT.md` in your workspace (`~/.nanobot/workspace/HEARTBEAT.md`). If the file has tasks, the agent executes them and delivers results to your most recently active chat channel. + +**Setup:** edit `~/.nanobot/workspace/HEARTBEAT.md` (created automatically by `nanobot onboard`): + +```markdown +## Periodic Tasks + +- [ ] Check weather forecast and send a summary +- [ ] Scan inbox for urgent emails +``` + +The agent can also manage this file itself โ€” ask it to "add a periodic task" and it will update `HEARTBEAT.md` for you. + +> **Note:** The gateway must be running (`nanobot gateway`) and you must have chatted with the bot at least once so it knows which channel to deliver to. diff --git a/docs/cli-reference.md b/docs/cli-reference.md new file mode 100644 index 00000000..667f8c13 --- /dev/null +++ b/docs/cli-reference.md @@ -0,0 +1,21 @@ +# CLI Reference + +| Command | Description | +|---------|-------------| +| `nanobot onboard` | Initialize config & workspace at `~/.nanobot/` | +| `nanobot onboard --wizard` | Launch the interactive onboarding wizard | +| `nanobot onboard -c -w ` | Initialize or refresh a specific instance config and workspace | +| `nanobot agent -m "..."` | Chat with the agent | +| `nanobot agent -w ` | Chat against a specific workspace | +| `nanobot agent -w -c ` | Chat against a specific workspace/config | +| `nanobot agent` | Interactive chat mode | +| `nanobot agent --no-markdown` | Show plain-text replies | +| `nanobot agent --logs` | Show runtime logs during chat | +| `nanobot serve` | Start the OpenAI-compatible API | +| `nanobot gateway` | Start the gateway | +| `nanobot status` | Show status | +| `nanobot provider login openai-codex` | OAuth login for providers | +| `nanobot channels login ` | Authenticate a channel interactively | +| `nanobot channels status` | Show channel status | + +Interactive mode exits: `exit`, `quit`, `/exit`, `/quit`, `:q`, or `Ctrl+D`. diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..96b5fa5b --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,809 @@ +# Configuration + +Config file: `~/.nanobot/config.json` + +> [!NOTE] +> If your config file is older than the current schema, you can refresh it without overwriting your existing values: +> run `nanobot onboard`, then answer `N` when asked whether to overwrite the config. +> nanobot will merge in missing default fields and keep your current settings. + +## Environment Variables for Secrets + +Instead of storing secrets directly in `config.json`, you can use `${VAR_NAME}` references that are resolved from environment variables at startup: + +```json +{ + "channels": { + "telegram": { "token": "${TELEGRAM_TOKEN}" }, + "email": { + "imapPassword": "${IMAP_PASSWORD}", + "smtpPassword": "${SMTP_PASSWORD}" + } + }, + "providers": { + "groq": { "apiKey": "${GROQ_API_KEY}" } + } +} +``` + +For **systemd** deployments, use `EnvironmentFile=` in the service unit to load variables from a file that only the deploying user can read: + +```ini +# /etc/systemd/system/nanobot.service (excerpt) +[Service] +EnvironmentFile=/home/youruser/nanobot_secrets.env +User=nanobot +ExecStart=... +``` + +```bash +# /home/youruser/nanobot_secrets.env (mode 600, owned by youruser) +TELEGRAM_TOKEN=your-token-here +IMAP_PASSWORD=your-password-here +``` + +## Providers + +> [!TIP] +> - **Voice transcription**: Voice messages (Telegram, WhatsApp) are automatically transcribed using Whisper. By default Groq is used (free tier). Set `"transcriptionProvider": "openai"` under `channels` to use OpenAI Whisper instead โ€” the API key is picked from the matching provider config. +> - **MiniMax Coding Plan**: Exclusive discount links for the nanobot community: [Overseas](https://platform.minimax.io/subscribe/coding-plan?code=9txpdXw04g&source=link) ยท [Mainland China](https://platform.minimaxi.com/subscribe/token-plan?code=GILTJpMTqZ&source=link) +> - **MiniMax (Mainland China)**: If your API key is from MiniMax's mainland China platform (minimaxi.com), set `"apiBase": "https://api.minimaxi.com/v1"` in your minimax provider config. +> - **MiniMax thinking mode**: Use `providers.minimaxAnthropic` when you want `reasoningEffort` / thinking mode. MiniMax exposes that capability through its Anthropic-compatible endpoint, so nanobot keeps it as a separate provider instead of guessing MiniMax-specific thinking parameters on the generic OpenAI-compatible `minimax` endpoint. It uses the same `MINIMAX_API_KEY`. Default Anthropic-compatible base URL: `https://api.minimax.io/anthropic`; for mainland China use `https://api.minimaxi.com/anthropic`. +> - **VolcEngine / BytePlus Coding Plan**: Use dedicated providers `volcengineCodingPlan` or `byteplusCodingPlan` instead of the pay-per-use `volcengine` / `byteplus` providers. +> - **Zhipu Coding Plan**: If you're on Zhipu's coding plan, set `"apiBase": "https://open.bigmodel.cn/api/coding/paas/v4"` in your zhipu provider config. +> - **Alibaba Cloud BaiLian**: If you're using Alibaba Cloud BaiLian's OpenAI-compatible endpoint, set `"apiBase": "https://dashscope.aliyuncs.com/compatible-mode/v1"` in your dashscope provider config. +> - **Step Fun (Mainland China)**: If your API key is from Step Fun's mainland China platform (stepfun.com), set `"apiBase": "https://api.stepfun.com/v1"` in your stepfun provider config. + +| Provider | Purpose | Get API Key | +|----------|---------|-------------| +| `custom` | Any OpenAI-compatible endpoint | โ€” | +| `openrouter` | LLM (recommended, access to all models) | [openrouter.ai](https://openrouter.ai) | +| `volcengine` | LLM (VolcEngine, pay-per-use) | [Coding Plan](https://www.volcengine.com/activity/codingplan?utm_campaign=nanobot&utm_content=nanobot&utm_medium=devrel&utm_source=OWO&utm_term=nanobot) ยท [volcengine.com](https://www.volcengine.com) | +| `byteplus` | LLM (VolcEngine international, pay-per-use) | [Coding Plan](https://www.byteplus.com/en/activity/codingplan?utm_campaign=nanobot&utm_content=nanobot&utm_medium=devrel&utm_source=OWO&utm_term=nanobot) ยท [byteplus.com](https://www.byteplus.com) | +| `anthropic` | LLM (Claude direct) | [console.anthropic.com](https://console.anthropic.com) | +| `azure_openai` | LLM (Azure OpenAI) | [portal.azure.com](https://portal.azure.com) | +| `openai` | LLM + Voice transcription (Whisper) | [platform.openai.com](https://platform.openai.com) | +| `deepseek` | LLM (DeepSeek direct) | [platform.deepseek.com](https://platform.deepseek.com) | +| `groq` | LLM + Voice transcription (Whisper, default) | [console.groq.com](https://console.groq.com) | +| `minimax` | LLM (MiniMax direct) | [platform.minimaxi.com](https://platform.minimaxi.com) | +| `minimax_anthropic` | LLM (MiniMax Anthropic-compatible endpoint, thinking mode) | [platform.minimaxi.com](https://platform.minimaxi.com) | +| `gemini` | LLM (Gemini direct) | [aistudio.google.com](https://aistudio.google.com) | +| `aihubmix` | LLM (API gateway, access to all models) | [aihubmix.com](https://aihubmix.com) | +| `siliconflow` | LLM (SiliconFlow/็ก…ๅŸบๆตๅŠจ) | [siliconflow.cn](https://siliconflow.cn) | +| `dashscope` | LLM (Qwen) | [dashscope.console.aliyun.com](https://dashscope.console.aliyun.com) | +| `moonshot` | LLM (Moonshot/Kimi) | [platform.moonshot.cn](https://platform.moonshot.cn) | +| `zhipu` | LLM (Zhipu GLM) | [open.bigmodel.cn](https://open.bigmodel.cn) | +| `mimo` | LLM (MiMo) | [platform.xiaomimimo.com](https://platform.xiaomimimo.com) | +| `ollama` | LLM (local, Ollama) | โ€” | +| `lm_studio` | LLM (local, LM Studio) | โ€” | +| `mistral` | LLM | [docs.mistral.ai](https://docs.mistral.ai/) | +| `stepfun` | LLM (Step Fun/้˜ถ่ทƒๆ˜Ÿ่พฐ) | [platform.stepfun.com](https://platform.stepfun.com) | +| `ovms` | LLM (local, OpenVINO Model Server) | [docs.openvino.ai](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html) | +| `vllm` | LLM (local, any OpenAI-compatible server) | โ€” | +| `openai_codex` | LLM (Codex, OAuth) | `nanobot provider login openai-codex` | +| `github_copilot` | LLM (GitHub Copilot, OAuth) | `nanobot provider login github-copilot` | +| `qianfan` | LLM (Baidu Qianfan) | [cloud.baidu.com](https://cloud.baidu.com/doc/qianfan/s/Hmh4suq26) | + + +
+OpenAI Codex (OAuth) + +Codex uses OAuth instead of API keys. Requires a ChatGPT Plus or Pro account. +No `providers.openaiCodex` block is needed in `config.json`; `nanobot provider login` stores the OAuth session outside config. + +**1. Login:** +```bash +nanobot provider login openai-codex +``` + +**2. Set model** (merge into `~/.nanobot/config.json`): +```json +{ + "agents": { + "defaults": { + "model": "openai-codex/gpt-5.1-codex" + } + } +} +``` + +**3. Chat:** +```bash +nanobot agent -m "Hello!" + +# Target a specific workspace/config locally +nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello!" + +# One-off workspace override on top of that config +nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -m "Hello!" +``` + +> Docker users: use `docker run -it` for interactive OAuth login. + +
+ + +
+GitHub Copilot (OAuth) + +GitHub Copilot uses OAuth instead of API keys. Requires a [GitHub account with a plan](https://github.com/features/copilot/plans) configured. +No `providers.githubCopilot` block is needed in `config.json`; `nanobot provider login` stores the OAuth session outside config. + +**1. Login:** +```bash +nanobot provider login github-copilot +``` + +**2. Set model** (merge into `~/.nanobot/config.json`): +```json +{ + "agents": { + "defaults": { + "model": "github-copilot/gpt-4.1" + } + } +} +``` + +**3. Chat:** +```bash +nanobot agent -m "Hello!" + +# Target a specific workspace/config locally +nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello!" + +# One-off workspace override on top of that config +nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test -m "Hello!" +``` + +> Docker users: use `docker run -it` for interactive OAuth login. + +
+ +
+Custom Provider (Any OpenAI-compatible API) + +Connects directly to any OpenAI-compatible endpoint โ€” llama.cpp, Together AI, Fireworks, Azure OpenAI, or any self-hosted server. Model name is passed as-is. + +```json +{ + "providers": { + "custom": { + "apiKey": "your-api-key", + "apiBase": "https://api.your-provider.com/v1" + } + }, + "agents": { + "defaults": { + "model": "your-model-name" + } + } +} +``` + +> For local servers that don't require authentication, set `apiKey` to `null`. +> +> `custom` is the right choice for providers that expose an OpenAI-compatible **chat completions** API. It does **not** force third-party endpoints onto the OpenAI/Azure **Responses API**. +> +> If your proxy or gateway is specifically Responses-API-compatible, use the `azure_openai` provider shape instead and point `apiBase` at that endpoint: +> +> ```json +> { +> "providers": { +> "azure_openai": { +> "apiKey": "your-api-key", +> "apiBase": "https://api.your-provider.com", +> "defaultModel": "your-model-name" +> } +> }, +> "agents": { +> "defaults": { +> "provider": "azure_openai", +> "model": "your-model-name" +> } +> } +> } +> ``` +> +> In short: **chat-completions-compatible endpoint โ†’ `custom`**; **Responses-compatible endpoint โ†’ `azure_openai`**. + +
+ +
+Ollama (local) + +Run a local model with Ollama, then add to config: + +**1. Start Ollama** (example): +```bash +ollama run llama3.2 +``` + +**2. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): +```json +{ + "providers": { + "ollama": { + "apiBase": "http://localhost:11434" + } + }, + "agents": { + "defaults": { + "provider": "ollama", + "model": "llama3.2" + } + } +} +``` + +> `provider: "auto"` also works when `providers.ollama.apiBase` is configured, but setting `"provider": "ollama"` is the clearest option. + +
+ +
+LM Studio (local) + +[LM Studio](https://lmstudio.ai/) provides a local OpenAI-compatible server for running LLMs. Download models through the LM Studio UI, then start the local server. + +**1. Start LM Studio server:** +- Launch LM Studio +- Go to the "Local Server" tab +- Load a model (e.g., Llama, Mistral, Qwen) +- Click "Start Server" (default port: 1234) + +**2. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): +```json +{ + "providers": { + "lm_studio": { + "apiKey": null, + "apiBase": "http://localhost:1234/v1" + } + }, + "agents": { + "defaults": { + "provider": "lm_studio", + "model": "local-model" + } + } +} +``` + +> **Note:** Set `apiKey` to `null` for LM Studio since it runs locally and doesn't require authentication. The model name should match what's shown in the LM Studio UI. +> `provider: "auto"` also works when `providers.lm_studio.apiBase` is configured, but setting `"provider": "lm_studio"` is the clearest option. + +
+ +
+OpenVINO Model Server (local / OpenAI-compatible) + +Run LLMs locally on Intel GPUs using [OpenVINO Model Server](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html). OVMS exposes an OpenAI-compatible API at `/v3`. + +> Requires Docker and an Intel GPU with driver access (`/dev/dri`). + +**1. Pull the model** (example): + +```bash +mkdir -p ov/models && cd ov + +docker run -d \ + --rm \ + --user $(id -u):$(id -g) \ + -v $(pwd)/models:/models \ + openvino/model_server:latest-gpu \ + --pull \ + --model_name openai/gpt-oss-20b \ + --model_repository_path /models \ + --source_model OpenVINO/gpt-oss-20b-int4-ov \ + --task text_generation \ + --tool_parser gptoss \ + --reasoning_parser gptoss \ + --enable_prefix_caching true \ + --target_device GPU +``` + +> This downloads the model weights. Wait for the container to finish before proceeding. + +**2. Start the server** (example): + +```bash +docker run -d \ + --rm \ + --name ovms \ + --user $(id -u):$(id -g) \ + -p 8000:8000 \ + -v $(pwd)/models:/models \ + --device /dev/dri \ + --group-add=$(stat -c "%g" /dev/dri/render* | head -n 1) \ + openvino/model_server:latest-gpu \ + --rest_port 8000 \ + --model_name openai/gpt-oss-20b \ + --model_repository_path /models \ + --source_model OpenVINO/gpt-oss-20b-int4-ov \ + --task text_generation \ + --tool_parser gptoss \ + --reasoning_parser gptoss \ + --enable_prefix_caching true \ + --target_device GPU +``` + +**3. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): + +```json +{ + "providers": { + "ovms": { + "apiBase": "http://localhost:8000/v3" + } + }, + "agents": { + "defaults": { + "provider": "ovms", + "model": "openai/gpt-oss-20b" + } + } +} +``` + +> OVMS is a local server โ€” no API key required. Supports tool calling (`--tool_parser gptoss`), reasoning (`--reasoning_parser gptoss`), and streaming. +> See the [official OVMS docs](https://docs.openvino.ai/2026/model-server/ovms_docs_llm_quickstart.html) for more details. +
+ +
+vLLM (local / OpenAI-compatible) + +Run your own model with vLLM or any OpenAI-compatible server, then add to config: + +**1. Start the server** (example): +```bash +vllm serve meta-llama/Llama-3.1-8B-Instruct --port 8000 +``` + +**2. Add to config** (partial โ€” merge into `~/.nanobot/config.json`): + +*Provider (set API key to null for local servers):* +```json +{ + "providers": { + "vllm": { + "apiKey": null, + "apiBase": "http://localhost:8000/v1" + } + } +} +``` + +*Model:* +```json +{ + "agents": { + "defaults": { + "model": "meta-llama/Llama-3.1-8B-Instruct" + } + } +} +``` + +
+ +
+Adding a New Provider (Developer Guide) + +nanobot uses a **Provider Registry** (`nanobot/providers/registry.py`) as the single source of truth. +Adding a new provider only takes **2 steps** โ€” no if-elif chains to touch. + +**Step 1.** Add a `ProviderSpec` entry to `PROVIDERS` in `nanobot/providers/registry.py`: + +```python +ProviderSpec( + name="myprovider", # config field name + keywords=("myprovider", "mymodel"), # model-name keywords for auto-matching + env_key="MYPROVIDER_API_KEY", # env var name + display_name="My Provider", # shown in `nanobot status` + default_api_base="https://api.myprovider.com/v1", # OpenAI-compatible endpoint +) +``` + +**Step 2.** Add a field to `ProvidersConfig` in `nanobot/config/schema.py`: + +```python +class ProvidersConfig(BaseModel): + ... + myprovider: ProviderConfig = ProviderConfig() +``` + +That's it! Environment variables, model routing, config matching, and `nanobot status` display will all work automatically. + +**Common `ProviderSpec` options:** + +| Field | Description | Example | +|-------|-------------|---------| +| `default_api_base` | OpenAI-compatible base URL | `"https://api.deepseek.com"` | +| `env_extras` | Additional env vars to set | `(("ZHIPUAI_API_KEY", "{api_key}"),)` | +| `model_overrides` | Per-model parameter overrides | `(("kimi-k2.5", {"temperature": 1.0}),)` | +| `is_gateway` | Can route any model (like OpenRouter) | `True` | +| `detect_by_key_prefix` | Detect gateway by API key prefix | `"sk-or-"` | +| `detect_by_base_keyword` | Detect gateway by API base URL | `"openrouter"` | +| `strip_model_prefix` | Strip provider prefix before sending to gateway | `True` (for AiHubMix) | +| `supports_max_completion_tokens` | Use `max_completion_tokens` instead of `max_tokens`; required for providers that reject both being set simultaneously (e.g. VolcEngine) | `True` | + +
+ +## Channel Settings + +Global settings that apply to all channels. Configure under the `channels` section in `~/.nanobot/config.json`: + +```json +{ + "channels": { + "sendProgress": true, + "sendToolHints": false, + "sendMaxRetries": 3, + "transcriptionProvider": "groq", + "telegram": { ... } + } +} +``` + +| Setting | Default | Description | +|---------|---------|-------------| +| `sendProgress` | `true` | Stream agent's text progress to the channel | +| `sendToolHints` | `false` | Stream tool-call hints (e.g. `read_file("โ€ฆ")`) | +| `sendMaxRetries` | `3` | Max delivery attempts per outbound message, including the initial send (0-10 configured, minimum 1 actual attempt) | +| `transcriptionProvider` | `"groq"` | Voice transcription backend: `"groq"` (free tier, default) or `"openai"`. API key is auto-resolved from the matching provider config. | + +### Retry Behavior + +Retry is intentionally simple. + +When a channel `send()` raises, nanobot retries at the channel-manager layer. By default, `channels.sendMaxRetries` is `3`, and that count includes the initial send. + +- **Attempt 1**: Send immediately +- **Attempt 2**: Retry after `1s` +- **Attempt 3**: Retry after `2s` +- **Higher retry budgets**: Backoff continues as `1s`, `2s`, `4s`, then stays capped at `4s` +- **Transient failures**: Network hiccups and temporary API limits often recover on the next attempt +- **Permanent failures**: Invalid tokens, revoked access, or banned channels will exhaust the retry budget and fail cleanly + +> [!NOTE] +> This design is deliberate: channel implementations should raise on delivery failure, and the channel manager owns the shared retry policy. +> +> Some channels may still apply small API-specific retries internally. For example, Telegram separately retries timeout and flood-control errors before surfacing a final failure to the manager. +> +> If a channel is completely unreachable, nanobot cannot notify the user through that same channel. Watch logs for `Failed to send to {channel} after N attempts` to spot persistent delivery failures. + +## Web Search + +> [!TIP] +> Use `proxy` in `tools.web` to route all web requests (search + fetch) through a proxy: +> ```json +> { "tools": { "web": { "proxy": "http://127.0.0.1:7890" } } } +> ``` + +nanobot supports multiple web search providers. Configure in `~/.nanobot/config.json` under `tools.web.search`. + +By default, web tools are enabled and web search uses `duckduckgo`, so search works out of the box without an API key. + +If you want to disable all built-in web tools entirely, set `tools.web.enable` to `false`. This removes both `web_search` and `web_fetch` from the tool list sent to the LLM. + +If you need to allow trusted private ranges such as Tailscale / CGNAT addresses, you can explicitly exempt them from SSRF blocking with `tools.ssrfWhitelist`: + +```json +{ + "tools": { + "ssrfWhitelist": ["100.64.0.0/10"] + } +} +``` + +| Provider | Config fields | Env var fallback | Free | +|----------|--------------|------------------|------| +| `brave` | `apiKey` | `BRAVE_API_KEY` | No | +| `tavily` | `apiKey` | `TAVILY_API_KEY` | No | +| `jina` | `apiKey` | `JINA_API_KEY` | Free tier (10M tokens) | +| `kagi` | `apiKey` | `KAGI_API_KEY` | No | +| `searxng` | `baseUrl` | `SEARXNG_BASE_URL` | Yes (self-hosted) | +| `duckduckgo` (default) | โ€” | โ€” | Yes | + +**Disable all built-in web tools:** +```json +{ + "tools": { + "web": { + "enable": false + } + } +} +``` + +**Brave:** +```json +{ + "tools": { + "web": { + "search": { + "provider": "brave", + "apiKey": "BSA..." + } + } + } +} +``` + +**Tavily:** +```json +{ + "tools": { + "web": { + "search": { + "provider": "tavily", + "apiKey": "tvly-..." + } + } + } +} +``` + +**Jina** (free tier with 10M tokens): +```json +{ + "tools": { + "web": { + "search": { + "provider": "jina", + "apiKey": "jina_..." + } + } + } +} +``` + +**Kagi:** +```json +{ + "tools": { + "web": { + "search": { + "provider": "kagi", + "apiKey": "your-kagi-api-key" + } + } + } +} +``` + +**SearXNG** (self-hosted, no API key needed): +```json +{ + "tools": { + "web": { + "search": { + "provider": "searxng", + "baseUrl": "https://searx.example" + } + } + } +} +``` + +**DuckDuckGo** (zero config): +```json +{ + "tools": { + "web": { + "search": { + "provider": "duckduckgo" + } + } + } +} +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `enable` | boolean | `true` | Enable or disable all built-in web tools (`web_search` + `web_fetch`) | +| `proxy` | string or null | `null` | Proxy for all web requests, for example `http://127.0.0.1:7890` | + +### `tools.web.search` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `provider` | string | `"duckduckgo"` | Search backend: `brave`, `tavily`, `jina`, `searxng`, `duckduckgo` | +| `apiKey` | string | `""` | API key for Brave or Tavily | +| `baseUrl` | string | `""` | Base URL for SearXNG | +| `maxResults` | integer | `5` | Results per search (1โ€“10) | + +## MCP (Model Context Protocol) + +> [!TIP] +> The config format is compatible with Claude Desktop / Cursor. You can copy MCP server configs directly from any MCP server's README. + +nanobot supports [MCP](https://modelcontextprotocol.io/) โ€” connect external tool servers and use them as native agent tools. + +Add MCP servers to your `config.json`: + +```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" + } + } + } + } +} +``` + +Two transport modes are supported: + +| Mode | Config | Example | +|------|--------|---------| +| **Stdio** | `command` + `args` | Local process via `npx` / `uvx` | +| **HTTP** | `url` + `headers` (optional) | Remote endpoint (`https://mcp.example.com/sse`) | + +Use `toolTimeout` to override the default 30s per-call timeout for slow servers: + +```json +{ + "tools": { + "mcpServers": { + "my-slow-server": { + "url": "https://example.com/mcp/", + "toolTimeout": 120 + } + } + } +} +``` + +Use `enabledTools` to register only a subset of tools from an MCP server: + +```json +{ + "tools": { + "mcpServers": { + "filesystem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"], + "enabledTools": ["read_file", "mcp_filesystem_write_file"] + } + } + } +} +``` + +`enabledTools` accepts either the raw MCP tool name (for example `read_file`) or the wrapped nanobot tool name (for example `mcp_filesystem_write_file`). + +- Omit `enabledTools`, or set it to `["*"]`, to register all tools. +- Set `enabledTools` to `[]` to register no tools from that server. +- Set `enabledTools` to a non-empty list of names to register only that subset. + +MCP tools are automatically discovered and registered on startup. The LLM can use them alongside built-in tools โ€” no extra configuration needed. + + + + +## Security + +> [!TIP] +> For production deployments, set `"restrictToWorkspace": true` and `"tools.exec.sandbox": "bwrap"` in your config to sandbox the agent. +> In `v0.1.4.post3` and earlier, an empty `allowFrom` allowed all senders. Since `v0.1.4.post4`, empty `allowFrom` denies all access by default. To allow all senders, set `"allowFrom": ["*"]`. + +| Option | Default | Description | +|--------|---------|-------------| +| `tools.restrictToWorkspace` | `false` | When `true`, restricts **all** agent tools (shell, file read/write/edit, list) to the workspace directory. Prevents path traversal and out-of-scope access. | +| `tools.exec.sandbox` | `""` | Sandbox backend for shell commands. Set to `"bwrap"` to wrap exec calls in a [bubblewrap](https://github.com/containers/bubblewrap) sandbox โ€” the process can only see the workspace (read-write) and media directory (read-only); config files and API keys are hidden. Automatically enables `restrictToWorkspace` for file tools. **Linux only** โ€” requires `bwrap` installed (`apt install bubblewrap`; pre-installed in the Docker image). Not available on macOS or Windows (bwrap depends on Linux kernel namespaces). | +| `tools.exec.enable` | `true` | When `false`, the shell `exec` tool is not registered at all. Use this to completely disable shell command execution. | +| `tools.exec.pathAppend` | `""` | Extra directories to append to `PATH` when running shell commands (e.g. `/usr/sbin` for `ufw`). | +| `channels.*.allowFrom` | `[]` (deny all) | Whitelist of user IDs. Empty denies all; use `["*"]` to allow everyone. | + +**Docker security**: The official Docker image runs as a non-root user (`nanobot`, UID 1000) with bubblewrap pre-installed. When using `docker-compose.yml`, the container drops all Linux capabilities except `SYS_ADMIN` (required for bwrap's namespace isolation). + + +## Auto Compact + +When a user is idle for longer than a configured threshold, nanobot **proactively** compresses the older part of the session context into a summary while keeping a recent legal suffix of live messages. This reduces token cost and first-token latency when the user returns โ€” instead of re-processing a long stale context with an expired KV cache, the model receives a compact summary, the most recent live context, and fresh input. + +```json +{ + "agents": { + "defaults": { + "idleCompactAfterMinutes": 15 + } + } +} +``` + +| Option | Default | Description | +|--------|---------|-------------| +| `agents.defaults.idleCompactAfterMinutes` | `0` (disabled) | Minutes of idle time before auto-compaction starts. Set to `0` to disable. Recommended: `15` โ€” close to a typical LLM KV cache expiry window, so stale sessions get compacted before the user returns. | + +`sessionTtlMinutes` remains accepted as a legacy alias for backward compatibility, but `idleCompactAfterMinutes` is the preferred config key going forward. + +How it works: +1. **Idle detection**: On each idle tick (~1 s), checks all sessions for expiration. +2. **Background compaction**: Idle sessions summarize the older live prefix via LLM and keep the most recent legal suffix (currently 8 messages). +3. **Summary injection**: When the user returns, the summary is injected as runtime context (one-shot, not persisted) alongside the retained recent suffix. +4. **Restart-safe resume**: The summary is also mirrored into session metadata so it can still be recovered after a process restart. + +> [!NOTE] +> Mental model: "summarize older context, keep the freshest live turns, **and overwrite the session file with the compact form.**" It is not a full `session.clear()`, but it is a write โ€” not a soft cursor move. +> +> Concretely, auto compact rewrites `sessions/.jsonl` in place: older messages (including their structured `tool_calls` / `tool_call_id` / `reasoning_content`) are replaced by just the retained recent suffix (currently 8 messages), while the archived prefix is preserved only as a plain-text summary appended to `memory/history.jsonl` (or a `[RAW] ...` flattened dump if LLM summarization fails). The original structured JSON of those turns is no longer recoverable from the session file. +> +> This differs from the **token-driven soft consolidation** that fires when a prompt exceeds the context budget: that path only advances an internal `last_consolidated` cursor and leaves the session file untouched, so the raw tool-call trail stays on disk and can still be replayed or audited. If you rely on that trail for debugging or auditing, leave `idleCompactAfterMinutes` at the default `0` and let only the token-driven path run. + +## Timezone + +Time is context. Context should be precise. + +By default, nanobot uses `UTC` for runtime time context. If you want the agent to think in your local time, set `agents.defaults.timezone` to a valid [IANA timezone name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones): + +```json +{ + "agents": { + "defaults": { + "timezone": "Asia/Shanghai" + } + } +} +``` + +This affects runtime time strings shown to the model, such as runtime context and heartbeat prompts. It also becomes the default timezone for cron schedules when a cron expression omits `tz`, and for one-shot `at` times when the ISO datetime has no explicit offset. + +Common examples: `UTC`, `America/New_York`, `America/Los_Angeles`, `Europe/London`, `Europe/Berlin`, `Asia/Tokyo`, `Asia/Shanghai`, `Asia/Singapore`, `Australia/Sydney`. + +> Need another timezone? Browse the full [IANA Time Zone Database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). + +## Unified Session + +By default, each channel ร— chat ID combination gets its own session. If you use nanobot across multiple channels (e.g. Telegram + Discord + CLI) and want them to share the same conversation, enable `unifiedSession`: + +```json +{ + "agents": { + "defaults": { + "unifiedSession": true + } + } +} +``` + +When enabled, all incoming messages โ€” regardless of which channel they arrive on โ€” are routed into a single shared session. Switching from Telegram to Discord (or any other channel) continues the same conversation seamlessly. + +| Behavior | `false` (default) | `true` | +|----------|-------------------|--------| +| Session key | `channel:chat_id` | `unified:default` | +| Cross-channel continuity | No | Yes | +| `/new` clears | Current channel session | Shared session | +| `/stop` finds tasks | By channel session | By shared session | +| Existing `session_key_override` (e.g. Telegram thread) | Respected | Still respected โ€” not overwritten | + +> This is designed for single-user, multi-device setups. It is **off by default** โ€” existing users see zero behavior change. + +## Disabled Skills + +nanobot ships with built-in skills, and your workspace can also define custom skills under `skills/`. If you want to hide specific skills from the agent, set `agents.defaults.disabledSkills` to a list of skill directory names: + +```json +{ + "agents": { + "defaults": { + "disabledSkills": ["github", "weather"] + } + } +} +``` + +Disabled skills are excluded from the main agent's skill summary, from always-on skill injection, and from subagent skill summaries. This is useful when some bundled skills are unnecessary for your deployment or should not be exposed to end users. + +| Option | Default | Description | +|--------|---------|-------------| +| `agents.defaults.disabledSkills` | `[]` | List of skill directory names to exclude from loading. Applies to both built-in skills and workspace skills. | diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 00000000..ad6283c0 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,94 @@ +# Deployment + +## Docker + +> [!TIP] +> The `-v ~/.nanobot:/home/nanobot/.nanobot` flag mounts your local config directory into the container, so your config and workspace persist across container restarts. +> The container runs as user `nanobot` (UID 1000). If you get **Permission denied**, fix ownership on the host first: `sudo chown -R 1000:1000 ~/.nanobot`, or pass `--user $(id -u):$(id -g)` to match your host UID. Podman users can use `--userns=keep-id` instead. + +### Docker Compose + +```bash +docker compose run --rm nanobot-cli onboard # first-time setup +vim ~/.nanobot/config.json # add API keys +docker compose up -d nanobot-gateway # start gateway +``` + +```bash +docker compose run --rm nanobot-cli agent -m "Hello!" # run CLI +docker compose logs -f nanobot-gateway # view logs +docker compose down # stop +``` + +### Docker + +```bash +# Build the image +docker build -t nanobot . + +# Initialize config (first time only) +docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot onboard + +# Edit config on host to add API keys +vim ~/.nanobot/config.json + +# Run gateway (connects to enabled channels, e.g. Telegram/Discord/Mochat) +docker run -v ~/.nanobot:/home/nanobot/.nanobot -p 18790:18790 nanobot gateway + +# Or run a single command +docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot agent -m "Hello!" +docker run -v ~/.nanobot:/home/nanobot/.nanobot --rm nanobot status +``` + +## Linux Service + +Run the gateway as a systemd user service so it starts automatically and restarts on failure. + +**1. Find the nanobot binary path:** + +```bash +which nanobot # e.g. /home/user/.local/bin/nanobot +``` + +**2. Create the service file** at `~/.config/systemd/user/nanobot-gateway.service` (replace `ExecStart` path if needed): + +```ini +[Unit] +Description=Nanobot Gateway +After=network.target + +[Service] +Type=simple +ExecStart=%h/.local/bin/nanobot gateway +Restart=always +RestartSec=10 +NoNewPrivileges=yes +ProtectSystem=strict +ReadWritePaths=%h + +[Install] +WantedBy=default.target +``` + +**3. Enable and start:** + +```bash +systemctl --user daemon-reload +systemctl --user enable --now nanobot-gateway +``` + +**Common operations:** + +```bash +systemctl --user status nanobot-gateway # check status +systemctl --user restart nanobot-gateway # restart after config changes +journalctl --user -u nanobot-gateway -f # follow logs +``` + +If you edit the `.service` file itself, run `systemctl --user daemon-reload` before restarting. + +> **Note:** User services only run while you are logged in. To keep the gateway running after logout, enable lingering: +> +> ```bash +> loginctl enable-linger $USER +> ``` diff --git a/docs/features-showcase.md b/docs/features-showcase.md new file mode 100644 index 00000000..bb988b06 --- /dev/null +++ b/docs/features-showcase.md @@ -0,0 +1,22 @@ +# Feature Showcase + + + + + + + + + + + + + + + + + + + + +

๐Ÿ“ˆ 24/7 Real-Time Market Analysis

๐Ÿš€ Full-Stack Software Engineer

๐Ÿ“… Smart Daily Routine Manager

๐Ÿ“š Personal Knowledge Assistant

Discovery โ€ข Insights โ€ข TrendsDevelop โ€ข Deploy โ€ข ScaleSchedule โ€ข Automate โ€ข OrganizeLearn โ€ข Memory โ€ข Reasoning
diff --git a/docs/MEMORY.md b/docs/memory.md similarity index 99% rename from docs/MEMORY.md rename to docs/memory.md index 414fcdca..2fde1c68 100644 --- a/docs/MEMORY.md +++ b/docs/memory.md @@ -65,7 +65,7 @@ This is why nanobot's memory is not just archival. It is interpretive. ## The Files -``` +```text workspace/ โ”œโ”€โ”€ SOUL.md # The bot's long-term voice and communication style โ”œโ”€โ”€ USER.md # Stable knowledge about the user diff --git a/docs/multiple-instances.md b/docs/multiple-instances.md new file mode 100644 index 00000000..d7c54cc0 --- /dev/null +++ b/docs/multiple-instances.md @@ -0,0 +1,126 @@ +# Multiple Instances + +Run multiple nanobot instances simultaneously with separate configs and runtime data. Use `--config` as the main entrypoint. Optionally pass `--workspace` during `onboard` when you want to initialize or update the saved workspace for a specific instance. + +## Quick Start + +If you want each instance to have its own dedicated workspace from the start, pass both `--config` and `--workspace` during onboarding. + +**Initialize instances:** + +```bash +# Create separate instance configs and workspaces +nanobot onboard --config ~/.nanobot-telegram/config.json --workspace ~/.nanobot-telegram/workspace +nanobot onboard --config ~/.nanobot-discord/config.json --workspace ~/.nanobot-discord/workspace +nanobot onboard --config ~/.nanobot-feishu/config.json --workspace ~/.nanobot-feishu/workspace +``` + +**Configure each instance:** + +Edit `~/.nanobot-telegram/config.json`, `~/.nanobot-discord/config.json`, etc. with different channel settings. The workspace you passed during `onboard` is saved into each config as that instance's default workspace. + +**Run instances:** + +```bash +# Instance A - Telegram bot +nanobot gateway --config ~/.nanobot-telegram/config.json + +# Instance B - Discord bot +nanobot gateway --config ~/.nanobot-discord/config.json + +# Instance C - Feishu bot with custom port +nanobot gateway --config ~/.nanobot-feishu/config.json --port 18792 +``` + +## Path Resolution + +When using `--config`, nanobot derives its runtime data directory from the config file location. The workspace still comes from `agents.defaults.workspace` unless you override it with `--workspace`. + +To open a CLI session against one of these instances locally: + +```bash +nanobot agent -c ~/.nanobot-telegram/config.json -m "Hello from Telegram instance" +nanobot agent -c ~/.nanobot-discord/config.json -m "Hello from Discord instance" + +# Optional one-off workspace override +nanobot agent -c ~/.nanobot-telegram/config.json -w /tmp/nanobot-telegram-test +``` + +> `nanobot agent` starts a local CLI agent using the selected workspace/config. It does not attach to or proxy through an already running `nanobot gateway` process. + +| Component | Resolved From | Example | +|-----------|---------------|---------| +| **Config** | `--config` path | `~/.nanobot-A/config.json` | +| **Workspace** | `--workspace` or config | `~/.nanobot-A/workspace/` | +| **Cron Jobs** | config directory | `~/.nanobot-A/cron/` | +| **Media / runtime state** | config directory | `~/.nanobot-A/media/` | + +## How It Works + +- `--config` selects which config file to load +- By default, the workspace comes from `agents.defaults.workspace` in that config +- If you pass `--workspace`, it overrides the workspace from the config file + +## Minimal Setup + +1. Copy your base config into a new instance directory. +2. Set a different `agents.defaults.workspace` for that instance. +3. Start the instance with `--config`. + +Example config: + +```json +{ + "agents": { + "defaults": { + "workspace": "~/.nanobot-telegram/workspace", + "model": "anthropic/claude-sonnet-4-6" + } + }, + "channels": { + "telegram": { + "enabled": true, + "token": "YOUR_TELEGRAM_BOT_TOKEN" + } + }, + "gateway": { + "host": "127.0.0.1", + "port": 18790 + } +} +``` + +Start separate instances: + +```bash +nanobot gateway --config ~/.nanobot-telegram/config.json +nanobot gateway --config ~/.nanobot-discord/config.json +``` + +Each gateway instance also exposes a lightweight HTTP health endpoint on +`gateway.host:gateway.port`. By default, the gateway binds to `127.0.0.1`, +so the endpoint stays local unless you explicitly set `gateway.host` to a +public or LAN-facing address. + +- `GET /health` returns `{"status":"ok"}` +- Other paths return `404` + +Override workspace for one-off runs when needed: + +```bash +nanobot gateway --config ~/.nanobot-telegram/config.json --workspace /tmp/nanobot-telegram-test +``` + +## Common Use Cases + +- Run separate bots for Telegram, Discord, Feishu, and other platforms +- Keep testing and production instances isolated +- Use different models or providers for different teams +- Serve multiple tenants with separate configs and runtime data + +## Notes + +- Each instance must use a different port if they run at the same time +- Use a different workspace per instance if you want isolated memory, sessions, and skills +- `--workspace` overrides the workspace defined in the config file +- Cron jobs and runtime media/state are derived from the config directory diff --git a/docs/MY_TOOL.md b/docs/my-tool.md similarity index 98% rename from docs/MY_TOOL.md rename to docs/my-tool.md index a8a273d1..bc22ed5a 100644 --- a/docs/MY_TOOL.md +++ b/docs/my-tool.md @@ -36,7 +36,7 @@ All modifications are held in memory only โ€” restart restores defaults. Without parameters, returns a key config overview: -``` +```text my(action="check") # โ†’ max_iterations: 40 # context_window_tokens: 65536 @@ -51,7 +51,7 @@ my(action="check") With a key parameter, drill into a specific config: -``` +```text my(action="check", key="_last_usage.prompt_tokens") # โ†’ How many prompt tokens I've used so far @@ -79,7 +79,7 @@ my(action="check", key="web_config.enable") Changes take effect immediately, no restart required. -``` +```text my(action="set", key="max_iterations", value=80) # โ†’ Bump iteration limit from 40 to 80 @@ -92,7 +92,7 @@ my(action="set", key="context_window_tokens", value=131072) You can also store custom state in your scratchpad: -``` +```text my(action="set", key="current_project", value="nanobot") my(action="set", key="user_style_preference", value="concise") my(action="set", key="task_complexity", value="high") @@ -117,21 +117,21 @@ Other parameters (e.g. `workspace`, `provider_retry_mode`, `max_tool_result_char ### "This task is complex, I need more room" -``` +```text Agent: This codebase is large, let me expand my context window to handle it. โ†’ my(action="set", key="context_window_tokens", value=131072) ``` ### "Simple question, don't waste compute" -``` +```text Agent: This is a straightforward question, let me switch to a faster model. โ†’ my(action="set", key="model", value="fast-model") ``` ### "Remember user preferences across turns" -``` +```text Turn 1: my(action="set", key="user_prefers_concise", value=True) Turn 2: my(action="check", key="user_prefers_concise") # โ†’ True (still remembers the user likes concise replies) @@ -139,7 +139,7 @@ Turn 2: my(action="check", key="user_prefers_concise") ### "Self-diagnosis" -``` +```text User: "Why aren't you searching the web?" Agent: Let me check my web config. โ†’ my(action="check", key="web_config.enable") @@ -149,7 +149,7 @@ Agent: Web search is disabled โ€” please set web.enable: true in your config. ### "Token budget management" -``` +```text Agent: Let me check how much budget I have left. โ†’ my(action="check", key="_last_usage") # โ†’ {"prompt_tokens": 45000, "completion_tokens": 8000} @@ -158,7 +158,7 @@ Agent: I've used ~53k tokens total so far. I'll keep my remaining replies concis ### "Subagent monitoring" -``` +```text Agent: Let me check on the background tasks. โ†’ my(action="check", key="subagents") # โ†’ 2 subagent(s): diff --git a/docs/openai-api.md b/docs/openai-api.md new file mode 100644 index 00000000..c88a8bed --- /dev/null +++ b/docs/openai-api.md @@ -0,0 +1,121 @@ +# OpenAI-Compatible API + +nanobot can expose a minimal OpenAI-compatible endpoint for local integrations: + +```bash +pip install "nanobot-ai[api]" +nanobot serve +``` + +By default, the API binds to `127.0.0.1:8900`. You can change this in `config.json`. + +## Behavior + +- Session isolation: pass `"session_id"` in the request body to isolate conversations; omit for a shared default session (`api:default`) +- Single-message input: each request must contain exactly one `user` message +- Fixed model: omit `model`, or pass the same model shown by `/v1/models` +- Streaming: set `stream=true` to receive Server-Sent Events (`text/event-stream`) with OpenAI-compatible delta chunks, terminated by `data: [DONE]`; omit or set `stream=false` for a single JSON response +- **File uploads**: supports images, PDF, Word (.docx), Excel (.xlsx), PowerPoint (.pptx) via JSON base64 or `multipart/form-data` (max 10MB per file) +- API requests run in the synthetic `api` channel, so the `message` tool does **not** automatically deliver to Telegram/Discord/etc. To proactively send to another chat, call `message` with an explicit `channel` and `chat_id` for an enabled channel. + +Example tool call for cross-channel delivery from an API session: + +```json +{ + "content": "Build finished successfully.", + "channel": "telegram", + "chat_id": "123456789" +} +``` + +If `channel` points to a channel that is not enabled in your config, nanobot will queue the outbound event but no platform delivery will occur. + +## Endpoints + +- `GET /health` +- `GET /v1/models` +- `POST /v1/chat/completions` + +## curl + +```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" + }' +``` + +## File Upload (JSON base64) + +Send images inline using the OpenAI multimodal content format: + +```bash +curl http://127.0.0.1:8900/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [{"role": "user", "content": [ + {"type": "text", "text": "Describe this image"}, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBOR..."}} + ]}] + }' +``` + +## File Upload (multipart/form-data) + +Upload any supported file type (images, PDF, Word, Excel, PPT) via multipart: + +```bash +# Single file +curl http://127.0.0.1:8900/v1/chat/completions \ + -F "message=Summarize this report" \ + -F "files=@report.docx" + +# Multiple files with session isolation +curl http://127.0.0.1:8900/v1/chat/completions \ + -F "message=Compare these files" \ + -F "files=@chart.png" \ + -F "files=@data.xlsx" \ + -F "session_id=my-session" +``` + +Supported file types: +- **Images**: PNG, JPEG, GIF, WebP (sent to AI as base64 for vision analysis) +- **Documents**: PDF, Word (.docx), Excel (.xlsx), PowerPoint (.pptx) (text extracted and sent to AI) +- **Text**: TXT, Markdown, CSV, JSON, etc. (read directly) + +## Python (`requests`) + +```python +import requests + +resp = requests.post( + "http://127.0.0.1:8900/v1/chat/completions", + json={ + "messages": [{"role": "user", "content": "hi"}], + "session_id": "my-session", # optional: isolate conversation + }, + timeout=120, +) +resp.raise_for_status() +print(resp.json()["choices"][0]["message"]["content"]) +``` + +## Python (`openai`) + +```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"}, # optional: isolate conversation +) +print(resp.choices[0].message.content) +``` diff --git a/docs/python-sdk.md b/docs/python-sdk.md new file mode 100644 index 00000000..d035238d --- /dev/null +++ b/docs/python-sdk.md @@ -0,0 +1,31 @@ +# Python SDK + +Use nanobot as a library โ€” no CLI, no gateway, just Python: + +```python +from nanobot import Nanobot + +bot = Nanobot.from_config() +result = await bot.run("Summarize the README") +print(result.content) +``` + +Each call carries a `session_key` for conversation isolation โ€” different keys get independent history: + +```python +await bot.run("hi", session_key="user-alice") +await bot.run("hi", session_key="task-42") +``` + +Add lifecycle hooks to observe or customize the agent: + +```python +from nanobot.agent import AgentHook, AgentHookContext + +class AuditHook(AgentHook): + async def before_execute_tools(self, ctx: AgentHookContext) -> None: + for tc in ctx.tool_calls: + print(f"[tool] {tc.name}") + +result = await bot.run("Hello", hooks=[AuditHook()]) +``` diff --git a/docs/quick-start.md b/docs/quick-start.md new file mode 100644 index 00000000..7112ba8c --- /dev/null +++ b/docs/quick-start.md @@ -0,0 +1,104 @@ +# Install and Quick Start + +## Install + +> [!IMPORTANT] +> This README may describe features that are available first in the latest source code. +> If you want the newest features and experiments, install from source. +> If you want the most stable day-to-day experience, install from PyPI or with `uv`. + +**Install from source** (latest features, experimental changes may land here first; recommended for development) + +```bash +git clone https://github.com/HKUDS/nanobot.git +cd nanobot +pip install -e . +``` + +**Install with [uv](https://github.com/astral-sh/uv)** (stable release, fast) + +```bash +uv tool install nanobot-ai +``` + +**Install from PyPI** (stable release) + +```bash +pip install nanobot-ai +``` + +### Update to latest version + +**PyPI / pip** + +```bash +pip install -U nanobot-ai +nanobot --version +``` + +**uv** + +```bash +uv tool upgrade nanobot-ai +nanobot --version +``` + +**Using WhatsApp?** Rebuild the local bridge after upgrading: + +```bash +rm -rf ~/.nanobot/bridge +nanobot channels login whatsapp +``` + +## Quick Start + +> [!TIP] +> Set your API key in `~/.nanobot/config.json`. +> Get API keys: [OpenRouter](https://openrouter.ai/keys) (Global) +> +> For other LLM providers, please see [`configuration.md`](./configuration.md). +> +> For web search capability setup, please see the web-search section in [`configuration.md`](./configuration.md#web-search). + +**1. Initialize** + +```bash +nanobot onboard +``` + +Use `nanobot onboard --wizard` if you want the interactive setup wizard. + +**2. Configure** (`~/.nanobot/config.json`) + +Configure these **two parts** in your config (other options have defaults). + +*Set your API key* (e.g. OpenRouter, recommended for global users): +```json +{ + "providers": { + "openrouter": { + "apiKey": "sk-or-v1-xxx" + } + } +} +``` + +*Set your model* (optionally pin a provider โ€” defaults to auto-detection): +```json +{ + "agents": { + "defaults": { + "model": "anthropic/claude-opus-4-5", + "provider": "openrouter" + } + } +} +``` + +**3. Chat** + +```bash +nanobot agent +``` + +That's it! You have a working AI agent in 2 minutes. diff --git a/docs/WEBSOCKET.md b/docs/websocket.md similarity index 99% rename from docs/WEBSOCKET.md rename to docs/websocket.md index 1e0ddbbb..ed1652be 100644 --- a/docs/WEBSOCKET.md +++ b/docs/websocket.md @@ -42,7 +42,7 @@ nanobot gateway You should see: -``` +```text WebSocket server listening on ws://127.0.0.1:8765/ ``` @@ -68,7 +68,7 @@ asyncio.run(main()) ## Connection URL -``` +```text ws://{host}:{port}{path}?client_id={id}&token={token} ``` diff --git a/images/GitHub_README.png b/images/GitHub_README.png new file mode 100644 index 00000000..a76f36df Binary files /dev/null and b/images/GitHub_README.png differ diff --git a/images/nanobot_arch.png b/images/nanobot_arch.png new file mode 100644 index 00000000..db4a42cd Binary files /dev/null and b/images/nanobot_arch.png differ diff --git a/nanobot_logo.png b/images/nanobot_logo.png similarity index 100% rename from nanobot_logo.png rename to images/nanobot_logo.png diff --git a/nanobot_arch.png b/nanobot_arch.png deleted file mode 100644 index 09251771..00000000 Binary files a/nanobot_arch.png and /dev/null differ