Skip to content

feat(hive): BBS 树形结构 + 任务原子锁防双抢#446

Open
YinZT1 wants to merge 2 commits into
lsdefine:mainfrom
YinZT1:feat/hive-bbs-tree-claim
Open

feat(hive): BBS 树形结构 + 任务原子锁防双抢#446
YinZT1 wants to merge 2 commits into
lsdefine:mainfrom
YinZT1:feat/hive-bbs-tree-claim

Conversation

@YinZT1
Copy link
Copy Markdown
Contributor

@YinZT1 YinZT1 commented May 20, 2026

概述

给 Hive 模式的 BBS 加两层结构,让多 worker 协作可见、可锁:

  1. parent_id 树形 — 帖子形成父子关系,前端用横向树展开「admin 公告 → master 任务 → worker 接单/完成 → master 验收 → 追加任务」的右展 pipeline
  2. /claim 原子锁 — 同一 task 只能被一个 worker 抢到,靠 SQLite 行级 UPDATE 保证原子性

两条独立配套:树是展示层(不强制),锁是协调层(机制硬约束)。旧 endpoint(/post /poll /posts /file/*)行为完全保留,向后兼容。


复现实验

memory/goal_hive_sop.md 启动 hive:BBS + admin 公告 + 2 worker + 1 master,预算 10 分钟。

admin #1 启动公告(原文,跑的时候用的就是这个;路径占位换成你自己的 hive 工作目录):

[Hive 启动公告]

# 任务目标
在 <HIVE_CWD>/ 目录下,写三个**彼此独立**的单文件命令行工具:
1. `calc.py` — 命令行计算器,支持 `+ - * /` 和括号
2. `todo.py` — TODO CLI(add/list/done + JSON 持久化)
3. `weather.py` — Mock 天气查询

三个工具完全独立,可以并行。

# Hive Master 职责
1. 你**负责任务调度和团队组织**,不允许亲自干活导致 worker 空转
2. 终极目标是完美的找不到任何问题的任务交付结果
3. 针对任务目标设计要做的子任务,发到bbs上,worker会接任务并完成
4. **识别可以并行的子任务,在一次 code_run 里同时投递多条任务帖**
5. 只要时间没到,就持续验收结果、检查问题、寻找下一个改进点
6. 时间没到不允许交付
7. **所有 POST /post 必须带 parent_id**:
   - 第一批响应用户目标的任务 parent_id = 本启动公告的 id(=1)
   - 验收某个 worker 交付的 parent_id = 那个 [完成] 帖 id(不是 task 帖 id)
   - 基于某个验收结果发追加任务的 parent_id = 那个验收帖 id
   - 最终汇总报告 parent_id = 最后一个验收帖 id
8. 你**不需要**调 /claim,你是创建者不是认领者

# Worker 行为约束
- worker 看到任务后,**必须先 POST /claim 拿锁**才能干活
- 第一个抢到锁的 worker 独占任务,其他 worker 收到 409 应立即放弃
- 锁能从机制层防止双抢,比之前只靠协议自觉强很多

# BBS 树形规则
- master 第一批任务 → parent_id = 本公告id
- worker 接单/完成 → parent_id = 任务帖id
- master 验收 → parent_id = worker 的 [完成] 帖 id
- 追加任务 → parent_id = 验收帖 id

# 工作目录
<HIVE_CWD>/

# 注意
此为最终目标,worker不要接单,先等hive master拆分子任务。

三个 CLI 任务彼此独立、可并行,每个 30 秒到 2 分钟级别的 LLM 推理工作量。无锁版本去掉公告里的 "Worker 行为约束" 段就行。


三轮初步观察

配置 结果
1 无锁、无 parent_id worker-1 接 calc,worker-2 接 weather → todo。没撞,纯运气
2 有 parent_id、无锁 TASK-1 ✓ 单 worker;TASK-2 双抢双交付TASK-3 双抢双交付
3 有锁 3 个 task 各自被 1 个 worker 独占,零 race;master 后续 4 轮追加任务(验收 → 派修复 → 再验收)全程零 race

第 2 轮最戏剧的是 TASK-2:worker-2 已经发了 [完成] 帖,worker-1 在 60 秒后又接同一 task 重做了一遍,覆盖了 worker-2 的文件。说明纯协议级约束(SOP 原本写的"确认最早接单")根本拦不住 LLM 自觉失败——抢占窗口贯穿整个 task 生命周期,不只是发布瞬间。

第 3 轮加 /claim 之后:任何 worker 抢同一 task 第二次都拿 409,prompt 强制它放弃这个 task 去找下一个。前端 🔒 标徽显示锁持有者。


加锁后的多场景验证(5 个不同任务形态)

为确认锁在不同负载/协作模式下都稳,又跑了 5 轮各异的场景:

场景 锁验证结果
1 3 个独立 CLI(baseline 复现) ✅ 3 task 全单 claim
2 refinement chain 单文件迭代 ✅ 1 task 单 claim(场景轻,master 多自查)
3 5 个小并行任务(压力测试) ✅ 5 task 全单 claim;worker-2 抢光,worker-1 优雅退出(拿到 409 主动 [待命])
4 并行 helper + merge 阶段 ✅ 3 task 全单 claim,阶段 A→B 串接正确
5 模糊探索(master 自由设计) ⚠ master 选择 53 turn 自己干完未派单——锁未触发,但非锁问题

5 轮下来 0 次双抢/双 [接单] 出现,SQLite 行级锁在所有用到的场景下表现一致。

另观察到一个不属于本 PR 的 dispatch 公平性问题:单 worker 容易抢光多 task。锁让"输的人 409 后优雅退出",这是正确的;但如何避免一个 worker 包揽,是 dispatch 层的事,不在本 PR 范围。


改动文件(3 个)

文件 + 内容
assets/agent_bbs.py 200 37 parent_id 字段 + /tree 接口 + claimed_by 字段 + /claim 接口 + 前端 HTML 重写(横向树 + worker 合成卡片 + history 内嵌 + 🔒 锁标徽 + ⚠ race 警告)
memory/goal_hive_sop.md 13 2 Master 职责加并行 dispatch + parent_id pipeline 规则
reflect/agent_team_worker.py 8 6 worker prompt 加 /claim 前置 + parent_id 规则

合计 +214 / −52 行。


已知仍存在的坑

task 锁只保证「一个 task 一个 worker」。但两个不同 task 的 worker 各自合法接到自己的 task 后,如果两个 task 都改同一个文件,file_write 还是会撞——典型场景:master 发 task A「改 utils.pyadd()」+ task B「改 utils.pymul()」,两个 worker 各自合法持锁,但 file_write 仍然后写覆盖先写。

task 锁拦不住这种文件层冲突——冲突在文件层不在 task 层。这个坑保留,后续单独 PR 处理。


测试

  • 单元 smoke:10 个线程并发 /claim 同一 task post,1 个赢 9 个 409,原子性确认(SQLite 行锁可靠)
  • 端到端:5 轮不同任务场景见上,5/5 零双抢
  • 兼容性:旧 endpoint 行为完全保留,只是返回多了 parent_id / claimed_by 字段;旧 client 忽略新字段照常工作

Test plan checklist

  • /claim 原子性:并发抢同一 task 只有 1 个赢
  • 加锁后端到端:5 轮多场景零双抢
  • 树形展示:前端横向树正确渲染 parent_id 关系
  • 兼容旧 client:不带 parent_id 的 POST 仍然能成功

后续观察点

  1. LLM worker 偶尔会忽略 /claim 直接发 [接单]——前端 "LOCK BYPASSED" 标徽用来检测这种协议违反,方便调试
  2. parent_id 不强制(POST 不带也能成功),靠 prompt 引导;违反时前端会形成孤立节点便于发现

YinZT1 added 2 commits May 21, 2026 02:21
…tion

Add parent_id field + /tree endpoint to agent_bbs.py so posts form a task
tree rather than a flat board. Frontend rewrites to a left-to-right tree:
fixed-width cards, synthesized worker nodes per (task, worker) pair with
embedded communication history, and pipeline-style state badges that
visualize double-claim races (⚠ N workers claimed / delivered by N workers).

Worker and master prompts updated so they post with parent_id pointing to
the relevant task / completion / verification, producing a right-extending
pipeline TASK → 接单 → 完成 → master 验收 → 追加TASK with no client-side
reparenting needed.
…laim

Add claimed_by column to posts + new POST /claim endpoint backed by SQLite
row-level atomic UPDATE WHERE claimed_by IS NULL. First worker to claim
wins (200); concurrent attempts get 409. Verified atomic under 10-way
concurrent claim race in unit smoke test.

Worker prompt updated to require /claim before any work — 409 means
abandon the task and look elsewhere. Frontend shows 🔒 locked badge on
claimed tasks; "LOCK BYPASSED" warning kept as a guard for the case
where an LLM ignores the protocol.

E2E with 2 workers + 3 parallel tasks (calc/todo/weather): previously
double-claimed 2 of 3 tasks under load; now each task is claimed by
exactly one worker, no race observed across master verification and
follow-up cycles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant