nanobot

Framework

HKUDS/nanobot

Lightweight open-source AI agent runtime for tools, chats, and workflows.

Overview

nanobot is an open-source, ultra-lightweight personal AI agent runtime you can truly own. It supports WebUI, chat channels, tools, memory, MCP, model routing, automation, and deployment. The core is small and readable, making it easy to customize and extend.

README Preview

\n  \n  \n\n\n\n  \n    English |\n    简体中文 |\n    繁體中文 |\n    Español |\n    Français |\n    Bahasa Indonesia |\n    日本語 |\n    한국어 |\n    Русский |\n    Tiếng Việt\n  \n  \n    \n    \n    \n    \n    \n        \n    \n        \n    \n        \n    \n    \n    \n    \n  \n\n\n🐈 **nanobot** is an open-source, ultra-lightweight personal AI agent you can truly own. It keeps the agent core small and readable while giving you the practical pieces for real long-running work: WebUI, chat channels, tools, memory, MCP, model routing, automation, and deployment.\n\n## Start Here\n\n| You want to... | Go to |\n|---|---|\n| Install nanobot with no terminal/config background | [Start Without Technical Background](./docs/start-without-technical-background.md) |\n| Install quickly and get one CLI reply | [Install](#-install) and [Quick Start](#-quick-start) |\n| Open the bundled browser UI | [WebUI](#-webui) |\n| Connect Telegram, Discord, WeChat, Slack, Email, Mattermost, or another chat app | [Chat Apps](./docs/chat-apps.md) |\n| Configure providers, fallback models, Langfuse, MCP, web tools, or security | [Docs](./docs/README.md) and [Configuration](./docs/configuration.md) |\n| Understand or extend the internals | [Architecture](./docs/architecture.md) and [Development](./docs/development.md) |\n| Deploy to the cloud in one click | [Deploy to Render](#deploy-to-render) |\n\n## Deploy to Render\n\nDeploy nanobot's gateway and bundled WebUI as a single web service with persistent memory. Render reads [`render.yaml`](./render.yaml) and prompts for two secrets on deploy: `ANTHROPIC_API_KEY` and `NANOBOT_WEB_TOKEN` (the password that gates the public WebUI — generate a strong random value, e.g. `openssl rand -hex 32`).\n\n> **Note:** The blueprint attaches a persistent disk so sessions, memory, and WebUI history survive restarts. Persistent disks require a paid service (they are not available on Render's free tier).\n\n[](https://render.com/deploy?repo=https://github.com/HKUDS/n

FAQ (5)

Troubleshooting
How to fix WhatsApp replay filter never firing and timestamps showing far-future dates in Nanobot?

The unit mismatch occurs because neonize provides MessageInfo.Timestamp in Unix milliseconds, while time.time() and downstream metadata use Unix seconds. Normalize the timestamp immediately after reading it in nanobot/channels/whatsapp/runtime.py:

timestamp = float(_safe_attr(info, "Timestamp", 0) or 0)
if timestamp >= 1e11:  # Unix milliseconds; seconds would be past year 5000
    timestamp /= 1000.0

After this change, timestamp < self._started_at correctly filters replayed messages, and "timestamp": int(timestamp) forwards integer epoch seconds. Add tests covering old millisecond timestamps being dropped, newer ones being kept, and metadata timestamps being in seconds.

GitHub Issue #6120
Troubleshooting
How to fix nanobot webui memory leak on Windows (Python process growing to 5GB+ when idle)?

This is a known bug caused by _windows_process_identity() in nanobot/process_runtime.py. It redefined a ctypes.Structure subclass, POINTER types, and a WinDLL('kernel32') binding on every call. Because ctypes keeps these in a permanent module-level _pointer_type_cache, ~6.5 KB leaked per call (~2.6 MB/min, ~3.7 GB/day) that the garbage collector cannot reclaim. Since the webui launcher polls runtime.status() every 0.5s and the gateway polls client state every 1s, memory grew continuously even when idle.

Fix: update nanobot to a version containing PR #6507. The patch hoists the FileTime structure and POINTER type to module level and caches the kernel32 binding once with @lru_cache(maxsize=1):

class _FileTime(ctypes.Structure):
    _fields_ = [("low", ctypes.c_uint32), ("high", ctypes.c_uint32)]

_FILE_TIME_PTR = ctypes.POINTER(_FileTime)

@lru_cache(maxsize=1)
def _windows_kernel32():
    kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
    # configure argtypes/restype once here
    return kernel32

def _windows_process_identity(pid: int):
    if os.name != "nt":
        return None
    kernel32 = _windows_kernel32()
    handle = kernel32.OpenProcess(0x1000, False, pid)
    ...

If you cannot upgrade immediately, restart the webui process periodically as a temporary workaround. Linux/macOS are unaffected since the code path is Windows-only.

GitHub Issue #6506
Troubleshooting
How to fix Obsidian CLI returning "unable to find Obsidian" in a nanobot CLI App when it works in terminal?

This happens because nanobot 0.3.5 CLI Apps run with a reduced environment and discard XDG_RUNTIME_DIR; Obsidian needs it to locate the running desktop app on GNOME/Wayland. Upgrade nanobot to a release containing the fix fix(cli-apps): preserve XDG_RUNTIME_DIR for desktop CLIs. As a workaround, run the Obsidian CLI through the shell exec tool with the variable explicitly set and use the CLI launcher path:

XDG_RUNTIME_DIR=/run/user/$(id -u) /home/<user>/.local/bin/obsidian tags total

For example:

XDG_RUNTIME_DIR=/run/user/1000 /home/alice/.local/bin/obsidian tags counts

Confirm the value with:

echo "$XDG_RUNTIME_DIR"

Also ensure the gateway is started from the desktop session so it inherits XDG_RUNTIME_DIR. Using /usr/bin/obsidian may resolve to the desktop launcher instead of the user-installed CLI at ~/.local/bin/obsidian.

GitHub Issue #6024
Troubleshooting
Why does nanobot CLI crash with UnicodeDecodeError at startup on macOS when the command line ends with Chinese/CJK characters, and how to fix it?

Cause: nanobot/cli/process_identity.py unconditionally sets SPT_NOENV=1 before importing setproctitle. On macOS this makes setproctitle (<=1.3.8) use a buffer exactly the length of argv, and its off-by-one bug drops the final byte (py-setproctitle#167). If the last argument ends with a multibyte UTF-8 char (CJK/emoji), the sequence is truncated and the macOS-only getproctitle() call at import time raises UnicodeDecodeError, killing the CLI before any model call. Fix: only set SPT_NOENV on Linux (it is meaningless on macOS). Edit nanobot/cli/process_identity.py:

import sys

def _set_process_title(title: str) -> None:
    if sys.platform == "linux":
        os.environ.setdefault("SPT_NOENV", "1")
    from setproctitle import setproctitle
    setproctitle(title)

Locate the file with python -c "import nanobot.cli.process_identity as m; print(m.__file__)" inside your uv tool environment, apply the change, and the CLI starts normally with CJK arguments. Optionally wrap the import/setproctitle call in try/except so process naming can never be fatal. Linux behavior is unchanged by this patch.

GitHub Issue #6231
Troubleshooting
How to fix NANOBOT_HOME being ignored and causing Telegram conflicts with multiple Nanobot instances on Windows?

On affected Windows builds, Nanobot resolves the default config and workspace from USERPROFILE, so both instances load C:\Users\<user>\.nanobot\config.json and use the same Telegram bot token.

**Workaround:** start each instance in a separate CMD window with a different USERPROFILE:

set "USERPROFILE=C:\Nanobot\instance2"
nanobot gateway

Initialize/onboard that home first if needed, and configure a separate Telegram bot token for each instance.

A permanent --home selector is being added in #6128. Once available, use:

nanobot --home C:\Nanobot\instance2 onboard
nanobot --home C:\Nanobot\instance2 gateway

GitHub Issue #1739