首页/新闻资讯/正文详情

zcode源码解析 Day2:多个 Subagent 同时改一个文件,为什么没打起来

发布时间:2026/9/25 21:17:59 来源:云帆数科 栏目:资讯中心
zcode源码解析 Day2:多个 Subagent 同时改一个文件,为什么没打起来
zcode源码解析 Day2多个 Subagent 同时改一个文件为什么没打起来本文是 zcode 源码学习系列第 2 篇。Day 1 拆了动态工作流和 AIMD 并发治理器——并发开起来了新的问题随之而来几个子代理并行干活要是同时去改同一个文件怎么办答案出人意料zcode 没有文件锁。它靠一套五层防线其中最关键的是乐观锁式的写入校验。本文从源码把这套防线完整拆开也说明校验与写入之间的竞态边界。一、问题从哪来并行是能力也是麻烦先回忆 Day 1 讲过的背景主 agent 启动后台子代理run_in_background: true后会被立即释放——工具调用马上返回async_launched agentId主 agent 可以继续干活或再开几个代理子代理完成后通过通知回到主 agent 的下一轮。这就是真并行。但并行有个经典隐患两个 agent 的执行流都可能去 Edit 同一个文件。假设子代理 A 和 B 都在改login.tsA 基于自己读到的旧内容算好了改动B 也基于同样旧的内容算好了改动先后写入——后写的把先写的覆盖掉A 的修改凭空消失。这就是并发编程里说的丢失更新lost update。传统的解法是加锁写文件前先锁住别人排队。但翻遍 zcode 的 core 包你找不到任何文件锁、mutex 或 lease 的实现。它的答案是分层防线分工约定减少重叠、调度串行化压缩冲突窗口、写入校验硬兜底、能力与目录隔离收窄战场。下面逐层拆。二、先看清地形只有一个调度者的星型拓扑理解防线的第一步是看清谁有可能同时写。zcode 的答案结构上就只允许一个并行调度者。证据在runtime/methods/subagent.ts——主 agent 构造子代理 runtime 时配置里赫然写着subagents:{backgroundBashMaxMs:this.config.subagents?.backgroundBashMaxMs,enabled:false,// ← 子代理的子代理能力被关死},同时子代理的工具面会被过滤tool/compat.ts里这份黑名单只有两个名字constsubagentDispatchToolNamesnewSet([Agent,Task]);也就是说子代理看不到 Agent/Task 工具不能再 spawn 子代理。递归被结构性禁止谁和谁可能同时写这个问题的决策权收敛在主 agent 一个节点上。再看主 agent 手里的控制旋钮。Agent工具的入参contracts/src/tools/agent.ts只有四个exportconstAgentInputSchemaz.object({description:...,// 一句话标题给人看的prompt:...,// 任务书成为子代理的首轮输入subagent_type:...,// 选角色 profile决定工具面/模型/权限run_in_background:...,// 前台还是后台});注意没有逐次挑工具的口子。工具面由subagent_type选中的 profile 决定Explore只读、general-purpose全量、自定义 profile 自带allowedTools。主 agent 决定的是用哪个角色 干什么活不是这次允许用哪些工具。通信结构同样是星型子代理之间没有直连线路上行只有RespondToCoordinator回到主 agent下行只有主 agent 能用的SendMessage按 agentId 定向投递进子代理正在运行的 turn。兄弟代理要交换信息必须主 agent 居间转述。一个调度者、一棵浅树、没有对等协商——这是所有后续防线的前提分工决策收敛在一个点防冲突才不需要分布式协调。三、防线一分工约定——写在提示词里的纪律最经济的第一道防线是从源头别安排重叠的活。zcode 在三个位置写了纪律位置一后台代理的派遣回执tool/handlers/agent.ts的formatAgentOutputForModel。主 agent 启动后台代理成功后读到的工具结果里有这么一段Do not duplicate this agents work - avoid working with the same files or topics it is using. Work on non-overlapping tasks, or briefly tell the user what you launched and end your response.大白话小弟在改合同你就别也去改合同——要么干完全不沾边的活要么汇报一句就歇着。连output_file都被明令禁止去读读半成品会诱导主 agent 插手重新打开冲突窗口进度的唯一通道是完成通知。位置二Agent 工具的 descriptionbuildAgentProviderDescription。“并行只用于 independent work”、“委派出去了自己就别再干一遍don’t also run it yourself”——把任务独立性定为并行的前置条件。位置三子代理的系统提示subagent/system-prompt.tsDo NOT Write report/summary/findings/analysis .md files. Return findings directly as your final assistant message — the parent agent reads your text output, not files you create.禁掉干完活写个 REPORT.md的习惯一举消除两颗雷多个代理起同样的报告文件名直接写冲突、中间文件变成绕开受控通道的文件黑板。结果只能走最终消息 → 工具结果这条受控管道回家。这一层的效果是概率性的模型大概率遵守但没有机械保证。所以它叫减少重叠不叫杜绝重叠。四、防线二调度串行化——压缩冲突窗口第二道防线让同时尽量少发生四道闸门由小到大闸门一单个 agent 一轮内的工具排队。模型一次响应可能发出多个工具调用工具调度器tool/scheduler.ts先做拓扑排序、再按层分组并行。分组的依据是每个工具的元数据声明——Write和Edit都写着concurrentSafe:false,groupByParallel遇到非并行项会先把当前并行组冲刷出去、让它单独成组。效果同一个 agent 的写操作绝不与其他工具交错执行。闸门二单个 actor 内任务串行。Day 1 讲过AskScheduler保证每个 actor 的 ask 严格 FIFO——actor.current一次只挂一个任务。闸门三跨 actor 并发上限。activeAsks maxConcurrency就不再派发新任务。闸门四进程级模型请求限流。就是 Day 1 拆过的 AIMD 治理器——被限流砍并发、连稳四次才 1间接压低了并行写盘的总量。这四道闸门把冲突窗口压得很小但压不到零。真正兜底的是下一层。五、核心防线readFileState——披着文件系统外衣的乐观锁这是全篇的主角。Edit和Write都采用先读后写与写入前校验源码在tool/handlers/edit.ts和write.ts两者的指纹比较顺序略有差异。第一段先读后写。没读过的文件直接拒绝写入File has not been read yet. Read it first before writing to it.每次Read会在 runtime 的readFileState表里记下这个文件的一组读取指纹interfaceReadFileStateEntry{path:string;content:string;// 读到的内容mtimeMs?:number;// 文件的最后修改时间sizeBytes?:number;// 文件大小revisionId?:string;// 文件系统适配器的版本号...}第二段写入前比对指纹。执行Edit/Write时现场重读文件比对会话中保存的读取状态。Write优先检查revisionId再看毫秒级mtime和大小最后可比对完整内容Edit优先看mtime与大小缺少这些信息时才比较revisionId。mtime前进但完整内容未变时严格完整读取过的文件还可放行。下面是逻辑示意// 简化示意具体优先级在 Edit / Write 中不同if(revisionChanged||mtimeAdvanced||sizeChanged){if(strictFullReadcontentUnchanged)allow();elserejectAsStale();}第三段判定 stale 就拒绝。File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.拿两个子代理改同一文件的场景走一遍时序子代理ARead login.ts → 记住 mtime 10:00 子代理BRead login.ts → 也记住 mtime 10:00 A 执行 Edit查磁盘 —— 还是 10:00没人动过 ✅ 写入成功 文件 mtime 自动推进到 10:05 B 执行 Edit查磁盘 —— 10:05我记的是 10:00 ❌ File has been modified since read拒绝写入 B 重新 Read拿到 A 改过的新版本→ 基于新内容重做编辑 ✅这是一种乐观并发控制不加锁先准备修改记录读取状态提交前比对若发现陈旧版本就拒绝并要求重读。Edit/Write还会把现场读取的expectedRevision传给文件系统适配器在落盘前复核一次这仍不是原子的比较并写入。两个细节见功力版本号是借来的。不是数据库那种自增 version 字段而是文件系统适配器提供的 mtime/大小/revision。外部写入者也能改变这些值因此常见的手动编辑、格式化和另一个 agent 写入都会被检测到毫秒精度内、同大小的变化和竞争写入仍需按下文边界理解。Edit 还有第二道天然防线。就算绕过 mtime 校验A 改过的段落会让 B 的old_string匹配不上——字符串替换直接失败。两道校验一个看时间戳、一个看内容双保险。选乐观锁而不是悲观锁写前锁文件、别人排队也符合场景冲突概率低多数任务各改各的文件加锁的成本和死锁风险反而高偶尔撞车就拒绝一次、重读重来代价很小。六、辅助防线能力隔离与目录隔离能力隔离——减少可能的写者。Explore代理是严格只读的工具面里没有 Write/EditBash 只允许白名单命令ls, git status, git diff, find, grep, cat, head, tail系统提示开头就是 “CRITICAL: READ-ONLY MODE”。自定义 profile 的allowedTools/disallowedTools加上强制剔除规则plan 工具、Agent/Task 派发工具进一步收窄。此外Write/Edit都是needsApproval: true——权限系统还压着一道确认闸。目录隔离——自己产生的文件一人一柜。系统自己要写的文件按归属人分目录物理上杜绝共享/tmp/zcode-agents//agent_1111/output.txt ← 后台代理A的成果 /tmp/zcode-agents//agent_2222/output.txt ← 后台代理B的成果路径天然不同 .zcode/agent-memory/Reviewer/MEMORY.md ← 审查员的记忆 .zcode/agent-memory/Coder/MEMORY.md ← 程序员的记忆各记各的工作流 journal 也按 runId 隔离。防冲突最彻底的方式不是协调而是根本不共享。七、诚实的边界它不是真的锁把话说满之前三点边界值得知道都来自源码注释与逻辑推演校验和写入不是原子的。数据库的乐观锁可以用一条UPDATE ... WHERE versionX原子完成这里的会话指纹校验和适配器expectedRevision复核都发生在最终写入之前。极端情况下两个 agent 同时通过校验再先后落盘会后写覆盖先写last-write-wins。之后某个持有旧指纹的 agent 再写时可能发现冲突但如果没有下一次写入覆盖可能不会自动暴露。路径策略当前不硬拦工作区外的路径。tool/path-policy.ts的注释明说这是有意取舍“Current release intentionally does not hard-block paths outside workspaceRoot”等权限适配器长出显式 ask/deny 规则再收紧。兜底是快照回退。workspace 有 checkpoint文件快照机制真发生覆盖式事故可以整体 rewind 回存档点——这是防线之外的安全网。八、总结zcode 防止多个子代理同时改一个文件的完整答案结构上星型拓扑只有主 agent 一个并行调度者子代理不能递归 spawn约定上提示词三处纪律——别抢小弟的活、并行只给独立任务、结果用嘴说别写文件调度上工具级串行组、actor FIFO、maxConcurrency、AIMD 四道闸门压缩冲突窗口校验上readFileState 与expectedRevision两次检查——先读后写常见 stale 写入会被拒绝并要求重读隔离上只读角色减少写者、产物目录一人一柜。一句话共享文件靠读取指纹和落盘前复核发现大多数过期写入系统产物靠分目录降低共享但这不是原子文件锁。

相关推荐

Playwright MCP Server 使用指南:让 Cursor 拥有浏览器自动化能力
Playwright MCP Server 使用指南:让 Cursor 拥有浏览器自动化能力

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 21:17:59

被说“太强势”时怎么回应:边界、权限与表达成本判断框架
被说“太强势”时怎么回应:边界、权限与表达成本判断框架

摘要:听到“你太强势了”时,人往往不是立刻进入理性分析,而是先委屈、想反驳,或开始怀疑自己。本文不把“强势”当作人格标签,而是把它拆回边界、角色权限、实际表达、对方接收和表达成本五个问题。目标不是无限优化别… · 2026/9/25 21:17:59

KKPrinter虚拟打印机:注册表改端口与属性实现跨网打印共享
KKPrinter虚拟打印机:注册表改端口与属性实现跨网打印共享

简介:面向需要实现跨网络共享打印、二次开发虚拟打印机的开发者与运维人员。资源包内含基于修改系统注册表打印机属性参数的KKPrinter实现方案,核心思路是让客户端通过虚拟打印机拦截打印文件,再转发至物理打印机完成远程打印,适用… · 2026/9/25 21:17:53

免费API接口资源整理与对接避坑指南
免费API接口资源整理与对接避坑指南

在日常开发里,API接口这件事几乎躲不掉。我做了几年后端和全栈开发,最头疼的不是自己写接口,而是接三方服务时找不到合适的免费API。市面上的接口平台不少,但很多要么隐藏收费陷阱,要么文档含糊其辞,真正能… · 2026/9/25 21:50:34

通达信超前MACD指标:源码、实战细节与未来函数识别,从原理讲到Python验证
通达信超前MACD指标:源码、实战细节与未来函数识别,从原理讲到Python验证

如果你在通达信里搜“MACD改进”“MACD超前”这类关键词,大概率会翻到一堆信号图亮得离谱的指标源码——红柱总是先一步出现,绿柱逃顶从来不含糊,复盘曲线像被剧本写好了一样。但等你真装进软件,盘后回看全是神操作,实… · 2026/9/25 21:50:28

快餐门店数字化降本增效:适配快餐店的门店管理系统选型分析
快餐门店数字化降本增效:适配快餐店的门店管理系统选型分析

快餐行业作为本地生活消费的核心赛道,具备出餐快、客单低、客流集中、周转高频的典型业态特征,门店盈利高度依赖人效、坪效与库存周转效率。在后疫情时代消费趋于理性、门店人力与食材成本持续走高的行业背景下,传统快餐门店人工记账、手动盘… · 2026/9/25 21:50:09

千笔AI解答:论文AIGC检测与AI降重工具常见疑问
千笔AI解答:论文AIGC检测与AI降重工具常见疑问

论文aigc率多少算正常 目前不同高校、期刊对论文AIGC率的合格标准没有统一的规定,主流的要求区间通常控制在10%-30%以内。千笔AI平台结合大量高校送检案例整理了常见的标准参考如下: 场景合理AIGC率区间说明本科毕业论文≤20%部分宽松院校可放宽至30%硕士… · 2026/9/25 21:50:09

现在性价比高的AI写作辅助网站有哪些品牌?学生党亲测反馈
现在性价比高的AI写作辅助网站有哪些品牌?学生党亲测反馈

每到期末、毕业答辩、课题申报阶段,很多学生都会陷入论文写作的焦虑中:选题毫无头绪、大纲搭建逻辑混乱、正文撰写耗时长、参考文献格式出错、查重重复率偏高、AIGC检测告警、本校论文排版标准复杂。依靠纯人工从零开始撰写、一遍遍修改格式和降重&#… · 2026/9/25 21:50:09

php人民币金额转大写
php人民币金额转大写

思路&#xff1a;分整数跟小数两个部分处理整数部分&#xff1a;从后往前按四位分组后加万、亿单位&#xff0c;每四位里面最后的零不要&#xff0c;中间的零不加修辞单位<?php $s 100,3401,7890.76; //壹拾壹万贰仟柒佰玖拾$daxie rmbUpper($s);var_dump($s); var_… · 2026/9/25 21:49:57

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码