系列文章:从零实现一个通用智能体
本篇主题:分层、状态所有权、执行链路与安全边界
代码版本:mx-claw 0.1.1
一次普通请求在 mx-claw 中会留下完整的执行轨迹:用户消息先写入 SQLite,模型以流式响应 返回文本或工具调用,Agent Core 校验工具参数、申请权限、执行工具并把结果交还模型,直到 模型不再请求工具或达到迭代上限。
这个过程同时涉及 CLI、Web、模型 Provider、文件系统、命令进程、权限规则、长期记忆和 Skill。只要状态没有单一归属,恢复、并发和审计很快就会互相冲突。mx-claw 的架构设计从 一开始就把不同状态放到不同层:
text交互入口 -> Agent Core -> 工具 / 模型 / 存储 \-> 权限 / Hook / 沙箱
本文先给出系统全景。后续文章分别深入 Query Loop、上下文、工具、沙箱、权限、记忆、 Skill、SQLite 和多客户端。
mx-claw 可以按职责拆成五层。
| 层次 | 当前实现 | 主要职责 |
|---|---|---|
| 入口层 | apps/cli、apps/server、apps/web | 接收输入、启动进程、连接本地 API |
| 交互层 | CLI REPL、Web 会话面板 | 展示流式消息、工具状态与审批 |
| 编排层 | AgentCore、SessionStore | 会话生命周期、恢复、事件与租约 |
| 核心循环层 | AgentCore.submitTurn() | 模型请求、工具调用、结果回填与迭代 |
| 执行与通信层 | builtin-tools、model-provider | 文件、搜索、编辑、命令和模型 API |
权限、Hook、沙箱、记忆和 Skill 不单独占用一个顺序层,而是横切到核心循环的不同节点:
text模型返回工具调用 -> schema 校验 -> PreToolUse Hook -> 权限规则与人工审批 -> 工具二次校验 -> 沙箱命令 / 工作区文件操作 -> tool result -> PostToolUse Hook -> 模型下一轮
这些横切能力共同决定一次工具调用能否执行、如何执行,以及执行后留下什么证据。
核心目录与职责如下:
textapps/ cli/ CLI 入口、REPL、命令和审批展示 server/ 本地 HTTP API、SSE 与 Web 静态资源 web/ React 面板、会话、工作区和设置 packages/ agent-core/ 会话编排、Query Loop、压缩与恢复 protocol/ 消息、事件、工具和权限协议 model-provider/ OpenAI-compatible 通信与密钥配置 builtin-tools/ 文件、搜索、编辑、命令和沙箱适配 permissions/ 规则、模式、命令分类和审计 hooks/ PreToolUse / PostToolUse storage/ SQLite、文件存储、租约、事件和检索 memory/ USER.md、MEMORY.md 与候选审核 skills/ Skill catalog、提案与自主学习 mcp/ MCP 工具客户端 plugins/ 可信插件目录 lsp/ 只读代码诊断 agent-tools/ Subagent 与 worktree 委托
apps/cli 与 apps/server 不实现第二套 Agent 逻辑。两者都实例化 AgentCore,并通过同一
套协议消费事件。差别只在交互形式和进程边界。
一个 turn 从 submitTurn() 开始。入口先取得会话的跨进程租约,然后执行以下步骤:
循环退出有两种正常方式:
maxIterations 后显式失败。取消、租约丢失、Provider 异常和消费者提前停止都会进入失败恢复路径。会话不会停留在
一个没有执行者的 running 状态。
架构能否恢复,取决于每类状态是否有明确归属。
| 状态 | 唯一归属 |
|---|---|
| 消息、事件、待审批请求、租约 | SQLite 会话库 |
| 当前进程内的活跃 turn 与等待器 | AgentCore 内存 |
| Provider 与默认模型 | config.json |
| API Key | 系统安全存储或加密文件 |
| 长期语义记忆 | memory/ 下的 Markdown 记录 |
| 程序性 Skill | skills/active/ |
| 权限规则与审计 | SQLite 权限表 |
CLI 与 Web 可以共享数据库和磁盘状态,但各自运行独立的 Core 实例。配置在进程启动后形成 快照;一个进程修改默认 Provider,不会自动改写另一个已运行进程的内存快照。
实现中的大部分复杂度都服务于五条不变量。
同一会话在同一时刻只能有一个 turn 执行。进程内使用 #activeTurns,跨进程使用 SQLite
租约。失去租约的循环会立即中止。
每个 assistant tool_call 都必须得到对应 tool result,包括参数错误、未知工具、权限拒绝
和执行异常。缺少结果的历史在发送给 Provider 前会被修复。
SQLite 保存完整 transcript。上下文压缩只改变 Provider 本轮看到的视图,不删除历史消息, 也不改写原始工具结果。
Skill 索引和长期记忆在会话首回合注入。会话进行中新增的 Skill 或记忆只影响后续新会话, 避免同一任务中的行为基准不断漂移。
批准只表示该动作允许进入执行路径。工作区路径限制、灾难命令黑名单、环境变量白名单和
平台沙箱仍然生效。allow-all 也不能越过显式 deny 和工具内部硬校验。
Agent Core 不直接更新界面。它产生带 sessionId、sequence 和 turnId 的有序事件:
ts{
type: 'tool.started',
sequence: 42,
sessionId: 'session-1',
turnId: 'turn-1',
timestamp: '2026-10-23T00:00:00.000Z'
}
CLI 将事件渲染成终端输出,HTTP 服务将事件转换成 SSE,Web 按 sequence 去重并恢复。 同一套事件协议使不同界面可以观察同一个执行过程。
/model 命令、Provider 与 API Key 存储mx-claw 的结构不是把模型、工具和界面塞进同一个对象,而是让每一层只拥有自己能够证明的 状态。Query Loop 决定下一步,工具层约束动作,存储层保存事实,权限与沙箱决定风险边界。
下一篇从 /model 命令开始,解释模型连接信息和密钥为什么不进入同一份配置。


本文作者:繁星
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!