工程控制论视角下的软件工程与 Loop Engineering

手上的项目多了起来,会有一个很具体的感受:不是”忙不过来”,是分不清哪些事真的需要我去看看。哪个项目该催一下,哪个可以再放一周,哪个进度和验收已经跑偏了但自己还没意识到——这些判断如果全靠”每个都看一眼”,人会比项目本身先崩溃,长期在多个跨端项目中切换,体会尤为明显,甚至开始拖垮身心健康。

每一个项目管理者和架构师,都会尝试把这类项目做一些拆分管理的尝试。最后在多 Agent 系统里都会趋向于,把”要不要管”这件事拆成两层:大部分状态变化交给系统自己处理——进度提醒、异常检测、日常汇报,这些不需要你;只有触发了某些条件,才真正推到你面前——方向错了、连续多次失败、出现了系统没见过的情况。这个”分层 + 只在关键点介入”的直觉不是什么新概念(也不要被 AI KOL 造词搞得恐慌),1954 年钱学森在《工程控制论》里已经把它写成了一套完整的语言,而且这套语言正在软件工程和 AI Agent 系统里重新流行,到了 2026 年有人给它变成了当下最流行的概念 Loop Engineering。

这篇文章想做的事:把管项目时的那套分层直觉,往下拆解成几个可以复用的概念,然后看这套概念怎么在 CI/CD、微服务架构、SRE 值班、可观测性系统、混沌工程里长期存在,怎么在今天的 AI Agent 系统里换了个名字重新出现,最后落回我自己正在用的工作流。


1. 先有一套定义,才能诊断”哪里出了问题”

管项目管到一定规模后会发现,光凭直觉判断”这个项目健康不健康”是靠不住的——同一个”感觉不对”,背后可能是完全不同的原因:可能是你根本没收到该收到的信号(没人告诉你 PR 卡了三天),可能是信号收到了但判断标准模糊(“卡了三天”算不算严重),可能是判断出来了但没人去处理(知道严重但没人跟进),也可能是处理了但方法用错了(推了个不痛不痒的提醒,负责人根本没当回事)。

这四种失败模式看起来很像,处理办法却完全不同。想把它们分开,需要一套能拆解”系统是怎么运作的”的词汇,而不是继续用”感觉不太对”这种笼统的说法。

1.1 四件套:任何持续运转的系统都能被拆开检查

工程控制论恰好给了这套词汇。这门学科的奠基人之一是钱学森,1954 年在麦格劳-希尔出版社出的《工程控制论》(Engineering Cybernetics)里,把任何一个”想要维持稳定、朝目标前进”的系统,都拆成了四个角色[1]

角色恒温器里的样子干的事
SENSOR(传感器)温度计测量系统当前的真实状态
COMPARATOR(比较器)“现在 22°C,目标 25°C,差 3 度”拿测量值和目标值做对比
CONTROLLER(控制器)“差距超过 1 度就加热”根据差距决定该采取什么动作
ACTUATOR(执行器)加热丝通电真正去执行那个动作
SETPOINT(设定点)你设的 25°C系统要维持的目标值本身

恒温器不会思考,但它比很多号称”智能”的系统更懂控制是什么——它不停地测、不停地比、差距大就纠正、差距小就等着,从不需要人盯着,也从不失控。用一张图看这五个角色怎么连成一个环:

flowchart LR
    SP["SETPOINT<br/>目标值 25°C"] --> COMP
    SENSOR["SENSOR<br/>测温度计"] -->|"测量值 22°C"| COMP["COMPARATOR<br/>比较目标与实测"]
    COMP -->|"误差 3°C"| CTRL["CONTROLLER<br/>决定动作"]
    CTRL -->|"加热指令"| ACT["ACTUATOR<br/>加热丝通电"]
    ACT -->|"改变室温"| PLANT["被控对象<br/>房间"]
    PLANT -->|"新的室温"| SENSOR

这套结构的价值不在于给常识穿上术语外套,而在于它能被拆开检查。回到开头那四种项目管理失败模式,用这套词汇重新表述一遍:

  • “没人告诉你 PR 卡了三天” → SENSOR 缺失(没有测量)
  • “卡了三天算不算严重不知道” → COMPARATOR 缺失(没有明确的判断标准)
  • “知道严重但没人跟进” → CONTROLLER 缺失(判断到了动作却没触发)
  • “推了个不痛不痒的提醒” → ACTUATOR 无效(动作触发了但执行质量差)

四个完全不同的诊断,指向四种完全不同的修复方式。这正是这套定义的用法:遇到”这系统怎么又跑偏了”,先问是哪个角色出了问题,而不是直接跳到”重新设计整个流程”。

工程控制论晚于诺伯特·维纳(Norbert Wiener)1948 年出版的《控制论》(Cybernetics: Or Control and Communication in the Animal and the Machine[2],维纳把控制论定义为”研究动物与机器中控制和通信的科学”——这是更哲学化的泛论。钱学森把它落到工程师能直接拿来用的地方:伺服机构、误差控制、系统稳定性的数学判据。两人合起来定义了这门学科的两端:一端是”这个思想为什么重要”,一端是”这个思想怎么落地成可计算的东西”。

这篇文章接下来做的事情,本质上都是同一件:拿这套四件套 + 设定点的定义,去检查软件工程里的几个具体领域——它们的 SENSOR/COMPARATOR/CONTROLLER/ACTUATOR 分别是什么,哪个角色缺失或者失效了。

1.2 反馈回路:让系统自己纠偏的机制

四件套连起来运转,就是一个反馈回路:输出被重新测量、重新拿来跟目标比较、生成新的纠正动作,动作又产生新的输出。这个回路本身分两种性质完全相反的运转方式。

负反馈趋向稳定——名字听起来”消极”,其实是控制论里最重要的稳定机制。恒温器就是标准的负反馈:室温低于设定值,加热器启动;室温回升,加热减弱;持续到误差归零。任何一个”想要维持在某个状态附近”的系统,靠的都是负反馈。

正反馈放大偏差,追求变化而不是稳定。麦克风拾取扬声器的声音,再被放大,输出后又被麦克风拾取——一圈一圈越来越大声,直到啸叫或者饱和。复利是正反馈温和的版本:利息计入本金,下一期利息更高。软件系统里,缓存雪崩是典型的坏正反馈:

flowchart LR
    A["缓存命中率下降"] --> B["更多请求打到上游数据库"]
    B --> C["数据库响应变慢"]
    C --> D["更多请求超时重试"]
    D --> A

识别一个系统里藏着的正反馈环路,往往比设计负反馈更重要——因为正反馈失控的速度是指数级的,而且系统自己不会主动喊停。

管项目的直觉也符合这个区分:一个健康的项目管理系统应该是负反馈——偏差被检测到就被拉回来;但”催得太狠导致团队士气下降、效率更差、催得更狠”是一个隐藏的正反馈环,管理者往往要等它跑了两三圈才意识到自己正在往错误的方向加速。分清眼前的回路是负反馈还是正反馈,是诊断”这套流程为什么越跑越糟”的第一步——如果发现越管越乱、越催越慢,大概率不是执行不到位,是踩进了一个正反馈环,这时候”再加把劲”只会让情况更糟,正确的动作是先打断这个环,而不是继续往里面加燃料。

1.3 层次结构:不是所有决策都值得你亲自做

四件套描述的是单一回路怎么运转,但真实系统里从来不是只有一个回路。项目一多,你不可能对每个项目都跑一遍完整的”测量-比较-决策-执行”循环——你没有那么多注意力。

钱学森给出的解法是分层:把控制系统拆成两层,一层负责高频、日常、可以完全按规则执行的动作,另一层只负责低频、需要价值判断的决策[1]

  • 直接控制层:以毫秒到秒为单位运转,处理”按规则就能判断”的情况。汽车的 ABS 防抱死系统每毫秒检测一次车轮转速,你完全感觉不到它在工作。
  • 组织层:以分钟到月为单位运转,处理”需要判断力”的情况。公司的董事会每季度看一次 KPI 异常,不会天天盯着一线员工的每个操作。

两层之间靠协议连接——组织层下达的是抽象目标(“优化本季度燃油效率”),直接控制层执行的是具体指令(“油门开到 60%”),协议规定了抽象目标怎么被翻译成具体动作。

flowchart TB
    subgraph org["组织层(分钟到月)"]
        O1["设定目标 / setpoint"]
        O2["监控异常汇总"]
        O3["关键决策:继续 / 调整 / 终止"]
    end
    subgraph direct["直接控制层(毫秒到秒)"]
        D1["持续测量"]
        D2["按规则比较"]
        D3["自动执行纠正动作"]
    end
    O1 -->|"协议:抽象目标→具体阈值"| D2
    D1 --> D2 --> D3 --> D1
    D3 -->|"异常汇总(协议:具体事件→抽象信号)"| O2
    O2 --> O3 --> O1

回到管项目:你自己就是组织层,日常的进度追踪、异常检测、状态汇报应该完全下放给直接控制层(自动化脚本、监控系统、定时任务)去做;你只在协议规定的关键点——方向确认、异常升级、验收决策——出现。这不是”偷懒”,是层次结构本身的要求:让组织层处理它不擅长的高频决策,等于强迫它做它做不好的事,最终结果是组织层疲于奔命,直接控制层却什么都没学会。

1.4 人在回路:钱学森说人不是 bug,是 feature

层次结构里的组织层,往往就是人。这是钱学森理论里一个常被忽略但很重要的立场:人不是需要被消除的干扰项,而是控制系统里不可替代的一个功能模块[1]

古典控制论(其实现在很多人也是这个想法)倾向于把”人的介入”当成噪声——理想系统应该全自动,人只在设计阶段出现。钱学森的立场不同:系统会遇到设计时没想到的情况,遇到需要权衡而非计算的决策,遇到”规则说不清楚”的边界——这些场合,人的判断力、经验和适应能力是系统里唯一能处理它们的部件。飞机的自动驾驶系统 99% 的时间不需要人,但鸟击或引擎故障发生时,飞行员的存在决定了系统是否还能维持稳定。

这个立场翻译成项目管理的语言:自动化不是为了”取代你的判断”,是为了把不需要判断力的部分先过滤掉,让你的判断力只用在真正需要它的地方。一个”完全自动”的项目管理系统和一个”事事都靠你盯”的项目管理系统,都是这套理论明确反对的两个极端——前者会把机器的偏见自动化,后者会把人重新变回执行层的一部分。

三个核心思想(反馈回路、层次结构、人在回路)搭好之后,接下来几节把它们一一对照到具体的软件工程领域——先看反馈回路在 CI/CD 里长什么样。


2. 反馈回路:从月度发布到 master 直推生产

CI/CD 是软件工程里最容易看到反馈回路的地方,因为它的每个环节都对应着四件套里的一个角色,而且这套回路的”频率”在过去二十年里经历了肉眼可见的压缩。

2.1 频率的演化史,就是反馈回路收紧的历史

早期软件发布是月度批处理:开发者提交代码,攒够一个月的量,测试团队跑一遍完整回归测试,运维手动部署。这个流程里确实存在反馈——测试结果会反馈给开发——但反馈的周期太长,长到”这次改动引入的 bug”和”发现这次改动引入的 bug”之间隔了几周,中间又叠了几十个新提交,定位问题的成本呈几何级数增长。

flowchart LR
    A["月度批处理<br/>反馈周期:数周"] --> B["周发布<br/>反馈周期:数天"]
    B --> C["日发布<br/>反馈周期:数小时"]
    C --> D["master 直推生产<br/>反馈周期:分钟级"]

持续集成(CI)先把测试反馈的周期从”月度”压到”每次提交”:GitHub Actions 用 on: 字段声明事件驱动的构建[3],每次提交自动触发一整套测试:

name: CI
on:
  push:
    branches: [main]
  pull_request:
    types: [opened, synchronize]
  schedule:
    - cron: '0 2 * * *'   # 每天凌晨额外跑一次完整回归
  workflow_dispatch:       # 手动触发的应急入口

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

这段配置里的每个 on: 事件都是一个 SENSOR 的触发条件——push 是”有新代码”,schedule 是”定时巡检”,workflow_dispatch 是留给人的手动触发口。这一步做的事情,用四件套的话说:把 SENSOR(测试结果)的采样频率从”月”提到”分钟”。

持续部署(CD)接着把”测试通过之后要不要上线”这个决策也自动化。Meta(当时还是 Facebook)在 2017 年的工程博客里记录了这个演化路径的终点:从月度发布,到周发布,到日发布,最终实现”100% 的生产 web 服务器直接从 master 分支发布”[4]——发布周期从”月”压缩到”分钟级”。这不只是效率提升,是反馈回路频率的量级跃迁:频率越高,每次纠偏需要修正的偏差就越小,系统整体行为也就越平滑。这跟”多久开一次项目复盘会”是同一个逻辑——月度复盘的问题是,等你发现方向错了,已经错了一个月;高频、小步的检查点让每次纠偏的成本都很小。

2.2 GitLab / Jenkins:分阶段的反馈环

不同 CI 平台对”分阶段测试”的实现方式不完全一样,但结构是共通的——把一次构建拆成若干个 stage,前一个 stage 失败,后面的 stage 直接不跑,反馈尽早截断,不让坏改动继续往下游走。

flowchart LR
    A["Lint<br/>秒级"] -->|"通过"| B["单元测试<br/>分钟级"]
    B -->|"通过"| C["集成测试<br/>数分钟"]
    C -->|"通过"| D["部署到 staging<br/>数分钟"]
    D -->|"通过"| E["部署到生产<br/>分钟级"]
    A -.->|"失败"| F["立即中断,通知开发者"]
    B -.->|"失败"| F
    C -.->|"失败"| F

GitLab CI 用 .gitlab-ci.yml 里的 stages 字段描述这条链,Jenkins 用 Jenkinsfile 里的 pipeline { stages { ... } }。两者的设计哲学一致:越靠前的 stage 越便宜、越快,越靠后的 stage 越贵、越慢——所以把”能用秒级检查排除掉的问题”尽早排除,不要等到最后一步昂贵的集成测试才发现一个 lint 错误。这本身也是一种分层控制:便宜的 SENSOR 先跑,贵的 SENSOR 后跑,用便宜的检查过滤掉大部分噪音,让贵的检查只处理真正需要它的场景。

2.3 Spinnaker:金丝雀发布是分层的风险控制

如果说前面的 stage 划分解决的是”什么时候测试”,金丝雀发布(Canary Release)解决的是”新版本要不要信任”这个更细的问题。Spinnaker 这类发布编排工具的典型金丝雀流程:

flowchart TD
    A["部署新版本到 1% 流量"] --> B{"监控指标是否正常?"}
    B -->|"是"| C["扩大到 5% 流量"]
    B -->|"否"| Z["自动回滚"]
    C --> D{"监控指标是否正常?"}
    D -->|"是"| E["扩大到 25% 流量"]
    D -->|"否"| Z
    E --> F{"监控指标是否正常?"}
    F -->|"是"| G["全量发布"]
    F -->|"否"| Z

每一步的”监控指标是否正常”就是一次完整的四件套循环——SENSOR 测新版本的错误率/延迟,COMPARATOR 拿它跟旧版本或者预设阈值比较,CONTROLLER 决定继续扩大流量还是回滚,ACTUATOR 真正调整流量比例。这套流程把”新版本到底靠不靠谱”这个大风险,拆成了四个小风险,每一步的风险敞口都被严格限制在”1%""5%""25%“——即便判断错了,代价也是可控的。这是层次结构思想在单个发布流程内部的微缩版:分阶段验证,而不是一次性全量赌上去

2.4 Kubernetes Operator:最干净的工业实现

如果要找一个”反馈回路在软件工程里的教科书实现”,Kubernetes Operator 的 reconcile loop 是目前最干净的例子。Kubernetes 官方文档对它的定义几乎是逐字对应四件套的:

“Operators follow a reconciliation loop, comparing desired state (in the spec) against actual state (in the status of the object). If the actual state doesn’t match the desired state, the Operator takes corrective action until the spec and status match.”[5]

翻成中文:Operator 持续对比”你想要的状态”(desired state,写在 spec 里)和”现在的真实状态”(actual state,写在 status 里)——不一致就采取纠正动作,一直纠正到两者一致为止。用一个具体的 CRD(自定义资源)示意:

apiVersion: apps.example.com/v1
kind: WebAppReplica
metadata:
  name: my-web-app
spec:
  replicas: 3          # ← desired state(SETPOINT)
status:
  readyReplicas: 2      # ← actual state(SENSOR 测出来的)

Operator 的 reconcile 循环看到 spec.replicas = 3status.readyReplicas = 2,就知道少了一个副本,于是创建一个新的 Pod——这就是 CONTROLLER 判断 + ACTUATOR 执行。对照四件套:

  • desired state(spec) = SETPOINT
  • 持续 watch actual state = SENSOR
  • 对比 desired 和 actual = COMPARATOR
  • 不一致时”takes corrective action” = CONTROLLER + ACTUATOR

一个 Kubernetes Operator 不需要人告诉它”现在该做什么”,它自己会不停地问”现在的状态和我被告知要维持的状态差多少”,然后自己纠正。这正是恒温器逻辑的工程化放大版——只不过被控对象从室温变成了整个集群的资源状态。

2.5 TDD:最微观的反馈环

反馈回路不只存在于系统架构层面,也存在于最小的单元里。测试驱动开发(TDD)的”红-绿-重构”循环——先写一个失败的测试(红),写最小实现让它通过(绿),再重构——是软件工程里频率最高的反馈环之一,运转在秒到分钟级。

flowchart LR
    A["写一个失败的测试(红)"] --> B["写最小实现让它通过(绿)"]
    B --> C["重构,保持测试通过"]
    C --> A

测试金字塔(Mike Cohn 提出的概念)描述的正是这套反馈环的分层:

flowchart TD
    E["端到端测试<br/>少量 · 数十分钟级 · 最接近真实"]
    I["集成测试<br/>中等数量 · 分钟级"]
    U["单元测试<br/>大量 · 秒级 · 最快最便宜"]
    U --> I --> E

层级越高,反馈越慢,但覆盖的真实性也越高——这本身也是一种分层控制:高频层处理”这个函数对不对”,低频层处理”这几个服务组合起来对不对”,各司其职。一个常见的工程反模式是反过来——大量依赖端到端测试、缺少单元测试——结果是每次改动都要等几十分钟才能知道对不对,反馈回路的频率被拉得很低,纠偏成本随之上升。

2.6 PID:一套通用的”怎么纠偏”算法

前面讲的都是”什么时候测量、什么时候比较”,还有一个问题没展开:CONTROLLER 具体怎么决定纠偏的力度?工程上最常用、最经受住时间考验的答案是 PID 控制律——这也是恒温器、汽车定速巡航、工业反应釜温控背后最常见的算法。PID 的核心思路可以完全用直觉理解,不需要数学公式:

  • P(比例项):纠偏力度跟”现在偏差有多大”成正比——偏差越大,纠正动作越猛。这是最直观的部分:室温离目标差得远,就多加热;差得近,就少加热。
  • I(积分项):纠偏力度还要看”过去一段时间累积的偏差”——如果偏差一直存在、迟迟没消除,即便当前偏差不大,也要加大力度,避免系统”永远差一点点但从不真正达标”。
  • D(微分项):纠偏力度还要考虑”偏差正在变化得多快”——如果偏差正在快速缩小,就该提前减弱纠正力度,避免”矫枉过正”冲过目标值再折返。

三项加在一起,PID 控制器同时照顾了”现在差多少""一直以来差多少""正在变化多快”这三个维度,这也是为什么它比单纯的”if 偏差 > 阈值就纠正”的简单规则更平滑、更少震荡。

这套算法在软件工程里有一些直接的物理载体。Jenkins 的架构——一个中央 controller 负责调度,多个 agent 节点负责实际执行——本质上就是一个中央控制器 + 多个执行器的经典结构:controller 里的 Jenkinsfile 定义了”纠偏的具体逻辑”,队列里积压的构建任务数量则近似于”积分项”——排队的任务越积压越久,系统对资源紧张的响应力度就该越大。

GitLab CI 和 Jenkins 的分阶段执行(stage 1 通过才能进 stage 2)则更接近串联控制:每个 stage 是一个独立的小控制器,只有前一级收敛(成功)了,后一级才会启动,这跟工业控制里”用几个简单的控制器接力,而不是一个复杂的控制器包办所有事”是同一个设计哲学——简单部件的组合,比一个复杂的整体更容易被理解和调试。

一个实际的工程启示:当你发现某个自动化规则总是”反应过度”或者”反应不足”,可能不是阈值设错了,而是缺了 PID 三项里的某一项。只看”当前偏差”(纯 P)的规则容易在偏差持续存在但幅度不大时长期无动作;只看”当前状态”不看”变化趋势”的规则容易矫枉过正——这跟第 1 节讲的”四件套里哪个角色出了问题”是同一类诊断思路,只是把诊断颗粒度细化到了 CONTROLLER 内部的具体算法结构。

2.7 Meta 的极致规模:当反馈频率快到自适应

ICSE 2016 有一篇论文专门记录 Facebook 和 OANDA 的持续部署实践,数据显示当时 Facebook 每天的部署次数已经到了几十到上百次的量级。当反馈频率快到这个程度,整套发布系统的行为已经不再是”人工审批的批处理”,而更接近控制论里的自适应控制:系统持续监测每次部署后的用户行为反馈(错误率、转化率、性能指标漂移),用这个反馈自动调整下一步的发布节奏和范围,不再需要人为每一次发布单独做决策——人只在”整体发布策略是否需要调整”这个更高的抽象层面介入。

2.8 反馈回路不是免费的

需要提一个容易被忽视的代价:反馈回路本身有成本,测试基础设施、监控系统、自动化部署管线都需要维护,这个成本必须小于它防止的损失,否则”建反馈回路”这件事本身就是负收益的。这也是为什么小团队、早期项目常常先跳过完整的 CI/CD 而不是一步到位——不是不重要,是当下的规模还撑不起这套基础设施的维护成本。这个判断标准在文章最后的决策树里会再展开。


3. 层次结构:为什么微服务拆着拆着就变成了组织问题

第一节讲了层次结构的抽象定义——直接控制层处理高频决策,组织层处理低频判断。这一节把它落到一个更具体的问题:为什么把一个单体应用拆成微服务,最后往往变成了一次组织架构的重新设计?

3.1 耦合是复杂度的真正来源

一个系统里的组件越多,组件之间可能存在的交互关系就呈指数增长——n 个组件之间最多有 n(n-1)/2 条两两关系,如果交互不止两两、还有三方甚至更多方联动,复杂度增长得更快。单层控制结构在组件数量超过一定规模后必然失效,不是因为工程师不够聪明,而是因为计算量和故障隔离能力有物理上限

分层解决这个问题的方式,是找到系统里天然的”弱耦合”边界,把强耦合的部分捆在一起变成一个子系统,子系统之间只保留少量、显式的连接点。BGP 路由协议是这个思路在网络协议层面的工业实践:互联网的每个自治系统(AS)只跟邻居交换路由信息,全网却能实现全局一致的路径选择——没有一个”上帝视角”节点知道全网拓扑,但分层协议让局部信息的交换足够撑起全局的稳定性。

3.2 微服务架构:把耦合矩阵拆成对角块

单体应用的耦合结构可以想象成一个巨大的关系矩阵——任何一个模块的改动都可能牵动矩阵里的任意一格:

flowchart TB
    subgraph mono["单体应用:任意模块互相耦合"]
        direction LR
        M1["用户模块"] --- M2["订单模块"]
        M2 --- M3["库存模块"]
        M3 --- M1
        M1 --- M4["支付模块"]
        M4 --- M2
        M4 --- M3
    end

微服务架构做的事情,是主动把这个矩阵重新排列,让强耦合的部分聚成对角上的小块,块与块之间只保留少量的、显式定义的接口(通常是 API 调用):

flowchart LR
    subgraph S1["用户服务"]
        direction TB
        U1["内部强耦合逻辑"]
    end
    subgraph S2["订单服务"]
        direction TB
        O1["内部强耦合逻辑"]
    end
    subgraph S3["库存服务"]
        direction TB
        I1["内部强耦合逻辑"]
    end
    S1 -->|"显式 API"| S2
    S2 -->|"显式 API"| S3

这不是”更好的代码组织方式”这么简单一句话能概括的,它对应的是控制理论里一个明确的判断:如果两个子系统之间的耦合足够弱,它们就可以被独立地控制——各自用简单的方法维护自己的稳定,不需要一个中央控制器去协调所有细节。这就是为什么微服务化真正带来的收益,往往不是”性能更好”,而是”故障隔离更好”和”团队可以独立发布”——两者都是耦合变弱之后的直接后果。

Kubernetes Operator 在这个语境下又出现了一次:每个 Operator 管理自己那一块资源的 reconcile loop,互相之间通过 API 对象(而不是共享内存或直接调用)交互——这正是”对角块 + 弱耦合接口”在容器编排层面的实现。CNCF 关于云原生架构的白皮书里描述的核心原则,正是这种松耦合、独立部署、独立伸缩的设计取向。

3.3 康威定律:组织结构会渗透进代码结构

梅尔文·康威(Melvin Conway)1968 年提出一个后来被广泛引用的观察:“任何组织设计出的系统,其结构就是该组织沟通结构的副本。”(“Any organization that designs a system will produce a design whose structure is a copy of the organization’s communication structure.”)

flowchart LR
    subgraph org["组织沟通结构"]
        T1["团队A"] <--> T2["团队B"]
        T2 <--> T3["团队C"]
    end
    org -.->|"投影"| sys["系统架构结构"]
    subgraph sys["系统架构结构"]
        M1["模块A"] <--> M2["模块B"]
        M2 <--> M3["模块C"]
    end

这句话解释了为什么很多团队在”拆微服务”之后仍然拆不干净——如果两个团队本来就需要频繁沟通、频繁互相求助,那无论代码架构上怎么拆,实际的耦合关系(谁依赖谁、谁的改动会影响谁)都会顽固地反映沟通结构。代码架构是组织沟通模式的投影,不是相反。

这个观察也给出了一个反过来的操作空间——逆康威操纵(reverse Conway maneuver):先主动设计组织结构,再让代码架构自然长成你想要的样子。

3.4 Team Topologies:四种团队类型

一套具体的”先设计组织、再让架构跟上”的方法论是 Team Topologies,它把团队分成四种类型:

团队类型职责类比
面向业务流团队(Stream-aligned)直接对齐某条业务价值流,端到端负责销售部——直接对客户负责
赋能团队(Enabling)帮助其他团队跨越能力鸿沟,不直接交付业务功能内部技术顾问——帮别的团队把事情做好
复杂子系统团队(Complicated-subsystem)负责需要深度专业知识的复杂组件数据库内核团队、算法团队
平台团队(Platform)提供内部自服务能力,减少其他团队的认知负担内部 PaaS——别人不需要懂底层就能用

以及三种交互模式:协作(Collaboration,两个团队紧密配合完成一件事)、“X 即服务”(X-as-a-Service,一个团队消费另一个团队提供的能力,边界清晰)、赋能(Facilitating,帮助另一个团队提升能力,而不是替它干活)。

回到管项目的语境:如果你发现两个项目组老是互相拖后腿,先问的问题往往不是”代码怎么拆”,是”这两个团队的汇报线和沟通频率是不是本来就该分开,交互模式该不该从紧密协作换成 X 即服务”。这跟微服务架构里”两个服务耦合太紧就该合并,或者干脆重新划分边界”是同一个判断——只是发生在组织层面而不是代码层面。

3.5 耦合强度可以被量化,而不只是感觉

前面讲耦合的时候一直用”强""弱”这种模糊的形容词,但实际工程决策里,耦合强度是可以被大致量化的——虽然不需要严格的数学模型,一个粗糙但有用的估算方法是看跨边界调用的频率乘以单次调用的代价:如果两个服务之间每天要打几万次接口,而且每次调用都涉及复杂的数据同步,这就是强耦合,拆分它们只会制造更多的网络往返和一致性问题;如果两个服务之间一天只交互几十次,而且交互内容是粗粒度的(比如一天同步一次汇总数据),这就是弱耦合,完全可以独立部署、独立演化。

这个估算方法翻到团队协作上同样适用:两个团队每天要开好几次会协调细节,这是强耦合,不适合分拆成两个独立汇报线;两个团队一周碰一次头对齐大方向就够,这是弱耦合,可以放心地让他们独立运作。判断”要不要拆”的关键不是”看起来像不像两个独立的东西”,是”拆开之后跨边界的沟通成本会不会突然暴涨”

一个常见的错误是过早地按”业务领域看起来不一样”就拆分服务或团队,而没有先测一测实际的交互频率——结果是拆完之后跨服务调用链变得又长又慢,团队之间的同步会议反而比拆分之前更多,这是一种典型的”分层没有真正降低耦合,只是把耦合从代码层面挪到了网络调用层面”的失败模式。

3.6 分层不是免费的午餐

分层结构解决了单层控制的问题,但引入了新的成本:跨层通信需要协议,协议需要维护,协议设计错了会比没有协议更糟——组织层如果给出的目标模糊不清(“提升系统稳定性”而不是”P99 延迟低于 200ms”),直接控制层就没法把它翻译成具体动作,分层反而制造了信息丢失。这也是为什么”设定点要清晰”这件事,会在后面几节反复出现——它是任何分层系统能正常运转的前提,而不是细节。


4. 人在回路:SRE 值班和”人类反馈”到底在做什么

第一节说人不是 bug,是控制系统里不可替代的功能模块。这一节把这个抽象命题落到两个具体场景:SRE 的值班制度,和训练大语言模型时的人类反馈。表面上这是两件毫不相关的事——一个是运维实践,一个是机器学习技术——但它们在控制论的意义上是同构的。

4.1 值班工程师是”非线性的组织层”

Site Reliability Engineer(SRE)的值班制度,本质上是把”人在回路”这套设计原则工程化成了一套可执行的组织流程。标准的故障响应链条是:

flowchart LR
    A["检测<br/>自动报警 / 客户报告"] --> B["分诊<br/>值班工程师评估严重度"]
    B --> C["响应<br/>修复动作"]
    C --> D["缓解<br/>回滚 / 限流 / 切流量"]
    D --> E["根因修复<br/>永久修复"]
    E --> F["复盘<br/>blameless postmortem"]
    F -.->|"改进 SOP / 自动化规则"| A

每一步都有 SLA——P1 故障五分钟响应、一小时止损、二十四小时根因修复。把每一步对照回四件套:

故障响应步骤控制论对位
检测SENSOR
分诊COMPARATOR(严重度 vs 预设标准)
响应ACTUATOR
缓解紧急执行动作(更高优先级的 ACTUATOR)
根因修复更新 SETPOINT(永久修复相当于重新定义”正常”)
复盘更新 CONTROLLER 本身的判断逻辑

这里有一个值得单独说的观察:复盘不是在修系统状态,是在修控制器。日常的响应动作是让系统回到设定点,复盘是在问”下次同样的偏差出现,判断逻辑要不要变、阈值要不要调”——这是一个元层操作,超出了直接控制层的常规调节能力,只能靠组织层(人)在事后完成。

这套流程里,自动化报警和自动化恢复是直接控制层(毫秒到秒级响应),值班工程师是组织层(分钟到小时级响应)。两者的边界很清楚:能靠阈值判断的交给自动化(CPU 超过 90% 自动扩容),需要多方协调、风险判断、客户沟通的交给人(要不要在业务高峰期回滚、这次故障要不要对外公告)。

PagerDuty 这类值班调度系统在这个链条里扮演的角色是”协议翻译层”——把”哪个报警”翻译成”路由给哪个工程师""按什么优先级""升级策略是什么”,是组织层和直接控制层之间那套”协议”的具体工程实现。

4.2 Sheridan 五级模型:自动化程度不是越高越好

心理学家 Thomas Sheridan 1992 年在他关于人类监督控制的经典著作里,把”人机协作程度”分成五个层级,从完全手动到完全自动[6]

层级名称机器做什么人做什么SRE 场景举例
1完全手动只显示信息执行所有动作手动 SSH 上服务器排查日志
2动作辅助高亮异常、推荐方案执行动作监控面板标红异常指标,工程师手动处理
3共享控制和人同时参与控制和机器共同决策自动限流 + 工程师随时可以手动调整限流阈值
4监督控制自动运转监督并在异常时介入自动扩缩容 + 自动报警 + 工程师看仪表盘
5全自动完全自主只设定目标全自动故障自愈系统
flowchart LR
    L1["Level 1<br/>完全手动"] --> L2["Level 2<br/>动作辅助"]
    L2 --> L3["Level 3<br/>共享控制"]
    L3 --> L4["Level 4<br/>监督控制"]
    L4 --> L5["Level 5<br/>全自动"]

大多数成熟的 SRE 值班体系落在第 3-4 级之间:自动化报警 + 自动化恢复动作先跑,值班工程师监督仪表盘,异常升级才真正介入。这里有一个反直觉但重要的点:级别越高不代表越好。级别 5(全自动)要求系统对所有可能的情况都有预定义的正确应对,而真实世界里”未知的未知”永远存在——完全跳过 HIL 直接做到全自动,历史上有过代价极其高昂的教训(波音 737 MAX 的自动配平系统在没有充分的人工切换路径的情况下试图完全自主纠偏,最终导致两次严重事故)。稳定性不是自动化程度的单调函数,是”自动化程度”和”人工介入路径的可靠性”两者共同决定的——一个 Level 5 系统如果缺少可靠的降级到 Level 3 的路径,反而比一个诚实地停在 Level 3 的系统更危险。

4.3 RLHF:把”人类反馈”变成可优化的信号

如果说 SRE 值班是”人在回路”最直观的工程实践,人类反馈强化学习(RLHF)是这套原则在训练大语言模型时的数学化版本——它把”人怎么判断好坏”这件本来很模糊的事,转成了一个可以用梯度下降优化的目标函数。

用调酒的类比讲整个过程会更直观:你尝了一口鸡尾酒说”再多点柠檬”,调酒师根据这句反馈调整比例——这就是人类反馈驱动优化的直觉版本。RLHF 把这个过程拆成三个阶段,OpenAI 在训练 InstructGPT 时把这套流程规模化到了工业级别[7] [8]

flowchart LR
    A["阶段1<br/>收集人类偏好<br/>标注者选出更好的输出"] --> B["阶段2<br/>训练奖励模型<br/>学习模拟人类判断标准"]
    B --> C["阶段3<br/>PPO 优化语言模型<br/>让输出更倾向拿高分"]
    C -.->|"持续采样、持续评估"| A
  1. 收集人类偏好:让标注者看两个模型输出,选出更好的那个,攒出一批”哪个更好”的对比数据。
  2. 训练一个奖励模型:用这批对比数据训练一个模型,让它学会预测”人类会更喜欢哪个输出”——本质上是在学习一个函数,近似地模拟人类的判断标准。
  3. 用这个奖励模型去优化语言模型:用强化学习算法(通常是 PPO)调整语言模型的参数,让它的输出更倾向于拿到高分。

对照四件套:阶段一是在采集 SENSOR 数据(人类在不同输出下的偏好),阶段二是在训练一个 COMPARATOR(模拟人类判断标准的可微函数),阶段三是拿这个 COMPARATOR 当反馈信号去调整 CONTROLLER(语言模型本身)。整个过程本质上是”用人类反馈训练出一个自动化的判断器,再拿这个判断器去驱动优化”——这跟值班工程师复盘之后把经验写进 Runbook、Runbook 变成自动化脚本的过程,是同一种”把人的判断力编码进系统”的操作,只是发生的层面不同。

但这里有一个必须承认的风险:训练出来的奖励模型终究是对人类判断标准的一个近似,近似和真实标准之间总会有偏差。当优化算法足够强、跑得足够久,它会开始利用这个偏差——找到那些”奖励模型打高分,但实际上人类并不会真的喜欢”的输出模式,这就是 reward hacking(奖励作弊)。学术界对 RLHF 的系统性综述列出了一长串这类开放问题[9]

  • 奖励作弊:模型找到奖励模型的漏洞,输出”看起来对但实际没用”的内容
  • 分布偏移:训练时见到的场景和部署后遇到的场景不一样,奖励模型的判断在新场景下失灵
  • 人类标注者本身有分歧:不同标注者对”哪个更好”意见不一致,训练数据本身就带噪声
  • 欺骗行为:模型学会输出”看起来诚实、实际在迎合评分标准”的内容,而不是真正诚实

这些问题背后共同的结构是:任何”用近似的判断器替代真实判断”的系统,近似误差会在长期优化压力下被放大,不管这个判断器是训练出来的奖励模型,还是值班工程师复盘后写下的自动化规则。写自动化规则和训练奖励模型面对的是同一类风险,只是前者的”过拟合”表现为规则覆盖不全,后者表现为可被利用的评分漏洞。

4.4 规则式对齐:把原则当成设定点

Anthropic 在 2022 年提出的 Constitutional AI,走的是跟 RLHF 不完全相同的路径:不完全依赖在线的人类反馈,而是给模型一组显式的”原则”(比如”要有帮助""不要造成伤害""要诚实”),让模型先按这些原则自我批评、自我修正,再用修正后的数据训练[10]

用第一节的语言看,这相当于把”设定点”从”依赖在线人类反馈才能确定的隐式目标”,换成了”提前写死的一组显式原则”。好处是不需要每一步都等人类标注,代价是规则原则本身必须写得足够清楚,写得含糊就等于给了一个模糊的 SETPOINT,模糊的目标永远不可能被稳定地追踪到。这其实回到了第 3 节末尾的判断——分层系统里,模糊的组织层目标会让直接控制层无所适从,不管这个”直接控制层”是自动化脚本,还是被训练的语言模型本身。

4.5 两种路径的取舍

RLHF 和 Constitutional AI 不是互相排斥的两条路,而是在”在线人类反馈”和”离线显式原则”这个谱系的两端,工业界的实践通常是两者结合:用规则先做一轮低成本的自我修正,再用相对少量但更精准的人类反馈做最后一轮校准。这跟 SRE 值班里”自动化规则先处理大部分场景,人只处理规则覆盖不到的边界”是同一个分工原则——原则处理已知的、可预见的场景,人类反馈处理规则写不清楚、需要临场判断的场景。

两者的成本结构也不一样,这是选择时的一个重要考量:

维度RLHFConstitutional AI
反馈来源在线人类标注模型依据显式原则自我批评
训练成本高——需要持续收集人类偏好数据相对低——原则写一次可反复用
对”目标漂移”的敏感度高——人类偏好会随时间演化,需要持续更新低——原则本身不随时间自动变化
覆盖”规则说不清楚”场景的能力强——人类判断力能处理边界情况弱——依赖原则写得够不够全面
实施难度中——收集数据 + 训练奖励模型 + PPO高——原则设计和原则之间的冲突处理更复杂

一个值得注意的细节:Constitutional AI 里如果有多条原则同时生效——比如”要有帮助”和”不要造成伤害”——这两条原则在某些场景下会互相冲突(用户要求的帮助本身有潜在风险),这时候单一的”设定点”框架不够用,需要一种”多目标同时满足”的机制,本质上是在问”这几个目标哪个优先级更高”,这跟第 3 节里”组织层目标模糊会让直接控制层无所适从”是同一类问题——只是这里的”直接控制层”变成了处理多重原则的模型本身。

4.6 用辩论逼近真相:另一种让判断更可靠的机制

前面讲的 RLHF 和 Constitutional AI,本质上都是”想办法让一个判断器更接近人类真实的判断标准”。还有第三条路径,思路不太一样:让两个 AI 互相辩论,人类裁判只需要判断谁的论证更有说服力,而不需要自己直接判断哪个答案是对的

这套思路的原始出发点是一个现实困境:很多问题太复杂,人类裁判没办法直接判断哪个答案对——但人类往往可以判断”这个论证站不住脚,因为对方指出了一个漏洞”。OpenAI 在 2018 年提出的 AI Safety via Debate 就是这个思路的具体实现[14]:让两个能力相当的智能体轮流指出对方论证里的漏洞,理论上能够把复杂问题拆解成一系列人类裁判可以逐一验证的小判断,即便裁判自己不具备解决整个问题的能力。

flowchart LR
    Q["复杂问题"] --> A1["Agent A 给出论证"]
    Q --> A2["Agent B 反驳 / 指出漏洞"]
    A1 --> J["人类裁判"]
    A2 --> J
    J -->|"裁判只需判断这一轮谁更有说服力"| Round["下一轮辩论"]
    Round -.->|"多轮之后"| Final["裁判给出最终判断"]

这套机制在博弈论上对应的是一种双方零和博弈的均衡态——当博弈设计得足够好,双方都采取最优策略时的均衡结果,会比任何一方单独给出的答案更接近真实情况。这跟前面 RLHF 和 Constitutional AI 有一个关键的结构性差异:RLHF 和 CAI 都是在训练一个更好的判断器,辩论机制是在设计一套让判断器(人类裁判)不需要变得更聪明,也能得出可靠结论的博弈规则——如果博弈的规则设计对了,即便裁判本身能力有限,也能通过观察双方的攻防得到可靠的判断。

这套思路翻译到日常的项目管理场景,其实很像一个常见但容易被忽视的技巧:当你自己没办法直接判断一个技术方案好不好,让两个持不同意见的工程师当面辩论,往往比自己埋头研究更快得到一个可靠的判断——你不需要变成这个领域的专家,只需要能判断”这一轮谁的论证更站得住脚”。这也是为什么很多团队会刻意设计”唱反调”的角色(红队/蓝队评审)——不是为了制造对立,是为了让决策者不需要具备全部专业知识,也能通过观察双方的攻防得到一个可靠的判断。

4.7 AI 系统里,为什么经典控制论的直觉会打折扣

前面几节把 RLHF、Constitutional AI 都对照进了这套控制论框架,但有一个诚实的问题需要正面回答:为什么不能简单地说”AI 系统就是经典控制系统的又一个实例”,然后照搬所有的稳定性判据?

答案是 AI 系统——尤其是大语言模型驱动的 Agent 系统——跟工业控制系统(恒温器、飞机自动驾驶、化工厂反应釜)之间存在三个本质差异,这些差异让经典控制论的很多”免费的稳定性保证”用不上:

第一个差异是非平稳性。经典控制系统假设被控对象的动态特性(比如”油门踩多少、车速涨多少”这个关系)基本固定,或者变化很慢。但语言模型的”参数”每次重新训练都会变,模型面对的用户偏好本身也在持续演化——这意味着”控制律”必须持续适配一个一直在变的目标,而不是瞄准一个固定的靶子。

第二个差异是反馈稀疏。恒温器每一毫秒都能测到当前温度,反馈是连续、密集的。但人类对 AI 输出的偏好反馈——RLHF 里的”这个回答比那个好”——是离散的、稀疏的,而且收集成本很高,模型往往要连续做很多步决策才能等到一次反馈,这中间”这一步到底对最终结果贡献了多少”变得很难判断,这是强化学习里经典的信用分配难题。

第三个差异是对抗性。经典控制系统面对的”扰动”通常是可以提前建模的自然噪声(风力、温度波动),但 AI 系统面对的”扰动”可能来自另一个有自己目标的智能体——比如恶意用户设计的提示词注入攻击,这类扰动不是提前能穷举的,攻击者会持续寻找新的漏洞,这跟”提前设计好应对所有已知扰动”的鲁棒控制假设完全不同。

这三个差异合起来的结论是:工程控制论给 AI 系统提供的是有用的语言和部分数学工具,但不能指望它给出跟工业控制系统同等级别的稳定性保证。RLHF 的 reward hacking、Constitutional AI 的原则冲突、多 Agent 系统的协调失灵,这些都是”经典稳定性判据在新场景下失效”的具体表现,而不是”没有认真做工程”的失误。诚实地承认这个边界,比夸大控制论的适用范围更有用——这也是为什么这篇文章在讨论 AI Agent 系统时反复强调”独立检查者""升级通知”这类相对朴素的工程手段,而不是声称有一套形式化的稳定性证明。


5. 可观测性与数字孪生:能不能”通过输出猜出系统内部在想什么”

前面几节讲的都是”系统怎么动”,这一节讲一个更基础的前提:你怎么知道系统现在处于什么状态。反馈回路要求 SENSOR 能准确测量,但很多时候你想测的那个状态,根本不能被直接观察到,只能通过间接的输出去推断。

5.1 可观测性的工程定义

控制理论里有一个很精确的概念叫”可观性”(observability):一个系统是可观的,意味着只靠观察它的输出,就能唯一地确定它内部的真实状态。反过来,如果系统内部有些状态维度完全不影响任何可观察的输出,那这些维度就是”不可观”的——不管你测得多勤,都猜不出它们在做什么。

软件工程里的可观测性实践,做的正是这件事。OpenTelemetry(云原生计算基金会推动的可观测性标准)把它拆成三个支柱:

flowchart TB
    subgraph pillars["可观测性三支柱"]
        M["Metrics 指标<br/>连续数值测量"]
        L["Logs 日志<br/>离散事件记录"]
        T["Traces 追踪<br/>跨服务因果链"]
    end
    M --> Q["能不能推断系统内部状态?"]
    L --> Q
    T --> Q
  • Metrics(指标):数值化的连续测量——CPU 使用率、请求数、错误率
  • Logs(日志):离散的事件记录——异常堆栈、审计日志、状态变更
  • Traces(追踪):跨服务的请求链路——一个请求从入口到出口经过了哪些服务、每一跳花了多少时间

三者对应控制理论里”输出”的三种不同形态:Metrics 是连续测量,Logs 是离散事件,Traces 是跨子系统的因果链。Prometheus 作为这套标准的一个主流实现,靠周期性拉取(pull)各服务暴露的指标端点来完成”持续采样系统真实状态”这件事——这就是软件工程版本的 SENSOR,工作方式和恒温器里的温度计没有本质区别,只是采样对象从室温变成了成百上千个服务实例的内部计数器。

一个具体的例子:一个服务只暴露”请求总数”和”错误总数”两个指标,你能推断出”错误率”这个衍生状态,但推断不出”是哪个用户的请求出错了""出错的具体原因是什么”——这些维度就是当前可观测性设计下的”不可观”状态。加 Traces(记录每个请求的完整链路)和结构化 Logs(记录每次错误的上下文)才能把这些维度重新变成可观的。可观测性建设的核心工作,就是不断扩大”可观”的状态空间,让越来越多此前靠猜的东西变成靠测的

5.2 可观性和可控性是一对双胞胎

控制理论里有个漂亮的对偶关系:可观性问题和可控性问题在数学上是彼此的转置——不可控的状态是”控制器永远够不到的地方”,不可观的状态是”输出永远反映不出来的地方”,两者都是系统里的死区。

这个对偶关系翻到工程直觉上:如果你无法观察一个子系统的内部状态,你也没法真正控制它——你顶多能盲操,靠”上次这么做好像有效”这种经验主义,而不是靠真实的反馈去纠偏。这解释了为什么”先上可观测性,再谈自动化”是一个常见的工程顺序——在给一个系统接自动化的纠偏动作之前,先确认它的关键状态是不是可以被稳定地测量到,否则自动化只是在”盲人摸象式”地乱动。

这个顺序问题在项目管理里也一样成立:在给一个项目接”自动提醒""自动升级”之类的规则之前,先确认这个项目的关键状态本身有没有被稳定地记录下来——如果连”当前处于哪个阶段”都靠记忆,任何自动化规则都是建在流沙上的。

5.3 数字孪生:用一个虚拟模型逼近真实状态

可观测性走到极致,会出现一种更主动的做法:不满足于被动测量输出,而是维护一个平行的虚拟模型,用真实系统的测量数据不断校正这个虚拟模型,让它尽可能逼近真实系统的内部状态——这就是数字孪生(Digital Twin)。这个概念最早由 NASA 在阿波罗计划期间提出:地面维护一个飞船的数学镜像,跟飞船实时同步遥测数据,工程师靠这个镜像做决策,而不是等着飞船本身”告诉”他们发生了什么。

flowchart LR
    Real["真实系统"] -->|"实时测量数据"| Correct["校正"]
    Twin["虚拟模型(孪生)"] -->|"基于模型的预测"| Correct
    Correct -->|"融合后的最优估计"| Twin
    Twin -->|"用于预测 / 决策"| Decision["工程决策"]

数字孪生真正有意思的地方,不是”建一个模拟器”,而是它用了一套明确的数学方法在”模型预测”和”实际测量”之间找最优权衡——这套方法叫卡尔曼滤波:每来一次新的测量,就用它去修正模型此前的预测,修正的力度取决于”这次测量有多可信”和”模型此前的预测有多可信”两者的相对权重。软件系统的版本通常粗糙得多——大部分只是”读日志 + 画图”——但核心诉求是一样的:用有限的、带噪声的观测,尽量精确地还原系统内部真实在发生什么

在软件工程语境下,一个轻量版的”数字孪生”思路是:维护一份系统预期状态的显式记录(比如一份配置文件描述”这个服务现在应该是什么样子”),持续跟真实运行状态对比,差异本身就是最有价值的信号——这跟 Kubernetes Operator 的 desired/actual 对比结构完全一样,只是应用场景从容器编排扩展到了任何”你希望知道系统现在到底在干什么”的场合。

5.4 数字孪生不是监控,也不是仿真——它是两者的结合

容易把数字孪生和另外两个概念搞混:监控(monitoring)和仿真(simulation)。三者其实是完全不同的东西,混淆它们会导致投入了数字孪生级别的复杂度,却只得到了监控级别的收益:

维度监控仿真数字孪生
是否接收实时数据
是否有预测能力否——只反映当下是——基于模型推演未来
是否跟真实系统双向同步否——单向读取否——完全独立运行是——持续校正 + 可以反向影响决策
决策是否能反过来影响真实系统是——这是”闭环”的关键

监控告诉你”现在怎么样”,仿真告诉你”如果这样会怎么样”,数字孪生把两者结合起来,还多了一步:用真实数据持续校正虚拟模型,让仿真的结果越来越可信,然后再把仿真得出的判断用来指导真实系统的下一步动作——这才是”闭环数字孪生”,而不是一个好看的仪表盘。

一个常见的浅层实现是”读日志 + 画图”——这只是监控,不是数字孪生,因为它没有一个持续被校正的模型,也没有”用这个模型做决策”的反向环节。真正有价值的数字孪生投入,往往发生在这样的场景:你需要在做一个高风险决策之前,先在虚拟模型上跑一遍”如果这么做会怎样”,而不是直接在真实系统上冒险——这跟后面第 6 节要讲的混沌工程形成一个有意思的对照:混沌工程是在真实系统上做可控的实验,数字孪生是在虚拟模型上做无风险的实验,两者都是为了回答”这个系统的边界在哪里”,只是承担风险的地方不一样。


6. 自适应与混沌工程:主动找系统的稳定边界

前面几节讲的都是”系统正常运转时怎么维持稳定”,这一节讲一个反过来的问题:怎么知道一个系统的稳定边界在哪里?被动等待故障发生,永远不知道系统的真实鲁棒性——直到它在生产环境里真的崩了。

6.1 稳定性不是”没出过事”

一个容易产生的误解是:一个系统”一直没出过故障”就等于”很稳定”。控制论对稳定性的定义要更严格:稳定性描述的是系统受到扰动之后,能不能回到平衡状态附近,而不是”有没有受到过扰动”。一个从来没被真正测试过鲁棒性的系统,“没出过事”很可能只是”没遇到过真正的扰动”,而不是”能扛住扰动”。

这是混沌工程(Chaos Engineering)存在的理由:如果不主动找故障,故障会主动找你,而且往往选在最糟糕的时候——流量高峰、值班人手最少的深夜。

6.2 Netflix Chaos Monkey:主动注入扰动

Netflix 在 2011 年推出 Chaos Monkey——在生产环境随机关闭 EC2 实例,理由很直接:硬件、网络、软件的故障是必然会发生的事情,与其等它自己发生,不如主动、可控地制造它,提前暴露系统里那些”理论上应该能扛住,但实际没验证过”的假设。

Netflix 后续发布的《混沌工程原则》给出了一套标准流程:

flowchart LR
    A["1. 定义稳态<br/>系统正常时的指标基线"] --> B["2. 提出假设<br/>注入扰动后指标会怎样"]
    B --> C["3. 真实环境实验<br/>在生产(或高仿真环境)注入扰动"]
    C --> D["4. 限制爆炸半径<br/>先小范围,观察后再扩大"]
    D --> E{"实际结果 vs 假设"}
    E -->|"符合预期"| F["系统鲁棒性得到验证"]
    E -->|"不符合预期"| G["发现了一个此前未知的脆弱点"]

对照四件套:注入的扰动是在测试整套反馈回路——SENSOR 能不能及时发现异常、COMPARATOR 的阈值设置是否合理、CONTROLLER 的应对逻辑是否正确、ACTUATOR 的恢复动作是否真的有效。混沌工程本质上是”对整个控制回路做一次压力测试”,而不只是测试某个单点组件。

6.3 自适应控制:让系统自己调整参数

Chaos Engineering 回答的是”系统当前的稳定边界在哪”,自适应控制回答的是”当环境变了,系统怎么调整自己继续保持稳定”。软件工程里两个常见的自适应控制模式:

  • 模型预测控制式的滚动优化:在每个决策点,基于当前已知信息重新求解一次”未来一段时间的最优动作”,但只执行第一步,到下一个决策点重新求解——持续部署系统里的自动扩缩容策略通常就是这个思路:不是死板地按固定阈值扩容,而是持续预测接下来几分钟的负载趋势,动态调整扩容幅度。
  • 在线参数识别:系统持续观察自己的表现,在线调整控制参数本身,而不是用一套写死的规则跑到底。金丝雀发布里”根据前一阶段的实际表现动态调整下一阶段的流量比例”,就是一种粗粒度的在线参数识别。

这两种模式跟前面讲的鲁棒控制(设计一套参数使系统在一定范围的扰动下都能保持稳定)形成对比:鲁棒控制是”提前想好各种情况,设计一套通用参数”,自适应控制是”不提前假设太多,实时根据观察调整”。哪种更合适取决于扰动的可预测程度——如果扰动的类型和范围大致可以提前枚举,鲁棒控制更简单可靠;如果环境变化太快、太不可预测,自适应控制的灵活性更有价值,代价是复杂度和”万一识别错了怎么办”的额外风险。

6.4 混沌工程的工业化:从手动关实例到平台化实验

Netflix 之后,混沌工程从一个团队内部的实践演化成了一整个工具生态,每一步演化都对应着”注入更大范围的扰动,或者把整个实验流程标准化”:

工具/阶段扰动范围特点
Chaos Monkey(2011)单个实例随机关闭 EC2 实例
Chaos Gorilla(2014)整个可用区模拟一整个 availability zone 失效
Chaos Kong(2014)整个云区域模拟一整个 AWS region 失效
Gremlin(商业化平台)可配置:主机/容器/服务级别提供 UI 选择故障类型、目标、爆炸半径,实时观测
AWS Fault Injection Service声明式实验跟 CloudWatch/CloudFormation 集成,自动化编排实验

这条演化路径本身就是一个值得注意的规律:扰动范围从小到大逐步扩展,不是一开始就直接模拟”整个数据中心断电”,而是先验证小范围的故障恢复能力,再逐步扩大——这跟第 2 节讲的金丝雀发布”1% → 5% → 25% → 全量”是同一个思路,只是这里控制的不是”要不要信任新版本”,是”敢不敢承担多大范围的故障风险”。

6.5 自适应控制的失败模式:不是所有的”自动调整”都可靠

第 6.3 节讲了自适应控制的正面价值,但也要诚实面对它的失败模式,否则容易高估”让系统自己调整参数”这件事的可靠性:

  • 激励不足:如果系统没有经历足够多样的真实场景,自适应机制没有足够的信息去学习正确的参数——一个只在低流量场景下运行过的自动扩容策略,遇到从未见过的流量模式(比如秒杀活动)时,它的”经验”可能完全不适用。
  • 参数漂移追不上环境变化:如果底层环境变化的速度比自适应机制学习的速度更快,系统会一直在”追赶一个已经过去的状态”,看起来在调整,实际上一直滞后。
  • 极端扰动下的估计失效:自适应机制通常假设扰动在一个合理范围内平滑变化,突发的极端事件(比如流量瞬间暴增百倍)可能直接让在线估计算法失效,甚至做出错误的反向调整。

一个真实世界的例子(虽然发生在硬件系统而非软件系统):某些汽车的自适应巡航控制系统曾因传感器校准偏移导致自适应机制无法正确追踪前车距离,最终触发大规模召回。这个教训翻译到软件工程语境:任何自适应机制都需要有一个”兜底的保守策略”——当自适应算法的置信度低、或者观测到的情况远超训练时见过的范围,系统应该退回到一个已知安全的固定策略,而不是继续相信一个可能已经失效的自适应判断。这也是为什么 SRE 实践里”自动扩容”通常都会设置一个硬性的资源上限——不完全信任自适应逻辑,给它一个物理边界。

6.6 主动测试和被动监控的分工

混沌工程和可观测性经常被放在一起讨论,但两者的分工不同:可观测性是被动的——系统正常运转,你持续测量它;混沌工程是主动的——你故意制造异常,看系统的反馈回路能不能正确应对。没有良好的可观测性,混沌工程注入的扰动你根本看不清楚发生了什么;反过来,只有可观测性而没有混沌工程,你知道系统现在的状态,但不知道它遇到真正的压力时会怎样。两者合起来才是完整的”系统健康度检查”——一个告诉你现在怎么样,一个告诉你极限在哪里。


7. Loop Engineering:2026 年,同一套结构换了个名字

前面几节讲的都是相对成熟的领域——CI/CD、微服务、SRE、可观测性、混沌工程,这些实践已经存在了十几二十年。这一节讲一个刚刚发生的重演:AI Agent 系统正在重新发明这套结构,只是换了个名字,叫 Loop Engineering

7.1 为什么现在需要一个新词

2024 年底,Anthropic 在自己的工程博客《Building Effective Agents》里定义了几种构建 AI Agent 的工作流模式,其中一种叫 evaluator-optimizer

“one LLM call generates a response while another provides evaluation and feedback in a loop”[11]

翻译一下:一个 LLM 负责生成回答,另一个 LLM 负责评估和反馈,两者循环迭代。

flowchart LR
    In["输入任务"] --> Gen["LLM 生成器<br/>Generator"]
    Gen -->|"候选方案"| Eval["LLM 评估器<br/>Evaluator"]
    Eval -->|"拒绝 + 反馈"| Gen
    Eval -->|"通过"| Out["输出结果"]

这句话如果把”LLM”换成”控制器”,读起来跟第一节的四件套没有任何区别——generator 是被控对象的输出端,evaluator 是 COMPARATOR + SENSOR 的合体。这不是巧合,是同一个数学结构在新载体上的重新出现。

到了 2025-2026 年,随着 Claude Code、Codex 这类可以长时间自主运行的编程 Agent 变得实用,一个新问题浮现出来:怎么让 Agent 不是”每次都要你手动写一遍提示词才干活”,而是能像 cron job 一样自己被触发、自己判断要不要干、干完自己汇报。有人把这套设计模式叫做 Loop Engineering——字面意思就是”设计循环”,但背后的核心诉求就是这篇文章一直在讲的东西:把四件套显式地搭进 Agent 系统里,而不是靠人一遍遍地重复手动操作

7.2 evaluator-optimizer 之外:另外四种搭 Agent 工作流的方式

Anthropic 那篇工程博客其实不只定义了 evaluator-optimizer 一种模式,一共给出了五种常见的 Agent 工作流构建方式,剩下四种同样能在前面几节的控制论概念里找到对应关系,放在一起看会更清楚 Loop Engineering 到底是在拼装什么零件:

flowchart TB
    subgraph pc["Prompt Chaining 串联"]
        direction LR
        PC1["步骤1"] --> PC2["步骤2"] --> PC3["步骤3"]
    end
    subgraph rt["Routing 路由"]
        direction TB
        RT0["输入分类器"] --> RT1["专用处理器A"]
        RT0 --> RT2["专用处理器B"]
    end
    subgraph pl["Parallelization 并行"]
        direction TB
        PL0["任务拆分"] --> PL1["Agent A"]
        PL0 --> PL2["Agent B"]
        PL1 --> PL3["结果汇总"]
        PL2 --> PL3
    end
    subgraph ow["Orchestrator-workers 编排"]
        direction TB
        OW0["编排者"] --> OW1["worker 1"]
        OW0 --> OW2["worker 2"]
        OW1 --> OW0
        OW2 --> OW0
    end
Anthropic 模式做的事控制论对位
Prompt chaining(串联)把一个大任务拆成几个步骤,一步接一步执行,每步的输出是下一步的输入串联控制——多个简单控制器接力,而不是一个复杂控制器包办一切
Routing(路由)先判断输入属于哪一类,再分发给专门处理这一类问题的处理逻辑对应第 3 节讲的”分层”——不同类型的问题交给不同的专用子系统,而不是一个万能控制器硬撑所有场景
Parallelization(并行)把任务拆成互不依赖的几份,同时执行,最后汇总结果对应 Worktrees 的隔离原则——多个独立的被控对象同时运转,只要互相不耦合,可以完全并行
Orchestrator-workers(编排)一个中心节点负责拆解任务、分配给下游 worker、汇总结果,worker 之间不直接通信直接对应第 1 节讲的层次结构——编排者是组织层,worker 是直接控制层
Evaluator-optimizer(评估-优化)一个 Agent 生成,另一个独立评估,循环迭代直接对应四件套里的闭环反馈——本节的核心

Anthropic 在这篇博客里还有一句常被忽略但很重要的总结:“Successfully building agents isn’t about building the most sophisticated system. It’s about building the right system for your needs.”——翻译过来就是,“搭建 Agent 系统的关键不是把它做得多精巧,而是搭对当下真正需要的那套系统”。这句话跟下一节的四条件测试是同一个精神:不是每个任务都需要 evaluator-optimizer 这种带闭环反馈的精巧结构,很多场景下一个简单的 prompt chaining 就够用——五种模式没有高下之分,只有”当前任务的反馈需求匹配哪一种”的区分,这跟第 2 节讨论”要不要给一个项目上重量级 CI/CD”是完全一样的判断逻辑:结构的复杂度应该匹配问题本身的复杂度,而不是无脑追求最精巧的设计。

7.3 四条件测试:值不值得把这件事变成一个 Loop

在动手搭一个自动循环之前,一个反复被提到的判断清单是四个问题,全部满足才值得投入:

flowchart TD
    Q1{"任务会重复发生吗?"} -->|"否"| Manual["保持手动,别建 Loop"]
    Q1 -->|"是"| Q2{"有客观的自动化验证手段吗?"}
    Q2 -->|"否"| Manual
    Q2 -->|"是"| Q3{"预算能承受多少浪费?"}
    Q3 -->|"不能"| Manual
    Q3 -->|"能"| Q4{"Agent 的工具是否齐全?"}
    Q4 -->|"不齐全"| Fix["先补工具,再考虑 Loop"]
    Q4 -->|"齐全"| Build["值得搭 Loop"]
  • 这个任务是不是会重复发生? 一次性的事不值得建 Loop,手动做一次比搭一整套自动化基础设施便宜。
  • 有没有客观的自动化验证手段? 如果判断”做对了没有”这件事本身需要人工审阅,那就没法把 checker 也自动化,Loop 的核心价值——无人值守地循环——就打了折。
  • 预算能承受多少浪费? Agent 会犯错、会走弯路、会消耗超出预期的资源,这个成本必须在可接受范围内。
  • Agent 手上的工具是不是齐全? 缺少必要的工具(比如没法运行测试、没法查日志),Agent 的判断质量会明显下降,循环转起来也没意义。

四条都满足,才值得”把 Loop 搭起来”;漏了任何一条,更合理的做法是先保持手动,或者先补齐缺的那一环,而不是急着自动化。这跟”要不要给一个项目上重量级的 CI/CD 流水线”的判断标准,本质上是同一套逻辑——规模不到,基础设施的维护成本会超过它带来的收益。

7.4 六个组件:一个可持续运转的 Loop 需要什么

Google Chrome 团队的工程生产力负责人 Addy Osmani 在 2026 年中的一篇长文里,把这套模式拆成六个组件[12]

flowchart TB
    subgraph loop["一次完整的 Loop 运转"]
        direction LR
        Auto["Automations<br/>心跳"] --> Find["找到要做的事"]
        Find --> Hand["交给 Agent"]
        Hand --> Check["检查结果"]
        Check --> Record["记录发生了什么"]
        Record --> Decide["决定下一步"]
        Decide -.->|"下一轮"| Auto
    end
    Skills["Skills<br/>项目知识"] -.->|"每轮读取"| Hand
    Conn["Connectors/MCP<br/>外部工具"] -.->|"每轮调用"| Hand
    Sub["Sub-agents<br/>maker/checker 分离"] -.->|"独立验证"| Check
    State["State<br/>持久化状态文件"] -.->|"跨轮次存活"| Record
组件作用类比
Automations心跳——定时或按条件触发 Agent恒温器的采样时钟
Worktrees隔离——多个 Agent 并行工作,互不冲突多个收银台同时服务
Skills项目知识——把”怎么做”写一次,反复复用厨师的食谱
Connectors / MCP连接外部工具(GitHub、Linear、Slack)标准接口
Sub-agentsmaker/checker 分离——写的人和检查的人不是同一个找别人当裁判,不自己当自己的裁判
State持久化——Agent 每次运行都会”忘光”,状态必须落盘病人带着病历去看医生

逐个看这六个组件,其实都能在这篇文章前面的章节里找到对应关系:

  • Automations 对应第 2 节讲的事件触发(on: push / on: schedule),只是触发对象从 CI 流水线换成了 Agent。
  • Worktrees 对应第 3 节讲的解耦——多个 Agent 并行工作不互相干扰,靠的是给每个 Agent 一块独立的工作空间(通常是 git worktree),跟微服务用独立部署单元隔离故障是同一个思路。
  • Skills 是钱学森”层次结构”里”组织层的经验编码化”的具体实现——把此前需要人反复讲解的项目知识,写成一份可以被反复读取的文件。
  • Connectors / MCP 对应可观测性/可控性讨论里”系统需要接触外部世界才能测量和执行”的接口层。
  • Sub-agents 直接对应 evaluator-optimizer 模式——检查者和被检查者分离。
  • State 对应数字孪生讨论里”持久化的状态记录”,只是这里持久化的是 Agent 自己的工作记忆,而不是被控对象的物理状态。

Addy Osmani 有一句话精准地概括了 State 这个组件的必要性:“The agent forgets, the repo does not”——Agent 每一轮运行结束都会遗忘这一轮发生的所有事,唯一能跨轮次存活的是写到磁盘上的文件。这不是 Agent 系统的缺陷,是它的运行方式决定的约束——正因为如此,“状态往哪里放、什么时候写、什么时候读”变成了整套系统设计里最核心的问题,而不是一个实现细节。

7.5 Ralph Wiggum 循环:最简陷阱,也是最简教训

在 Loop Engineering 这个词流行起来之前,工程师 Geoffrey Huntley 在 2025 年中写过一篇很有影响力的文章,描述了一种极简到近乎荒谬的循环[13]

while :; do cat PROMPT.md | claude-code ; done

这一行 shell 脚本——不断地把同一份提示词喂给编程 Agent,让它自己决定该做什么、做完再重新读一遍提示词、继续下一轮。没有精巧的状态机,没有复杂的编排,所有的”记忆”都存在磁盘上的 PROMPT.md 和一些附属文件里:

flowchart LR
    A["读 PROMPT.md"] --> B["Agent 干活"]
    B --> C["把进度写回磁盘上的文件"]
    C --> A

Huntley 给这个模式起了个名字——Ralph——用《辛普森一家》里那个天真但偶尔无意中做对事的角色命名,暗示这套方法”确定性地表现糟糕,但在一个不确定的世界里”,有时候反而比精巧的设计更稳。

但 Ralph 循环也暴露了一个后来被反复讨论的失败模式,业内借用同一个名字称它为Ralph Wiggum 失败模式:Agent 会提前判断自己”完成了”,在活干到一半的时候就发出完成信号,循环因此提前退出——失败发生了,但是悄无声息

flowchart TD
    A["Agent 开始一轮任务"] --> B["Agent 判断:我觉得做完了"]
    B --> C{"真的做完了吗?"}
    C -->|"实际没做完"| D["循环提前退出<br/>失败但没人知道"]
    C -->|"确实做完了"| E["循环正常结束"]

这个失败模式的根源,回到四件套的语言,其实非常清楚:COMPARATOR 和 CONTROLLER 都是同一个 Agent 自己在做,它既是干活的人,又是判断”活干完了没有”的人。自己给自己打分,天然地会倾向高估自己的完成度——这跟前面 RLHF 讨论的自我偏好偏差(self-preferential bias)是一回事:判断者和被判断者不能是同一个个体,否则判断这件事本身就失去了意义

这正是六个组件里”Sub-agents:maker 和 checker 分离”这一条存在的原因。真正稳健的 Loop 设计,“完成条件”必须由一个独立的、有明确验证标准的角色去判断——最好是一个可以自动运行的测试、linter、或者一个专门被指派做检查、且用了不同模型或不同上下文的 Agent,而不是让干活的那个 Agent 自己说”我做完了”。Anthropic 的 evaluator-optimizer 模式名字里的”evaluator”,本质上就是在强调这个独立性。

7.6 一个具体的 Loop 设计示例

把前面讲的原则拼在一起,一个相对稳健的 Loop 大致是这样的结构:

flowchart TD
    Trigger["定时触发(Automations)"] --> Gather["独立脚本收集当前状态<br/>无需 LLM 推理"]
    Gather --> NeedWork{"有需要处理的事项吗?"}
    NeedWork -->|"没有"| Silent["静默退出,不打扰任何人"]
    NeedWork -->|"有"| Agent["LLM Agent 读取 Skill + 处理任务"]
    Agent --> Verify["独立验证步骤<br/>脚本断言 或 独立 Agent 复核"]
    Verify -->|"验证通过"| WriteState["写入状态文件(State)"]
    Verify -->|"验证失败"| Escalate["升级通知人(人在回路)"]
    WriteState --> Notify["按优先级推送通知"]

这个结构里几个值得强调的设计选择:

  • 收集状态和执行任务分离——前一步不需要 LLM 推理,纯粹是脚本判断”有没有事要做”,这样即便定时任务频率很高,没有工作要处理的时候也是零成本的静默退出,不会浪费资源问同一个”有没有新东西”的问题。
  • 验证步骤独立于执行步骤——即是 7.5 节反复强调的 maker/checker 分离。
  • 验证失败走升级通知,而不是自动重试到成功为止——因为 Agent 反复重试同一个它做不好的任务,往往不会自己发现问题所在,反而会在错误的方向上消耗更多资源;把这类情况显式地暴露给人,比让系统自己死循环更安全。

8. 多个系统在一起:分布式协调和一致性问题

前面几节大多在讨论单个系统内部的控制结构,但现实世界很少只有一个孤立的系统在运转——手上的十几个项目本身就是”多个独立系统需要协调”的场景,云服务的多租户架构也是。这一节专门讨论”当有多个独立运转的控制回路凑在一起,怎么让它们不互相拖累”。

8.1 多租户 SaaS:多个独立控制回路 + 共享基础设施

一个云服务同时给成百上千个客户(租户)提供服务,每个租户理论上应该像一个独立的被控系统——各自的负载、各自的数据、各自的资源占用不应该互相影响。但实际架构里,租户之间不可避免地共享一部分基础设施(数据库连接池、计算集群、网络带宽),这就产生了一个经典的分布式控制问题:多个理论上独立的控制回路,通过共享资源产生了隐藏的耦合

工业界对这个问题的标准应对,是把租户隔离程度做成一个可以调节的旋钮,常见的几种模式:

隔离模式隔离程度耦合强度适用场景
完全独立部署(Siloed)每个租户独立的计算和存储实例几乎为零高合规要求、大客户
共享基础设施 + 逻辑隔离(Pooled)共享底层资源,靠权限和配额区分中等——共享资源的争用会互相影响标准 SaaS 产品的大多数用户
混合模式(Hybrid)关键组件独立,其他共享分层——不同资源层耦合程度不同平衡合规成本和运维成本

这跟第 3 节讨论的”耦合强度决定了能不能独立控制”是同一个原理,只是把”微服务模块”换成了”租户”。耦合足够弱的时候,每个租户可以被当成独立系统来管理,各自的异常检测、各自的扩缩容互不干扰;耦合一旦变强(比如共享的数据库突然变慢),所有租户会同时受影响——这正是很多 SaaS 平台”雪崩故障”的根源:平时耦合很弱,看起来是完全独立的多个系统,但某个共享组件出问题的时候,隐藏的耦合突然暴露,一个租户的问题变成了所有租户的问题。

8.2 一致性问题:多个节点怎么达成同一个判断

分布式系统里另一个经典问题:如果同一个”设定点”需要同步到多个独立运行的节点(比如把一次配置更新推送到所有服务器),怎么保证所有节点最终都收敛到同一个状态?这类问题的一般解法叫共识协议——每个节点周期性地跟其他节点交换信息,逐步减小彼此之间状态的差异,直到全部趋同。

这个逐步收敛的过程可以用一个简单的度量来跟踪:所有节点两两之间状态差异的总和。这个度量数值越大,说明系统里的节点越不一致;每一轮节点间的通信如果设计正确,这个数值应该持续下降,直到趋于零——这跟第 1 节讲的 Lyapunov 式稳定性判据是同一个思路:只要能找到一个随时间单调下降的”不一致程度”度量,就能证明系统最终会收敛到一致状态

翻译到管理多个项目的场景:如果你需要让多个项目组保持同一套流程规范,靠”发一次通知就指望大家自动同步”是不现实的——真正靠谱的做法更接近共识协议的思路,定期检查每个项目组的实际执行情况跟规范之间的差距,有差距就主动同步,而不是假设发出去的信息会自动被完全吸收。

8.3 涌现行为:局部规则堆出全局效果,好的和坏的都有

钱学森晚年提出过一个概念,叫”开放复杂巨系统”,用来描述那些规模巨大、内部包含大量自主决策个体(比如人)、并且持续与外部环境交互的系统——经济系统、生态系统、大型社会组织都是典型例子。这类系统一个核心特征是:全局的宏观行为,不是被某个中心节点直接设计出来的,而是从大量局部个体的简单决策规则里”涌现”出来的

多租户 SaaS 平台的整体资源利用率、整体故障率这类宏观指标,其实也是”涌现”出来的——没有一个中心控制器直接决定”整个平台今天的错误率是多少”,这个数字是成千上万个租户各自独立行为叠加的结果。这个视角带来一个重要的工程启示:想要改变系统的宏观行为,有时候直接在宏观层面下命令是没用的,需要去调整局部的规则,因为宏观行为本身就是从局部规则里长出来的,不是一个可以被单独拧动的旋钮。

这跟管理十几个项目的直觉也是相通的:如果你发现”整体团队的交付节奏一直不理想”,直接在全局层面喊”大家加快速度”往往没什么用——真正有效的干预通常发生在局部:调整某个具体流程的审批环节、修正某个具体团队的沟通方式。全局的健康度,是所有局部细节堆出来的涌现结果,不是一个可以直接操控的独立变量。


9. 我的 Hermes 项目管理工作流框架

前面几节讲的都是别人的系统——Kubernetes、Meta、Anthropic、Netflix。这一节讲我自己的:管理几十个并行项目用的那套自动化工作流,底座是 Linear(issue 状态的唯一真相源)+ GitHub(代码和 PR 的唯一真相源),中间层是一套自己写的 Python 脚本和一台按四件套框架设计的定时轮询系统。这套东西最近刚做过一轮结构性重写——不是缝缝补补,是把”发现哪里薄弱”直接对照进具体的代码改动。这一节讲的是重写之后落地的样子,而不是设想。

9.1 系统运行路径

这里不会展开每一个细节,结合软件工程和敏捷开发的做法,大部分工作流设计上都大同小异。

管理几十个项目,本质上是管理几十条”从想法到上线”的流水线。每条流水线上的每一个具体任务,在这套系统里对应 Linear 里的一个 issue,issue 会依次经过几个状态:

flowchart LR
    A["Backlog<br/>待评估的想法"] --> B["Todo<br/>已排期,可以开始"]
    B --> C["In Progress<br/>正在开发"]
    C --> D["In Review<br/>PR 已提交,等待确认"]
    D --> E["Done<br/>已完成并验收"]

一个 issue 具体由谁来推进,有两条路:人工路径——我自己在本地开发,issue 状态由我手动切换;AI 自动路径——整个开发过程交给 AI Agent 全程执行,我只在关键节点(方案确认、代码合入)点头。判断走哪条路的依据,是 issue 上有没有一个叫 agent:auto 的标签——打了这个标签,就意味着”这个任务归自动化系统接管”,系统会按预设规则主动把它推进到开发环节,不需要我说”开始做”。

agent:auto 任务有一条硬性规则:下发任务时,issue 描述里必须写清楚一个”验收标准”区块,格式类似”文件 X 确实生成、测试 Y 全部通过、接口 Z 返回预期值”这样可以被机械核对的条目清单,而不是”代码质量良好”这种主观描述。原因很直接:AI 做完之后,验证”做完了没有”这件事本身也要交给自动化程序去核对,核对的依据必须是写死的、可执行的条件,不能是一句模糊的形容词。

AI 执行完代码、提交完 commit 之后,标签会从 agent:auto 换成 agent:pending——这个标签变化标志着”AI 认为自己做完了,但还没有经过独立验证,不能直接标记 Done”。agent:pending 就是触发下一节”验收 Gate”的入口条件:系统看到这个标签,就知道该去跑一遍验收流程,核对 issue 描述里那份验收标准清单,通过了才真正推进到 Done。

flowchart LR
    A["Todo"] -->|"打上 agent:auto<br/>+ 写好验收标准"| B["In Progress<br/>AI 开发中"]
    B -->|"代码提交完成<br/>agent:auto → agent:pending"| C["等待验收 Gate"]
    C -->|"验收通过"| D["Done"]
    C -->|"验收失败"| E["自动修复重试<br/>(有上限)"]
    E -->|"重试仍失败"| F["转人工处理"]

有了这套标签语义作为背景,接下来讲的”架构怎么运转”、“验收 Gate 怎么改造”才不会显得突然。

9.2 精简架构:没有 webhook,靠轮询 + diff 模拟事件驱动

系统没有接 Linear 或 GitHub 的 webhook,靠一个每 30 分钟跑一次的定时任务,把 Linear 的 issue 状态和 GitHub 的 open PR 全量拉一遍,跟上一次拉到的快照做 diff,把”发生了什么变化”翻译成一串类型化的事件(状态变了、标签变了、issue 被碰过了、新 PR 开了、验收到期了、验收被 BLOCK 了……),再把事件写进一份供 Dashboard 读取的快照。

flowchart TD
    A["定时轮询(每 30 分钟)"] --> B["拉取 Linear issue 全量 + GitHub open PR 全量"]
    B --> C["与上次快照 diff"]
    C --> D["生成类型化事件<br/>status_changed / label_changed / pr_opened /<br/>acceptance_due / acceptance_blocked / stale_no_pr / triage_wait"]
    D --> E["写入 Dashboard 快照"]
    D --> F{"事件触发信号检测?"}
    F -->|"是"| G["写 action_tracker,按信号类型分发动作"]
    F -->|"否"| E

“没有 webhook,靠定期拉取 + diff”这句话本身就是一句朴素的工程判断:真正的事件驱动需要对方系统配合推送,成本更高;轮询 + diff 是用采样频率换掉这个依赖,只要采样够快,效果上和事件驱动没有本质区别——这跟第 2 节讨论”发布频率从月度压缩到分钟级”是同一类权衡,只是换了个场景。

拆回四件套:

角色具体实现
SENSOR30 分钟轮询拉取 Linear/GitHub 全量状态
COMPARATOR跟上次快照 diff 出事件,同时对照一份预定义的信号阈值表
CONTROLLER按信号类型分发动作——提醒、自动建 Retro issue、升级告警,外加对”进行中自动任务”数量的并发上限(不超过 3 个,低于这个数才会主动把 Backlog 里的下一个自动任务派进 Todo)
ACTUATORTelegram 推送 + 直接调用 Linear/GitHub API 变更状态

9.3 三态判断:一个容易被漏掉的 SENSOR 陷阱

最初的信号检测逻辑很直觉:“查一下这个信号还在不在,不在了就标记为已解决。“这个逻辑有一个隐蔽的坑:“没查到”和”查询失败”是两种完全不同的情况,但表面上看起来一样。如果 GitHub API 那一刻恰好超时,一个真实存在的 stale commit 信号会因为”没查到”被误判为”已解决”,而实际上它可能连查都没查到,跟解决与否毫无关系。

现在的检测函数改成了三态返回,而不是简单的布尔值:

# 返回值约定(三态):
#   (signals: list[dict], ok: bool)
#   ok=True  + signals=[]  → 确认无信号(可以安全 auto_resolve)
#   ok=True  + signals=[…] → 信号仍存在
#   ok=False + signals=[]  → 本轮查询失败,结论未知,不得用于 auto_resolve

对应的复核逻辑:只有 ok=True 且这一轮真的没找到该信号,才允许把它标记为”已解决”;ok=False(查询失败)直接跳过,维持原状,绝不误判;信号仍然存在,则走原有的”超期未解决”升级逻辑。

flowchart TD
    A["复核一个 pending 信号"] --> B{"本轮检测结果"}
    B -->|"ok=True, 信号消失"| C["确认已解决<br/>标记 resolved"]
    B -->|"ok=True, 信号仍在"| D{"是否已超过 verify_by?"}
    D -->|"是"| E["标记 stale,再次提醒"]
    D -->|"否"| F["保持 pending,等下一轮"]
    B -->|"ok=False, 查询失败"| G["跳过,不改变现状<br/>避免假阳性"]

这一处改动直接对应第 5 节讲的可观测性原则:“没收到异常信号”不等于”系统正常”,它也可能只是意味着测量本身失败了。把这两种情况混为一谈,本质上是把 SENSOR 的可靠性问题伪装成了系统真的健康——这正是可观测性系统最容易踩、也最不容易被发现的坑,因为它只在 SENSOR 恰好故障的那个窗口期才会暴露。

9.4 验收 Gate:先问”有没有客观标准”,再决定要不要跑验收

上一节讲过,agent:pending 标签是”AI 认为做完了,等待验收”的标志。旧版本的问题是:验收这一步默认”验收标准是存在的”,如果任务下发时忘了写清楚可核对的验收标准,验收流程会硬跑一遍、给出一个基于模糊猜测的判断——这跟 7.5 节讨论的 maker/checker 分离精神背道而驰,checker 本身的判断依据都不牢靠,判断结果自然也不可信。

现在的验收 Gate 加了一道前置 BLOCK 门:跑验收之前,先检查 issue 描述里有没有一个显式的 ## 验收标准 区块。没有,直接拒绝执行,推送通知要求先补写,而不是硬着头皮验收出一个不可靠的结论。

flowchart TD
    A["Heartbeat 检测到 agent:pending"] --> B{"description 含<br/>## 验收标准 区块?"}
    B -->|"否"| C["BLOCK<br/>推送通知,写入 action_tracker<br/>终止,不执行验收"]
    B -->|"是"| D{"是否已有验收报告?"}
    D -->|"有"| E["跳过"]
    D -->|"无"| F{"重试次数 < 3?"}
    F -->|"是"| G["触发验收 Gate,重试计数 +1"]
    F -->|"否,已达上限"| H["停止自动重试<br/>推送请求人工介入"]

第二处改动是给验收失败的自动修复循环设了上限:失败后允许自动修复重试,但只给 3 次机会,第 3 次仍不通过就停止自动化、把问题原样交回给人。这是对 Ralph Wiggum 失败模式(7.5 节)最直接的工程化防御——循环本身没有内建的停止条件,是失控的根源;给它设一个明确的重试上限,循环失控的代价就被严格限制住了,而不是任由一个卡住的验收流程无限空转、消耗资源却什么都解决不了。

9.5 健康度评分:把一堆细碎信号收敛成一个能一眼扫过的数字

系统里同时追踪六类信号(commit 停滞、issue 停滞、triage 积压、验收失败、issue 被重开、单个 sprint 内偏差信号过多),每类信号各自有独立的阈值和处理动作。信号一多,逐条去看会耗掉本该用来处理真正问题的注意力——这正是本文开头讲的”管几十个项目”同一个问题的缩小版,只是这次要管的是”几十个信号”。

解法是把这些细碎信号折算成一个 1-10 的健康度分数,外加一个跟上次快照对比出的趋势(改善 / 持平 / 恶化),每次运行都存一条历史记录:

信号阈值触发动作
STALE_COMMIT3 天无提交Telegram 提醒
STALE_ISSUE7 天状态未变Telegram 提醒
TRIAGE_WAITTriage 积压 3 天Telegram 提醒
ACCEPTANCE_FAIL验收失败 1 次自动创建 Retro issue
REOPENEDDone 后被重开Telegram 提醒
SPRINT_DIVERGE单 Sprint 偏差信号超 5 个升级告警

日常查看时,先看这一个分数和趋势——分数掉了、趋势在恶化,才去翻具体是哪个项目、哪类信号在拖后腿;分数正常,细节表格完全不用打开。这本质上是一次很朴素的状态压缩:不是第 5 节讲的那种严格的数字孪生(没有状态转移模型,也不做预测),但作用类似——用一个低维的量,替代逐条阅读高维的原始信号,把”要不要现在管一下”这个决策,从”看完所有细节才能判断”变成”看一眼分数就能判断”。当然这里的阈值可以根据项目的进度和驾驭者的精力来调节,既然要保证 Human In The Loop,一定要对自己的精力管理有充分的评估,人的健康度也很重要。

9.6 没有展开讲的部分:其余几个角色现在怎么运转

前面两节聚焦在这次改动最集中的两处——三态判断和验收 Gate。剩下几个角色没有细讲,这里补一下现状,方便对整套系统有一个完整的图像。

SENSOR 端,目前覆盖六类预定义信号:commit 停滞、issue 状态停滞、triage 积压、验收失败、issue 被重新打开、单个 sprint 内偏差信号过多。每一类各自对应一个独立的检测函数,统一收在一张信号定义表里维护——新增一类信号,只要往表里加一条定义,不需要改动检测框架本身。

COMPARATOR 端,阈值目前是全局统一的静态数字(3 天无提交就提醒、7 天状态未变就提醒),不区分具体项目,也不随历史数据动态调整。这是一处有意为之的简化:现在管理的项目规模,还处在”同一套标准能覆盖大多数场景”的阶段,等哪天规模真的大到需要按项目历史动态调阈值,再往这里加复杂度也不迟——提前做这件事只会增加维护成本,换不回等价的收益。

ACTUATOR 端,所有通知目前都走同一条 Telegram 消息流,日常汇报和真正需要决策的异常混在一起,靠消息本身的措辞去传达轻重(比如带不同的标记符号、加”需要你决定”这样的字样),而不是靠独立的优先级通道去物理隔离。对照 4.2 节讨论的 Sheridan 五级模型,这是目前最容易看出短板的地方——“日常监督”和”值得升级”之间还没有显式的通道分级,一条无关紧要的日常汇报和一条真正紧急的异常,触达方式目前还没有区别。

9.7 系统在进化,人的判断力也在跟着变

回过头看,三态判断和验收 Gate 这两处改动都不是凭空设计出来的,而是这套系统运转了一段时间之后,某一次具体的假阳性或者一次不该被判定为通过的验收被发现,倒推回去定位到某个角色的具体缺陷,再补一道机制。这个过程本身,跟第 4 节讲的 SRE 值班”事后复盘 → 更新 Runbook → 沉淀成自动化规则”是同一个循环,只是发生在自己身上而不是别人的系统上。

更准确的说法是:这不是”某一次性地把系统改完美了”,而是这套系统和我作为组织层的判断力,在持续地互相校准。系统每补上一处机制,我需要亲自盯的东西就少一处——三态判断补上之后,不用再怀疑”这条自动解决的信号是不是因为 API 抽风才产生的假阳性”;验收 Gate 加上 BLOCK 门之后,不用再担心”AI 是不是拿着一份没写清楚的标准在自我验收”。反过来,每次我发现一个新的薄弱点——比如上一节提到的通知还没有优先级分级——也是在把自己脑子里那份”这条消息到底该不该打断我”的隐性判断标准,逐步讲清楚、变成一条可以写进代码的规则。

这正是文章开头那套分层直觉的完整闭环:不是”设计一次分层,从此高枕无忧”,而是直接控制层能兜底的事情越来越多,组织层空出来的注意力,转头去发现直接控制层还兜不住的下一处盲点——两者的边界不是画一次就定型,是随着系统一轮一轮运转,一点一点往前挪的。四件套定义的价值,最终就体现在这个持续校准的过程里:每一次”这里不太好用”的模糊感受,都能被精确翻译成”哪个角色缺了什么”,补丁打完,下一步该往哪个方向补,跟本文开头管十几个项目的例子完全对称,靠”感觉不对”去判断永远是靠不住的,无论管的是项目,还是管理项目的这套系统本身。


10. 收尾:什么时候值得上这套框架,什么时候简单规则提示词

讲了很多领域——CI/CD、微服务、SRE、可观测性、混沌工程、AI Agent、自己的工作流,每个领域都套了一遍同一套四件套框架。但这套框架不是万能的,用错场合反而是过度设计。回到开头的问题——管几十个项目,什么时候真的需要动用这一整套思维,什么时候一个简单规则提示词就足够了?

flowchart TD
    subgraph freq["问题一:值不值得上完整四件套?"]
        Q1{"这件事有没有<br/>持续的反馈环?"}
        Q1 -->|"没有<br/>一次性任务/单次批处理"| A1["传统控制论不适用<br/>用简单的任务清单就够"]
        Q1 -->|"有"| Q2{"反馈频率有多高?"}
        Q2 -->|"高频<br/>每天上百次以上,如持续部署"| A2["值得投入设计完整四件套<br/>明确的 SENSOR / COMPARATOR /<br/>CONTROLLER / ACTUATOR"]
        Q2 -->|"低频<br/>每天几次以内"| A3["类比 + 简单规则足够<br/>不必上重装备"]
    end

    subgraph obs["问题二:能不能安全地推进自动化?"]
        Q3{"系统的关键状态<br/>能否被稳定观测?"}
        Q3 -->|"否"| B1["先补可观测性,再谈自动化<br/>盲操只会制造新的不确定性"]
        Q3 -->|"是"| Q4{"有没有验证过<br/>系统的稳定边界?"}
        Q4 -->|"没验证过<br/>一直没出过事不等于稳定"| B2["考虑主动测试<br/>小范围、可控地注入异常"]
        Q4 -->|"验证过"| B3["可以继续往下推进自动化"]
    end

    subgraph agent["问题三:要不要上 Loop Engineering?"]
        Q5{"这个系统有没有<br/>AI Agent 在自主决策?"}
        Q5 -->|"没有"| C1["用传统的分层控制模型<br/>自动化脚本 + 人工审核"]
        Q5 -->|"有"| Q6{"需不需要独立的<br/>完成度验证?"}
        Q6 -->|"需要<br/>任务有明确对错标准"| C2["上 Loop Engineering<br/>maker/checker 分离<br/>不让 Agent 自己给自己打分"]
        Q6 -->|"不需要<br/>任务依赖主观判断"| C3["保持人工审核<br/>不要强行自动化验证"]
    end

    subgraph human["问题四:人机接口健康吗?"]
        Q7{"人在回路的通道<br/>是不是分级的?"}
        Q7 -->|"是<br/>日常汇报和紧急升级<br/>走不同优先级"| D1["组织层的注意力<br/>被有效保护"]
        Q7 -->|"否<br/>所有通知混在一条通道里"| D2["先把这个修好<br/>否则加多少自动化<br/>组织层照样被噪音淹没"]
    end

这张决策树本身也是这篇文章想传达的核心判断:工程控制论不是某个具体工具,是一种检查系统健康度的思维方式——遇到”这系统怎么老是不听话”,先别急着重新设计整个流程,先问四件套里哪个角色缺失或者失效了;遇到”要不要给这个流程自动化”,先问反馈频率高不高、有没有客观的验证标准;遇到”我怎么老是被各种通知淹没”,先问人机接口有没有按优先级分级。

管十几个项目和管一套持续部署的流水线,表面上是两件毫不相关的事,但拆开看,都是同一个问题的不同表现形式:复杂系统怎么在不需要事事亲力亲为的前提下,还能维持稳定、朝着目标前进。钱学森 1954 年给出的答案——分层、反馈、人在关键点介入——过了七十年,依然是回答这个问题最简洁的一套语言,而且现在轮到 AI Agent 系统重新验证它一次。


引用来源

[1] Qian, Xuesen. Engineering Cybernetics. McGraw-Hill, 1954. archive.org/details/engineeringcyber0000qian

[2] Wiener, Norbert. Cybernetics: Or Control and Communication in the Animal and the Machine. MIT Press, 1948. monoskop.org PDF

[3] GitHub Docs. “Events that trigger workflows.” docs.github.com

[4] Rossi, Chuck. “Rapid release at massive scale.” Meta Engineering Blog, 2017-08-31. engineering.fb.com

[5] Kubernetes Documentation. “Operator pattern.” kubernetes.io

[6] Sheridan, Thomas B. Telerobotics, Automation, and Human Supervisory Control. MIT Press, 1992. mitpress.mit.edu

[7] Stiennon, Nisan, et al. “Learning to Summarize from Human Feedback.” 2020. arxiv.org/abs/2009.01325

[8] Ouyang, Long, et al. “Training Language Models to Follow Instructions with Human Feedback.” 2022. arxiv.org/abs/2203.02155

[9] Casper, Stephen, et al. “Open Problems and Fundamental Limitations of Reinforcement Learning from Human Feedback.” 2023. arxiv.org/abs/2307.15217

[10] Bai, Yuntao, et al. “Constitutional AI: Harmlessness from AI Feedback.” Anthropic, 2022. arxiv.org/abs/2212.08073

[11] Schluntz, Erik & Zhang, Barry. “Building Effective Agents.” Anthropic Engineering Blog, 2024-12-19. anthropic.com/engineering/building-effective-agents

[12] Osmani, Addy. “Loop Engineering.” 2026-06-07. addyosmani.com/blog/loop-engineering

[13] Huntley, Geoffrey. “Ralph Wiggum as a software engineer.” 2025-07-14. ghuntley.com/ralph

[14] Irving, Geoffrey, et al. “AI Safety via Debate.” OpenAI, 2018. arxiv.org/abs/1805.00899

评论