tools.md 16.3 KB

WorkGraph Planning 工具箱

你的全部工作靠五类工具完成:读骨架 → 读本体清单 → 落盘规划记录 → 招聘 → 交回图。 按 agent.md 的八步工作流顺序使用。你不执行图、不辞人、不改 org graph。

🚨 交回图(§6)没有端点可调。 落库是引擎的活:它读你回复的正文取图、自己校验、自己写库。 你只负责把图放到它认得的位置(§6)。

🚨 招聘(§5)必须排在交回图(§6)前面。 反了会外键挂 + 节点跑起来找不到人,详见 agent.md Step 6。

🧭 路径口径:CLI 进程的 cwd 是你自己的 agent 目录 (data/companies/<公司ID>/agents/workgraph-planning),不是引擎仓库根。 本页所有仓库相对路径(system/work-graph-templates/、bin/lemon.js、data/companies/...) 都要自己拼上引擎仓库根的绝对路径再用——那个绝对路径在首轮消息的「写入边界」段里。读允许,写禁止。

🚨 发 JSON 的铁律(先读这条,否则你会陷入死循环) 本环境的工具调用在解析带多重双引号的 bash 命令时会崩溃,导致你反复"修格式"直到进程被杀。 任何带 JSON body 的请求(现在只剩招聘那一条),一律先把紧凑单行 JSON 写进 /tmp/*.json 文件, 再用 curl -d @文件 发送。 JSON 里不许出现真实换行符,不许内联在 command 参数里。


1. 读候选骨架(Step 2)

ls system/work-graph-templates/
for f in system/work-graph-templates/*.json; do echo "=== $f ==="; cat "$f"; done

永远以 ls 的实际结果为准逐个读,目录里可能有比下表更多的文件。选型只看每份的 whenToUse。

文件 宿主 org node 节点链
planner.json 规划类(org-planner) brd → prd → sdd → benchmark → validator
executor.json 执行类,单交付物 impl → test → validator
executor-fanout.json 执行类,可并行拆分 split → (impl/test)×N → merge → integration → validator
validator.json org-validator 专用(强制) validator-triager → benchmark-validator → testdata-validator

system/work-graph-templates/README.md 有完整的字段说明与占位符清单,规划前值得读一遍。

2. 读可引用的 system agent 本体(Step 3)

node bin/lemon.js company templates
curl -s http://localhost:3010/api/company/<公司ID>/agents

前者是「能雇到哪些」(全局 agent 模板,• <id> — <description> 每行一个), 后者是「这家公司已经雇了哪些」。两份都要读,但别混: 不在后者里 ≠ 不能用,只意味着 §5 要真去雇它一次。 骨架 JSON 里写的 systemAgentId 只是建议,{{implAgent}} 这类占位符更是必须替换的空位。

找不到理想本体的三条出路见 agent.md Step 3。你不亲手写 agent 定义,那是 agent-creator 的活; 但把本体雇进公司是你的活(§5)。

3. 读上游留下的共享产物(Step 1)

ls -R data/companies/<公司ID>/runs/<rootRunId>/artifacts/

artifacts/ 是 root run 级的任务级共享产物区,跨 org node 只读可见—— goal-benchmark.json / goal-test-data/ / goal-test-script/ 这些验收契约就在这里。

读 写
artifacts/ ✅ 任意层级只读 ⚠️ 只有你在图里显式标了 writesArtifacts 的节点才有权写
别的节点的 nodes/{其他}/ ❌ 禁止 ❌

「下游只能看到上游的契约,看不到实现」——别让你画的节点去重造 artifacts/ 里已经有的产物。

4. 落盘规划记录(Step 5,别忘)

用文件写入工具写 <项目根>/workgraph-plan-<taskId>.md(绝对路径,项目根由首轮消息的 「写入边界」段给出;没分到项目根就一个文件都不落,把记录写进回复正文)。 七节结构见 agent.md Step 5(第 7 节「招聘结果」在 §5 招完人之后回填)。 图 + 理由 + 五项校验自查,三样缺一不可——这是人类事后复核你判断力的唯一线索。

⚠️ 别写相对路径 workspace/...:它会落进引擎源码仓库,正是边界段禁止写的位置。

5. 招聘(Step 6)—— 必须在交回图之前跑完

对图里 nodes[].systemAgentId 的全集去重,逐个执行:

node bin/lemon.js company hire --id <公司ID> --role <本体名> --type executor --name "<中文岗位名>"
参数 说明
--id 公司 ID,首轮消息会给
--role 本体名(systemAgentId),不是节点名。必填
--type 一律 executor(work 节点是执行流上的节点,不是 cxo)。省略时 CLI 默认 cxo,别省
--name 中文岗位名,省略时默认等于 --role
--desc 可选,一句话职责。新造的本体(模板库里没有)建议写上——它会被转给 agent-creator 当写定义的依据

这条命令等价于 POST /api/company/<公司ID>/hire,body {"role":...,"type":...,"name":...}。 CLI 起不来时可以走 curl 降级(照铁律先写文件,/tmp/hire-<本体名>.json 单行紧凑):

curl -s -X POST http://localhost:3010/api/company/<公司ID>/hire \
  -H "Content-Type: application/json" -d @/tmp/hire-<本体名>.json

返回体形如 {"status":"hired_from_template","agentId":"coder","agentDir":"..."}。 status 的四档判读、出错的三级处理(重试 / 换掉 / 停)、以及幂等规则,全在 agent.md Step 6, 不要凭感觉处理——尤其 already_exists 是正常结果,不是错误。

⚠️ CLI 把 HTTP 4xx/5xx 包成一段文本,不是 JSON。 bin/lemon.js 在状态码 ≥400 时打印的是 ❌ [CRITICAL ERROR]: API returned 400.\nDetails: {"error":"role 为必填项"} 这种字符串 (还附一段催你重试的话)。照 agent.md Step 6 的分级表处理,别被那段催促语带着无脑重发同一条命令—— 真正的原因在 Details: 后面那个 JSON 里,先读它。

--layer 这个参数不存在,别自己发明。 work 层招聘该不该写进 org graph 由服务端判定 (27-P3B §四 W7),不是你的入参;你只管把人雇进来。

招完核对(引擎判定本体在不在,用的就是这个目录):

ls data/companies/<公司ID>/agents/

6. 交回图(Step 7)—— 没有端点,引擎读你的回复

没有 POST,没有 curl,没有 sqlite。 把图贴在这一轮回复正文的一个 json 围栏块里(三个反引号 + json) (或写成一个 .json 文件、在回复里给出绝对路径),引擎自己取出来校验 + 落库。

{"nodes":[{"nodeName":"org-coder-impl","systemAgentId":"coder"},{"nodeName":"org-coder-test","systemAgentId":"unit-tester"},{"nodeName":"org-coder-validator","systemAgentId":"coder-validator"}],"edges":[{"from":"org-coder-impl","to":"org-coder-test","condition":"success","produces":["artifact"]},{"from":"org-coder-test","to":"org-coder-validator","condition":"success","produces":["log"],"needs":["artifact"]},{"from":"org-coder-test","to":"org-coder-impl","condition":"fail","edgeKind":"backward"},{"from":"org-coder-validator","to":"org-coder-impl","condition":"fail","edgeKind":"backward"}]}

produces / needs 就是 §6bis 那两个键。forward 边上少了它们,下游节点的提示词里 就没有上游产物的路径 —— 先读 §6bis 再写这份 JSON。

引擎取图的死规矩(完整版在 agent.md Step 7):严格 JSON(注释 / 尾逗号 / 省略号一律解析失败, 整块当没写)· 顶层要有 nodes 数组 · 文件路径必须绝对 · 回复里不许出现 wg_ + 12 位十六进制的字符串(会被当成"你已落库的图 id",查不到就白烧一轮)。

字段约定:

字段 说明
nodes / edges 引擎只读这两个键。ownerOrgNodeId / goalId / taskId / okrNodeId 由引擎从 run 上下文自己填,写在 JSON 里不生效(要留痕写进规划记录第 4 节)
nodes[].nodeName 必填,图内唯一,带宿主 org node 全名前缀
nodes[].systemAgentId 引用的本体,不带前缀;省略时默认等于 nodeName(几乎总该显式写)
nodes[].nodeKind "agent_call"(默认可省略)\
edges[].from / to 用节点名,不是 id(图还没落库,你手里没有 id)
edges[].condition "success" \
edges[].failReason 仅 fail 边可带;省略即为兜底边
edges[].edgeKind "forward"(默认可省略)\
edges[].produces 数据契约:这条边的上游会产出哪些 kind,交给下游。见 §6bis,forward 边基本都要写
edges[].needs 数据契约:这条边的下游要吃哪些 kind,而产出方不是紧邻上游(跨节点中继)。见 §6bis

图没过校验时引擎把 errors 贴进下一轮提示词(不是 HTTP 400,没有响应体可读)。 errors[].rule 取值为 output(压根没找到图)/ acyclic / single-entry / reachable / backward-labeled / fail-fallback-edge / data-contract / schema / reference, 逐条怎么修见 agent.md Step 7 的规则对照表。读懂 → 改图 → 重交,引擎只给 3 轮。

交回图不豁免 §5 的招聘——引擎在你这一轮回复结束的那一刻就落库,人没雇齐照样外键挂, 而那时你已经下班了。

6bis. 边上的数据契约(produces / needs)—— 不写就等于下游拿不到输入 🚨

为什么必须写(这不是可选装饰)

第四次真 CLI 冒烟里 org-planner-prd 失败,它自己的归因原话是:

「不是上游没产出,是它的产出结构上到不了我手上。」

核实的结果:上游 brd 节点的 brd.md 确实写出来了,但它躺在那个节点的私有目录里, 而 brd --success--> prd 这条边只携带控制流(condition / edgeKind / failReason), 不携带任何文件引用。于是 prd 节点的提示词里一条上游产物路径都没有, 而跨节点私有目录是禁止读的 —— 它只能失败。

引擎现在会替下游把输入递到手上:节点执行前,按「该节点入边上声明的 kind」 去产物索引查真实文件,把绝对路径拼进提示词。这条链的起点就是你在边上写的声明。

你不声明 → 索引查不到该给谁 → 下游提示词里没有路径 → 它重演上面那句话。 这是你画的图能不能跑过第一个节点的分水岭,不是锦上添花。

声明的是「契约」,不是文件名

规划时节点还没跑,谁也不知道文件叫什么(brd.md?brd-v2.md?)。 所以你声明的是类型:「这条边传一份 brd」。运行时引擎自己把落盘的实例登记进索引, 按 kind 配对。不要在图里写文件名、不要编路径。

kind 的取值域(写错会被 rule=schema 打回)

kind 是什么
brd 业务需求文档
prd 产品需求文档
sdd 系统设计文档
acceptance 验收契约
contract 节点间接口契约
artifact 上面都不算的一般交付物(代码、数据、报告…)
log 运行/测试日志类产物

大小写会被归一("PRD" == "prd"),但别自创取值(blueprint / design-doc 这类一律被拒)。

两个键的分工

键 写在哪条边上 语义
produces 产出方的出边 「这条边的上游节点会产出这些 kind,交给下游」 —— 绝大多数情况只用它
needs 消费方的入边 「下游要吃这些 kind,但产出方不是紧邻上游」 —— 跨节点中继才用
// brd → prd → sdd:sdd 既要 prd 也要 brd,而 brd 的产出方隔了一个节点
{"from":"p-brd","to":"p-prd","condition":"success","produces":["brd"]}
{"from":"p-prd","to":"p-sdd","condition":"success","produces":["prd"],"needs":["brd"]}

三种写法都收:"produces":"brd" / "produces":["brd","prd"] / "produces":[{"kind":"brd","required":false}]。

required:false 的唯一正当用途:产出方根本不在这张图里(例如 acceptance 是上一层 org graph 或 artifacts/ 给的)。它同时是逃生口——见下面第六项校验。

两条硬规则

  1. backward 回退边上不许声明(rule=schema)。引擎解析输入只看 forward 入边, 写在回退边上等于没写,而"看起来写了"比"没写"更难排查。
  2. 第六项校验 data-contract:你声明某个节点要吃 kind=X(needs), 但图上没有任何节点的出边声明产出 X → 整张图被 400 拒。 修法二选一:给真正的产出方那条出边补 "produces":["X"]; 或者产出方确实不在本图,改成 "needs":[{"kind":"X","required":false}]。

不用声明的那一类:任务级共享产物

acceptance / contract 这类挂在 task 上的共享产物,引擎自动按 task + kind 递给节点, 不受拓扑相邻性限制(R-9.3 的「planner 产验收契约、validator 执行」中间隔着 coder)。 它们已经在 artifacts/ 里时你不必也不该沿边声明;只有当本图里某个节点自己产出它、 再交给下游时才写 produces。

7. 交回前自查五项校验(可选但强烈建议,引擎只给 3 轮)

引擎用的就是这个函数,你可以先在本地跑一遍(<引擎仓库根> 用「写入边界」段给的那个绝对路径, require 也要用绝对路径 —— 你的 cwd 不在仓库根,写 ./server/... 会 Cannot find module):

node -e "
const {validateWorkGraph}=require('<引擎仓库根>/server/services/work-graph-db.js');
const t=JSON.parse(require('fs').readFileSync('<项目根>/workgraph-<taskId>.json','utf-8'));
console.log(JSON.stringify(validateWorkGraph(t.nodes,t.edges),null,1));"

{"ok":true,"errors":[]} 才交回。这个函数连第六项 data-contract 一起查 (produces / needs 的取值域、有没有写到 backward 边上、needs 的 kind 有没有产出方), 所以 §6bis 那几条也在这一步就能自查出来。

但它不检查占位符有没有替换干净——{{implAgent}} 在校验器眼里是个合法字符串, 它照样返回 ok。占位符靠你自己看。


你不该用的工具

⚠️ company hire 已经不在这张表里了(R-18.2 定案)。以前这里写着「你不招人也不辞人」, 那条已作废:招人现在是你的活(§5),只有辞人还不是。看到旧提示词里的老话以本节为准。

别用 为什么
POST /api/company/:id/work-graphs NO-SUCH-ENDPOINT — 这个端点不存在,从来没有实现过,不要调用。 从前这份模板教过它,实测每次真跑都吃一个 404、白烧一轮调用。落库归引擎(§6),谁也不用 POST 图
POST/PATCH /api/company/:id/org-graph org graph 是上一层,归 orggraph-planning / HR。你只画 work graph
node bin/lemon.js company fire 你只招不辞。图用完即归档,但雇进来的本体留给后续任务复用(下次 hire 会返回 already_exists)
node bin/lemon.js template create / template train 造本体、训本体是 agent-creator / innovator 的活。你只 hire,模板库里没有的角色 hire 会自动去继承+触发它们
手工 mkdir / cp 出 data/companies/<公司ID>/agents/<本体名>/ 看着像招聘,其实不写 system_agents 表,引擎落库时照样 rule=reference 被打回。只能走 hire
直接写 data/ 下的 sqlite / json 绕过五项校验就是把非法图塞进系统
POST /api/graph-runs/:id/{start,next,...} 执行是引擎的活。你把图交出去(§6)就下班
对已落库的 work graph 做任何修改 work graph 生命周期是一次性的,要改就重新规划一张,没有增量补丁
task transition 绝对不要。它是 base 四段式的换阶段命令,与图流转是两层状态机(agent.md 约束四)