# MetaBoard **给编码以外的工作,留下可检视的执行轨迹。** [English](README.md) | 简体中文 编码 agent 有一样别的工具几乎都没有的东西:一份可回溯、可检视的记录,说明这件事**究竟是怎么做成的**。 每一次检索、每一次工具调用、每一次重试都是一条结构化事件——因为执行器自己会掉出来, 不是靠谁记得去记一笔。 内容创作没有这份记录。看板能告诉你一篇稿子从 `todo` 挪到了 `in_review`, 但告诉不了你大纲是从哪二十篇同题材里提炼的、第二次改稿花了多久、编辑打回时具体说了什么。 而这些恰恰是数据不好时你要回头查的东西。 MetaBoard 把 agent 轨迹带到非编码业务上,首个业务域是内容生产。 --- ## 当前状态 第一阶段——独立 dsh 插件的形态——已经实现,并且在真实会话上验证过; [`docs/phase-1-acceptance.md`](docs/phase-1-acceptance.md) 是记录。当前分支在此之上 加了命令行、项目、留言流、会话绑定的认领,测试套件全程保持全绿。 还有两项验收没有走完,都因为需要一个真实的 dsh 会话才能跑:完整走一遍全流程, 以及两个会话并发认领同一个工作项。认领冲突这部分有单元测试覆盖,还没有在真实运行时上验过。 想找现在就能装的东西,过段时间再来。对「执行轨迹如何脱离软件领域」这个问题感兴趣,可以往下读。 --- ## 想法 MetaBoard 是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的插件。 它复用 `dsh` 里真正难写、也真正与领域无关的那一部分——会话节点装配器,替换掉与编码绑定的那一层。 装配器把原始事件流变成物化的业务对象:每个注册的 Definition 从单个事件里抽出稳定业务 ID, 在匹配到的事件上折叠状态,产出视图节点。旧一页历史加载进来时,只有答案发生变化的 Context 会被重放。 这套增量重放是长轨迹能保持流畅的原因,也是最不该自己重写一遍的部分。 这套机制里没有任何一处知道 token、工具 schema 或 TTFT。那些在上面一层, 在轨迹 UI 自己的 Definition 里。换掉那一层,同一个引擎渲染的就是另一个领域。 ### 内容轨迹长什么样 内容轨迹:两个轮次的工具调用,中间夹一次人工评审,蓝色派生边显示每个产物来自哪些上游 竖向顺序是时间。蓝色的边是看板表达不了的东西:这一稿来自哪几条素材、这一版改的是哪一稿。 它们不是推断出来的——每个工具在自己的结果里就记下了。 选中一行打开完整载荷:检索到的素材、稿件全文、它从哪几条来、花了多久。 --- ## 怎么做的 架构:MetaBoard 的工具和评审写入器产生核心会话事件,共享的会话装配器供给 Chat、Trajectory、MetaBoard 三套 Definition,各自渲染自己的标签页 MetaBoard 是一个普通的、仓库外的 npm 包,两半: **host 半**注册内容生产工具。每次调用产生核心的 `tool/call` 和 `tool/result` 事件, 领域载荷挂在 `tool/result.meta` 上——那是 `dsh` 本来就提供的、工具私有、核心不透明、 持久化保存的 JSON 通道。 **client 半**注册自己的会话 Definition 和一个视图标签页。Definition 只认领 MetaBoard 自己的调用, 把它们装配成领域对象再渲染。 不 fork,不改宿主,不加新事件类型。 最后这条不是审美偏好,是测出来的边界,而且它塑造了整个设计。 --- ## 命令与工具 MetaBoard 有两个入口:人直接用的命令行,和 agent 在会话里调的 dsh 工具。两边写的是同一份 工作项日志——谁建的都能被另一边立项、留言、打回。 ### 命令行 | 命令 | 作用 | |---|---| | `metaboard new <标题> [--project ]` | 记一个想法,落在「待立项」——agent 碰不到,直到被批准。 | | `metaboard approve ` | 立项:允许 agent 认领并开始做。 | | `metaboard ls [--project ] [--all]` | 看板:只显示已立项的流水线,按状态分组。`--all` 连待立项、完成、取消一起列。 | | `metaboard show ` | 这个工作项的完整时间线:看板动作与 dsh 会话事件并成一条线。 | | `metaboard status <状态>` | 把工作项挪到任意状态。 | | `metaboard comment <正文>` | 留一句话——要求、疑问、说明。 | | `metaboard return <理由>` | 打回:等你确认 → 处理中,理由记进留言。 | | `metaboard rename <新标题>` | 改标题。 | | `metaboard archive ` | 归档。归档只是从看板隐藏,历史仍在。 | | `metaboard set-project ` | 归属到某个项目,`-` 取消归属。 | | `metaboard project new <名字> [--path <绝对目录>]` | 建一个项目。`--path` 把一个绝对目录存进项目记录——目前没有任何地方会反过来用它,把当前目录解析回项目。 | | `metaboard project ls` | 列出项目。 | | `metaboard project rename <新名字>` | 改项目名。 | | `metaboard project archive ` | 归档项目。归属它的工作项不受影响。 | | `metaboard doctor` | 检查工作项日志、项目日志、dsh 会话存储三边读不读得到。 | agent 正在做的工作项带着一个**认领**:第一个往它上面写的那个会话。认领还在的时候,别的 对话写它一律被拒——两个会话同时写同一个工作项就是这样被拦住的。`metaboard show ` 会印出认领在谁手里。任意 `metaboard status <状态>` 都会解除认领,会话中途死掉时就靠 这一条把活儿收回来。 ### dsh 工具 | 工具 | 作用 | |---|---| | `metaboard_work_create` | 记一个新工作项,可以在建项时就指定项目。落在待立项——记录一个需求不是授权去做它。 | | `metaboard_research` | 把已经检索到的素材逐字记下来。 | | `metaboard_draft` | 把已经写好的草稿逐字记下来。 | | `metaboard_revise` | 把改稿和依据的意见逐字记下来。 | | `metaboard_review` | 把一次人工评审的结论记进轨迹。 | | `metaboard_work_read` | 读一个工作项的标题、状态、项目归属和留言流。读不需要授权。 | | `metaboard_report` | 把 agent 自己做了什么写进同一条留言流,可以选择顺带把工作项交回去等待确认。 | --- ## 已经验证过什么 在写产品代码之前,先直接对着上游代码库把可行性边界探了一遍——测试写进真实的 JSONL 日志, 再用全新挂载的栈读回来。六条命题,全部确认: | # | 命题 | 结果 | |---|---|---| | 1 | `Session.append` 能否让插件把自己的事件标成可忽略 | **不能。** 公开写入 API 打不了这个标记 | | 2 | 含插件自定义事件类型的日志会怎样 | **整份拒收**——`SessionFormatUnsupportedError`,不是跳过那一行 | | 3 | 同一条事件带上可忽略标记呢 | **完整读回**,一字不差。存储层支持,只是写入侧没开口 | | 4 | 50KB 的 `tool/result.meta` 经真实文件往返 | **能**。没被 spill,没被截断 | | 5 | 插件直接 append 的 `user/message` 能否往返 | **能**——不需要模型参与 | | 6 | 插件事件类型会不会进模型上下文 | **不会**。它不是 surface 事件类型 | 第 2 条是 MetaBoard 不定义自己事件类型的原因。第 4、5 条是它不需要定义的原因。 第 3 条记录了一个存在于存储层、但在写入 API 上被有意关闭的口子—— 上游文档写明 `Session.append` 会「在第一个使用者出现时」补上这个入口。 --- ## 设计 完整设计文档暂时不在这个仓库里。要紧的几个决定: **数据落点只看一个问题——它是事件还是状态。** | 类别 | 例子 | 落点 | |---|---|---| | 事件,模型发起 | 检索到的素材、稿件全文、改稿 diff | `tool/result.meta` | | 事件,人发起 | 编辑打回、临时指令 | `user/message`(plugin 来源) | | 状态 | 选题状态、负责人、看板位置、播放量 | MetaBoard 自有存储 | **Context 身份是一次调用,不是一个选题。** 一次调用一个 Context, 让 live append 保持 `O(1)`,也让 prepend 不会让整个选题的历史失效。 选题 ID 放在节点载荷里,分组在快照装配时做。 **失败的调用也必须写信封。** `tool/result` 的载荷里不带工具名——只有 `tool/call` 带—— 而匹配时不能查历史。信封是唯一的认领依据。工具失败时省掉它,账本上就留下一行永远显示 「运行中」的记录,重新加载也不会好,因为日志里存的就是这个。 **引用在渲染时解析,不走依赖 reader。** 走 reader 会记下窗口缺口依赖, 每次往回翻都触发链式重放。而引用指向的目标其值从不改变,只是有时还没加载。 渲染一个未解析的引用,代价是它灰一下;另一条路的代价是每条长轨迹的滚动性能。 --- ## 路线 **第一阶段 · 可行性切片。** 三个工具、两个 Definition、一个标签页、一张最简行表。 没有检视面板、没有时间轴、没有看板、没有存储层。及格线:层级装得出来、引用解析得出来、 50KB 载荷重开还在、失败的调用渲染成一行完整的失败记录而不是卡住的行。 **第二阶段 · 工作项。** 看板视图、自有存储、跨会话汇总、平台数据接入。 **第三阶段 · 其他领域。** 数据落点规则和信封不是内容生产专用的。 法务审查、研究综述、设计迭代是同一个形状:一个多步过程,中间产物比最终状态更值得看。 --- ## 来源与致谢 MetaBoard 来自两个项目: - [**deepseek-ai/deepseek-harness**](https://github.com/deepseek-ai/deepseek-harness)—— 装配器、事件流、轨迹账本,这份设计从中学习。MIT。 - [**chuspeeism/dashi-taskboard**](https://github.com/chuspeeism/dashi-taskboard)—— 本地优先的任务看板,带工作流图和第三方发布节点。它把字段级审计和 AI 会话记录分成两套的做法, 让中间缺的那一层变得明显。 MetaBoard 与这两个项目均无隶属关系。 --- ## 许可 MIT