Flowness 真实问题 · 第 1 期 · 阅读系列导读 →
文章里碰到不熟悉的内部概念,在这里查。词条按主题分组,每条一到三句,只写文章里已经写过的内容;文章写得含糊的地方,这里同样含糊。“出现在”后面是文章标题的前半句,对应关系见文末。
系统与参与者
Flowness:本系列讲的多 agent 工程系统,属于 Harness 这一类;Harness 指围绕 AI agent 运行的工程系统,负责分派任务、准备上下文、检查结果。这一类系统在业内常称为 agent harness。 出现在:《两个进程在交替写账本》、《一个任务派出 9 个执行会话》、《只见过“通过”的检查》、《子 agent 拒绝写入的那条记录》、《上下文只用了一半就被压缩》
负责人:文中下决定的一方,例如批准程序启动的会话一律关闭 advisor,裁定同一任务被反复派发要用机制解决、不要逐个修补,也会向协调 agent 提问(例如问某个任务为什么反复重做)。 出现在:《一个任务派出 9 个执行会话》、《上下文只用了一半就被压缩》
协调 agent:长期运行的 Claude Code 会话,负责拆分工作、派出子 agent、收回结果、处理异常,也亲自执行写账本这类动作。 出现在:《两个进程在交替写账本》、《一个任务派出 9 个执行会话》、《子 agent 拒绝写入的那条记录》、《上下文只用了一半就被压缩》
子 agent(subagent):协调 agent 通过 Claude Code 的 Agent 工具或 workflow 派出的一次性会话;一个 workflow 可以同时运行若干个。 出现在:《两个进程在交替写账本》、《子 agent 拒绝写入的那条记录》、《上下文只用了一半就被压缩》
执行会话:后台运行的 Claude Code 会话,领一个任务去做,结束时应当写一条终态记录。 出现在:《一个任务派出 9 个执行会话》、《上下文只用了一半就被压缩》
规划会话:读执行会话中止的原因、修改任务包并发布新版本的会话;这个过程叫重新规划。终态“需要重新规划”表示验收条件或规划有问题。 出现在:《一个任务派出 9 个执行会话》
程序启动的会话:编排程序调用 Claude Code 命令行启动的会话,方式是后台会话(claude --bg)或一次性无界面进程(claude -p),交给它一个任务。与它并列的另外几类会话是:人在终端里手动打开的手动会话,以及由这些会话再派出的子 agent 和队员会话。 出现在:《上下文只用了一半就被压缩》
手动会话:人在终端里手动打开的 Claude Code 会话,区别于程序启动的会话。 出现在:《上下文只用了一半就被压缩》
队员会话(teammate):Claude Code 的 Agent Teams 功能启动的协作会话。 出现在:《上下文只用了一半就被压缩》
顾问:Flowness 内部的一个机制:执行会话遇到自己无法决定的问题时发出咨询,由另一个使用更强模型的会话给出裁决;它和 Claude Code 自带的 advisor 工具不是一回事,两个词不互换。 出现在:《一个任务派出 9 个执行会话》
advisor 工具:Claude Code 提供的一个服务端工具,与 Flowness 内部的“顾问”机制不是一回事;从会话记录的形态看,主模型在处理一个请求的中途调用它,第二个模型读取对话后给出建议,再由主模型继续。我们看不到它被问了什么、答了什么:记录里调用的输入字段恒为空,返回内容是加密的。 出现在:《一个任务派出 9 个执行会话》、《上下文只用了一半就被压缩》
派发信:协调 agent 写给子 agent 的任务说明。 出现在:《子 agent 拒绝写入的那条记录》
结果文件:子 agent 在运行过程中写到磁盘上的产出文件,协调 agent 随时可以读。它带产物,不带子 agent 对“这份产物该不该用”的判断;那层判断写在最终报告里。 出现在:《子 agent 拒绝写入的那条记录》
最终报告:子 agent 完成时写进 workflow 运行日志的报告,全部子 agent 结束后才一次性正式交回协调 agent。子 agent 对产物该怎么用、能不能落账的判断写在这里。 出现在:《子 agent 拒绝写入的那条记录》
错误记录文件:协调 agent 接班时使用的文件,事故之后会把这次事故和相应的规则写进去。 出现在:《两个进程在交替写账本》、《子 agent 拒绝写入的那条记录》
进程登记工具:启动后台脚本的辅助工具:启动时给脚本一个标签,同标签的进程已登记且还活着就拒绝再启动;检查状态时同时核对进程号和进程启动时间。它最初是为了避免误杀进程(只停止自己登记过的进程)而写的。它是可选的,脚本不通过它启动,就没有单实例保护。 出现在:《两个进程在交替写账本》
账本与提交
事件账本(append-only event log):只追加的 JSONL 日志,是系统共享状态的记录:每条事件有全局递增的序号和时间戳,写入后不能修改或删除,订正只能再追加一条新事件。每条事件还记录写入者标识,包括写入时所属的会话编号和写入身份,不包括进程号。 出现在:《两个进程在交替写账本》、《一个任务派出 9 个执行会话》、《只见过“通过”的检查》、《子 agent 拒绝写入的那条记录》
撤销事件:在只追加的账本里订正一条记录的办法:追加一条新事件,让重放出的状态停用那条记录。文中的例子是“撤销边”事件:让概念图停用后写的那条重复的边。 出现在:《两个进程在交替写账本》
概念图(projection):从账本事件重放出来的当前状态视图,记录设计对象、工程组件以及它们之间的关系边;内部也叫“投影”。读图命令每次读取前会先追上账本的最新位置。一条边在提交过程中,对其他读者不可见。 出现在:《两个进程在交替写账本》
提交关口(commit gate):所有写账本操作的必经之路:按顺序运行一串检查函数,全部通过才把改动连同一条接受记录写入账本;有一项拒绝,这批改动不写入。一次提交在账本里留下 3 条记录:提交申请、改动本身、关口的接受记录。 出现在:《两个进程在交替写账本》、《一个任务派出 9 个执行会话》、《只见过“通过”的检查》、《子 agent 拒绝写入的那条记录》
提交临界区(critical section):提交关口内部由一把全局互斥锁(提交锁)保护的区段,同一时刻只允许一个进程进入。文中的例子里,这把锁让两个写入者的记录按提交顺序一条接一条地排进账本,写入者标识又相同,所以从账本看不出来源。 出现在:《两个进程在交替写账本》
读取基线:写入方在提交里声明的“读取状态时账本走到了第几条”;提交关口据此做乐观并发保护(optimistic concurrency control):检查自那个位置以来,写入方要读写的对象有没有被别人改过。声明为 0 时,关口跳过这项检查。 出现在:《两个进程在交替写账本》
接受记录:提交被接受时写入账本的记录,列出这次提交跑过的每道检查和它的结果。每道检查的结果只有三种:通过、不适用、本来会拒。 出现在:《两个进程在交替写账本》、《只见过“通过”的检查》
拒绝记录:提交被拒绝时账本里留下的记录;被拒绝的改动不写入,记录里写的是失败原因的名称(例如“概念编号已存在”),不是检查的名称。 出现在:《两个进程在交替写账本》、《一个任务派出 9 个执行会话》、《只见过“通过”的检查》
任务与派发
任务包:一个任务的规格:要做什么、允许修改哪些文件、验收条件是什么;任务包每发布一个版本,系统计算一个哈希值记入账本。 出现在:《一个任务派出 9 个执行会话》、《上下文只用了一半就被压缩》
验收条件(acceptance criteria):任务或待办的完成条件,写成程序可以复算的形式,例如某项测试通过,或某个读数不超过某个值。 出现在:《一个任务派出 9 个执行会话》、《只见过“通过”的检查》、《子 agent 拒绝写入的那条记录》
终态记录:执行会话结束时写的记录,结果有五种:成功;需要重新规划;无法完成;需要外部动作;经顾问裁决后中止。“需要重新规划”表示验收条件或规划有问题;“需要外部动作”的例子是等某条分支合并。 出现在:《一个任务派出 9 个执行会话》
不一致记录:执行会话在复测中发现验收条件与实测不符时,在账本里登记的记录;这类记录必须处理后,会话才能成功结束。 出现在:《一个任务派出 9 个执行会话》
完成检查:执行会话写“成功”终态时,提交关口要求通过的若干检查。 出现在:《一个任务派出 9 个执行会话》
派发程序:每 20 分钟由定时任务运行一次的程序:先算出当前可以派的任务,再对每个任务调用单任务派发命令,启动一个执行会话。 出现在:《一个任务派出 9 个执行会话》
单任务派发命令:派发程序对每个任务调用的命令;关于重派次数,据代码阅读,它只读取熔断标记是否存在,不比较重试计数。 出现在:《一个任务派出 9 个执行会话》
常驻编排服务:系统原有的常驻服务,做批量派发;同一任务的重派上限和重派耗尽的告警都在它的批量派发里。 出现在:《一个任务派出 9 个执行会话》
编排程序:调用 Claude Code 命令行自动启动会话的程序;它的派发记录里有完整的启动命令和会话 ID。文中没有说明它与派发程序、常驻编排服务是不是同一个程序。 出现在:《上下文只用了一半就被压缩》
派发标记:一个文件,存在就表示这个任务已经派过、不要再派;按事故当时的规则,执行会话以非成功结果结束后,系统自动删除它,并把重试计数加一。执行锁和待处理标记也是防止同一任务被重复启动的文件。 出现在:《一个任务派出 9 个执行会话》
会话锁:执行会话用到的一把锁;文中只写到一个执行会话先直接释放了它,之后开新一轮时被拒绝,没有说明它保护什么。 出现在:《一个任务派出 9 个执行会话》
重试标记:存放某个任务自动重派次数的文件,放在这个任务的目录里;失败后自动重派时计数加一。据代码阅读和运行时计数文件,在单任务派发命令这条实际运行的路径上,没有程序读取它。 出现在:《一个任务派出 9 个执行会话》
重派上限(retry limit):同一任务最多自动重派几次的上限,默认是 3 次,超过就熔断,即不再自动重派。它只在常驻编排服务的批量派发里比较重试计数。 出现在:《一个任务派出 9 个执行会话》
熔断标记:与重派上限配套的标记;单任务派发命令关于重派次数,只检查这个标记在不在。 出现在:《一个任务派出 9 个执行会话》
同包重派:派发时,这个任务最近一次终态不是成功、而且之后没有发布过新版本的重派,即用上一次失败时的同一份任务包再派一个会话。 出现在:《一个任务派出 9 个执行会话》
输入等价 token:把缓存读取、缓存写入和输出按价格比例折算成输入 token 后得到的数;是估算口径,不是账单。 出现在:《一个任务派出 9 个执行会话》
检查与评审记录
观察模式(shadow mode):检查只记录、不阻断:判为不合规时记“本来会拒”,提交本身仍被接受。 出现在:《只见过“通过”的检查》
本来会拒:观察模式下的检查判为不合规时写下的结果:检查认为这次提交不合规,但不拦下提交。接受记录里每道检查的结果只有三种:通过(检查认为合规)、不适用(检查认为这次提交里没有它要判断的对象)、本来会拒。 出现在:《只见过“通过”的检查》
已跳过:检查结果的附注:检查在缺少所需材料(例如找不到上下文包或基线)时放行并标记为已跳过,但结果字段仍记“通过”,只有附注里写了“已跳过”。 出现在:《只见过“通过”的检查》
阶段衔接检查:一组检查:一项工作从设计阶段进入工程阶段、再进入团队共识时,检查交接的对象是否合格。 出现在:《只见过“通过”的检查》
共识候选:工程设计对象上的一个标记,表示提议把它写进团队共同遵守的约定;带这个标记的对象,状态必须已是“已接受”,并且附有书面理由。 出现在:《只见过“通过”的检查》
工程设计对象:系统里记录的设计侧对象,例如一个组件或一项工程决策;它有“草稿”“已审阅”“已接受”等状态。 出现在:《只见过“通过”的检查》
上下文包:系统发给 agent 的一份工作材料,里面划定了它可以改动的范围。一组检查据此判断提交是否越出了它所基于的上下文包划定的范围。 出现在:《只见过“通过”的检查》
测试守卫:代码合并前必须通过的一组测试,每一组针对一类曾经出过的问题。规则要求每类守卫附带一条判别力测试:人为构造的同类错误必须让它报错,正确写法必须让它保持通过。 出现在:《只见过“通过”的检查》
健康度报告:系统定期生成的报告,汇总各项工作的运行状况;其中一个指标设计上有四种取值:活跃、休眠、从未活跃、数据不足。 出现在:《只见过“通过”的检查》
可信等级:系统给设计中的每个概念记录的等级;裁决记录可以作为升级的依据。有一条规则:来自按定义不产出裁决的角色的裁决记录,不能用来升级。 出现在:《子 agent 拒绝写入的那条记录》
待办:系统里登记的未完成事项,每项附有验收条件。 出现在:《只见过“通过”的检查》、《子 agent 拒绝写入的那条记录》
问题记录:账本里的一种事件类型:登记一个问题。需求追溯评审这个角色唯一的产出就是问题记录。 出现在:《子 agent 拒绝写入的那条记录》
裁决记录:账本里的一种事件类型:某个评审角色对某个对象给出了通过或不通过的裁决。记录里有“裁决类型”字段,由写入方填写。写入命令会检查评审是否由独立启动的进程完成、裁决内容是否为空,不检查这个角色是否应该产出裁决记录。 出现在:《子 agent 拒绝写入的那条记录》
评审角色:写在角色说明文件里的一组行为规范,规定这个评审角色做什么、产出哪种记录。 出现在:《子 agent 拒绝写入的那条记录》
需求追溯评审:一个评审角色:对设计里的每个元素追问“这个元素服务哪条需求”,三步之内追溯不到需求的,就记一条问题记录,指出这里可能是多余设计或缺失需求。它的角色说明文件写明:唯一的产出是问题记录,不产出裁决记录。 出现在:《子 agent 拒绝写入的那条记录》
会话记录与自动压缩
会话记录(session transcript):Claude Code 为每个会话写下的 JSONL 文件;模型的每个内容块各写一行,同一个请求产生的多行带着同一份用量数据(usage),所以一行不等于一个请求。 出现在:《两个进程在交替写账本》、《一个任务派出 9 个执行会话》、《子 agent 拒绝写入的那条记录》、《上下文只用了一半就被压缩》
自动压缩(auto-compact):上下文变大时出现的一种现象(我们的观察):会话记录里留下一行压缩记录,之后会话的历史被一份摘要取代。在我们的系统里,压缩之后需要重新读取之前读过的大文件。 出现在:《上下文只用了一半就被压缩》
压缩记录:会话记录里发生自动压缩时留下的一行(subtype 为 compact_boundary),其中的 preTokens 字段是客户端记下的“压缩前上下文大小”。 出现在:《上下文只用了一半就被压缩》
压缩窗口:我们为程序启动的会话设置的上下文上限;调低它会让自动压缩更频繁。 出现在:《上下文只用了一半就被压缩》
顶层用量:会话记录每行用量数据顶层的 token 数;在我们核对过的含 advisor 请求里,它是主模型各段推理读入量之和,advisor 那一段不计入。所以它回答“读了多少”是对的,回答“上下文有多大”会大约多出一倍。含 advisor 的请求,usage 里还有分段数组 iterations,每一段是一次模型推理:主模型的段标为 message,advisor 的段标为 advisor_message。 出现在:《上下文只用了一半就被压缩》
真实上下文:一个请求真正占用的上下文大小,取这个请求里主模型各段推理中最大的一段。 出现在:《上下文只用了一半就被压缩》
倍增压缩:压缩记录的“压缩前大小”约为真实上下文 2 倍的自动压缩;判据是:压缩前最后一个请求的顶层用量不小于主模型最大一段的 1.8 倍,并且压缩前大小与这个顶层用量相差不超过 10%。 出现在:《上下文只用了一半就被压缩》
虚假压缩:每日成本报表里的一项统计:压缩前大小超过真实上下文 1.3 倍的自动压缩。 出现在:《上下文只用了一半就被压缩》
每日成本报表:按日统计会话成本的报表,由脚本生成;它目前只读每个项目目录下的顶层会话记录,不读子 agent 的记录。 出现在:《上下文只用了一半就被压缩》
本系列的写法
文章标题对照
- 《两个进程在交替写账本》:两个进程在交替写账本,账本里看不出是两个写入者
- 《一个任务派出 9 个执行会话》:一个任务派出 9 个执行会话,只有最后一个成功结束
- 《只见过“通过”的检查》:只见过“通过”的检查,不能证明它在工作
- 《子 agent 拒绝写入的那条记录》:子 agent 拒绝写入的那条记录,协调 agent 自己写了
- 《上下文只用了一半就被压缩》:上下文只用了一半就被压缩:advisor 调用与我们自己的计数错误