ClawdBot(MoltBot)实现研究 (二)
现在大火的ClawdBot(MoltBot)成为一个现象级产品,网上很多关于使用和安装的视频和文章。其实我最感兴趣的是它内部的实现机制。因为我也在做微信客服机器人,先从几个基本的我感兴趣的点做了点研究。
作为AI智能体,其中最重要的两个功能是:
- 如何控制桌面,像人一样操作电脑
- 如何让智能体拥有记忆,这样不用每次都重复之前教它的
一、项目如何实现让大模型长时间操控本地电脑?它可能定义了很多本地的工具,或者是skills,它如何调用本地的工具,或者是skills,然后把结果返回给大模型?这是遵循了一种什么标准协议吗?
1. 长时间操控本地电脑是怎么实现的?
本质是一个 Agent 循环:用户发一条消息后,本地跑一轮「LLM → 工具调用 → 执行 → 结果回传 → 再调 LLM」,可以多轮重复,直到模型不再发起工具调用或达到停止条件。
- 入口:
runEmbeddedPiAgent(src/agents/pi-embedded-runner/run.ts)→runEmbeddedAttempt(src/agents/pi-embedded-runner/run/attempt.ts)。 - 会话与驱动:用 pi-coding-agent 的
createAgentSession()创建/恢复会话,把「内置工具 + 自定义工具」传进去;用户输入通过activeSession.steer(prompt)驱动这一轮。 - 工具执行与循环:
- LLM 返回的 assistant 消息里带有 tool_calls(工具名 + 参数)。
- Session 内部根据这些 tool_calls 调用对应工具的
execute(...),得到结果。 - 把结果作为 tool result 消息追加到对话历史,再请求 LLM。
- 如此重复,形成多步「思考 → 调工具 → 执行 → 再思考」,从而实现对本地电脑的长时间、多步操控(执行命令、读改文件、发消息等)。
也就是说:长时间操控 = 多轮「LLM 决策 + 本地工具执行 + 结果回传」的循环,由 pi-coding-agent 的 session 和 Moltbot 的工具实现一起完成。
2. 本地「工具」和「Skills」分别是什么?如何被调用?
Tools(可被模型直接调用的函数)
- 定义位置:
- 核心工具在
src/agents/tools/(如bash-tools、browser-tool、channel-tools、moltbot-tools等)。 - 汇总与策略在
src/agents/pi-tools.ts(createMoltbotCodingTools)。
- 核心工具在
- 形态:每个工具是一个 AgentTool(来自
@mariozechner/pi-agent-core):name,descriptionparameters:JSON Schema,描述参数execute(toolCallId, params, signal, onUpdate):真正在本地执行,返回AgentToolResult
- 如何交给模型:
- 在创建 session 时,Moltbot 把这一批工具通过 pi-tool-definition-adapter 转成 pi-coding-agent 的
ToolDefinition,传给createAgentSession({ tools: builtInTools, customTools: allCustomTools, ... })。 - pi-coding-agent / pi-ai 再把这些工具转成各厂商 API 的 function calling 格式(OpenAI / Anthropic / Google 等),随请求发给 LLM。
- 在创建 session 时,Moltbot 把这一批工具通过 pi-tool-definition-adapter 转成 pi-coding-agent 的
- 调用与回传:
- 模型在回复里返回 tool_calls(id、name、arguments)。
- Session 根据 name 找到对应
ToolDefinition.execute,在本地执行,得到结果。 - 结果被格式化成 tool result 消息,追加到对话历史,再继续请求 LLM。
插件也可以通过 api.registerTool({ name, description, parameters, execute }) 注册额外工具;是否对某 agent 开放由 tools.allow / tools.deny、tools.profile 等配置控制。
Skills(教模型「怎么用」的说明,不是可执行函数)
- 本质:AgentSkills 兼容 的说明文档(例如
SKILL.md),放在skills/、~/.clawdbot/skills、插件等目录(见docs/tools/skills.md)。 - 用途:通过
resolveSkillsPromptForRun等,把「技能列表 + 按需加载说明」拼进 system prompt,告诉模型有哪些技能、怎么用工具,不直接执行代码。 - 和 Tools 的关系:Skills 是「说明书」;真正被模型调用并执行的是 Tools 的
execute。Skills 帮助模型更正确地选择和使用这些工具。
所以:
- 调用本地能力 = 调用 Tools 的
execute,由 session 根据 LLM 的 tool_calls 自动完成。 - 把结果返回给大模型 = 把
AgentToolResult转成对话里的 tool result 消息,再发给 LLM,形成下一轮输入。
3. 遵循了什么标准/协议?
-
工具调用层(和 LLM 的约定)
- 与 OpenAI / Anthropic / Google 等厂商的 Function Calling(Tool Use) 一致:
- 请求里带
tools(name、description、parameters 的 schema); - 模型回复里带
tool_calls(id、name、arguments); - 应用执行后把 tool results 再塞回 messages,继续请求。
- 请求里带
- pi-ai(
@mariozechner/pi-ai)负责把这一套抽象成统一接口,并转换成各厂商的 API 格式;pi-agent-core 提供AgentTool/AgentToolResult等类型;pi-coding-agent 负责 session、历史与工具执行循环。 - 因此:「大模型 ↔ 本地工具」的协议 = 各厂商通用的 function calling / tool use 协议,由 pi-ai 适配到具体 API。
- 与 OpenAI / Anthropic / Google 等厂商的 Function Calling(Tool Use) 一致:
-
会话与消息结构
AgentMessage、AgentToolResult等来自 pi-agent-core,是 Moltbot 与 pi-coding-agent/pi-ai 之间的内部约定,用于在本地表示「带工具调用的多轮对话」。
-
与 MCP、ACP 的关系
- MCP(Model Context Protocol):在仓库里主要出现在 mcporter、Claude Code CLI、或禁用 MCP 配置等场景;主流程里「大模型调本地工具」并不是用 MCP 驱动,而是用上面的 function calling + pi-ai。
- ACP(Agent Client Protocol,
@agentclientprotocol/sdk):在src/acp里使用,是 IDE/客户端与 gateway 之间的通信协议(例如把sessionUpdate: "tool_call"推给前端);和「模型 ↔ 工具」的协议不是同一层。
4. 简要对照表
| 问题 | 答案 |
|---|---|
| 如何长时间操控本地电脑? | Agent 循环:用户消息 → session.steer → LLM 可能返回 tool_calls → 本地执行对应 Tool 的 execute → 结果写回对话 → 再调 LLM,循环多轮。 |
| 本地工具如何定义? | 在 src/agents/tools/ 和 pi-tools.ts 中定义为 AgentTool(name/description/parameters/execute);插件用 api.registerTool。 |
| Skills 是什么? | AgentSkills 兼容的 SKILL.md 文档,被注入 system prompt,教模型如何使用工具;不直接执行,真正执行的是 Tools。 |
| 如何调用并回传结果? | Session 根据 LLM 的 tool_calls 调用 ToolDefinition.execute,得到 AgentToolResult,转成 tool result 消息追加到历史再请求 LLM。 |
| 遵循的协议? | 工具调用:与 OpenAI/Anthropic/Google 等一致的 Function Calling / Tool Use,由 pi-ai 做厂商适配;客户端通信:ACP;主流程不用 MCP 驱动工具调用。 |
二、项目中如何实现让大模型即有长期记忆,又有短期记忆
1. 概念区分(文档里的定义)
-
Context(文档 Context):每次请求时真正发给模型的那一段内容,受模型 context window 限制。
文档原话:“Context is not the same thing as ‘memory’: memory can be stored on disk and reloaded later; context is what’s inside the model’s current window.” -
Memory(文档 Memory):以 Markdown 文件 形式持久化在 agent workspace 里,可跨会话、跨请求存在;模型通过 工具 按需读取,而不是整份塞进 context。
因此可以对应理解为:
- 短期记忆 ≈ 当前 context 里的内容(本会话的近期对话 + 摘要),受 context window 限制。
- 长期记忆 ≈ 存在磁盘上的 memory 文件,通过
memory_search/memory_get按需取回。
2. 短期记忆:怎么实现、怎么不爆窗
载体
- 会话历史写在 session transcript:
~/.clawdbot/agents//sessions/.jsonl
里面是整段对话(用户/助手消息、工具调用与结果、以及 compaction 产生的摘要条目)。
如何变成「短期」
- 每次请求不会把整份 transcript 都发给模型,而是:
- 在 context window 内放:system prompt + 一段近期历史 + 必要时的一个 compaction 摘要。
- 当历史太长、接近或超过 context 限制时:
- Compaction(Compaction、Session management & compaction):把更早的对话压缩成一条摘要写入 transcript,之后请求里只带「摘要 + 摘要之后的近期消息」,这样模型看到的仍是连贯但有限的「短期」窗口。
- Pruning(Session pruning):在单次请求前,从当前请求的 in-memory 上下文里删掉一部分旧的 tool results,减轻体积,不改写 transcript。
相关代码/配置
- Compaction 配置:
agents.defaults.compaction(如reserveTokensFloor、触发阈值等)。 - 摘要写入 transcript、以及之后从 transcript 重建「发给模型的 messages」的逻辑在 pi-coding-agent 的 session 与 Moltbot 的 compaction 流程里。
所以:短期记忆 = 当前 context 窗口内的会话内容,由 transcript 持久化 + compaction(摘要旧对话)+ pruning(裁掉部分旧 tool 结果) 共同保证「既保留历史,又不超窗」。
3. 长期记忆:怎么实现、怎么被模型用到
载体
- 纯 Markdown 文件,在 agent workspace(默认
~/clawd)下:MEMORY.md(或memory.md):持久事实、偏好、决策(长期)。memory/YYYY-MM-DD.md:按天的日志/近期记录(偏中期,但也是持久存储)。
模型如何「记得」
- 模型不会在每次请求时把整份 MEMORY 塞进 context。
- 需要时由模型主动调用工具:
memory_search(memory-tool.ts):对MEMORY.md+memory/*.md(及可选 session 来源)做语义检索,返回相关片段(path + 行范围 + 片段内容)。memory_get:按 path(及可选的 from/lines)读取某段内容。
- System prompt 里有一节 「Memory Recall」(system-prompt.ts):要求模型在回答与「过往工作、决策、日期、人、偏好、待办」相关的问题前,先对 MEMORY.md + memory/*.md 做
memory_search,再用memory_get只拉需要的那几行,以控制 context 占用。
长期记忆的写入
- 用户说「记住这个」时,模型用 write 等工具把内容写到
MEMORY.md或memory/YYYY-MM-DD.md。 - Pre-compaction memory flush(Memory):在快要触发 compaction 前,会做一次静默的 agent 轮次,用固定 prompt 提醒模型「把该留的笔记写到 memory/YYYY-MM-DD.md」,避免重要信息只在对话里、被 compaction 压成摘要后细节丢失;写完后通常用
NO_REPLY,用户看不到这轮。
检索实现(记忆索引)
- 由 memory plugin(默认
memory-core)提供:- 对上述 Markdown 做分块、embedding,写入 SQLite 索引(如
~/.clawdbot/memory/.sqlite)。 - 支持本地 / OpenAI / Gemini 等 embedding;可选 BM25 + 向量 的混合检索。
- 对上述 Markdown 做分块、embedding,写入 SQLite 索引(如
- 配置在
agents.defaults.memorySearch(enabled、provider、extraPaths、session 等)。
因此:长期记忆 = 磁盘上的 memory Markdown + 按需通过 memory_search / memory_get 取回,既不被 context 长度限制,又能被模型在需要时「想起来」。
4. 二者如何一起工作(小结)
| 维度 | 短期记忆 | 长期记忆 |
|---|---|---|
| 存什么 | 当前会话的对话 + 工具结果 + 摘要 | MEMORY.md、memory/YYYY-MM-DD.md 等 Markdown |
| 存在哪 | Session transcript(jsonl) | Workspace 下的 memory 文件 |
| 模型如何用 | 直接作为本次请求的 context 发过去 | 通过 memory_search + memory_get 按需拉取 |
| 长度限制 | 受 context window 限制 | 不受 context 限制,只受磁盘与检索条数限制 |
| 典型机制 | Compaction(摘要旧对话)、Pruning | 向量/混合检索、pre-compaction memory flush |
整体设计可以概括成:
- 短期 = 当前对话窗口内的内容(transcript + compaction/pruning 控制「窗口内」是什么)。
- 长期 = 持久化的 memory 文件 + 通过 memory 工具在需要时检索并注入到当前对话中,从而让大模型同时拥有「当前会话的短期记忆」和「跨会话的长期记忆」。










