Files
workbuddy_skills/session-mechanism/graph/README.md
T
admin 42a130e4f2 fix(session-mechanism/graph): 并发写不丢状态,测试不再被批量删除守卫拦
- graphctl.py 并发修复
  · 临时名原来固定成 graph.json.tmp ⇒ 两进程同时写直接撞 PermissionError(桌面实测第 14 条)
  · 新增 _update():读最新状态 → 重放本次变更 → 原子替换 → 回读核对 _rev,
    被覆盖则重读重做(最多 3 次),仍失败就报错退出
  · 修掉更隐蔽的一层:后写的整份覆盖前写会让节点状态静默消失
  · start / report 改为走 _update,业务校验放在 mutate 内
- test_graphctl.py
  · 每例只新建自己的目录、跑完不删,整轮收尾清一次 ⇒ 不再触发沙箱「批量删除」守卫
    (此前 49 条里 20 条报 ERROR,全部是本机策略拦清理,用例本身零失败)
  · 新增 T9_Concurrency 三个用例:并发开工、并发回报、版本号每次写都前进
  · 49 → 52 条,实跑 Ran 52 tests OK(13 s)
- README:补「并发写是怎么保证的」三层说明,测试数与用时更新
2026-10-19 20:09:24 +08:00

126 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 图模式(独立功能)
> 2026-10-18 新增。**这一套与现有「项目机制」完全并列**:不读写 `goal.json`、`tasks.json`、`collabd-state.json`,不参与六道闸,不接管常驻循环,不改变现有任何会话的行为。
> 停用方式:不调用它就行;它没有常驻进程、没有注册任何钩子、没有改任何现有文件。
## 一、它解决什么
现有机制是「一棒干一堆、由会话读摘要决定下一棒」。图模式把这层换成:
- **节点**:一块独立的活,带自己的**域**(允许写哪些目录)和**判据**。
- **边**:节点之间的依赖;依赖全 done 才放行。
- **状态**:写在一份图上,跨会话共享,不依赖任何会话的上下文。
于是可以做到:**一批就绪节点同时干、干完各自回报、下一批自动放行**。
## 二、怎么用
工作区参数 `--ws` 指向要被推进的工作区根目录。图落在 `<ws>/.workbuddy/graph/<gid>/`。
### 1、建图
按判据建(每条判据一个节点,默认无依赖、无域):
python graph/graphctl.py init --ws "<ws>" --title "目标名" --kpi "判据A;判据B;判据C"
按节点定义建(要并行、要依赖、要域,就用这个):
python graph/graphctl.py init --ws "<ws>" --title "目标名" --nodes-json - <<'JSON'
{
"max_parallel": 3,
"nodes": [
{"id": "A", "title": "改界面", "domain": ["ui/"], "accepts": ["页面能打开"], "deps": []},
{"id": "B", "title": "改文档", "domain": ["docs/"], "accepts": ["索引已登记"], "deps": []},
{"id": "C", "title": "改服务", "domain": ["server/"], "accepts": ["本地能起"], "deps": ["A"]},
{"id": "D", "title": "汇总", "domain": ["docs/index/"], "accepts": ["三处齐"], "deps": ["A","B","C"]}
]
}
JSON
### 2、校验
python graph/graphctl.py check --ws "<ws>" --gid "<gid>"
报三件硬错(环、依赖指向不存在的节点、状态非法)和三类警告(没有域、没有判据、两个节点域重叠)。
### 3、算这一批派谁
python graph/graphctl.py ready --ws "<ws>" --gid "<gid>"
输出 `ready`(这一批可以同时开工的节点)、`backlog`(就绪但被并发上限挡住)、`waiting`(还差什么)。
### 4、开一个节点、回报
python graph/graphctl.py start --ws "<ws>" --gid "<gid>" --node A --by "执行会话-xxx"
python graph/graphctl.py report --ws "<ws>" --gid "<gid>" --node A --state done --evidence "页面 1440 宽截图在 ui/shot.png"
回报 `done` 后会立刻返回下一批的 `ready`,这就是图在往前走的信号。
### 5、看状态
python graph/graphctl.py list --ws "<ws>"
python graph/graphctl.py show --ws "<ws>" --gid "<gid>"
## 三、就绪的四个条件
1. 依赖全部 `done`。
2. 节点自己是 `pending`。
3. 域不与任何 `running` 节点重叠(`docs/` 与 `docs/ui/` 算重叠)。
4. 加上在跑的节点后,不超过 `max_parallel`(默认 3)。
## 四、与现有机制的关系
| 事项 | 现有机制 | 图模式 |
|---|---|---|
| 目标定义 | `goal.json` | 图文件里的 `title` / `nodes` |
| 任务台账 | `tasks.json` | 图里每个节点的 `status` + `events.jsonl` |
| 派活决策 | 检查会话读摘要判断 | `ready` 按图算 |
| 触发 | 常驻 + 六道闸 + 静默 2 分钟 | 调用方驱动(人或会话),无后台进程 |
| 会话创建 | 自动化排期 | 由调用方决定(图不管怎么开会话) |
| 域锁 | 开工时声明 | 同左,节点的 `domain` 直接喂给开工命令 |
**两套可以同时存在**:图模式不读也不改现有那三份文件,现有机制也永远不会看到 `graph/` 目录。
## 五、怎么跑测试
python graph/test_graphctl.py -v
全绿应看到 `Ran 52 tests ... OK`,用时 15 秒上下(每条用例起一次子进程)。
测试只往临时目录写:`<GRAPH_TEST_TMP>/graph-test-<pid>/<用例名>`,默认 `GRAPH_TEST_TMP` = `AIProject/ai1net-dsh-server/tmp`。每条用例只**新建**自己的目录、跑完不删,整轮收尾由 `tearDownModule` 清一次;这样一次跑下来新建几十个文件、不触发本机沙箱的「批量删除」守卫(那个守卫一次删满 50 个文件就会拦,曾让 20 条用例报 ERROR,而用例本身零失败)。想换落点就设环境变量 `GRAPH_TEST_TMP`。
## 五之二、并发写是怎么保证的
两个进程可以同时对同一张图动手(比如两个节点同时开工),做法是三层:
1. 临时文件名带进程号与随机数,两个进程写字不会撞同一个名字(早期版本用固定名 `graph.json.tmp`,并发时直接报 `PermissionError`)。
2. 每次写都先读最新磁盘状态、在它上面重放本次变更,再原子替换——避免「后写的整份覆盖前写的」导致节点状态静默消失。
3. 图里带 `_rev` 版本号:写完回读核对,发现自己不是最新一版就重读重做,连续三次仍被覆盖才报错退出(要的是「宁可报错,也不静默丢状态」)。
这条路径由 `T9_Concurrency` 的三个用例守着:并发开工两个节点、并发回报两个节点、版本号每次写都前进。
清残留在本机是常态:跑完后 `tmp/` 下偶尔还能看到 `graph-test-<pid>/`(进程内已确认删空,是本机沙箱在进程退出后把删除路径又放回来了,与测试代码无关)。一条命令清掉:
rm -rf "E:/ProgramData/AIProject/ai1net-dsh-server/tmp"/graph-test-*
八个用例类各管一摊:
1、`T1_Init` —— 建图:`--kpi` 按条数生成节点(全角分号也认)、`--nodes-json` 带依赖与域、重复 id 报错、自动编号避让、`max_parallel`。
2、`T2_CheckErrors` —— 硬错三条:环(含自环)、依赖指向不存在的节点、状态非法。
3、`T3_CheckWarnings` —— 警告三类:无域、无判据、域重叠(`docs/` 与 `docs/ui/` 双向都算)。
4、`T4_Ready` —— 就绪集:依赖未满足、域与在跑冲突、超 `max_parallel`(running 占名额)、两个域重叠的节点不进同一批。
5、`T5_Flow` —— 流转:`pending→running→done`、`events.jsonl` 只追加、`blocked` 累加 `attempts`、回报后回带 `next_ready`。
6、`T6_Illegal` —— 非法操作:`running`/`done` 再 `start` 拒绝,`blocked` 可重开;`--state` 只认 `done` / `blocked`;不存在的节点/图报错。
7、`T7_Edge` —— 边界:单节点图、空节点图、gid 含中文(显式与自动生成各一条)、`list` 统计与空工作区。
8、`T8_Isolation` —— 独立性:工作区里预埋的 `goal.json` / `tasks.json` / `collabd-state.json` 跑完字节不变,且不新建这类文件,写入范围只有 `<ws>/.workbuddy/graph/`。
测试对 `graphctl.py` 的唯一要求是「能当命令行跑」,不 import 它的内部函数 —— 所以改内部实现不会连带改测试。
## 六、还没做的(下一步)
- 自动派活:读 `ready` 后自动建排期(要复用现有排期硬规则:cwds 正斜杠、workspace_scope、next_run_at 毫秒、scheduled_at ISO)。
- 判定节点完成:现在靠回报 `--state done`,还没有「看证据够不够」的自动判定。
- 回退边:`attempts` 已经在记,还没有「判不过回到哪个节点」的字段与规则。
这三件都只在本目录内加,不影响现有机制。