首页 / 文章

Claude Code的生产级架构:从Slack到合并代码请求的六层结构

六层架构如何将Slack工单转化为合并后的代码:事件接收、受限代理循环、持久状态、自我修复功能、可观测性以及操作员控制。

发布者:REACTAPP.TOP阅读约 42 分钟9771 词

来源: Building a Production Agent Harness: Turning Claude Code Into a Multi-Agent Engineering Pipeline

将编码模型单独放置,就只会得到一个“罐子里的大脑”。它能正常编写代码和调用函数,但却无法在凌晨3点查看你的Slack私信,无法重新尝试失败的CI任务,无法解决其正在处理的合并请求中出现的冲突,也无法记住昨天有审稿人提出了问题却无人回复。

让模型变得实用的关键在于配套的框架:即为它提供感知能力、操作手段和记忆功能的运行时支撑结构。该框架的组成部分包括事件接收、智能体协调、持久化状态存储、能够自动修复错误的循环机制、可观测性功能,以及供人类操作员进行控制的手段。下面描述的系统是实际投入使用的版本,而非研究演示版本。它能监控Slack频道,针对五个内部仓库创建合并请求,在夜间回复审稿人的意见,并在无人要求的情况下自动修复失败的CI任务。

这是一个基于 Claude Code 构建的内部多智能体流程系统。它已连续运行数月,负责从 Slack 获取任务并分配实际的工程工作。以下是从整体到细节的说明:它的功能、各组成部分之间的连接方式,以及影响设计形成的各种实际问题。

内容将按以下顺序展开:为何需要这样的框架、该框架的具体功能、它解决的三个问题、六个层级(事件接入、智能体协调——重点介绍自主循环与自我改进层、持久状态管理、自愈机制、可观测性以及人工控制)、层级之外的额外功能、端到端的流程演示,以及其他类似框架的对比。

为何仅靠聊天窗口和部分工具不够

2023年当聊天助手插件出现时,许多团队选择了显而易见的捷径:在大型语言模型前端添加聊天界面,提供一些函数调用工具,然后将输出结果称为“工程代理”。这种方案在演示环境中勉强可行,但根本无法应用于实际生产环境。不到一周就出现了三种故障模式。

  1. 操作员关闭标签页后上下文就会丢失。记忆功能必须能够持续保存多个聊天会话的内容。而真正的工程任务往往需要数天时间:创建工单、创建分支、起草内容、等待审核、回复评论、等待持续集成测试通过,最后合并代码。没有任何一个模型调用能够承载所有这些流程。
  2. 无法对外部变化做出响应。审核员在下午4点发表评论,第三次提交时持续集成测试失败,同事又在讨论串中回复。这类事件发生时代理必须能够立即响应,而不能一直轮询或等待人工再次触发它。
  • 故障永远无法恢复。代理程序尝试执行操作,持续集成流程失败,操作员不得不第二天重新开始并从头解释故障原因。或者代理程序与身份提供方的会话在任务执行到一半时过期,整个流程就会无声无息地终止。这类情况每天都在发生,工程师们往往在晚上或周末被叫去处理本应由代理程序处理的任务。
  • 解决这些问题正是工具框架的职责。这也正是普通聊天界面与 Claude Code 本身、Cursor 的后台代理、Devin 或此处描述的系统之间的区别所在。它们底层的模型几乎可以互相替换,不同的在于包裹在模型之外的工具框架。

    模型只能通过工具框架与外界交互,而几乎所有让大型语言模型表现得像一个系统的工程工作都是由工具框架来完成的。

    该工具的设计初衷

    最初的问题很具体。有一个名为#support的Slack频道,每周会收到大约30份工单。每份工单都需要进行调查,之后通常还需要在五个代码仓库中的一个中进行代码修改。一份工单的处理往往会持续数天。审查人员会对合并请求发表意见,持续集成测试也会出现故障,有些调查结果竟与上个月的重复。负责处理这些工作的团队总是在晚上和周末被叫去处理问题,而且由于同样的工单类型不断重复出现,情况始终没有改善。

    该工具承担三项职责,按干预深度由浅入深排列:

    1. 调查。当有新工单进入#support时,追踪相关讨论,从各个仓库收集相关信息,并在同一条讨论中发布结构化的分析报告。
  • 修复。操作员批准后,提交包含建议更改的合并请求,回复审核者的意见,监控持续集成流程,一旦出现故障立即修复,随后将合并请求推送到合并阶段。
  • 自我改进。每个已关闭的案例都会提供反馈。如果出现某种模式,比如某个领域内有五张工单都涉及相同的配置,该系统会识别出其中的缺陷并自行提出改进方案。
  • 正是这第三项功能让它有别于那些复杂的工单处理机器人。该系统的原始材料就是其自身的源代码,而大型语言模型可以通过经过人工审核的拉取请求来对其进行修改。

    该系统由六层逻辑结构组成,每层都在下方有专门的介绍。

    纯模型无法填补的三个缺陷

    整个设计理念基于这样一个观点:无状态、纯粹反应式的大型语言模型无法用于生产环境。这六层结构的存在就是为了填补模型单独无法解决的三个缺陷。

    第一个缺陷是反应性。模型无法自行启动。第一层(事件接收)负责处理这一问题,因为Slack的消息、GitLab CI的运行结果以及PagerDuty的警报都会被导入同一个调度队列中。大语言模型不会主动轮询,而是由外部调用其运行。

    第二个缺陷是持久性。模型会在会话之间忘记所有内容,当任务在不同机器间转移时也会丢失上下文,而且它们无法区分只是处理了某个问题与实际已提交到代码仓库的更改。第三层(持久化状态)解决了这个问题:内存结构可在单个进程内去重,本地JSON文件用于存储操作映射,而通过git同步的工作空间则能保持任务的持久状态。稍后提到的跨机器会话连续性也是基于同一理念,即状态应存储在git中,而非运行中的进程内部。

    第三个差距在于质量。在缺乏结构化的情况下,大型语言模型的推理机制会依据不足的证据就轻易得出结论,在未核查所有可获取的资料前就宣布调查已完成,而且生成的修改请求描述从第一句起就显得像是机器所写。第二层架构(智能体协调)与调查循环通过结构化的输出规范、每次迭代时的质量检查、循环结束后的对抗性审查,以及围绕代码变更、持续集成和合并流程的外层循环中的自我修复机制来解决这一问题。

    各种机制的协同作用将各层紧密联系在一起,因为每个已解决的案例都能加快下一个案例的处理速度。获批的调查结果会成为知识库中的条目,而获批的修改请求则会转化为差距分析工具可以参考的模式。一旦某个案例处理完毕,就会针对相关工具本身提出改进型修改请求。随着循环的推进,处理不当的工单类型也会逐渐减少。

    第一层:事件如何唤醒系统

    有三个外部信号源可以唤醒该集成系统:

    • Slack消息,包括频道提及、主题回复以及以私信形式发送给自己的管理员命令。
    • GitLab操作记录,即代码审查评论、流水线运行结果及流水线失败信息。
    • PagerDuty警报,当值班人员提及该集成系统已处理过的工单时使用,以便调出之前的相关上下文。

    这三种信号源在延迟和可靠性方面存在差异,因此各有独立的接入路径。

    有两个实际细节并未出现在该图表的标准版本中。

    最终一致性。 Slack的conversations.history接口可能存在1到30秒的不及时响应。如果轮询程序看到返回结果为空便直接将指针向前移动,那么所有ts值落在该时间窗口内的消息都将被永久跳过。这样一来,指针会在空结果情况下停滞,下一次尝试会从相同的最早时间戳重新开始,如此反复直到至少有一条消息返回。通过基于每条消息ts值的去重集合,可以过滤掉这些重复请求产生的重复数据。

    Socket模式再加上轮询机制,可谓万全之策。 Socket模式能够以亚秒级的延迟传递线程回复,但WebSocket连接却可能中断。轮询机制则起到安全网的作用,能够捕捉到Socket模式遗漏的内容,而且两者都会使用socket-dedup文件来确保消息不会被重复发送。一旦缺少了这一机制,在连接中断期间传入的任何消息都会悄无声息地丢失,操作员也不会收到任何提示表明有消息被遗漏。

    MR监控的cron循环行为有所不同。GitLab不会将评审评论推送到流水线中,因此只能通过轮询方式获取信息,而如何优化轮询机制本身便是最大的难题。每新增一个长期存在的MR,就会使10分钟的循环时间进一步延长,导致大量无效线程的轮询记录堆积,而且每个周期都需要调用一次Slack API。如果不加以处理,这些情况会持续引发429限流风暴,进而影响关键轮询任务的正常进行。修复方案是在上游API返回thread_not_found时自动移除对应轮询记录,使其不再参与下一周期的循环。在修复之前,一个周期内可能需要多次重复尝试,持续数小时;修复之后则不再需要。

    第二层:代理节点的协调方式

    大语言模型实际上是在这里运行的。该框架会将工作进程作为临时的 systemd 单元启动,每个进程都会收到一个 JSON 数据包,其中指定要运行的工作流、使用的 case_dir 以及所属的线程。

    每个处理流程都相当于一次长时间的 Claude Code 运行(即“大脑”),它会收到经过精心设计的提示语以及一个可写的 case_dir。该目录充当智能体的临时存储空间,用于存放该案例的 TASK.md 文件、不断增多的 followup_transcript_*.md 转录文件、智能体生成的任何 gap_report*.md 报告,以及各种中间 JSON 文件。

    智能体的执行顺序也是设计的一部分。并非由一个智能体完成所有工作,而是将多个专业化的智能体串联起来,每个智能体在特定节点将任务传递给下一个。

    • oncall_run会首先进行初步分析,最多进行20次推理迭代,并设置质量检查环节,最终输出明确的自我评估结果。它的输出包括一份描述问题所在的ANALYSIS.md文件以及一份说明后续行动方案的TASK.md文件。
    • case_followup是一个长期运行的聊天智能体。操作员可以在案件讨论帖中通过私信回复数天,每次回复都会将迄今为止的完整对话重新传递给case_followup。此外,它还会每隔N次回复就调用作为子智能体的gap_analyzer,通过这一循环生成指向该工具自身代码的gap_report*.md文件(自我改进循环的详细内容将在后文介绍)。
  • finalize_case负责整合工作。当某个案例已闲置四小时,或者操作员执行了admin: finalize <case>命令后,它会收集所有的缺陷报告,剔除误报,然后基于剩余的有效修复方案创建一个单独的MR。该MR针对的是harness代码库,而非工单对应的原始仓库,这正是自我改进循环的具体体现。
  • dev-agent则负责实际的操作工作。它在为每个案例单独创建的git工作树中编辑代码、推送更改、监控CI流程,当CI出现故障时进行修复,并回复审阅者的意见。它共有三种运行模式:address_review、fix_ci以及task(即最初的实现方式)。
  • 这张图表中有两处细节很容易被忽略。

    首先,没有任何内容会直接触及主结算流程,因为所有操作都在工作树中完成。这样就能让多个处理任务并行运行而不会相互冲突。轮询器与开发代理调度器会将工作树路径放入请求载荷中,因此大型语言模型根本无法看到主结算流程。

    其次,case_dir是所有内容的基准。位于~/.harness/中的状态文件(详见持久化部分)将每个Slack对话线程与某个case_dir关联起来,代理则会根据该基准解析所有路径。这样一来其他一切都会变得更简单:只需移动目录、更新映射表,各个工作流就会随之调整。

    图表展示了哪些任务正在运行。下一节将解释其运行机制,即如何将Slack指令处理为完整的调查结果。

    自主循环,逐步解析

    如果有人问这个系统究竟是做什么的,最简短且诚实的答案是:操作员输入一条Slack命令,之后系统就会自动进入一个由反复推理、自我批判和工具使用构成的闭环,持续运行直至任务完成或系统判定无法继续。几乎每一个重要的设计决策都隐藏在这句话之中,而本节的其余内容将对此进行详细阐述。

    在该系统中存在四个不同的智能体循环,每个循环都针对特定类型的问题而设计:

    那张图展示了这些循环的形态。接下来会介绍具体内容,即各个机制的运行顺序。该图表涵盖了所有这些机制,一旦读完下面的章节,其节点的含义就会变得清晰。系统中使用的门控代码为G1至G4、N1至N3、N5、A1至A3以及D1至D15。

    循环之前:预先锁定机制

    在首次调用 Claude 之前会有两项保障措施,第一项是结构上的。在任何调查迭代开始之前,值班流程会要求 Claude 先在 hypothesis_slate 中确定初步假设。该文件至少会列出六个候选解释以及四到五个用于从中筛选的标准,且会被锁定在 kickoff_precommitment.json 中,之后不得对其进行任何修改。

    在系统中的所有机制中,这种锁定机制具有最强的方法论约束力,却最不为人所察觉。不受限制的LLM推理循环往往会倾向于接受其最初获得的证据,然后将后续的所有证据都视为对其观点的佐证。这种锁定机制无法阻止这种倾向,但它能强制要求每一次分析都必须从完整的假设空间出发,并记录其得出结论的完整过程。随后,对抗性审核者(第二阶段)会提出比内部一致性更严格的问题:基于现有证据,这是否就是原始模型原本会预测出的结论?

    第二项保障措施是实证依据(C阶段)。仅在第一次迭代时,会在不涉及大型语言模型的情况下运行一些操作系统级命令:journalctl的错误记录查询、磁盘与内存快照以及日志文件搜索。这些结果会作为原始证据纳入初步调查提示中,让Claude在形成任何假设之前就能了解机器的实际状况。该步骤不会造成阻塞,会默默处理错误,耗时不到一秒。它之所以重要,是因为后续的推理步骤能够以事实为依据,而非仅依据问题描述展开。

    结构化输出:完成报告

    每次迭代都会生成一个可解析的JSON格式文件completion_report,工具会读取该文件以决定下一步的操作。其结构是固定的:

    {
      "status": "IN_PROGRESS" | "BLOCKED" | "COMPLETE",
      "confidence": 78,
      "open_questions": ["..."],
      "unchecked_sources": [{"name": "...", "access_status": "..."}],
      "contradiction_register": ["..."],
      "assumption_register": ["..."],
      "adjacent_problems": [{"summary": "...", "status": "...", "blocker": "..."}],
      "draft_response": "the user-facing Slack message"
    }
    

    自由形式的散文可以围绕该块内容出现,但验证机制会评估解析后的报告,而状态文件则会保存这些信息。为了使输出内容能够在处理流程中继续被使用,最大的改进措施就是用一定的散文表达自由度来换取可解析的格式。如果省略架构定义,后续的所有步骤都不得不从散文中重新提取信息;而保留架构定义的话,验证机制就变成了普通的确定性 Python 代码。

    赋予置信度实际意义的验证机制

    如果 Claude 能直接输入 confidence: 95 就继续处理,那么置信度分数就只会成为营销噱头。因此,每次迭代产生的输出都会经过质量验证机制的检验,只有当证据与所声明的置信度一致时,流程才会继续推进。

    在每次 Claude 处理完成后,会有三类验证机制依次运行:

    • G门用于检查逻辑一致性,确保各轮推理之间不会自相矛盾。最典型的情况是G1:当某一轮中open_questions的数量增加而confidence值上升时,G1会被触发,因为分母的增加会抵消置信度的上升。该机制还会检查文中表示矛盾的表述,并确保相邻问题被视为有效的状态对象而非占位符字符串。此类门共有四种。
    • N门用于检查结构的完整性,确保所有元素都已完整形成而非残缺不全。N1要求每一个可执行的修复操作都必须配有一个verify_action,该动作需说明具体内容、预期结果及执行时间。没有验证方式的修复方案被视为未完成的工作。此类门共有三种。
  • A门设定了散文无法绕过的机械性上限。A1会将硬性置信度上限设定为1.0,每有一个未解答的问题则减去0.08,每有一个未经核实的来源则再减去0.05,而无论Claude写下什么,该门都会通过算术方式限制这个数值。其他A门会在相邻问题仍未解决时阻止COMPLETE状态的输出,要求假设寄存器不能为空,并且必须确保至少有一个提出的解决方案能直接针对根本原因。这类门共有七种。
  • 还有两项抽查机制。EQ1要求根本原因节点引用确凿证据(不能是INFERRED类型),否则置信度上限为60%;P7则会对每个被标记为FAIL的对抗性维度将上限再降低7%,最低不低于40%。

    整个框架共有19种功能。一个便于记忆的方法是:G门负责响应Claude所写的内容,N门负责响应它省略的内容,而A门则负责响应它断言的内容。

    违规行为会生成一条 pending_guard_notes 记录,该记录会被置于下一次迭代提示的最上方,标题为 ## SYSTEM QUALITY GUARD NOTES (from previous iteration),并明确要求首先处理所有记录中的问题。这就是强制机制的体现。这些规则并不会阻止模型运行,而是让它在下一轮首先处理之前的违规行为。模型的礼貌态度并无意义,只有通过结构化的强制措施才能推动问题调查的进展。

    无论是否出现违规情况,每个提示生成器的最后都会执行上下文固定步骤(A7)。该步骤会将当前报告中尚未解决的矛盾与未经验证的假设逐字重新附加到下一个提示的末尾。这有助于避免众所周知的注意力衰减现象,即模型在第三轮注意到某个复杂问题,到第七轮时却已忽略它。

    以具体数字表示的置信度

    最关键的设计决策是,该智能体在每次迭代的结构化输出中都会为自己设定一个0到100分之间的confidence: 值。这个数字并非装饰性内容,因为它实际上控制着智能体的行为:

    • 若置信度低于70,则无法结束循环。处理流程会强制再进行最多三轮,并添加一条提示说明该智能体在置信度未达标时声称已完成任务。
  • 当评分达到70分及以上,且状态为COMPLETE同时没有open_questions时,流程将进入对抗性审查阶段(红队对阵蓝队)。
  • 评分达到95分及以上时,智能体可自动执行一些不会造成破坏的操作,如在Jira上添加评论、更改状态或分配任务。若评分低于95分,则这些操作仍为草稿,需由操作员决定处理方式。
  • 若连续两轮的评分下降幅度超过10%,则流程将以degrading为原因终止。此时智能体表现出现倒退,会停止运行并通知操作员。
  • 每个案例的完整置信度变化轨迹会以折线图的形式记录在ANALYSIS.md文件中,例如45% → 62% → 78% → 80%。操作员可通过查看该图表来判断结果是否值得信赖,而系统自身的自我改进机制在选择用不同提示重新处理哪些历史案例时也会参考这些数据。

    循环无法提前终止的三种原因

    在图中,有三条路径会到达ready_for_adversarial或blocked状态,而循环并不会随之结束。每一种情况都对应一条强制继续运行的规则:系统会忽略退出信号,转而插入相应的保护性机制。

    • 当status=COMPLETE且open_questions=[],但confidence值低于70时,就会触发CONFIDENCE_FORCE规则。模型虽宣布胜利,但通过该数值表达了不确定性。此时系统会强制进行最多3次迭代,这些迭代次数计入20次迭代的限制范围内,而不会额外增加。
  • 当status=COMPLETE且confidence值至少为70,但causal_chain_complete为假时,会应用CAUSAL_CHAIN_FORCE。这是一种仅能发现症状而无法追溯到根本原因的情况,它允许在20次迭代的总限额内再进行最多3次迭代。
  • 当status=BLOCKED,且可访问的技术来源因推理被跳过或判定为不可用(而非真实错误所致)时,会应用G_BLOCKER_FORCE。在未彻底排查所有可能情况之前,调查不能以“已阻塞”状态结束,该机制允许在相同的20次迭代限额内继续进行最多3次排查。
  • 通过这些机制,可以避免三种最常见的过早终止情况:看似有把握的不确定性、仅能发现症状的“已完成”状态以及臆想的“不可用”状况。

    对抗性审查者

    当待处理的流程以70%或更高的置信度声明处理完成时,控制权会交给另一个负责扮演对抗性审核者的Claude实例。该审核者无法看到调查人员的提示内容,它只会收到分析文本以及包含15个评估维度的检查表,即D1至D15,这些维度涉及假设的合理性、证据链、因果推理、替代性假说等等。它会返回一个结构化的判定结果:

    审核过程有三种结局:

    1. 若存在严重问题,这些问题会被退回给调查人员。他们的备注会以pending_guard_notes的形式被纳入下一次迭代中,随后循环重新开始,此时调查人员会明确得知审核者认为有某些内容被遗漏了。
    2. 如果初次审核仅存在轻微问题,循环会再执行一次,并附带这些轻微的批评意见。
  • 连续两次仅出现次要错误的结果,或一次无错误的测试结果,都会使循环以complete状态结束,此时调查结果就会被公布。
  • 将对抗性步骤设置为使用不同提示的独立调用是经过刻意设计的。同一模型在不同角色下,能够捕捉到单次提示迭代所遗漏的诸多自我吹嘘内容。在某个案例中,从调查开始到结果审核期间,该模型的自信度评分从90降到了65,而审核者指出了调查者之前忽略的关键假设。

    与此同时还有另一种审查方式,其构建方式有所不同:即红队审查(G5)。对抗性审查者会阅读完整的调查报告,而红队审查者仅能看到最初的问题描述和最终结论,无法看到任何中间推理过程、证据链或之前的审查环节。红队审查者会根据这两项内容自行形成独立假设,并指出调查结果与外部人士的推测出现分歧之处。

    其目的是实现认知隔离,而非额外覆盖。对抗性审查会检查调查在结构上是否完整(从D1到D15)。红队则会探讨在没有该调查框架的情况下是否也能得出相同结论:推理确实基于问题本身,还是说调查仅依据早期发现就草率下结论,让结论跟随假设而非证据?这两种测试都是作为独立的Claude调用来进行的,且都必须通过才能将案例标记为COMPLETE。

    五种诚实的结束方式

    调查循环可能因五种原因而结束。无论哪种原因触发,都会被记录下来并显示给操作员,如下所示:

    exit=complete       confidence=89%  iterations=12  elapsed=13min
    exit=blocked        confidence=62%  iterations=4   elapsed=4min    (cannot proceed without info)
    exit=stalled        confidence=58%  iterations=7   elapsed=22min   (two rounds, zero new facts)
    exit=degrading      confidence=42%  iterations=5   elapsed=8min    (dropped >15% twice)
    exit=timeout        confidence=73%  iterations=18  elapsed=50min
    

    在这五种状态中,blocked是操作员能够最直接干预的状态。智能体会在unchecked_sources或adjacent_problems中记录明确的阻塞原因,需要人工介入才能清除。其余四种则是技术故障模式,系统会将其识别出来而不会盲目重试。

    Claude从内部能够访问什么

    此处工具的边界非常重要,因为置信度判定在某种程度上取决于Claude实际能够操作的内容。如果缺乏用于检查的相应工具,要求智能体检查未处理的来源这一指令就毫无意义。每个处理流程都会以--permission-mode bypassPermissions参数启动一个Claude Code子进程。提示词中不会出现内嵌的工具允许列表。其边界由模型上下文协议(MCP)目录中定义的内容决定,该目录涵盖了五个外部系统:

    有几点限制值得提及,因为它们对模型行为的影响超过了模型卡片上的其他任何内容:

    • 查询流程明确禁止使用 Bash(--disallowedTools Bash),这是为避免 git 竞态条件而设,因为两个 Claude 进程同时编辑同一个工作树会导致状态损坏。开发代理流程虽可使用 Bash,但它会在隔离的工作树中运行。
    • 在每次流程调用中都会阻止使用 Slack 写入工具。send_message_to_self 和 edit_message_to_self 被禁止,因此 Claude 无法直接在 Slack 上发帖。所有外部通信都必须通过结构化完成报告中的 draft_response 字段进行,而该框架只有在验证了退出条件后才会发布此内容。
  • bypassPermissions在实践中并不意味着完全不受限制。每个流程的框架提示都包含了严格的规则。ANALYSIS.md可以编辑,但message.json则不可修改。在case_dir之外的写入内容会在运行结束后通过git diff自动恢复原状。生产数据库的写入操作需要根据指令获得操作员的明确确认。安全性体现在框架层以及运行后的边界检查中,而非工具调用层。
  • 级联备用方案存在于提示语中,并非硬编码的。如果search_datadog_services返回权限被拒的结果,提示语会指导代理切换到mcp__glean_default__search,并以Jira关键词作为替代。这种备用链是确保标准操作流程得到执行的方式之一,不会出现在运行时环境中。
  • 只读工具并行运行,而写入工具则依次执行。流式子进程会实时解析传入的tool_use事件,并同时发起最多10个读取操作(Datadog查询、Glean搜索、文件读取)。任何写入操作,无论是会产生副作用的Bash命令、编辑操作、MR评论还是数据库写入,都必须逐一执行。因此,单次调查迭代可以同时发起六个并行日志查询,同时仍能保证写入操作的顺序性。
  • 一个框架,四种规模

    该工具集中的每个流水线都运行着同一框架的不同版本:

    待处理的调查流程最多会进行20次迭代。任务分析阶段最多有8次迭代。实现阶段会执行一次框架代码,但其构建步骤会循环3次。dev-agent没有内部循环,每次启动只会运行一次,但mr-monitor实际上将迭代过程移到了外部:每当CI测试连续失败3次时,它就会启动一个新的dev-agent,之后才会将问题转交给人处理。

    这种一致性是刻意设计的。之所以存在这四个循环,是因为不同的问题各有特点,但它们都使用相同的语言:置信度是一个数字,问题结束时有对应的名称,自我批评则是另一次Claude调用,而问题升级则意味着在最初发起该问题的聊天线程中发送Slack私信。操作员只需了解一次这种结构,就能在所有情况下识别出来。

    自我提升层

    这便完成了之前描述的循环——在人类监督下,该工具集会修改自身的底层代码。实际上有两个在不同时间尺度上运作的循环来完成这项工作。

    第一个循环会逐个修复问题,从发现漏洞到提交修复建议。 当一项调查结束后,gap_analyzer会查找工具集表现不佳的地方:遗漏的数据源、错误的工具选择、流程中的错误、缺失的测试以及格式不当的修复建议描述。在finalize_case处理这些问题之前,分类和置信度过滤器会先对漏洞进行筛选。一旦某个问题在四小时内未被处理,finalize_case就会在专用的工作树中启动patch_suggester,针对工具集的代码库生成一个修复建议,涉及代码文件、提示模板或标准操作流程相关部分。合并这个修复建议需要人工操作,而生成修复建议本身则不需要。

    循环2旨在强化各种场景下的行为规范,从故障模式到标准操作流程建议均涵盖其中。在每个场景结束时,auto_retrospective步骤会记录出现的故障模式,并将其分为三类:数据收集失败(是否有可访问的源数据被跳过?)、质量管控违规(置信度声明是否与证据相矛盾?)以及分析完整性缺失(根本原因追溯是否未彻底完成?)。这些记录会按项目存储在案例注册表中。每周,名为knowledge-updater的定时任务会统计最近十个场景,一旦某种故障模式达到设定阈值,就会生成标准操作流程建议:10个场景中有3个则提出建议,6个则发出警报。这些建议都很具体。例如,本月“跳过直接数据库查询”的情况出现了3次,因此现在的标准操作流程要求在进行任何架构推断之前必须先执行直接表查询。随后由人工进行审核并予以应用。

    嗯,按照设计,这里没有任何内容会自动应用,因为自我修改系统中,那些会不断累积错误的自动化决策本身就是已知的隐患。

    个性化功能由此产生。标准操作流程记录了团队进行调查的方式,最初是通用的模板。随着时间的推移,知识库更新工具会从各类案例中总结出某个项目的故障模式,并不断重复这些模式。当积累到20到30个案例后,该项目的标准操作流程就不再与通用模板相同:它会记录哪些数据源总是被忽略、哪些工具被误用,以及在该特定代码库中哪些调查思路经常会出错。各个项目都拥有独立的标准化操作流程,因此A团队的错误只会影响A团队的规则,而不会波及B团队。

    耐用性是另一个优势。上下文窗口仅持续单个会话时间,而合并后的代码变更则会进行版本控制、审核、测试,并成为永久性记录。一旦操作员批准了标准操作程序的更新,它就会出现在之后的所有调查中。底层系统持续改进,因此每个新案例所使用的工具框架都会比之前的更完善。

    第三层:可跨重启保留的状态

    编排机制是针对特定事件运行的,而持久状态则是在进程重启、机器重新启动或操作员从笔记本电脑切换到云虚拟机时依然保留的。该工具框架将状态分为三层,每层的特性都有显著差异:

    内存缓存体积较小且可重新构建。它们存在于轮询进程和保持连接进程中,重启后会根据本地状态重新生成。

    ~/.harness/*.json中的本地状态即操作映射表。关键文件包括:

    • task-thread-map.json将Slack的thread_ts映射到JIRA编号及绝对的task_dir路径。
    • oncall_thread_map.json用于记录值班处理案例的相同结构信息。
    • monitored-mrs.json列出了harness已打开的MR及其CI状态。
    • patch-pr-state.json记录每个案例的最终状态(如merged、no_gaps、failed等)。
    • oncall-state.json保存着第6层中提到的强制关闭标志。
    • socket-dedup存储了Socket模式已处理的最新消息的时间戳。

    该层属于某台机器,不会被共享。系统重启后它依然存在,但若该机器故障,则此层数据会丢失。

    Git同步状态才是持久化的层。每当状态发生重要变化时,位于case-workspaces//下的案例工作空间就会被提交并推送到私有的Git仓库中。另一台克隆了该仓库的机器可以获取相同的对话上下文,从而实现跨机器的会话连续性。操作员可以通过笔记本电脑或远程开发虚拟机与系统进行交流,而不会丢失任何信息。

    后来,记忆整合成了 git-synced 状态的第三项功能。一次长对话会生成数十个 followup_transcript_*.md 文件。当积累足够多的内容后,一个子代理会读取所有这些文件,重写 TASK.md 以保存精简后的状态,之后工作流就不再向后续步骤输入原始对话记录。跳过记忆整合会导致提示内容超出模型的上下文窗口限制。而强行整合则有可能过度压缩从而导致信息丢失,实际上确实发生过这样的情况:一份14KB的文档被压缩到了587字节。从那次事件中衍生出了三项保护措施:要求使用“更新而非重写”的提示模板、设定输出大小限制以避免写入小于输入大小50%的内容,以及为每个案例创建锁文件以防止两次记忆整合操作同时进行。

    从单个 Slack 线程到对应案例的 TASK.md 的查找路径虽然较短,但值得标注出来:

    两个状态文件都存储相同类型的数据,即案例工作空间的绝对路径。任何工作流最终都会定位到case_dir,之后便以该路径为基准进行操作。如果在项目执行过程中工作空间根目录被重命名,有一个迁移脚本会在移动目录的同时重写两个映射表中的绝对路径,从而解决此问题。

    持久化状态是基础。接下来的问题是:基于该状态的操作失败时会发生什么?答案是,系统能够自行恢复,而不会将故障传递给操作员。

    第4层:能够自我修复的循环

    在这一层,系统不再仅仅是通知者,而是具备了自主性。它包含三个循环,每个循环都在无需操作员干预的情况下完成。

    前两种是完全自主运行的。第三种则刻意设计为非自主运行。刷新身份提供方会话需要通过硬件支持的多因素认证提示,因此代理程序会暂停并要求人工操作,在私信中发送30秒的操作指南。这是按设计实现的有人干预机制,不存在能力缺口。

    这些循环有一个共同的特点:每个循环都有重试次数限制,并且能够识别自己已经执行过哪些操作。CI自动修复功能每份合并请求仅能使用有限次数,因此连续三次失败后就会升级给操作员处理。审核者自动回复功能会按作者进行去重,也就是说当机器人留在讨论中的最新留言比审核者的更新时,由于回复已存在,该循环就不会再触发动作。身份提供者辅助功能允许15分钟的审批时间窗口。如果取消这些限制,这些循环有时会陷入恶性循环。早期曾有一份合并请求因为集成测试不稳定,在一个下午内就产生了80次毫无实际作用的“修复CI”提交,直到有人注意到为止。

    从单次操作中恢复

    上文所述的 CI 自动修复与审核者回复循环能够让整个工作流从头到尾恢复正常。在它们之下,每一个可能出错的原始操作(如推送、重基、合并请求评论、分支重置)都基于一个独立的、更小的三层设计来实现针对该操作的自我恢复。这与第 6 层的三层安全模型不同,后者侧重于限制,即智能体可以执行什么操作;而这一层则侧重于恢复,即在允许的操作失败时会发生什么。两者很容易混淆,因此要将其区分开来。

    L1是一种确定性防护机制。它基于规则且始终处于运行状态,响应时间远低于一秒。对于推送操作,其对应的函数为_pre_push_rebase(repo_path, branch):在推送之前,该机制会先执行git fetch origin和git rebase origin/命令。如果重基操作成功,推送即可继续进行;若出现冲突或获取失败,L1会安全地中止操作,列出有冲突的文件,并将问题转交给L2处理。L1刻意不会具备创造性,其规则设计得足够简单以便分析,同时成本极低,无需消耗任何令牌,因此可以应用于每一项操作。

    L2是一个具备自主修复功能的循环机制。当L1无法解决当前问题时,该系统会启动一个受限的Claude会话(_self_heal_push_loop),并将与故障相关的所有信息传递给该会话:git状态、冲突文件、触发此次推送的任务以及一些严格的限制条件。绝不能强制推送受保护的分支,绝不能丢弃未推送的提交记录,也绝不能盲目选择冲突中的某一方。这三项限制确保了操作的安全性:

    1. 有限次重试机制。每次故障最多允许3次尝试,每次Claude的响应都有4分钟的超时限制。在Claude执行完每个操作后,系统会再次执行真正的git push操作。Claude关于冲突是否已解决的判断并不具有权威性,只有git push返回的0值才是唯一的标准。如果下一次推送返回非0值,无论Claude如何声称操作成功,系统都会记录此次尝试失败。
    2. 70分及以上的置信度阈值。该阈值直接沿用自故障排查流程中的oncall_run配置。Claude会在每次响应时给自己打0到100分的分数,若分数低于70则必须停止操作而非继续执行。低于该阈值的自我评估结果不得对工作目录进行任何修改。
  • 真实性不变性得以保持。 L2流程成功意味着代码确实已提交到远程仓库。后续的回复机制会向审核者发送“已完成,已更新”的通知,该机制始终直接与git push返回的0值结果相连,而与Claude自身输出的任何信息无关。L2无法绕过这一机制,它只能通过真实的推送操作来触发该机制。
  • L3则是向操作员的升级处理。 如果L2因置信度不足而放弃或用尽尝试次数,系统会向操作员发送私信,附上L2流程的完整记录,包括每一次尝试、Claude执行的每项操作、它给出的每个置信度评分以及最终的git status状态。操作员是从完整记录的状态开始处理问题,而非从“出现故障”的状态开始。处理时间以小时计,而非秒。

    有两个原则尤为突出:

    • L2 是按每次任务触发的,并非始终处于运行状态。由于成本较低,L1 会在每次推送时都运行;而 L2 会消耗代币,且仅在 L1 失败后才会启动,因此其成本取决于罕见情况而非常见情况。
    • 真实标准来自实际系统操作。无论 Claude 对自己的工作有何判断,验证过程都会重复真实的操作:对于推送操作,会检查 git push 的退出码;对于测试修复,会实际运行测试;对于合并请求的回复,则会检查推送操作的退出码。智能体的自我报告从不被视为真实标准,因为测试框架会再次与实际系统进行比对。

    同样的封装机制不仅适用于推送操作。当某个操作同时具备三种条件时,它就符合要求:基于规则的简单修复方式(L1)、严格限制的智能重试机制(L2),以及将任务转交给人处理的清晰途径(L3)。这种三部分结构有助于区分那些能够自行从已知故障中恢复的自主循环,与那些一旦环境发生变化就需要人工干预的循环。

    第5层:查看事件发生情况的四个途径

    该框架会将数据写入四个可观测性界面,每个界面都针对不同的使用者进行了优化:

    • 实时的 Slack 更新。在客服人员处理任务期间,案件讨论线程中的占位消息会依次显示不同状态(正在阅读 TASK.md,然后是 正在搜索代码库,接着是 正在编写补丁),最终变为 已完成 或 失败:<原因>。这种流式报告功能会直接修改现有消息,因此讨论线程不会被冗余信息填满。
    • 结构化日志保存在 ~/.harness/logs/agent.log 和 ~/.harness/logs/error.log 中。每次任务启动、链式调度以及重试尝试都会生成一条结构化事件,当需要诊断问题时就会查询这些文件。
  • 终端事件的 Slack 私信通知,例如 MR 打开、MR 合并、身份提供者会话过期或 CI 紧急升级等。这些通知被视为一级操作员警报,并会关联到相应的事例下。
  • 系统日志保存在 ~/.harness/logs/system.log 中,记录着部署完成、监控动作及同步状态等跨领域事件。
  • 有意未设置控制面板,因为 Slack 和 grep 已足以作为操作界面。额外的控制面板只会增加信息获取的渠道,而一旦数据过时,工程师们首先就会停止查看它。流式消息机制已能提供足够的操作信息,因此无需再叠加用户界面。

    监控 MCP 连接

    该工具依赖五台外部MCP服务器(Slack、Datadog、Jira和Confluence、Glean、GitLab)运行。每台服务器都有独立的OAuth令牌、刷新频率以及不同的故障处理方式。当令牌过期而未被察觉时,Claude会开始回复“我无法访问该工具”,而操作员却看不到任何错误提示。

    一个名为mcp-watchdog的小型服务可以弥补这一缺陷。它每10分钟就会查询每个MCP的健康状态接口,并尝试执行一个无实际作用的工具调用。如果该操作失败,它会采取以下措施:

    1. 尝试静默刷新令牌。
    2. 如果刷新失败,它会向操作员发送Slack私信,内容类似“Atlassian MCP需要重新认证,请点击此处。”
    3. 在连续两次断开连接后(达到DISCONNECT_CONFIRM_THRESHOLD阈值),它会在最新的工单上添加:warning:标记以提示问题。

    如果没有这个循环,过期的令牌就会被误认为是“代理程序今天出故障了”,这种故障很难诊断,因为 agent.log 中从不会显示“令牌已过期”,而只会显示“工具返回了空结果”。将身份验证状态视为首要的可观测性指标本应更早被纳入设计。

    第6层:操作员控制界面

    这是使用最频繁的界面,也是最初设计最为不足的部分。所有操作员的交互都通过同一个渠道——即私信进行,这些命令可归为六类:

    • 任务:命令 admin: task <jira> 可执行完整的分析、实现及提交代码流程,加上 --auto 参数后即可实现无人值守运行。
  • 待命模式:admin: oncall-run <url>可从 Slack 线程启动调查,而 oncall-toggle则用于开启或关闭自动监控功能。
  • MR 控制:admin: mr rescan会重新扫描指定的 MR,mr pause/resume则可切换该 MR 的自动回复循环的开启与关闭状态。
  • 暂停:admin: oncall pause [duration]可暂时停止自动派单,oncall resume/status则可结束暂停状态或反馈当前处理进度。
  • 完成处理:admin: finalize <case>可汇总相关报告,并针对该工具本身提交改进提案。
  • 注册表: admin: register-thread 可手动将 Slack 线程与案件关联,而 close-case 用于标记案件为已完成。
  • 暂停功能的设计耗时最长。第一版会暂停所有操作,包括管理员命令,这导致暂停系统的操作员无法重新启动它。第二版的暂停范围过窄,因此“停止干扰”的需求无法得到满足。当前的设计依赖于一个明确的区分标准:暂停只会使由他人操作触发的自动派单停止,而不会影响操作员自身的管理员命令或在线内的操作。在轮询器中,派单任务被分为两组。一组是由他人操作触发的自动任务,在暂停时会停止;另一组则是操作员主动发起的任务,会保持正常运行。

    这种校准方式历经波折。在首个暂停功能发布后不久,某个案例中自动回复的审核员评论悄然触发,却无人察觉,因为当时看起来暂停功能确实处于生效状态。当前的设计将暂停视为有针对性的静音:在保持操作员控制权的同时让系统安静下来。

    一张图展示了这种区分:

    规则是操作员自身的操作永远不会被静音,而其他人的操作在暂停期间会被限制。这就是admin: oncall pause所控制的全部内容。

    另外两层安全保障

    暂停功能的紧急关闭开关是最显而易见的安全措施,但并非唯一。还有另外两种无需任何运行时配置即可生效的安全机制,一种存在于代码中,另一种则通过部署规范实现:

    Slack频道白名单的重要性远超表面所见。在项目初期曾发生过一次险情,由于Slack令牌配置错误,导致其能够向内部团队频道发送消息。此时机器人可以直接将内容发布给队友而无需经过操作员,而且没人能区分这些消息是人工发送的还是机器人发出的。现在,每次执行出站chat.postMessage操作时,系统都会在发送前将目标地址与白名单进行比对。唯一被允许的目标是操作员的私信,以及一个预置好的频道ID,用于操作员与机器人的对话。如果机器人想要联系队友,它必须将内容转交给操作员,由操作员决定是否转发。这一架构规则屡次证明其有效性:机器人只能通过操作员的邮箱向外发送消息。

    超越六层架构:为何能投入实际应用

    这六层构成了承重结构。还有若干机制不在该分类体系之内,但同样重要。它们能够区分那些表现良好的配套系统与那些运行流畅无中断的系统。

    从单个 YAML 文件管理多个项目

    目前该配套系统上运行着两个项目,每个项目都有各自的运行规则;要添加第三个项目无需修改任何代码,只需在 agent-config/projects.yaml 中新增一条记录即可:

    projects:
      team-a:
        sop: "team-a/SOP.md"
        kb: "team-a/KNOWLEDGE-BASE.md"
        dfr_threads_dir: "team-a/threads"
        task_dir: "task-workspaces"
        slack_channels: ["support-channel-a", "support-channel-b"]
        jira_prefixes: ["AAA", "BBB", "CCC"]
      team-b:
        sop: "team-b/SOP.md"
        kb: "team-b/knowledge-base/kb.md"
        dfr_threads_dir: "team-b/threads"
        task_dir: "task-workspaces"
        slack_channels: []
        jira_prefixes: ["XX", "YY"]
    

    resolve_project()函数会根据优先级来处理每个传入的信号:首先考虑Jira前缀,其次是Slack频道,最后才是默认值。每个项目都有自己独立的SOP文件、知识库文件和目录结构,而用于调查的提示组装功能会自动根据信号所对应的项目选择正确的SOP和知识库。这便使得同一个平台实例能够为两个团队提供两套不同的规则。

    这种模式具有通用性。任何在一个平台上拥有多个产品团队的组织都可以采用相同的方法:通过声明式配置实现管理,无需为每个项目创建独立的代码分支。此外,这种方式还能确保规范的执行,因为任何特定于某个项目的内容都必须存储在该项目的SOP文件中,而绝不能出现在代码里,因为代码是共享的。

    在任意一台机器上恢复会话

    操作员使用两台设备:一台笔记本电脑和数据中心中运行时间较长的云虚拟机,且同一个 Claude 会话需能在这两台设备上继续使用。这很困难,因为 Claude Code 默认会将会话状态存储在本地磁盘上,所以在其他设备重启后就无法找到该会话。

    该工具通过基于 git 的会话存储机制解决了这个问题:

    该机制由三部分组成:

    1. 会话信息通过 git 进行跟踪。每当会话状态发生重要变化时,该会话的 .jsonl 和 .meta 文件就会被提交并推送到一个私有仓库,而该仓库就是状态的真实来源。
    2. 每台设备上都会每隔 5 秒运行一个后台获取程序,以保持本地副本的实时性。它会预先下载相关上下文,因此恢复 Claude 会话时无需在每次按键时都等待网络响应。
  • 会话 UUID 是通过工作目录来匹配的。当 Claude 在某个位置启动时,session-start.sh 钩子会查找本地的 .meta 文件,找到与该工作目录对应的记录并获取其 UUID。这样,同一个 UUID 就会跟随操作者在不同机器之间传递。
  • 同步钩子 user-prompt-submit.sh 会在每次 Claude 提出问题后触发,并按严格顺序执行三项操作,这些操作通过 flock 实现串行处理:

    1. 获取并重置。 运行 git fetch origin main && git reset --hard 后,本地的 active.jsonl 文件中仍会保留另一台机器的对话记录。
    2. 在覆盖之前先提取差异部分。它会读取水印信息,即另一台机器已注入的行数,仅提取较新的行并保存到临时文件中。此步骤必须在第3步之前完成。之前的一个漏洞导致两者的顺序颠倒,使得注入过程不断读取机器自身的转录内容。
    3. 上传自己的转录内容。它会将本地的Claude会话转录内容复制到active.jsonl中,进行提交并推送,然后将水印设置为本地的行数。

    当集群解除同步后,第2步中保存的差异部分会以系统消息的形式传递给Claude,说明自上次同步以来另一台机器新增了N条消息。之后的提示词就能获取到两台机器的上下文信息。

    一个独立的 .linecount 文件用于记录真实的行数总和,因为一旦为了上传而截断转录内容,wc -l 会给出错误数值。为避免超过 git 的 25 MiB 限制,转录内容在上传前会被截断至 1,000 行。如果没有 .linecount 文件,每次截断都会重置行数标记,从而导致系统重新传输已经处理过的数千行内容。

    flock 序列化的 git 操作可以防止并发上传导致 index.lock 文件损坏,此前曾发生过因会话启动失败与元数据处理任务同时进行而引发的问题。系统还设置了可配置的节流机制,默认时间为 30 秒,可在快速输入时跳过完整同步,从而避免出现逐字符的 git 延迟。

    值得借鉴的更广泛理念是将 git 用作状态存储。在此,git 不仅仅是一个代码仓库,它还充当类似 CRDT 的持久化代理状态日志,而操作员的兩台机器则作为最终一致的读取端。

    记忆固化与机器间的连续性密切相关。负责将转录内容重写为格式规范的TASK.md和ANALYSIS.md的memory_consolidate步骤并非按定时器运行。有一个名为consolidate_trigger的独立服务,它会检查每一轮对话中的四种信号:转录内容中的提交SHA值或决策相关动词、刚刚关闭的评审帖子、累计转录内容超过5KB的案例工作区,或是距离上次记忆固化已至少一小时。只要出现任意一种信号,该服务就会触发记忆固化流程。其原理在于,信息是在做出决策后停止累积的,而非经过固定时间后,而这四种信号共同用于判断对话是否已自然暂停,从而无需操作员手动干预。

    工作树不仅仅是实现细节

    早先有说法称所有操作都在工作树中完成。这一观点值得进一步阐述,因为其背后的选择具有重大意义。

    最初的设计是让代理生成差异文件,然后在主仓库的检出环境中运行git apply。但这导致了两种反复出现的故障模式:

    1. 不稳定的差异文件头部信息。Claude生成的差异文件的行号在生成时是正确的,但一旦有其他提交同时生效,这些行号就会出错,从而导致git apply拒绝整个补丁。
    2. 主仓库状态卡住。如果git apply --check在执行过程中失败,主仓库就会处于半应用状态,随后在同一仓库上运行的下一个任务会继承这种混乱的状态。

    worktree_manager 就是解决方案。在任何需要修改代码的智能体之前,它会在 ~/.worktrees/case-<case_id>-<stamp>/ 下创建一个沙箱式工作树,该工作树从干净的分支中检出代码。Claude 可以使用其内置的编辑和写入工具在该工作树中进行操作,从而省去了 git apply 以及通过差异文本来回处理的步骤。它的操作方式就像人类工程师一样:新建分支、检出代码,然后进行编辑、提交并推送。

    一旦合并请求被合并或关闭,该工作树就会被删除。部分失败只会影响对应的工作树,而不会影响到主分支及其他正在处理的案例。

    这正是让多智能体能够在同一台机器上安全运行的原因。如果没有针对每个案例的工作树,同一个仓库中的两个案例就会在文件系统状态上发生竞争;而有了工作树之后,这些案例从架构上就被隔离开了。

    该工具还能自动处理自己的合并请求

    该工具包的源代码保存在一个私有的 GitHub 仓库中。当差距报告、最终确认及合并请求处理流程为该工具包本身生成改进型合并请求后,该请求会经过 pytest、代码检查工具以及 Python 语法检查器的自动化测试流程。另一个名为 pr-ci-fixer 的服务会以与 mr-monitor 处理工单合并请求中的故障相同的方式处理这些测试失败问题。

    这样一来,为工单编写的代码以及与工具包相关的代码都能实现自我修复。假设某次合并请求添加了新的重试次数限制,导致现有测试失败并使自动化测试失败,pr-ci-fixer 会检测到这一问题,在工作目录中启动 Claude 会话,修复问题后再重新推送代码,这样人工审核者就能看到状态正常的合并请求并直接进行合并。在实际的工具包修改场景中,这一循环已经多次发挥作用。

    将循环指向工具集自身的代码源,是判断其是否配得上“生产级”名称的最佳方式。一个能够让自己的拉取请求始终通过审核的代理,很可能也值得被委以处理他人代码的任务。

    针对五种故障类型的五层测试体系

    该工具集在126个文件中包含了4,656个测试函数。如果没有框架来明确每个测试能检测什么,这些数字就毫无意义,因为不同的测试功能并不具备同等效力。

    L0到L4的框架会根据每个测试所隔离的范围,将其归类为五层之一:

    • L0:纯边界测试。不进行任何模拟,也没有文件系统或I/O操作。它用于检测纯粹的逻辑,如门函数、JSON解析以及置信度计算相关功能,凡是在L0阶段通过测试的代码,在任何部署环境中都能正常运行。
  • L1,文件隔离。它使用真实的tmpdir文件系统与真实线程,且不模拟pathlib。并发写入操作之间的竞争条件以及状态文件中的原子性错误都会在此层显现,而按设计L0无法检测到这些问题。
  • L2,服务隔离。真实的子进程会在模拟的外部API(Slack、GitLab、Claude)上运行。组件之间的交互问题,包括调度路由、负载格式以及状态文件转换方面的缺陷,都会在此层暴露出来,因为L1仅能隔离文件系统行为。
  • L3,沙箱环境。通过子进程创建真实的git仓库,而Slack和Claude的响应则为模拟数据。整个工作流流程都在真实的git对象模型上执行,例如任务注册、实现阶段启动以及待确认状态等。
  • L4,集成矩阵。其六个子类别包括参与者契约(L4a)、按角色划分的管理矩阵(L4b)、多次运行中的状态一致性(L4c)、服务间的链式关联(L4d)、外部参与者角色(L4e)、方法论缺陷(L4f)以及时间顺序问题(L4h和L4i)。
  • L4g,生产环境回放。共有十三项测试用于通过回归测试重现特定的生产环境故障。这些测试是在生产环境中出现漏洞后编写的,能够记录触发故障的具体参与者、上下文及状态,并会被永久保存以防止同类故障再次发生。
  • L4g测试的编写成本最高,但保留它们的价值也最大。它们记录了关于故障模式的机构记忆,而这些故障模式在再次发生之前往往是不可见的。

    避免输出内容显得像机器生成的风格指南

    每份提交的上报代码前都要经过另一层过滤,有一套专门制定的标准操作流程,旨在避免内容看起来像是人工智能生成的。规则十分明确:

    • 严禁出现任何与人工智能相关的标注。提交信息中不得出现Co-Authored-By: Claude、"Generated with Claude Code"这类字样,也不得在提交信息后加上[bot]后缀。该提交属于团队而非模型。
    • 禁止使用填空式标题。诸如"此提交的作用/我们为何需要它"之类的模板均被禁止。描述部分应为连贯的散文体,附带具体的Changes:项目列表,写作风格应如同工程师手写一般。
    • 不得使用内联粗体标签。Root cause:标签后跟段落、Fix:以及Risk:这类标签也都是不允许的。标签形式的粗体是机器生成内容最容易识别的特征。
  • 工作量证明必须能够通过命令重新生成。“在本地已验证”不算数。使用真实命令运行 pytest tests/test_foo.py::test_bar -v 并显示相关的3行输出,这才算有效。
  • 在编写合并请求描述时,标准操作流程会被注入到补丁生成提示中。它与提示模板是分开的,因为操作员可以自行编辑它:随时进行修改,下一次提交的合并请求就会遵循新规则,而无需重启后台进程或更改代码。甚至间隙循环也能提出对标准操作流程的修改建议,因此关于智能体如何编写合并请求的元规则也会以合并请求的形式存在,并与存储这些规则的文件一同处理。

    这比听起来更重要。没有什么比团队发现所有描述都像是机器生成的更快地破坏对自动化MR流程的信任了。一旦形成这种印象,合并难度就会上升,人工输入的真实信号会被淹没,操作人员还会开始想要手动重写描述,这反而违背了初衷。风格规范确保描述能与人工撰写的描述融为一体,从而维持整个流程的顺畅运行。

    一个工单,一次MR处理,从头到尾

    当真实的工单到达时,流程如下。时间均为大致数值。

    对于小型或中型工单,整个流程通常在30到90分钟的实时时长内完成。操作人员所需的总处理时间一般不超过5分钟:确认分析结果,必要时对模糊的决策进行指导,以及选择是否执行合并操作。

    调查结果不断积累。30个案例之后便产生了30个gap_report*.md文件,每个文件都记录了大语言模型认为可以改进的地方。每隔两周会运行一次admin: gap-patterns命令,将这些问题归类为常见的故障模式,随后在最终处理阶段会将最集中的问题转化为针对harness仓库的修改请求,从而使harness本身得到改进。这就是具体化的自我提升循环。

    与其他harness的对比

    还有其他几个系统采用类似的结构。下表展示了该系统与它们在哪些方面相似,又在哪些方面存在差异:

    该harness在哪些方面具有独特深度:

    • 承诺前假设锁定机制。 Claude在每次调查迭代开始前会确定至少六个候选假设,从而避免事后的合理化解释。对比中的任何商业工具均不具备类似功能。
    • 包含机械式置信度上限的19项功能质量检测框架。 置信度通过固定公式限制,初始值为1.0,每有一个未解决的问题会减少0.08,每有一个未核查的来源会减少0.05,因此这并非单纯的自我报告数值。在涵盖逻辑一致性、结构完整性及断言上限的三大检测类别之外,还设有针对各维度的对抗性惩罚机制。对比中的所有商业工具均无此功能。
  • 通过修改工具集自身代码库实现自我提升。差距分析报告会转化为针对该工具集自身代码、提示规则及标准操作流程的筛选后的拉取请求。最接近的学术研究为SICA(arXiv 2504),虽然报告了基准测试中的改进,但仍属于离线研究,且没有任何生产系统会对其工具集代码进行自动修改。
  • 针对项目特点定制标准操作流程。每个项目的标准操作流程都是根据其自身的故障模式数量逐步完善的,通过20到30个案例即可掌握该领域中反复出现的故障类型。经比较,没有其他生产工具集能做到这一点。
  • 具有明确任务交接的多智能体协调机制。工作流程从oncall_run开始,依次经过case_followup、gap_analyzer、finalize_case和dev-agent。每个环节都有明确的任务交接规范及独立的处理流程,因此工作会在多个智能体之间分配,而不会集中在某一个智能体上。
  • 通过git支持的水印注入实现跨机器状态同步。在笔记本电脑或远程虚拟机上启动的Claude会保留所有上下文信息,无需人工进行任何同步操作。
  • 内置的CI自动修复与审核者自动回复机制。商业产品Devin是唯一具备此类内置自愈功能的工具,而其他所有框架都需要自行实现类似功能。
  • 其设计上的简化之处:

    • 没有正式的评估工具。该大型语言模型并未在任何基准测试中接受评分,其性能评估依靠实际使用反馈。
    • 没有控制面板,仅有 Slack 消息和日志。
    • 没有费用统计功能。令牌使用情况仅被粗略记录,不会显示在控制面板上,费用则由人工审核环节来控制。

    尚待完善的部分及元循环机制

    该工具仍在持续发展中,目前仍有自主循环覆盖、费用统计以及正式评估工具等待实现,但这些都不是阻碍其发展的因素。它已经实现了设计初衷:将 Slack 中的指令转化为合并后的代码,能在夜间回复审阅者的反馈,实现 CI 环节的自我修复,并将自身的改进反馈到下一个版本中。

    真正的教训在于这个元循环。防护罩上的每一道伤痕都是因为真实的任务对其造成了压力。构建与运行是一个以两种不同速度运转的循环:生产信号会反馈到防护罩的源头,代理负责起草修复方案,人类工程师进行审核并合并修改,如此一来又有一类错误更接近被消除。

    这个系统尚未完成。如果某个生产防护罩认为自己已经完成,那就意味着它不再承受实际负载,也就不会再产生新的伤痕。

    首先构建这个循环,然后再让它具备自主性。一个没有反馈路径连接到自身基础结构的自主系统只会更快地、以更多数量地犯同样的错误,这就是为什么顺序如此重要的原因。