nanobot
FrameworkHKUDS/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)
TroubleshootingHow 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.
TroubleshootingHow 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.
TroubleshootingHow 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.
TroubleshootingWhy 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.
TroubleshootingHow 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 onboardnanobot --home C:\Nanobot\instance2 gateway