不用 SSH、不装数据库:我用 1 个 Python 脚本 + Git,把 15 个 AI Agent 串成了一条流水线
title: CSDN-hive-多机Agent协同任务总线
不用 SSH、不装数据库:我用 1 个 Python 脚本 + Git,把 15 个 AI Agent 串成了一条流水线
发布摘要(粘到 CSDN「摘要」框):多台机器上的 AI Agent 并行改代码,最难的从来不是让它们干活,而是别抢同一张卡、别互相覆盖、干完还查得到。本文复盘蜂巢 Hive 的真实设计——用
git push的非快进失败当分布式锁、lease_id+ fence 拦住旧 worker 复活、任务卡即 Git 文件、六列看板与「搁置」状态,以及 133 张卡跑下来踩过的坑。零第三方依赖,开源。
写在前面:这不是一篇「五分钟搭建多智能体框架」的爽文,也不是概念科普。蜂巢 Hive 是我们每天真在用的工具,跑了 133 张任务卡之后,我觉得值得把为什么这么设计和哪些坑是真踩过的写下来。所有命令、状态、数字都来自实跑快照,不是示意图。
一句话矛盾:跨 Windows / FreeBSD / macOS 的 15 个 AI Agent,能力不一样、有的连 CLI 都没有,怎么让它们并行改代码而不互相覆盖、不重复领活、干完还查得到?
一、起点:聊天窗口管不住多台机器
最开始的协同方式特别朴素:我在对话框里写好一段需求,贴给 A 机器的 agent;跑完了它把结论贴回来,我再贴给 B 机器。很快就有四个问题同时冒出来:
- 谁在干什么,全靠我记。隔一小时回来,分不清哪张卡在跑、哪张卡其实早死了。
- 两个 agent 改同一片文件。各自都能提交,合流时才炸,而且炸完还得人工分辨谁的结果该留。
- 旧进程会「复活」。一个卡在慢命令里的 worker,租约被回收、卡被别人重新领走之后才醒过来,最后把新人的结果 force push 覆盖了。
- 结果散在各台机器的对话里。想复盘「这张卡为什么停在这儿」,只剩一句「看板操作」。
常见解法我都不太想用:SSH 进 Windows 机器(国产 GUI agent 很多压根没有 CLI)、共享目录(网络一抖就全乱)、中央数据库或消息队列(为了跑几个 agent 先运维一套中间件)。
后来想明白一件事:每台机器上唯一肯定有、且天然支持原子操作的东西,是 Git。于是整套设计收敛成一句话——
一个 Python 脚本 + 一个 Git 裸仓当协调仓,用
git push的非快进失败当分布式锁。
不需要 SSH、不需要共享目录、不需要中央数据库。
二、Hive 是什么(以及不是什么)
hive.py 一个 Python 脚本,标准库,零第三方依赖 协调仓(git bare) 只存任务卡、结果、租约、审计 dashboard/ 零依赖静态看板(HTML + 原生 JS + 标准库 serve.py) mcp_server.py MCP stdio server,让 agent 自己来领活 hive_cluster.py 可选:常驻集群,秒级同步 + SQLite store三个角色,职责切得很硬:
| 角色 | 只做三件事 | 不做什么 |
|---|---|---|
| 总控(人或主控 agent) | 写任务卡、分派、验收合流 | 不替 worker 写代码 |
| worker(每台机器一个) | 拉取、原子领取、独立 worktree 干活、推回结果 | 不自己合并 main |
| 看板 | 只读全局 + 少量带鉴权的运维写操作 | 不调度、不做业务判断 |
顺带澄清一个常被误会的点:Hive 和「光明(LightLang)」中文编程语言没有关系。光明是独立项目,早期只是拿它的代码仓当过登记示例。Hive 是自包含的工具,没并入任何其它项目。
三、核心机制:把git push当分布式锁
一张卡的一生大致是这样:
总控写卡 tasks/T-0101.md (state: ready) │ ▼ dispatch 按能力/负载/依赖挑卡 state: assigned ──► inbox/<agent>/T-0101.md │ ▼ worker 原子抢锁:push refs/heads/claim/T-0101(非快进即失败) claim/T-0101 分支 ← 只有一个 worker 能 push 成功 │ ▼ 独立 worktree 干活,push task/T-0101 代码分支 res/T-0101 分支 ← 结果 JSON(verify 全绿才 status=done) │ ▼ 人工 review 或总控 hive land 串行合流 state: done + results/T-0101.json 归档,claim/res 分支删除三套 ref 各管一件事,这是整套设计里我最满意的部分:
| ref | 内容 | 生命周期 |
|---|---|---|
claim/<tid> | 租约:谁领的、lease_id、fence | 只在进行中存在,close 时删 |
res/<tid> | 结果:verify 输出、耗时、head、summary | 合流后归档成results/<tid>.json |
task/<tid> | 代码分支 | 交给 PR / land |
抢锁的原子性完全来自 Git 服务端:两个 worker 同时 pushclaim/T-0101,非快进的那个一定被拒,不需要额外的锁服务。配合每人一个 worktree,agent 物理上就看不到对方没提交的改动。
但只有锁是不够的:fencing
上面第 3 个坑(旧 worker 复活)是纯 Git 模型答不了的:res/*用的是--force-with-lease,本意是「同一张卡重跑能覆盖上一次」,结果恰好也允许旧 worker 覆盖新 worker。
补法是每次领取写两样东西:
lease_id:这次领取的唯一编号,写结果前必须对账;fence:单调递增序号,每次回收 / 重派 +1。
旧 worker 醒来写结果时,lease_id对不上直接拒;就算它绕过校验硬推,res.fence < card.fence也会让land在合流前把这份过期结果认出来。锁决定「谁能干」,fencing 决定「过期的结果不算数」,两层缺一不可。
再加两层才闭环:
| 层 | 防的是什么 |
|---|---|
paths互斥 | 两张不同的卡改同一片文件 → dispatch 就不派下去(粒度是路径,不是整个仓) |
land串行 | 一次只合一张,冲突显式暴露,不静默覆盖 |
四、任务卡长什么样
一张卡就是一个带 frontmatter 的 Markdown 文件,Git 就是数据库,所以天然有历史、有 diff、能离线看:
---id:T-0101title:修复 CSV 导出在中文 Windows 下的编码repo:demobase:mainstate:readyowner:nullpriority:P1needs:[windows]# 能力标签:没有对应 agent 的卡不该派出去paths:# 声明会动哪些文件,防冲突的第一道闸-src/export/csv.py-tests/test_csv.pyverify:# 真跑的命令,不是人肉勾选-python-m pytest-q tests/test_csv.pydepends_on:[]max_retries:3---复现步骤:…… 判据:导出的 CSV 用 Excel 打开不乱码。几个字段是踩坑踩出来的:
verify必须真跑过。曾经有一条验收命令自己写错了(sys.exit却没import sys),代码全对,submit 判 failed。paths要写全。有次顺手改了 README 却没列进 paths,被自己的越界检测抓回来重写。needs要确认有 agent 能满足。写过一张needs: windows的卡,结果四个 agent 一个都没声明这个能力,它就在 ready 里躺成了僵尸卡——现在draft lint会拦。- 有结果才算完成。早期
close无条件写done,于是「中途掐掉」和「真做完」在看板上长得一模一样。现在没res就记closed(作废),并把原状态、owner、失败原因一起写进卡面备注。
五、五分钟跑起来
以下命令都在工作目录执行(比如~/dswork),hive 仓位于./hive;Windows 把路径分隔符换成反斜杠、解释器写python,参数完全一样。
# 1) 拿代码。推荐直接把工具仓推成自己的协调仓,hive.py / docs / windows 都跟着走gitclone https://gitcode.com/skywalk163/hive.git hivegit-Chive remoteaddorigin<你的协调仓地址>git-Chive push-uorigin main# 2) 初始化:补齐 tasks/ inbox/ results/ claims/ agents/ 等目录python3 hive/hive.py--dirhive init# 3) 登记一台机器(agent 名全局唯一)python3 hive/hive.py--dirhive agentadd--namewin-a--modemanual\--capabilitieswindows--repodemo=<本机代码仓路径># 4) 建一张卡python3 hive/hive.py--dirhive task new\--title"修复 CSV 导出编码"--repodemo\--pathssrc/export/csv.py\--verify"python -m pytest -q tests/test_csv.py"# 5) 先看分派计划,再落库python3 hive/hive.py--dirhive dispatch# 只看计划python3 hive/hive.py--dirhive dispatch--apply# 真分派# 6) 各台机器上起 worker(--once 先跑一轮,常驻用 --interval 30)python3 hive/hive.py--dirhive work--agentwin-a--once# 7) 全局视图python3 hive/hive.py--dirhive status python3 hive/hive.py--dirhive doctor# 体检:冲突/僵尸租约/land 滞后python3 hive/hive.py--dirhive stats--json# 成功率、耗时也可以从完全空的裸仓开始(
init --remote <地址>会建目录并推第一版),但那样就没有现成的windows/和docs/,得自己拷。
看板另起一个零依赖静态服务(浏览器打开http://127.0.0.1:8787):
# 同样在工作目录里,脚本会自己定位 hive 仓PORT=8787INTERVAL=20./hive/scripts/serve-dashboard.shworker 一定用
setsid、tmux 或计划任务常驻,我吃过亏:随父 shell 回收,日志静默停在半路,六小时后才发现。
六、看板:一眼看到全局,且不会骗你
六列按单向互斥判定,一张卡只出现在一列:待领取 / 进行中 / 待合并 / 异常 / 最近完成 / 已关闭。租约过期在渲染时重新计算,哪怕快照没刷新,时间一到卡片自己变红落进异常列。
有个细节我特别想安利:响应头带快照时刻,页面右下角显示「数据源:/api/status(快照 N 分钟前)」,超过 10 分钟加警示。起因是刷新循环曾经停摆,/api/status依然返回 200,页面安安静静渲染了一份 2 小时 43 分钟前的数据,害我误判「退回没生效」。接口是活的,不等于数据是新的——这种坑不显式摆出来就只能靠人肉发现。
其它:仓库 / owner / 优先级筛选、文本搜索、详情抽屉(完整结果摘要 + 每条 verify 的命令与退出码)、深浅双主题、窄屏单列。看板写操作(退回、重试、关闭、搁置)默认只在回环绑定下免鉴权,绑到内网必须配 token,每次写操作落一条 append-only 审计日志。
最新加的「搁置」:给做不了的卡一个体面的去处
有些卡当前确实做不了(比如需要 macOS arm64 机器,手边只有 x86_64)。打回待领取会占位置、干扰 agent 领取;一直挂在异常列又太显眼;直接删更不行,删了就真忘了。
于是加了shelved:不算挂账,但不能忘。
- 搁置时释放
claim/res/status/分支、归档结果、收件箱信号和本机/集群租约,推进 fence;但卡面连同「为什么停、什么时候停、之前什么状态」全部留着,连完整结果摘要都抄进卡面折叠块——因为释放res/分支会删掉那份结论,而「不要忘记」不能只留 200 字。 - 排版上没加第七列:六列已经够挤,搁置卡也不该和活跃卡平级。做成顶栏常驻的「搁置 N」+ 一条默认收起的横条,点开才显示卡片与三个出口:放回待领取 / 另立新卡 / 废弃。
- 它不进任何列、不计入活跃统计、
doctor不报、不归档;调度、worker、MCP 领取、land --all全跳过。复制按钮拿到的也不是「请领取任务」,而是一段写明不要领取的搁置说明。
七、让 agent 自己来领活:MCP + 跨机留言
hive mcp是一个标准库实现的 stdio JSON-RPC server,把任务总线暴露成 15 个工具,agent 可以自助闭环:
hive_whoami → hive_next → hive_claim → 干活 → hive_submit └→ 干不下去 hive_abort / hive_block一个 server 进程 = 一个 agent,只能操作自己:不能替别人领卡、提交、终止租约,越权直接返回错误,失败时还会给hint告诉你下一步。
但这里有个很现实的约束:我们登记的 15 个 agent 全是manual模式——国产 GUI agent(小浣熊、Trae、CodeArts、WorkBuddy 之类)很多没有 CLI,没法自己起进程调 MCP。所以看板每张卡上有个「复制派发指令」按钮,复制出来粘到 agent 对话框里。这是最土的办法,也是跨机器、跨客户端最可靠的办法,我们保留着,没硬拗全自动。
跨机沟通同样不走聊天软件:看板右下角「留言」,频道分大厅 /task:T-0231/agent:名字,消息 1~2 秒同步到全网,卡片和详情抽屉里都有「讨论 n」入口,甩一条#chat=task:T-0231的深链过去,人点开就是那个线程。集群没起时降级到本机文件,照样能发能看,只是会明确提示「别的机器暂时看不到」。
八、踩过的坑,比特性更值得写
| 现象 | 根因 | 后来的做法 |
|---|---|---|
| 旧 worker 复活覆盖新结果 | res/*的 force push 认不出过期租约 | lease_id+ 单调fence,写前对账、land 再验一次 |
| 代码全对但判 failed | verify 命令自己没import sys | 卡面入库前手动把每条 verify 真跑一遍 |
| 卡在 ready 里永不派发 | needs写了没有 agent 声明的能力 | draft lint拦截 + 写卡前hive agent list |
| worker 日志静默停在半路 | 没用setsid,随父 shell 回收 | 常驻启动脚本统一收口 |
| 「完成」和「被掐掉」长得一样 | close无条件写done | 有res才done,没有记closed并写现场 |
| 看板显示几小时前的旧数据 | 刷新循环停了但接口仍 200 | 快照时刻响应头 + 超 10 分钟警示 |
| 重试卡反复被字段吞掉 | 重写卡面时未登记字段被丢 | write_card保留自定义字段 |
这些都不是理论上的「最佳实践」,是半夜排障排出来的。它们现在都变成了代码里的守卫或卡面里的一个字段。
九、跑到现在的数据
一份真实的当前快照(不是压测,不是 benchmark):
| 项 | 值 |
|---|---|
| 任务卡 | 133 张(131 done / 2 shelved) |
| 有结果归档 | 129 张(128 done / 1 failed) |
| 登记的 agent | 15 个,跨 Windows / FreeBSD / macOS |
| 涉及仓库 | 4 个 |
| 几个核心文件合计 | 约 1.7 万行(hive.py7.1k、hive_cluster.py3.9k、看板前端 2.6k 等,不含文档与测试) |
两套模式可以叠加:纯 Git模式零依赖、单机也能跑;常驻集群模式(hived+ SQLite store)面向更大规模,目标 70 个并发 Agent × 7 张卡/天、可扩展到 100 节点,选主和复制按 OSPF 的思路做(Hello/Dead、term/vote、分区内多数),Git 退回只写「建卡 / 结果归档 / 合并关闭」三个里程碑。一致性是分层的:claim / lease / result走可复制的 op log,progress走可丢最后几秒的临时通道。
明确不保证:单节点分区最后几秒的进度、分区期间可能重复干活、Gitea 挂掉时 land 延后。写清楚比藏着强。
十、有哪些「故意不做」
- 不自动合并 main。合并留给人 review PR,或总控用
land一次一张串行做,冲突显式暴露。 - 一个 worker 串行跑。要并行就同机起多个 worker + 多个 agent 名,不做复杂的本地调度。
- 不做自动扩缩容、不接 K8s。拓扑是运行态不是配置项,1 台能起、100 台能加,但成员靠发现不靠编排平台。
- 看板不做拖拽改状态、不搞后端数据库。页面只读为主,写操作走 CLI 并留审计。
- 没有中央调度服务。没有那个单点,也就没有那个单点要运维。
适合谁:已经有 Git、机器异构、agent 能力参差、需要「谁在干什么 / 为什么停」可追溯的团队。
不适合谁:单机单 agent、要毫秒级调度、或已经有成熟 CI/CD 编排体系的场景——那套比这个合适。
结语
回头看,这套东西最值钱的不是代码量,而是把「协同」这件事压回到了 Git 已有的语义里:状态是文件、锁是 ref、结果是分支、审计是 commit。于是它天然能离线、能回溯、能用git log查半年前某张卡为什么被搁置,也天然不需要我们再运维一个「协同系统」。
开源部分永久免费,直接用就行。真卡住了、或者要接进自己的多机产线,可以写信聊:hello@quye.com。
- 仓库:https://gitcode.com/skywalk163/hive.git
- 使用指南:仓内
docs/使用指南.md;架构串讲:docs/架构与排障全景.md
如果只记一句:别急着给多智能体协同上中间件,先看看你手里的 Git 能不能当锁用。
标签:AI Agent多机协同GitPythonMCP分布式锁工程实践开源