nanobot 是香港大學 HKUDS 團隊開源的超輕量級個人 AI 助手,宣稱只用約 3,400 行核心程式碼,就實現了 Claude Code(Clawdbot)430k+ 行程式碼中 99% 的核心功能。這個專案的價值不只在於「小」,更在於它用極簡的設計揭示了一個 AI Agent 框架的本質結構:Message Bus 解耦通訊、Tool Registry 動態擴展、Subagent 背景任務、Skills 漸進式載入,以及 Heartbeat 主動喚醒機制。讀完這份原始碼,你能學到如何從零設計一個可擴展的 Agent 架構,而不被複雜性淹沒。
nanobot GitHub Repository — 專案原始碼,包含本文解讀的所有設計實作
專案概覽
nanobot 是一個 Python 3.11+ 的 AI Agent 框架,核心定位是「個人 AI 助手」。它支援多家 LLM Provider(OpenRouter、Anthropic、OpenAI、DeepSeek、Gemini、vLLM 等),可透過 Telegram、Discord、WhatsApp、飛書等通訊渠道與使用者互動,內建檔案操作、Shell 執行、網頁搜尋、排程任務、記憶系統等工具。技術棧:Python + asyncio + Pydantic + LiteLLM + Typer CLI,使用 hatchling 打包。
架構總覽
nanobot/
├── agent/ # 核心 Agent 邏輯
│ ├── loop.py # Agent 主迴圈(LLM ↔ Tool 執行)
│ ├── context.py # Prompt 組裝器(System Prompt Builder)
│ ├── memory.py # 持久化記憶系統
│ ├── skills.py # Skills 載入與漸進式披露
│ ├── subagent.py # 背景子代理管理
│ └── tools/ # 工具系統
│ ├── base.py # Tool 抽象基類(含參數驗證)
│ ├── registry.py # Tool 註冊表
│ ├── filesystem.py # 檔案工具
│ ├── shell.py # Shell 執行(含安全防護)
│ ├── web.py # 網頁搜尋與抓取
│ ├── spawn.py # Subagent 啟動工具
│ └── message.py # 訊息發送工具
├── bus/ # 訊息匯流排
│ ├── events.py # InboundMessage / OutboundMessage
│ └── queue.py # Async Queue + Pub/Sub
├── channels/ # 通訊渠道
│ ├── base.py # Channel 抽象基類
│ ├── manager.py # Channel 管理器
│ ├── telegram.py # Telegram 實作
│ ├── discord.py # Discord 實作
│ ├── whatsapp.py # WhatsApp 實作
│ └── feishu.py # 飛書實作
├── providers/ # LLM Provider 層
│ ├── base.py # LLMProvider 抽象介面
│ └── litellm_provider.py # LiteLLM 統一適配
├── config/ # 設定系統
│ ├── schema.py # Pydantic Schema
│ └── loader.py # JSON 載入 + camelCase 轉換
├── session/ # Session 管理
│ └── manager.py # JSONL 持久化對話歷史
├── cron/ # 排程任務
│ └── service.py # Cron/Interval/One-shot 排程引擎
├── heartbeat/ # 心跳喚醒
│ └── service.py # 定期喚醒 Agent 檢查任務
├── skills/ # 內建 Skills
│ ├── github/SKILL.md # GitHub CLI 操作指引
│ ├── skill-creator/ # Skill 建立指南
│ └── ...
├── cli/ # CLI 入口
│ └── commands.py # Typer CLI 命令定義
└── workspace/ # Workspace 模板
├── AGENTS.md # Agent 行為指引
├── SOUL.md # Agent 人格設定
├── USER.md # 使用者資訊模板
├── TOOLS.md # 工具使用說明
└── HEARTBEAT.md # 心跳任務清單
Code language: PHP (php)這個架構的核心設計哲學是「分層解耦」:Channel 層負責收訊息,Bus 層負責路由,Agent 層負責思考和執行,Provider 層負責呼叫 LLM。每一層都可以獨立替換。
核心設計解析
設計一:Message Bus — 用 asyncio Queue 實現 Channel 與 Agent 的完全解耦
nanobot 最核心的架構決策之一,是在 Channel(通訊渠道)和 Agent(核心邏輯)之間放了一個 Message Bus。這個設計讓 Agent 完全不需要知道訊息來自 Telegram 還是 WhatsApp,Channel 也不需要知道 Agent 怎麼處理訊息。
nanobot/bus/queue.py — Message Bus 實作,用兩個 asyncio.Queue 實現雙向通訊
class MessageBus:
def __init__(self):
self.inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()
self.outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()
self._outbound_subscribers: dict[str, list[Callable[[OutboundMessage], Awaitable[None]]]] = {}
Bus 的設計極其精簡:一個 inbound Queue 負責接收所有 Channel 的訊息,一個 outbound Queue 負責發送回覆。出站訊息支援 Pub/Sub 模式 —— Channel 透過 subscribe_outbound 訂閱自己 channel 名稱的訊息,dispatch_outbound 方法會根據 msg.channel 欄位自動路由到正確的 Channel。
nanobot/bus/events.py — 訊息事件定義,使用 dataclass 保持極簡
@dataclass
class InboundMessage:
channel: str # telegram, discord, whatsapp
sender_id: str
chat_id: str
content: str
timestamp: datetime = field(default_factory=datetime.now)
media: list[str] = field(default_factory=list)
@property
def session_key(self) -> str:
return f"{self.channel}:{self.chat_id}"
Code language: CSS (css)注意 session_key 的設計 —— 用 channel:chat_id 組合作為 Session 的唯一識別。這意味著同一個使用者在 Telegram 和 WhatsApp 會有不同的 Session,這是有意為之的設計選擇:每個通訊渠道維護獨立的對話上下文。
設計二:Agent Loop — 經典的 LLM ↔ Tool 迴圈
Agent 的核心是一個 while 迴圈:呼叫 LLM → 如果有 tool call 就執行 → 把結果餵回 LLM → 重複直到 LLM 純文字回覆。這是 ReAct 模式的直接實作。
nanobot/agent/loop.py — Agent 主迴圈,整個框架的心臟
while iteration < self.max_iterations:
iteration += 1
response = await self.provider.chat(
messages=messages,
tools=self.tools.get_definitions(),
model=self.model
)
if response.has_tool_calls:
messages = self.context.add_assistant_message(
messages, response.content, tool_call_dicts
)
for tool_call in response.tool_calls:
result = await self.tools.execute(tool_call.name, tool_call.arguments)
messages = self.context.add_tool_result(
messages, tool_call.id, tool_call.name, result
)
else:
final_content = response.content
break
Code language: PHP (php)關鍵設計決策:
max_iterations限制:預設 20 次迭代上限,防止 Agent 無限迴圈。這是一個簡單但必要的安全閥。- Session 持久化只保存最終結果:只有迴圈結束後的 user input 和 final response 才會寫入 Session,中間的 tool call 過程不保存。這大幅節省了 Session 檔案的大小。
- System message 路由機制:當
msg.channel == "system"時,走不同的處理邏輯。Subagent 完成後的回報就是透過這個管道,chat_id 格式為"original_channel:original_chat_id",實現了跨 channel 的結果回傳。
設計三:Tool 系統 — 抽象基類 + Registry 動態註冊
Tool 系統的設計遵循了「定義與執行分離」的原則。每個 Tool 需要實作四個元素:name、description、parameters(JSON Schema)、execute 方法。Registry 負責統一管理和呼叫。
nanobot/agent/tools/base.py — Tool 抽象基類,包含內建的 JSON Schema 參數驗證
class Tool(ABC):
_TYPE_MAP = {
"string": str, "integer": int, "number": (int, float),
"boolean": bool, "array": list, "object": dict,
}
@property
@abstractmethod
def name(self) -> str: ...
@property
@abstractmethod
def parameters(self) -> dict[str, Any]: ...
@abstractmethod
async def execute(self, **kwargs: Any) -> str: ...
def validate_params(self, params: dict[str, Any]) -> list[str]:
"""Validate tool parameters against JSON schema."""
...
def to_schema(self) -> dict[str, Any]:
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": self.parameters,
}
}
亮點在於 validate_params 方法 —— Tool 基類自帶了一個遞歸的 JSON Schema 驗證器,在執行前先檢查 LLM 回傳的參數是否合法。這避免了在每個 Tool 實作中重複寫驗證邏輯。而 to_schema 方法直接輸出 OpenAI function calling 格式,讓 Tool 的定義和 LLM API 規格無縫對接。
nanobot/agent/tools/registry.py — Tool Registry,用 dict 管理所有已註冊的工具
class ToolRegistry:
def __init__(self):
self._tools: dict[str, Tool] = {}
def register(self, tool: Tool) -> None:
self._tools[tool.name] = tool
async def execute(self, name: str, params: dict[str, Any]) -> str:
tool = self._tools.get(name)
if not tool:
return f"Error: Tool '{name}' not found"
errors = tool.validate_params(params)
if errors:
return f"Error: Invalid parameters: " + "; ".join(errors)
return await tool.execute(**params)
Registry 的 execute 方法把「查找 → 驗證 → 執行 → 錯誤處理」統一封裝,Agent Loop 只需要呼叫 self.tools.execute(name, args) 一行程式碼。錯誤不拋異常而是回傳錯誤字串,讓 LLM 看到錯誤訊息後可以自我修正。
設計四:Shell 安全防護 — Deny Pattern 黑名單機制
賦予 LLM 執行 Shell 命令的能力是一把雙面刃。nanobot 用 regex deny pattern 實現了一層安全防護。
nanobot/agent/tools/shell.py — Shell 執行工具,包含危險指令攔截
class ExecTool(Tool):
def __init__(self, ...):
self.deny_patterns = deny_patterns or [
r"\brm\s+-[rf]{1,2}\b", # rm -r, rm -rf
r"\b(format|mkfs|diskpart)\b", # disk operations
r"\bdd\s+if=", # dd
r"\b(shutdown|reboot|poweroff)\b", # system power
r":\(\)\s*\{.*\};\s*:", # fork bomb
]
def _guard_command(self, command: str, cwd: str) -> str | None:
for pattern in self.deny_patterns:
if re.search(pattern, lower):
return "Error: Command blocked by safety guard"
設計特點:
- 黑名單而非白名單:預設允許所有命令,只攔截已知危險操作。這符合「個人助手」的定位 —— 使用者需要靈活性。
- 可選的 workspace 路徑限制:
restrict_to_workspace選項開啟後會攔截路徑穿越(../)和 workspace 外的路徑。 - 輸出截斷:最長 10,000 字元,防止大量輸出塞滿 LLM context window。
- Timeout 保護:預設 60 秒超時,避免 hang 住整個系統。
設計五:Context Builder — 模組化的 System Prompt 組裝
nanobot 不是把 System Prompt 寫死在程式碼裡,而是從多個來源動態組裝。
nanobot/agent/context.py — Prompt 組裝器,從 workspace 檔案動態建構 System Prompt
class ContextBuilder:
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md", "IDENTITY.md"]
def build_system_prompt(self, skill_names: list[str] | None = None) -> str:
parts = []
parts.append(self._get_identity()) # 核心身份
bootstrap = self._load_bootstrap_files() # Workspace 設定檔
if bootstrap:
parts.append(bootstrap)
memory = self.memory.get_memory_context() # 記憶
if memory:
parts.append(f"# Memory\n\n{memory}")
# Skills...
return "\n\n---\n\n".join(parts)
System Prompt 由五層組裝而成:
- Identity:硬編碼的核心身份(包含時間、系統資訊、workspace 路徑)
- Bootstrap Files:從 workspace 目錄讀取
AGENTS.md、SOUL.md、USER.md等設定檔 - Memory:長期記憶(
MEMORY.md)和當日筆記 - Always-on Skills:標記為
always=true的 Skill 全文載入 - Skills Summary:其他可用 Skills 只載入摘要,需要時再用
read_file讀取
這個設計讓使用者不需要改程式碼,只要編輯 workspace 裡的 Markdown 檔案,就能自訂 Agent 的行為、人格和知識。
設計六:Subagent — 輕量級背景任務架構
nanobot 支援主 Agent 透過 spawn 工具啟動子代理(Subagent)來處理耗時任務。Subagent 的設計巧妙地平衡了能力和安全性。
nanobot/agent/subagent.py — Subagent 管理器,用 asyncio.Task 實現背景執行
class SubagentManager:
async def spawn(self, task: str, label: str | None = None, ...) -> str:
task_id = str(uuid.uuid4())[:8]
bg_task = asyncio.create_task(
self._run_subagent(task_id, task, display_label, origin)
)
self._running_tasks[task_id] = bg_task
bg_task.add_done_callback(lambda _: self._running_tasks.pop(task_id, None))
return f"Subagent [{display_label}] started (id: {task_id})."
Subagent 的權限被刻意限縮:
- 可以:讀寫檔案、執行 Shell、搜尋網頁
- 不可以:發送訊息給使用者(沒有 message tool)、再 spawn 子代理(沒有 spawn tool)
完成後的結果透過 Message Bus 的 system channel 回報,chat_id 編碼為 "origin_channel:origin_chat_id" 格式,讓主 Agent 知道要把結果發送到哪裡:
async def _announce_result(self, ...):
msg = InboundMessage(
channel="system",
sender_id="subagent",
chat_id=f"{origin['channel']}:{origin['chat_id']}",
content=announce_content,
)
await self.bus.publish_inbound(msg)
Code language: JavaScript (javascript)設計七:Skills 漸進式載入 — 三層 Progressive Disclosure
Skills 系統的設計解決了一個核心矛盾:Agent 需要知道很多能力,但 LLM 的 context window 有限。
nanobot/agent/skills.py — Skills 載入器,實現三層漸進式載入
class SkillsLoader:
def build_skills_summary(self) -> str:
lines = ["<skills>"]
for s in all_skills:
lines.append(f" <skill available=\"{str(available).lower()}\">")
lines.append(f" <name>{name}</name>")
lines.append(f" <description>{desc}</description>")
lines.append(f" <location>{path}</location>")
lines.append(f" </skill>")
lines.append("</skills>")
return "\n".join(lines)
Code language: HTML, XML (xml)三層載入策略:
| 層級 | 何時載入 | Token 成本 | 內容 |
|---|---|---|---|
| L1: Metadata | 永遠載入 | ~100 tokens/skill | 名稱 + 描述 |
| L2: SKILL.md | Agent 判斷需要時 | ~500-2000 tokens | 完整操作指引 |
| L3: Resources | 具體操作時 | 不限 | 腳本、參考文件、資源 |
Skills 還有一個精巧的設計 —— 需求檢查:每個 Skill 可以在 metadata 中宣告需要的 binary(如 gh CLI)和環境變數。載入器會在啟動時檢查這些依賴是否存在,不符合的 Skill 會標記為 available="false" 並提示缺少什麼。
def _check_requirements(self, skill_meta: dict) -> bool:
requires = skill_meta.get("requires", {})
for b in requires.get("bins", []):
if not shutil.which(b):
return False
for env in requires.get("env", []):
if not os.environ.get(env):
return False
return True
Code language: PHP (php)設計八:LLM Provider 抽象 — 用 LiteLLM 實現多 Provider 統一介面
nanobot 沒有自己寫各家 LLM 的 API 對接,而是站在 LiteLLM 肩膀上,加了一層薄包裝。
nanobot/providers/base.py — LLM Provider 抽象介面,定義統一的 chat 方法
@dataclass
class LLMResponse:
content: str | None
tool_calls: list[ToolCallRequest] = field(default_factory=list)
finish_reason: str = "stop"
usage: dict[str, int] = field(default_factory=dict)
@property
def has_tool_calls(self) -> bool:
return len(self.tool_calls) > 0
class LLMProvider(ABC):
@abstractmethod
async def chat(self, messages, tools, model, ...) -> LLMResponse: ...
Code language: CSS (css)nanobot/providers/litellm_provider.py — LiteLLM 實作,處理多 Provider 的 model prefix 邏輯
LiteLLM Provider 的核心工作是處理各家 Provider 的 model 名稱前綴:
if self.is_openrouter and not model.startswith("openrouter/"):
model = f"openrouter/{model}"
if self.is_vllm:
model = f"hosted_vllm/{model}"
if "gemini" in model.lower() and not model.startswith("gemini/"):
model = f"gemini/{model}"
Code language: PHP (php)以及將 LLM 回傳的錯誤包裝成 content 而不是拋異常,讓 Agent 可以 graceful 地處理 LLM 錯誤:
except Exception as e:
return LLMResponse(
content=f"Error calling LLM: {str(e)}",
finish_reason="error",
)
Code language: PHP (php)設計九:Channel 抽象 — 統一的聊天平台整合模式
所有聊天平台都遵循同一個抽象:start(開始監聽)、stop(停止)、send(發送訊息),以及一個內建的 _handle_message 方法做權限檢查和訊息路由。
nanobot/channels/base.py — Channel 抽象基類,定義聊天平台的統一介面
class BaseChannel(ABC):
def is_allowed(self, sender_id: str) -> bool:
allow_list = getattr(self.config, "allow_from", [])
if not allow_list:
return True # 空列表 = 允許所有人
return str(sender_id) in allow_list
async def _handle_message(self, sender_id, chat_id, content, ...):
if not self.is_allowed(sender_id):
return
msg = InboundMessage(channel=self.name, ...)
await self.bus.publish_inbound(msg)
is_allowed 的白名單設計是安全考量 —— 這是個「個人助手」,不應該讓陌生人使用。空列表表示「允許所有人」的設計,讓使用者在測試階段可以快速上手。
nanobot/channels/manager.py — Channel 管理器,按需初始化和路由訊息
Channel Manager 用了 lazy import 模式 —— 只在 config 中啟用某個 channel 時才 import 對應的模組。這避免了未安裝某個 SDK 時整個系統無法啟動的問題。
設計十:Heartbeat — 主動喚醒機制讓 Agent 不只被動回應
大多數 AI 助手只能被動回應使用者訊息,nanobot 的 Heartbeat 機制讓 Agent 可以主動「醒來」檢查任務。
nanobot/heartbeat/service.py — 心跳服務,定期喚醒 Agent 執行 HEARTBEAT.md 中的任務
HEARTBEAT_PROMPT = """Read HEARTBEAT.md in your workspace (if it exists).
Follow any instructions or tasks listed there.
If nothing needs attention, reply with just: HEARTBEAT_OK"""
class HeartbeatService:
async def _tick(self) -> None:
content = self._read_heartbeat_file()
if _is_heartbeat_empty(content):
return # 沒任務就跳過
response = await self.on_heartbeat(HEARTBEAT_PROMPT)
if HEARTBEAT_OK_TOKEN in response.upper():
logger.info("Heartbeat: OK (no action needed)")
設計巧思:
- 先檢查檔案是否有實質內容:
_is_heartbeat_empty會過濾掉空行、標題、HTML 註解,只有真正有待辦事項時才喚醒 Agent,避免浪費 LLM token。 - HEARTBEAT_OK Token:Agent 判斷沒事做時回覆
HEARTBEAT_OK,系統據此決定是否記錄日誌。 - 純 Markdown 驅動:使用者只需要編輯
HEARTBEAT.md就能新增或移除週期性任務,不需要寫任何程式碼。
Prompt 設計解讀
nanobot 的 prompt 系統採用「Workspace-as-Prompt」的理念——把 System Prompt 拆成多個 Markdown 檔案,每個檔案負責不同面向的人格和行為定義。
workspace/SOUL.md — Agent 人格設定,使用者可以自訂 AI 的個性和價值觀
# Soul
I am nanobot 🐈, a personal AI assistant.
## Personality
- Helpful and friendly
- Concise and to the point
## Values
- Accuracy over speed
- User privacy and safety
- Transparency in actions
Code language: PHP (php)workspace/AGENTS.md — Agent 行為指引,定義工具使用規範和記憶策略
workspace/USER.md — 使用者資訊模板,Agent 會根據這些資訊個性化回應
這個設計的精妙之處在於:使用者不需要理解 prompt engineering,只需要填寫一份「個人檔案」。USER.md 模板設計了時區、語言偏好、技術水準、工作背景等欄位,Agent 會自動將這些資訊融入回應中。而 SOUL.md 讓進階使用者可以完全重塑 Agent 的人格。
設計理念萃取
- Message Bus 解耦是多 Channel 系統的根基 — 只要用一個 async Queue 就能讓 N 個 Channel 和 Agent 之間完全解耦。新增一個 Channel 不需要改動 Agent 的任何程式碼,反之亦然。這個模式適用於任何需要多輸入源的系統。
- Tool 系統 = 抽象基類 + Registry + 自動驗證 — 把「Tool 定義」(name/description/parameters)和「Tool 執行」(execute)統一在一個抽象類別中,Registry 負責查找和錯誤處理。這套模式讓新增工具只需要建一個新 class,不用改任何已有程式碼。
- 「錯誤當成回傳值」比拋異常更適合 Agent 系統 — nanobot 在 Tool 執行和 LLM 呼叫中都採用「錯誤回傳為字串」而非拋異常的策略。這讓 LLM 能看到錯誤訊息並嘗試自我修正,比直接 crash 更有彈性。
- Workspace-as-Prompt 讓非工程師也能自訂 Agent 行為 — 把 System Prompt 拆成多個 Markdown 檔案放在 workspace 中,使用者編輯檔案就能改變 Agent 的行為,不需要改程式碼。這是「設定優於編碼」原則在 AI Agent 領域的極致體現。
- 漸進式載入是 LLM 資源管理的關鍵模式 — Skills 的三層載入策略(Metadata → SKILL.md → Resources)解決了「能力多但 context window 有限」的矛盾。這個模式可以推廣到任何需要在有限 context 中管理大量知識的場景。
延伸思考
nanobot 的架構雖然只有 3,400 行,但它展示了一個「完整」AI Agent 框架需要的所有核心組件。如果你正在建構自己的 Agent 系統,可以考慮以下應用:
- 先建 Message Bus,再建其他一切。即使你一開始只有一個 Channel(比如 CLI),Bus 的存在也讓未來擴展變得零成本。
- Tool Registry 模式可以直接複製。抽象基類 + dict registry + 自動 JSON Schema 驗證,這套模式不到 100 行程式碼就能實現,卻能支撐整個 Tool 生態。
- Heartbeat 機制值得加入任何長駐型 Agent。讓 Agent 有能力主動檢查待辦事項、發出提醒,是從「工具」進化到「助手」的關鍵一步。
- 用 Markdown 檔案作為設定介面。比起 JSON/YAML 設定檔,Markdown 檔案對使用者更友好,而且可以包含富文本說明,讓「設定」本身成為一份指南。
Vibe Coding:把觀念變成程式碼
以下是你可以直接給 AI 的 prompt 指令,將本文介紹的設計觀念應用到你的專案中:
Message Bus 解耦通訊層
場景:你正在建構一個需要支援多個輸入來源的系統(例如同時支援 API、WebSocket、CLI),而且希望未來能輕鬆新增通道而不改動核心邏輯。
給 AI 的指令:
請幫我在專案中實作一個 Message Bus 模式,用 Python asyncio.Queue 實現雙向通訊。需要:(1) 一個 InboundMessage dataclass 包含 channel、sender_id、content 欄位,(2) 一個 MessageBus 類別包含 inbound 和 outbound 兩個 asyncio.Queue,outbound 支援 Pub/Sub 訂閱機制讓不同 channel 只接收屬於自己的訊息,(3) Channel 抽象基類定義 start/stop/send 介面。目標是讓 Channel 層和核心處理邏輯完全解耦,新增 Channel 不需要改動任何已有程式碼。請參考 nanobot 的 Message Bus 設計。
效果:AI 會產出一組完整的 Message Bus 基礎設施程式碼,包含事件定義、Bus 實作、Channel 抽象基類,以及一個範例 Channel 實作展示如何串接。
Tool Registry 動態註冊與自動驗證
場景:你正在為 AI Agent 建構工具系統,希望新增工具時只需要建一個新 class,不用改動其他程式碼。
給 AI 的指令:
請幫我實作一個 Tool Registry 系統,參考 nanobot 的「抽象基類 + Registry + 自動驗證」模式。需要:(1) Tool 抽象基類,定義 name、description、parameters(JSON Schema 格式)屬性和 async execute 方法,基類內建 validate_params 方法根據 JSON Schema 自動驗證 LLM 回傳的參數,以及 to_schema 方法輸出 OpenAI function calling 格式,(2) ToolRegistry 類別用 dict 管理工具,execute 方法統一處理「查找 → 驗證 → 執行 → 錯誤處理」,錯誤回傳字串而非拋異常讓 LLM 能自我修正,(3) 一個具體 Tool 實作作為範例。
效果:AI 會產出一套可直接使用的 Tool 系統,包含基類、Registry、範例工具。新增工具只需繼承基類並 register,完全符合開放封閉原則。
錯誤回傳字串而非拋異常
場景:你在 Code Review 一個 AI Agent 專案,發現 Tool 執行錯誤時直接拋異常導致 Agent 中斷,想改成更有彈性的錯誤處理策略。
給 AI 的指令:
請重構我專案中的 Tool 執行錯誤處理機制,採用 nanobot 的「錯誤當成回傳值」策略。目前 Tool 執行失敗時會拋異常導致整個 Agent Loop 中斷。請改成:(1) Tool 的 execute 方法在內部 catch 所有異常,回傳格式化的錯誤字串如 “Error: [錯誤描述]”,(2) LLM Provider 呼叫失敗時也回傳包含錯誤訊息的 LLMResponse 物件而非拋異常,(3) 確保 Agent Loop 不會因為單一 Tool 失敗就停止,而是讓 LLM 看到錯誤訊息後能嘗試替代方案或自我修正。這個模式的核心理念是:在 Agent 系統中,LLM 本身就是最好的錯誤處理器。
效果:AI 會逐一檢查 Tool 和 Provider 的錯誤處理邏輯,將 raise Exception 改為 return error string,並確保 Agent Loop 的容錯性。
System Prompt 模組化組裝(Workspace-as-Prompt)
場景:你的 Agent 的 System Prompt 越來越長且難以維護,想把它拆成多個可管理的模組。
給 AI 的指令:
請幫我重構 Agent 的 System Prompt 系統,採用 nanobot 的「Workspace-as-Prompt」模式——把 System Prompt 拆成多個 Markdown 檔案動態組裝。需要一個 ContextBuilder 類別,從指定目錄讀取以下檔案來組裝 System Prompt:(1) IDENTITY.md — 核心身份與能力描述,(2) RULES.md — 行為規範與限制,(3) USER.md — 使用者偏好與背景資訊,(4) MEMORY.md — 長期記憶。組裝時用 “—” 分隔各區塊,檔案不存在就跳過。這樣使用者編輯 Markdown 檔案就能自訂 Agent 行為,不需要改程式碼。
效果:AI 會建立一個 ContextBuilder 和對應的 workspace 目錄結構,包含各模板檔案和讀取邏輯,讓 System Prompt 變成可維護的模組化結構。
Skills 漸進式載入節省 Context Window
場景:你的 Agent 支援越來越多功能,但把所有 Skill 說明都塞進 System Prompt 會佔用太多 token,影響實際對話品質。
給 AI 的指令:
請幫我實作一個 Skills 漸進式載入系統,參考 nanobot 的三層 Progressive Disclosure 設計。三層策略為:L1 Metadata(永遠載入,每個 Skill 約 100 tokens,只含名稱和描述摘要),L2 SKILL.md(Agent 判斷需要時才載入,包含完整操作指引),L3 Resources(具體操作時才載入的腳本和參考資料)。需要:(1) 每個 Skill 目錄包含 metadata.json(名稱、描述、依賴檢查)和 SKILL.md,(2) SkillsLoader 類別啟動時掃描所有 Skill 目錄,檢查依賴是否滿足(如特定 CLI 工具是否安裝),(3) build_skills_summary 方法產出 L1 摘要嵌入 System Prompt,(4) Agent 需要某個 Skill 時用 read_file 工具讀取 L2 內容。
效果:AI 會建立完整的 Skills 目錄結構、Loader 邏輯和依賴檢查機制,讓你的 Agent 在有限的 context window 中管理大量功能而不浪費 token。
Heartbeat 主動喚醒機制
場景:你有一個長駐型 AI Agent,目前只能被動回應使用者訊息,想讓它能定期主動檢查待辦事項並執行任務。
給 AI 的指令:
請幫我為現有的 Agent 加入 Heartbeat 主動喚醒機制,參考 nanobot 的設計。需要:(1) HeartbeatService 類別,用 asyncio 定時器按設定間隔執行心跳,(2) 每次心跳先讀取 HEARTBEAT.md 檔案,過濾空行和純標題,只有存在實質待辦事項時才喚醒 Agent,(3) 喚醒時發送固定 prompt 請 Agent 讀取並執行 HEARTBEAT.md 中的任務,(4) Agent 判斷沒事做時回覆特定 token(如 HEARTBEAT_OK),系統據此記錄日誌。關鍵是先檢查再喚醒,避免無謂的 LLM 呼叫浪費 token。
效果:AI 會產出一個完整的 Heartbeat 服務,包含定時器、檔案檢查邏輯、Agent 喚醒流程,讓你的 Agent 從被動工具進化為主動助手。
延伸閱讀
- nanobot GitHub Repository — 專案原始碼與完整文件,包含安裝指南、架構說明,以及所有本文解讀的設計實作
- Building Effective AI Agents — Anthropic — Anthropic 官方的 Agent 設計指南,涵蓋 Workflow vs Agent 的選擇、工具使用模式、多代理架構等,是理解 AI Agent 設計哲學的必讀文件
- A Simple Python Implementation of the ReAct Pattern for LLMs — Simon Willison — 用不到 100 行 Python 實作 ReAct 模式,清楚展示了 nanobot Agent Loop 背後的核心概念
- From 150K to 2K Tokens: How Progressive Context Loading Revolutionizes LLM Development Workflows — 深入解析漸進式 Context 載入如何將 token 使用量降低 98%,與 nanobot 的 Skills 三層載入策略理念相同
- Nanobot: Ultra-Lightweight Personal AI Assistant — Hacker News 討論 — 社群對 nanobot 架構設計的討論,包含與其他 Agent 框架的比較觀點