nanobot 架构解析:从 AgentLoop 到 Provider、Channel、Tools 的源码级运行地图
【免费下载链接】nanobotUltra-lightweight, open-source, self-hosted personal AI agent framework in Python with WebUI, tools, memory, MCP, multi-agent workflows, automation, and chat apps项目地址: https://gitcode.com/gh_mirrors/nanob/nanobot
nanobot 是一个超轻量级的自托管个人 AI Agent 框架,其架构文档的核心价值在于把运行时行为精确映射到源码文件。读完本文,你将掌握 nanobot 的完整消息流转链路(Channel → MessageBus → AgentLoop → AgentRunner → Provider → Tools),理解 AgentLoop 与 AgentRunner 的职责分界、Provider 注册与自动推断机制、Channel/Tool/MCP 等扩展点的接入方式,以及配置与路径体系的默认值约定——这些内容能帮助你快速定位任意一个用户可见行为背后的源码位置。
核心消息流:从 InboundMessage 到 OutboundMessage
nanobot 的运行时可以概括为一条闭环消息流:
官方文档给出了核心文件对照表,这张表是调试内部的起点:
| 区域 | 文件 |
|---|---|
| 消息事件与队列 | nanobot/bus/events.py、nanobot/bus/queue.py |
| 回合(turn)编排 | nanobot/agent/loop.py |
| Provider/工具对话循环 | nanobot/agent/runner.py |
| 上下文构建 | nanobot/agent/context.py |
| 会话存储与压缩 | nanobot/session/manager.py |
| 长期记忆与 Dream | nanobot/agent/memory.py |
从源码看,消息总线由两个数据类和一个队列组件构成。在 nanobot/bus/events.py 中:
InboundMessage携带channel、sender_id、chat_id、content、timestamp、media、metadata等字段,并提供session_key属性——默认为f"{channel}:{chat_id}",也支持session_key_override做线程级会话覆盖;OutboundMessage携带回复内容、reply_to、media、buttons,以及用于内部运行时/UI 语义的event字段;- 源码中还定义了内部保留的 metadata 键(如
INBOUND_META_RUNTIME_CONTROL),注释明确这些键只能由受信传输层铸造,不能来自不受信客户端——这是消息边界安全的一个细节体现。
MessageBus(nanobot/bus/queue.py)负责入站/出站队列的流转。无论消息来自 CLI、WebUI、Telegram 还是 Discord,进入 AgentLoop 之后的流程完全一致。
AgentLoop 与 AgentRunner 的职责分界
这是整个架构中最关键的设计决策:一个偏"渠道侧"的编排层,一个偏"模型侧"的执行层。
AgentLoop(nanobot/agent/loop.py 中的class AgentLoop)拥有面向渠道的回合:
- 接收入站消息;
- 确定生效的 session 与 workspace 作用域;
- 构建上下文(历史、记忆、技能、渠道元数据);
- 接入 hooks、进度事件与渠道元数据;
- 发布出站消息。
AgentRunner(nanobot/agent/runner.py 中的class AgentRunner)拥有面向模型的循环:
- 向选定的 provider 发送消息;
- 处理流式 delta 与推理(reasoning)块;
- 执行工具调用;
- 把工具结果回喂给模型;
- 在产出最终答案或触及运行时限制(迭代上限等)时停止。
AgentRunner的类注释也印证了这一定位:"Run a tool-capable LLM loop without product-layer concerns."(运行一个无产品层关切的可调用工具的 LLM 循环)。从源码看,AgentRunner的执行结果封装在共享结果对象中,包含final_content、messages、tools_used、逐轮round_usages、stop_reason、provider 会话状态检查点等字段,支撑恢复(recovery)与继续注入(injection)等机制。
MCP 连接属于应用层基础设施
架构文档特别强调:MCP 连接是应用拥有的(application-owned)基础设施,而非 AgentLoop 的一部分。组合根(composition root)负责创建MCPProvider、把它的ToolRegistry共享给AgentLoop、在使用前await connect()、并在关停时保证aclose();AgentLoop 不管理这个生命周期。因此AgentLoop.from_config()要求调用方提供自己拥有的ToolRegistry——使用 MCP 的调用方需将其与自有的MCPProvider共享。相关实现可见 nanobot/agent/tools/mcp.py 中的MCPProvider与 nanobot/agent/tools/registry.py 中的ToolRegistry。
调试时的分工口诀:如果问题涉及渠道路由、session 键、workspace 选择或出站投递,从 nanobot/agent/loop.py 入手;如果涉及 provider 调用、工具调用、流式输出或迭代限制,从 nanobot/agent/runner.py 入手。
Providers:集中注册表驱动的模型后端
Provider 元数据集中在 nanobot/providers/registry.py,配置字段在 nanobot/config/schema.py。该注册表文件头部注释写得很直白——新增 provider 只需两步:
- 在
PROVIDERS中添加一个ProviderSpec; - 在
config/schema.py的ProvidersConfig中添加对应字段。
环境变量、配置匹配、状态展示都由此派生。从源码看,ProviderSpec是一个 frozen dataclass,关键字段包括:
name/keywords/env_key:配置字段名、模型名匹配关键词、API key 环境变量名;backend:决定使用哪个 provider 实现(openai_compat、anthropic、azure_openai、openai_codex、bedrock等);is_gateway/is_local/detect_by_key_prefix/detect_by_base_keyword:网关型(可路由任意模型)、本地部署、API key 前缀与 base URL 推断的标记;thinking_style、reasoning_effort_remap、supports_prompt_caching等:处理各厂商推理开关、提示缓存等协议差异;is_oauth/is_direct:OAuth 型(如 OpenAI Codex)与直连型 provider 的区分。
Provider 选择遵循以下优先级(与文档一致):
- 显式的
agents.defaults.provider或 preset 中的 provider; - provider 注册表关键词(模型名匹配);
- API key 前缀与 API base URL 提示;
- 配置了
apiBase时的本地 provider 回退; - 网关型 provider 回退(可路由多模型家族)。
Provider 实现位于nanobot/providers/:大多数托管 provider 复用 OpenAI 兼容实现,而 Anthropic、Azure OpenAI、AWS Bedrock、OpenAI Codex 与 GitHub Copilot 走专门实现路径(对应 anthropic_provider.py、azure_openai_provider.py、bedrock_provider.py、openai_codex_provider.py、github_copilot_provider.py)。实操配置可参考 providers.md 与 configuration.md#providers。
Channels:自包含包式发现
Channel 负责把外部平台翻译成InboundMessage事件,并把OutboundMessage发回平台。核心文件:
| 区域 | 文件 |
|---|---|
| 基础 Channel 契约 | nanobot/channels/base.py |
| 各 Channel 包 | nanobot/channels/<channel>/ |
| 发现与生命周期 | nanobot/channels/manager.py |
| WebSocket/WebUI 通道 | nanobot/channels/websocket/ |
从源码结构看,Channel 采用插件化发现机制:ChannelManager通过nanobot.channels.registry.discover_plugins()扫描nanobot/channels/下的自包含包,每个包导出一个ChannelPlugin描述符(定义于 nanobot/channels/plugin.py),并把运行时与可选的 setup 表面封装在同一个包内。新增 channel 只需贡献一个遵循 channel-package-guide.md 规范的包,无需改动核心代码。
WebUI 与 Gateway:长驻进程的组成
nanobot gateway启动的内容包括:
- 已启用的聊天 channel;
- 配置了 WebSocket 时的 WebSocket channel;
- workspace 作用域的 cron 服务;
- Dream、heartbeat 等系统作业;
gateway.port上的健康检查端点。
一个容易混淆的点:打包的 WebUI 由 WebSocket channel 提供,而不是健康检查端点。两者的默认地址:
| 表面 | 默认地址 |
|---|---|
| Health endpoint | http://127.0.0.1:18790/health |
| WebUI/WebSocket | http://127.0.0.1:8765 |
源码印证:nanobot/config/schema.py 中 gateway 配置的port字段默认值即为18790。WebUI 前端源码位于 webui/,生产构建输出到nanobot/web/dist/并打入 wheel。相关文档:webui.md(使用指南)、webui/README.md(前端源码开发)、websocket.md(协议细节)。
Tools:模型契约的一部分
工具从 nanobot/agent/tools/ 与插件入口点发现。重要文件对照:
| 工具区域 | 文件 |
|---|---|
| 工具基类与 schema | nanobot/agent/tools/base.py、nanobot/agent/tools/schema.py |
| 工具发现 | nanobot/agent/tools/registry.py |
| Shell 执行 | nanobot/agent/tools/shell.py |
| 文件系统工具 | nanobot/agent/tools/filesystem.py |
| Web 搜索/抓取 | nanobot/agent/tools/web.py |
| MCP 工具 | nanobot/agent/tools/mcp.py |
| Cron | nanobot/agent/tools/cron.py、nanobot/cron/ |
| 图片生成 | nanobot/agent/tools/image_generation.py |
| 运行时自省 | nanobot/agent/tools/self.py |
当前仓库的工具目录实际还包含apply_patch.py、exec_session.py、long_task.py、search.py、spawn.py(子代理)、sandbox.py、message.py等模块,可见工具面比文档表格更宽。架构文档特别警告:工具行为是模型契约的一部分——除非有意变更,应保持用户可见的工具名、schema 与错误消息稳定。
配置与路径:默认值与作用域边界
配置 schema 在 nanobot/config/schema.py,加载与保存在 nanobot/config/loader.py,运行时路径助手在 nanobot/config/paths.py。默认路径:
| 路径 | 默认值 |
|---|---|
| Config | ~/.nanobot/config.json |
| Workspace | ~/.nanobot/workspace/ |
| Sessions | <config-dir>/sessions/<workspace-id>/*.jsonl(默认~/.nanobot/sessions/...) |
| Memory | <workspace>/memory/ |
| Cron 存储 | <workspace>/cron/jobs.json |
| WebUI/media/日志运行时数据 | 配置目录下的子目录,如webui/、media/、logs/ |
源码印证:nanobot/config/paths.py 中get_workspace_path()在未指定时解析到~/.nanobot/workspace;get_runtime_subdir()负责media/、cron/、logs/、webui/等实例级运行时子目录。schema 同时接受 camelCase 与 snake_case 键,但回写磁盘时统一使用 camelCase 别名(如apiKey、modelPresets、intervalS)。
Agent 自有状态 vs 生效项目上下文
运行时区分了"配置的 agent workspace"与会话作用域携带的"生效项目 workspace"。两者常常是同一目录,但 WebUI 聊天可以选择独立项目:
| 关切 | 路径所有者 |
|---|---|
会话命名空间、SOUL.md、USER.md、memory、自定义 skills | 配置的 agent workspace |
项目AGENTS.md、相对工具路径、shell 工作目录 | 生效的项目 workspace |
| workspace 访问模式与项目元数据 | 会话的 workspace 作用域 |
ContextBuilder(nanobot/agent/context.py)把项目指令与 agent 自有的 profile、记忆合并;文件系统与搜索工具以项目为常规边界,仅对内置/agent 技能与精确的 agent 历史文件获得能力特定的只读访问。架构文档提醒:保持这些跨根(cross-root)能力只读且显式,不要将整个 agent workspace 当作允许的根目录。
Memory 与 Sessions:两级状态存储
会话历史是"近端"的对话回放,memory 是"远端"的 workspace 状态:
| 存储 | 文件区域 |
|---|---|
| 会话 JSONL 文件 | <config-dir>/sessions/<workspace-id>/ |
| 长期记忆 | <workspace>/memory/MEMORY.md |
| 整合来源历史 | <workspace>/memory/history.jsonl |
| 引导身份文件 | <workspace>/SOUL.md、<workspace>/USER.md,模板见 nanobot/templates/ |
Dream(记忆整合/梳理机制)实现在 nanobot/agent/memory.py,由运行时在启用时调度。会话的存储与压缩逻辑在 nanobot/session/manager.py。
安全边界:与安全相关的代码路径
架构文档列出了安全敏感路径,供修改工具/渠道/文件访问/网络抓取时对照:
| 边界 | 文件 |
|---|---|
| Workspace 作用域 | nanobot/security/workspace_access.py、nanobot/security/workspace_policy.py |
| Shell 沙箱 | nanobot/agent/tools/shell.py |
| SSRF/网络检查 | nanobot/security/network.py、nanobot/agent/tools/web.py |
| PTH 防护与 CLI 启动安全 | nanobot/security/ 与 CLI 入口 |
| 渠道访问控制 | 各nanobot/channels/*.py中的渠道配置 |
文档同时要求:当变更涉及工具、channel、文件访问、WebUI workspace 行为或网络抓取时,把安全当作功能行为的一部分,并在用户可见边界变化时同步更新文档。
扩展点:接入新能力的官方路径
架构文档以表格形式给出七类扩展的标准做法:
| 扩展 | 方式 |
|---|---|
| Provider | 在 providers/registry.py 添加ProviderSpec,在 config/schema.py 添加 schema 字段;仅在通用后端不够时才实现新 provider |
| Channel | 导出ChannelPlugin描述符,运行时与 setup 表面放在一个包内,遵循 channel-package-guide.md |
| Tool | 在agent/tools/下实现工具,或暴露插件入口点 |
| Agent Plugin | 在<workspace>/plugins/下添加 v1 包并从 Apps 启用 |
| MCP | 添加tools.mcpServers配置,或在 Agent Plugin 中捆绑 server |
| Skill | 添加<workspace>/skills/下的 workspace 技能、在 Agent Plugin 中捆绑,或在 nanobot/skills/ 添加内置技能 |
| CLI App | 加入 CLI Apps 目录;安装器负责可执行文件生命周期并写出一个 skills-only 的 Agent Plugin |
总原则是:优先复用已有的注册/发现模式,避免临时接线(ad hoc wiring)。
测试与验证:按变更面选择最小验证
常用检查命令:
pytest tests/test_openai_api.py::test_function -v ruff check nanobot/ cd webui && bun run test cd webui && bun run build按变更面选择最小验证:
| 变更 | 最小有效验证 |
|---|---|
| Provider 行为 | Provider 单元测试或 mock API 路径;条件允许时用安全配置跑nanobot agent -m "Hello!" |
| Channel 行为 | Channel 测试 +nanobot gateway启动路径 |
| WebUI 行为 | WebUI 测试/构建;路由/设置/聊天变更需通过 gateway 做浏览器级验证 |
| 工具行为 | 工具单元测试;schema 或面向模型行为变化时补充 agent 运行路径 |
| 文档 | 链接检查、命令与 CLI/schema 的一致性核对、git diff --check |
对面向用户的流程,架构文档建议至少走一条用户实际接触的公共表面:CLI 命令、HTTP 端点、WebSocket/WebUI、聊天渠道或打包导入。
小结
nanobot 的架构可以浓缩为三句话:一条"Channel → MessageBus → AgentLoop → AgentRunner → Provider/Tools → 回写"的消息闭环;一组按"元数据集中在注册表 + 自包含包发现"组织的扩展点(Provider、Channel、Tool、Skill、Plugin、MCP);以及一套围绕~/.nanobot/配置目录与 workspace 的清晰路径边界。这份源码映射文档配合 concepts.md 的产品级心智模型、configuration.md 的参数参考,构成了理解、调试乃至扩展 nanobot 的完整入口。
【免费下载链接】nanobotUltra-lightweight, open-source, self-hosted personal AI agent framework in Python with WebUI, tools, memory, MCP, multi-agent workflows, automation, and chat apps项目地址: https://gitcode.com/gh_mirrors/nanob/nanobot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考