WorkBuddy 技术方案
说明:本文两部分来源不同 —— 产品定位引自官方文档;实现机制来自对本机安装目录
~/.workbuddy/的实测反推(目录结构、配置字段、日志 schema)。 后者是"可观测到的真实实现",前者是"官方承诺的能力边界"。二者交叉印证,未观测到的部分标注为推断。
1. 定位:从"对话"到"执行"
| 维度 | 传统 AI 对话 | WorkBuddy |
|---|---|---|
| 能力边界 | 只能对话、给建议 | 实际执行任务 |
| 文件操作 | 用户手动 | 授权后自动操作本地文件 |
| 任务粒度 | 单步骤简单任务 | 多步骤复杂任务 |
| 输出形态 | 文字回复 | 可验收的产物(文档/表格/PPT/网站) |
一句话概括技术目标:把"模型输出文本"变成"模型驱动工具链操作真实文件系统,并交付可验收成果"。这个转变带来了四个必须解决的工程问题,也就构成了下面四层的由来:
- 意图 → 多步执行计划的分解(编排层)
- 模型能与真实世界交互的接口(能力层)
- 高风险操作的约束与可追溯(执行与安全层)
- 长任务的上下文与状态持久化(存储层,横跨各层)
2. 分层架构
┌─ 交互层 Electron 桌面端:三种工作模式 / 任务对话 / 结果区产物
├─ 编排层 Agent Loop · 任务规划 · 子 Agent 并行 · 上下文压缩
├─ 能力层 内置工具 · Skill 技能 · MCP 连接器 · 专家包
├─ 执行与安全 权限沙箱 · 托管运行时 · 审计哈希链 · 文件回滚
└─ 模型层 云端多模型路由 · 本地 llama.cpp 推理
2.1 交互层:桌面端形态
本机 ~/.workbuddy/app/ 下有 CodeCache、Crashpad、electron-log-preload.js、renderer-version.json —— 确认是 Electron 应用(主进程 + renderer + preload 桥接)。选桌面端而非纯 Web 的原因很直接:需要真实文件系统访问、本地运行时、本地模型推理,这些在浏览器沙箱里做不到。
界面三区对应三种信息态:
- 侧边栏:任务列表,按文件夹(项目)分组 → 说明"项目"是一等公民,不是聊天记录
- 对话区:任务标题 + 消息流 + 输入框
- 结果区:
概览/产物两类切换 → 产物是独立于文本的一等对象
三种工作模式是权限与自主度的预设,不是三种模型:
Agent:直接执行(默认,写文件/跑命令)Plan:先出计划,人审后执行Ask:只读,不改文件
2.2 编排层:Agent Loop
核心循环:
分析上下文 → 判断是否更新计划 → 选择工具 → 执行动作 → 接收观测 → 迭代
这是标准的 ReAct 式循环,工程难点不在循环本身,而在三个配套机制:
a) 上下文压缩(compaction)
长任务必然撑爆窗口。方案是把历史压缩成摘要(保留决策、结论、文件路径、未完成项),然后继续。代价是摘要会丢细节 —— 这就是为什么会话被压缩后需要回读原始 transcript(本机 projects/<项目>/<sessionId>.jsonl 保留全文)。
b) 子 Agent 隔离
用独立的上下文物化并行任务(如同时探索多个目录),子 Agent 只回传结论,不回传过程。好处是主上下文不被过程性输出污染;代价是子 Agent 看不到主对话,prompt 必须自包含。
c) 任务列表的状态机
多步任务用 pending → in_progress → completed 跟踪,且明确规则:未真正完成不得标记 completed,被阻塞则新建"解除阻塞"任务。这是防止 Agent 自我欺骗式收敛的关键约束 —— 没有它,模型倾向于把"我做了一些事"表述成"我完成了"。
2.3 能力层:四种扩展机制的差异
这是设计上最值得说的一层,因为四者容易被混为一谈,实际职责完全不同:
| 机制 | 本质 | 加载方式 | 解决什么 |
|---|---|---|---|
| 内置工具 | 直接函数调用 | 常驻 | 文件读写、命令、搜索、网页抓取 |
| Skill | 操作手册(Markdown + 脚本) | 命中才加载正文 | 领域工作流、用户约定、踩过的坑 |
| MCP 连接器 | 标准协议接第三方服务 | 工具发现 → 懒加载 schema | 外部数据与操作(文档、日历、数据库) |
| 专家包 | 角色 + 方法论 + 技能组合 | 会话级选中 | 专业判断(投研、法务、写作) |
关键工程决策:工具 schema 懒加载。
工具一多(几十上百个 MCP 工具),全部 schema 塞进 prompt 会直接吃掉大量上下文,且稀释注意力。方案是两级:先用 ToolSearch 按关键词/精确名检索,拿到 schema 后再 DeferExecuteTool 真正调用。同理,Skill 只把 name + description 放进上下文,命中后 Skill 工具才把正文注入 —— 这叫渐进式披露(progressive disclosure)。
本机 mcp-tool-list.json 约 32KB,只是索引缓存,不是全量 schema,正对应这个设计。
交付层是技能链,不是单点能力。
文档类任务实际是多阶段流水线:
doc-writer(写内容,产出 Markdown)
→ doc-formatter(排版,产出 HTML)
→ doc-converter(HTML → DOCX 转换 + 校验)
编排层负责路由(有无参考模板走不同分支、是否只需编辑已有文件走另一条链),单个 Agent 只做一件事。这个拆分的好处是每段可独立替换和验证:排版出错不必重跑写作。
2.4 执行与安全层
a) 权限沙箱:有序规则 + 预置版本
本机 settings.json 实测结构:
sandbox.orderedRules.file { presetVersion, customized, rules[135] }
sandbox.orderedRules.command { presetVersion, customized, rules[] }
sandbox.orderedRules.network { presetVersion, customized, rules[] }
sandbox.extraAllowWrite[] len 12
三个设计点:
- 按类别分流:文件、命令、网络三类各自独立规则集。命令名和路径的风险模型不同,混在一起没法做细粒度控制。
- 有序匹配:
orderedRules是数组不是 map —— 意味着规则有优先级,先命中先裁决(如"禁止全盘删除"优先于"允许写入工作区")。 - 预置规则可升级但保留定制:
presetVersion+customized两个字段并存,版本号用于跟随产品升级推送新规则,customized标记用户改过则合并时保留 —— 这是配置演化的常见难题(升级覆盖 vs 用户定制),这里用"版本号 + 脏标记"解决。
b) 托管运行时隔离~/.workbuddy/binaries/ 下托管了 PortableGit、node(22.22.2)、python(3.13.12),并按版本号分目录、用 current 软链切换。Python 包必须装进 envs/default 的 venv、Node 包装进 workspace/node_modules,并分别用绝对路径调用。
目的有三:不受用户系统环境影响(用户机器上 Python 3.8/3.13 共存也不会崩)、不污染用户环境、可回滚(版本目录并存)。这是"确定性执行"的基础 —— Agent 生成的脚本必须在可预测的运行时里跑。
c) 审计哈希链
本机 audit-log/2026-10-26.jsonl 单条记录 schema:
sessionId, requestId, toolCallId, category, eventType, decision,
messageKey, messageParams, id, timestamp, source, schemaVersion,
sequence, prevHash, hash
重点在最后两个字段:prevHash → hash 构成哈希链,配合自增 sequence。效果是日志不可静默篡改 —— 删掉或修改中间一条,后续所有 hash 都对不上。commandPreview + commandHash 单独记录命令摘要,decision 记 allowed/denied。
这解决的是 Agent 系统的核心信任问题:必须能事后回答"它到底做了什么、是谁批的",且答案不能由被审计方(Agent 自己)随意改写。
d) 文件回滚file-history/<sessionId>/ 存快照,projects/<项目>/<sessionId>.file-rollback.ndjson 存回滚索引。按会话维度组织 → 一次任务的所有改动可以整体撤销。这也是为什么批量改文件要小心:回滚粒度和会话绑定,跨会话的改动不在一张网里。
2.5 模型层:双轨
- 云端多模型路由:按任务性质选模型 —— 继承默认 / 轻量快速(
lite)/ 强化推理(reasoning)。工程上就是"同一次会话内可按 turn 切模型",路由策略而非硬编码。 - 本地推理:
local-models/state.json实测
{ "llamaServer": { "version": "b10573-q80-kvcache",
"binPath": "...llama-server.exe",
"variant": "Intel-GPU" },
"engineKeepAlive": false }
即 llama.cpp 的 llama-server 作为本地推理服务,模型为 GGUF 量化格式(local_model.gguf.partial),按硬件出变体(Intel GPU 加速)。engineKeepAlive 控制引擎常驻还是按需启动 —— 本地模型加载要几秒到几十秒,常驻换响应速度、按需换内存占用。
- 容错:
failover.json表明有故障切换(云端不可达时降级)。
2.6 数据与状态:本地优先
| 目录 | 作用 | 设计要点 |
|---|---|---|
| projects/<slug>/<sessionId>.jsonl | 会话全文(append-only) | 按行 JSON,可流式读;压缩后仍能回读原始细节 |
| projects/.../<id>.file-rollback.ndjson | 回滚索引 | 与会话绑定 |
| blobs/00..xx | 内容寻址多媒体 | 二级散列目录,避免单目录海量文件 |
| artifact-index/、changes-index/ | 产物与变更索引 | 支撑"结果区"的概览/变更视图 |
| memory/ | 三层记忆 | 见下 |
| connectors/、plugins/ | 连接器状态、插件清单 | connector-states.v3.json 带版本号,可迁移 |
三层记忆的职责分离(这是很实用的设计):
- 云端画像:服务端生成,本地只读缓存 → 学到的是隐式偏好
- 用户级
MEMORY.md:跨项目,用户明确要求长期记住的规则 - 项目级
.workbuddy/memory/:YYYY-MM-DD.md日追加 +MEMORY.md长期沉淀
分层的理由是作用域与写入权不同:改代码的约定不该污染其他项目;用户的个人习惯又该跨项目生效。用"作用域"而不是"一个无限长大的记忆文件"来组织,避免检索时的信噪比崩塌。
3. 一次任务的完整链路
用户自然语言
→ 意图解析 + 是否需要澄清(不足则提问,不猜)
→ 规划(多步则建任务列表;复杂则 Plan 模式交人审)
→ Agent Loop:
选工具(ToolSearch 检索 → 取 schema)
→ 危险操作进沙箱裁决(file/command/network 规则,有序匹配)
→ 允许:托管运行时内执行;拒绝:错误回传模型改路径
→ 每次裁决写审计链(prevHash → hash)
→ 观测结果入上下文
→ 上下文逼近上限则压缩,保留决策与路径
→ 产物落盘 + 索引(artifact-index / changes-index)
→ present_files 交付,供人验收
→ 记忆沉淀(项目日志 / 长期笔记)
4. 方案取舍总结
做对了什么
- 懒加载贯穿始终:工具 schema、Skill 正文、子 Agent 上下文都按需加载,这是长任务可行的前提。
- 安全与能力同层设计:不是"先做完再加权限",沙箱、审计、回滚是执行链的内建环节。
- 审计用哈希链而非普通日志:把"可追溯"从承诺变成密码学保证。
- 托管控件而非依赖环境:确定性执行的必需品。
- 本地优先存储:会话、产物、记忆全在本地,云端只做模型推理,隐私边界清晰。
代价与风险
- 压缩必然丢信息:摘要质量决定长任务成败,压缩时机不好会出现"忘了为什么要这么做"。
- 规则匹配是启发式的:路径规则挡得住"误删桌面",挡不住精心构造的边界情况;所以还有"禁止递归删除系统目录"这类硬红线兜底。
- 桌面端形态限制协作:任务列表按本地文件夹分组,多人共享场景要靠云端工作区补齐。
- 本地模型与云端模型能力差:本地适合隐私敏感、离线、轻量判断,复杂编码仍得走云端。
5. 如果想借鉴这套设计
最小可行集(按实现成本排序):
- Agent Loop + 工具调用:先跑通"模型 → 工具 → 观测"闭环
- 有序规则沙箱:三类分流 + 优先级数组,是性价比最高的安全投入
- 会话级文件快照与回滚:比精细的权限系统更早该有 —— 出事能撤比出事能拦住更实际
- 审计哈希链:几十行代码,换来可信的事后追溯
- Skill 渐进披露:领域知识 Markdown 化,按需注入,别塞 system prompt
- 上下文压缩:等真的撞到窗口上限再做,但要在存储层先保证原文可回读
最新评论
还没有评论