AiRobotNews 人工智能机器人技术网

图工程实战:从零搭一套 CI 分诊系统,让 agent 不再挤在一个循环里

图工程实战:从零搭一套 CI 分诊系统,让 agent 不再挤在一个循环里

一个 agent 在一个循环里可以规划、写码、测试、评审。但任务一复杂它就噎住:上下文窗口塞满、推理走样,于是你又坐回椅子,逐个读 diff。

2026 年跑得更快的人换了做法:不再让一个 agent 干五件事,而是把活拆给专科 agent——一个写码、一个评审、第三个测试——再用一套系统把它们接起来,由这套系统分发任务、共享状态、验证输出,全程不需要人坐在椅子上盯。

这套系统就是一张图。2026 年 7 月,AI 工程社区给这个做法起了名字:

图工程(graph engineering):为你的 agent 设计它们所运行的组织结构。

图工程是循环之上的一层

每一层都建立在上一层之上,没有哪一层取代前一层。图工程是第四层。

这篇 90% 是操作步骤。我们从头搭一张真实的图:一套 CI 失败分诊系统,把坏掉的构建分类、路由给对应的专科 agent、能修的修掉、修不了的升级给人。每一段提示词都填好了,可以直接复制;每一份文件都是真的。把它跑通,你就能改名套用到自己的代码库上。

前面两节短一点,讲清楚图工程是什么、什么时候才需要它。其余全是搭建。

一、三个原语,图工程就这么多

节点负责干活。 每个节点是一个专科 agent,只干一件事:规划、写码、评审、跑测试。每个节点有自己的上下文窗口、自己的指令、自己的工具集。它看不见的东西,它也就破坏不了。

边负责路由。 一条边决定某个节点干完之后去哪。「分类器说是 bug,就去修 bug 的节点;说是 flake(偶发失败),就去重试节点。」边可以是无条件的(永远下一步去这里),也可以是有条件的(读一个值,再选路径)。

状态沿边流动。 一个共享的数据对象,装着已经做了什么、什么失败了、下一步是什么。每个节点读它需要的那一片,干完把自己的输出写回状态,下一个节点接着用。

节点、边、状态三个原语

循环本身是图的最小形态:一个节点,一条指回自己的边。所以图工程不取代循环工程,它就在循环上面一层。先学会循环;当一个 agent 干五件事成了瓶颈,再把活拆成一张图。

二、四问过滤:多数任务不需要图

一个搭得好的单循环能处理大部分真实工作。把图工程当流行词骂的人有一句话值得认真对待:没必要的复杂度不叫工程,叫额外开销。

动手之前先回答四个问题。任何一问答不上「是」,就继续用循环。

四问过滤:任一答否就别画图

CI 分诊四条全过:失败每晚都重复;不同失败类型要不同处理(缺一个环境变量密钥和真 bug 不是一回事);写修复的 agent 不该同时是批准修复的那个;而且如果你已经用单 agent 循环跑过 CI 修复,你就知道它一半的失败会分类错,还会把 token 浪费在它根本修不好的重试上。

循环能用,别用图去拆它;循环成了瓶颈,图才是解法。

第二部分 · 从这里开始逐块搭。

从这里开始我们只搭一套真系统:CI 失败分诊图。它读一段失败的 GitHub Actions 日志、分类、路由给对应的专科 agent、能修的修、修不了的升级给人。提示词全部填好,文件全部真实,把名字改成你的代码库就能跑。

三、先画组织架构图,再碰代码

搭图最常见的错误:打开 Claude Code 就开始建 agent,压根不知道自己这张图长什么样。这等于不知道程序干什么就先写函数。

先在纸上回答三个问题:

  1. 最终产物是什么? 说具体。「修好 CI」是空话。「所有测试通过、lint 干净、开一个 PR 并附上失败原因和改了什么」才是这张图能拿去验证的目标。
  2. 哪些子任务是独立的? 并排画出来。如果修环境变量和重试 flaky 测试不共享任何输入,它们可以同时跑;如果修复节点需要分类节点的输出,画一条箭头。
  3. 质量在哪一步被检查? 穿过这张图的每条路径,到产物之前至少要过一个闸门。没有闸门就没有验证,你只会得到一群各自宣布「看起来没问题」的 agent。

就在纸上开始。方框是 agent,箭头是路由。这张草图就是你的蓝图,别跳过它——这 10 分钟能省掉后面几个小时的重接线。

下面这段提示词用来生成我们这套 CI 分诊系统的草图:

I need to design an agent graph for CI failure triage.

FINAL OUTPUT:
For fixable failures: a draft PR with the fix,
  all tests passing, lint clean, summary of root cause.
For unfixable failures: a Slack message to #dev-alerts
  with classification, logs, and suggested next steps.

SUBTASKS:
1. Read the CI log and classify the failure type
   (env, flake, bug, dependency, infra)
2. For env failures: check if the secret exists in
   vault, add it or flag the gap
3. For flake failures: retry the test once. If it
   passes on retry, file a flaky-test issue. If not,
   reclassify as bug.
4. For bug failures: read the failing test, read the
   code it tests, draft a fix, run the suite.
5. For dependency failures: check which version broke,
   propose a rollback or pin, run the suite.
6. For infra failures (timeout, OOM, runner issue):
   escalate to humans. Do not attempt to fix.

QUESTIONS TO ANSWER:
- Which subtasks are independent?
- Where do results converge?
- Where is the verification gate?
- Which model should each node use?

Output a node-edge list. Do not build anything yet.

这段提示词的输出就是你的图草图。我们这张长这样:

五节点加分类器与闸门的草图

五个专科节点,入口一个分类器,上线之前一道闸门。升级路径整个跳过闸门,直接找人。草图有了,下面逐块搭。

四、一个节点一个动词,不要通才

图里每个节点是一个 agent 干一件事,不是什么都干的通才。

为什么重要:通才 agent 会把整个任务的上下文都装进一个窗口。窗口填满之后(复杂任务上它总会填满),推理开始退化,模型逐渐丢掉自己在干什么,对话越长输出越差。

专科 agent 拿到的是一个范围收窄的窗口,只装它需要的信息。它看得少,但看得清。Anthropic 自己的多智能体研究印证了这点:把 agent 拆成上下文隔离的专科节点,在内部研究基准上比单 agent 基线提升 90.2%。

每个节点守三条:

  1. 一个节点一个动词。 分类、修复、评审、重试。节点描述里出现两个动词,那它其实是两个节点。分类器只分类,不修;修复者只修,不评审自己的修复。
  2. 收窄工具。 评审节点不该有写文件的权限;修复节点不该有合并 PR 的权限。收窄工具能挡住专科 agent 越界,图在无人值守跑的时候这点尤其要紧。
  3. 模型配任务。 分类器负责路由,要快,用便宜模型(Haiku,或者低 effort 的 Sonnet)。修 bug 的要读测试失败、理解代码、写出精准补丁,用前沿模型 + 高 effort。循环给每个任务同一个模型,图把成本配给难度。

节点规则:一个动词、收窄工具、模型配任务

在 Claude Code 里,节点就是 .claude/agents/ 下的 agent 文件:一份 markdown,写着这个 agent 的角色、指令、允许用的工具。我们这套 CI 分诊图里,分类器和修复者长这样。

原文这两份文件的首行分别是 # CI Failure Classifier 和 # Bug Fixer。下面把标题放在代码块外,只是为了不和站点标题解析打架,文件内容没有改动。

.claude/agents/classifier.md:

You are a CI failure classifier.
Your only job: read a failing CI log and decide
what kind of failure it is.

CATEGORIES:
- env: missing secret, wrong env variable, infra
  not provisioned. Clue: "secret not found",
  "variable not set", "connection refused to vault"
- flake: test passed before, no code changed.
  Clue: intermittent timeout, race condition,
  network-dependent assertion
- bug: deterministic failure tied to a recent commit.
  Clue: assertion error in test, type error, logic
  failure that reproduces consistently
- dependency: failure appeared after a version bump.
  Clue: "module not found", breaking change in
  changelog, pinned version mismatch
- infra: runner-level issue. Timeout, OOM, disk full.
  Clue: "killed", "out of memory", "no space left"

OUTPUT: JSON with category, confidence (0-1),
  reasoning (one sentence), and the relevant
  log lines that led to the classification.

RULES:
- If confidence is below 0.7, flag for human review
- Never guess. If the log is ambiguous, say so.
- Do not attempt to fix anything. Only classify.

.claude/agents/bug-fixer.md:

You are a bug fix agent.
Your only job: fix deterministic test failures
caused by recent code changes.

INPUT: the failing test name, the error message,
  and the git diff of the most recent commit.

PROCESS:
1. Read the failing test to understand what it expects
2. Read the code the test covers
3. Read the recent diff to find what changed
4. Write the smallest fix that makes the test pass
5. Run the test suite to verify

OUTPUT: the fix as a git diff, plus a one-paragraph
  explanation of root cause and what was changed.

RULES:
- Smallest fix wins. Do not refactor.
- Do not modify the test to make it pass.
- Do not touch files outside src/ and lib/
- If the fix requires changing more than 3 files,
  escalate instead of fixing.

注意边界。分类器不能写文件;修复者不能改测试;修复者不能碰 src/ 和 lib/ 以外的文件。这些约束不是建议,是硬墙。图在你睡觉的时候跑,这些墙就是「早上醒来看到干净的 PR」和「醒来看到一团乱」的区别。

五、边决定谁接什么活

边是连接节点的路由规则。它决定三件事:下一个是谁、在什么条件下、什么数据顺着边走。

边有三类,任何一张图至少用到两类。

三类边的对比

我们这套 CI 分诊图在分类器处用条件路由:分类器读日志、给类别,边逻辑把它送进对应路径。这是确定性代码,不是模型判断。 分类器只负责挑类别,路由由边逻辑强制执行。

关键认识:硬约束不该写在提示词里。 如果某条路由规则是关键的(「涉及支付的失败永远不要交给自动修复」),就把它写进边逻辑,而不是 agent 的指令。提示词是建议性的,边是确定性的。提示词在长会话的第 47 轮可能被忽略;边是代码,它不会忘。

路由逻辑提示词(CI 分诊):

Here is the graph for CI failure triage.

NODES:
- Classifier (reads log, outputs category)
- Env Agent (handles missing secrets/vars)
- Flake Agent (retries, files flaky-test issue)
- Bug Fixer (reads test + code, drafts fix)
- Dep Agent (rollback or pin version)
- Escalator (sends Slack alert to humans)
- Test Gate (runs full test suite, lint)

EDGE RULES:
Classifier outputs category:
  env    -> Env Agent
  flake  -> Flake Agent
  bug    -> Bug Fixer
  dep    -> Dep Agent
  infra  -> Escalator (skip gate, alert humans)

All fix agents -> Test Gate
Test Gate pass -> open PR
Test Gate fail -> retry (max 3), then escalate

HARD CONSTRAINTS (enforce in code, not prompts):
- Never auto-fix files in src/auth/ or src/billing/
- Never modify CI config without human approval
- If classifier confidence < 0.7, escalate

六、状态:图在两次运行之间写下的那个文件

状态是图的记忆。没有它,每个节点从零开始,每次运行都忘了之前发生过什么。有了它,节点接着上一个的进度干,明天的运行知道今天试过什么。

状态对象是沿边流动的共享数据结构。每个节点读自己需要的那一片,干完把输出写回,下一个节点接着用更新后的状态。

状态在节点之间流动

设计规则:白名单,不要广播。 每个节点只收到它需要的那一片。分类器拿原始日志;修复者拿分类结果、失败的测试名、最近的 diff——它不需要整份日志。如果每个节点都读全部状态,你就没有图了,你有一个多绕了几步的臃肿上下文窗口。

我们这套图跑完一次之后,状态文件长这样。

STATE.md(首行标题 CI Triage State):

## Last run
2026-08-04 03:30 UTC

## Current run
workflow: ci-main #4821
trigger: push to main by @david
status: completed

## Classification
category: bug
confidence: 0.94
reasoning: assertion error in test_checkout_flow,
  deterministic, tied to commit a3f7b2c

## Fix
diff: +2 lines in src/cart/checkout.ts
root_cause: price rounding used Math.floor
  instead of Math.round, off-by-one on
  totals ending in .5

## Gate
tests: 142/142 passing
lint: clean
verdict: pass

## Output
pr: #482 "fix: rounding in checkout total"
escalations: none

## Lessons learned
2026-08-04: checkout price tests are brittle
  around .5 boundaries. Added to watch list.

明天的运行会读这个文件:知道哪些已经修过、学到了什么、观察名单上有什么。图不必每个周期都从零重新推导项目上下文。状态是把一次性运行变成复利系统的东西。

图每次运行都会忘;状态文件不会。没有状态的图是重启,有状态的图是续跑。

七、那个只负责判断「完成 / 未完成」的节点

这一块决定了你的图是「能干活」,还是「一群没人验证的 agent 互相点头说都很棒」。

没有闸门,图产出点东西就叫完成。有闸门,图产出东西之后,还有一个客观的东西检查它到底对不对。闸门是唯一有资格判定「完成」的节点,其他节点都在干活,只有闸门在裁决。

Karpathy 那句话在这直接适用:可验证的任务才可优化。 闸门就是你让任务变得可验证的地方。定义不出闸门,这个任务大概不该放进图里。

三类闸门,从强到弱

闸门从强到弱有三类:

硬闸门。 测试套件、类型检查、linter、构建。过或不过,没有意见、没有判断、没有偏好。最强,能用就用。我们的图用的就是这个:每次修复之后跑全套测试,142 个测试要么全过,要么没过。

软闸门。 让第二个模型来审输出。比没有闸门好,但弱于测试套件——评审模型有自己的偏见和盲区。它是硬闸门的补充,不是替代。

人闸门。 合并、部署或任何不可逆动作之前由人来看。图把活摆出来,人做最后决定。我们这套图里,人闸门就是 PR 评审:图开一个草稿 PR,人批准再合并。

Cherny 给过一个具体数据:给 Claude 有效的验证方法,通常能把最终产出质量提升 2–3 倍。光这一个数字就够当「别跳过闸门」的理由。

测试闸门提示词:

You are the test gate for the CI triage graph.
Your only job: verify the fix is correct.

RUN THESE CHECKS IN ORDER:
1. Run the full test suite: bun test
2. Run the type checker: bun run typecheck
3. Run the linter: bun run lint
4. Verify only files in src/ and lib/ were modified
5. Verify no files in src/auth/ or src/billing/
   were touched (hard constraint)

OUTPUT:
verdict: "pass" or "fail"
tests: 142/142 passing (or which failed)
lint: clean (or list of issues)
scope_check: pass (or list of out-of-scope files)
suggestion: if fail, what to fix first on retry

RULES:
- Be strict. A false pass costs more than a false fail.
- Do not fix the code. Only verify.
- If all checks pass but something feels wrong,
  add a warning to the output. Don't block on it.

闸门会腐坏。 半年前很好的测试套件,可能抓不住今天的失败模式。定期挑几个图批准过的 PR,人工验证闸门真的抓到了它该抓的东西;没抓到,先补测试再让图继续跑。

八、五种形状,所有图都由它们拼出来

你以后搭的每一张图,都是由五种可组合的模式拼的。Anthropic 在工程指南里写过,Cherny 把 Claude Code 的工具化围着它们做。它们可以自由组合:串联两个、嵌套一层、三条并行。

五种可组合的图模式

我们这张 CI 分诊图用了其中三种:入口处 Route(分类器把不同失败类型送进不同路径)、每条路径内 Chain(先修,再验证)、重试时 Evaluate(闸门给批评,修复者迭代)。三种模式,一张图。

多数图里最便宜的结构性收益就是路由:入口一个分类器,把简单任务发给便宜模型、难任务发给前沿模型,token 账单能砍一半,输出质量不变。

组合模式提示词:

Build the CI triage graph using these patterns:

PATTERNS USED:
1. Route at entry: classifier sends each failure
   type to the right specialist
2. Chain within each path: specialist fixes,
   then gate verifies
3. Evaluate on retry: if gate fails, fixer gets
   feedback and iterates (max 3 rounds)

COMPOSITION:
CI Log -> Route(Classifier)
  -> env:   Chain(Env Agent -> Gate)
  -> flake: Chain(Flake Agent -> Gate)
  -> bug:   Evaluate(Bug Fixer <-> Gate, max 3)
  -> dep:   Chain(Dep Agent -> Gate)
  -> infra: Escalate (skip gate)

Gate pass -> open PR
Gate fail after 3 rounds -> escalate

九、上线:/goal、/loop、Routines

图设计完了,节点建好了,边接上了,闸门设好了。现在跑它。

自主程度分三级,每升一级,你离循环更远一点。从第一级开始,只有信任这张图之后才往上提。

三级自主:手动、定时、云端例程

第一级:手动跑 /goal。 你敲一次命令,图跑到你设的条件满足为止。完成与否由另一个评估模型判断,所以写修复的那个 agent 不是自己宣布完工的那个。

/goal all tests pass and lint is clean

CI workflow ci-main #4821 just failed.
The log is at .github/logs/ci-main-4821.log

Run the CI triage graph:
1. Read the failing log
2. Classify the failure type
3. Route to the right specialist:
   env -> check secrets, add if missing
   flake -> retry once, file issue if flaky
   bug -> read test + code, draft smallest fix
   dep -> find breaking version, propose rollback
   infra -> skip fix, alert #dev-alerts on Slack
4. Run the test gate: bun test, typecheck, lint
5. If gate passes: open draft PR with summary
6. If gate fails: retry with gate feedback (max 3)
7. If still failing after 3: escalate to humans

Update STATE.md after each step.

第一次要把这次运行从头看到尾,每个 diff 都读,确认闸门真的抓到了你以为它会抓到的东西。至少手动跑通 5 次、你开始信这张图了,再升第二级。

第二级:/loop 定时跑。 图按节奏检查新失败,你不再是人肉触发器。

/loop every 30 minutes, check the CI dashboard.

If any workflow failed in the last 30 minutes:
1. Pull the failing log
2. Run the CI triage graph (same steps as above)
3. Open a draft PR for fixable issues
4. Escalate unfixable issues to #dev-alerts

If nothing failed, log "all green" to STATE.md
and wait for the next cycle.

Stop after 8 hours or when I say stop.
Update STATE.md after every cycle.

第三级:Routines。 /loop 在你关掉终端时就死了;Routines 跑在 Anthropic 的云端,你的笔记本可以关着。触发器有三种:定时(cron)、API 调用(webhook)、GitHub 事件(开 PR、push 到 main)。

Run the CI triage graph as a Routine.

Trigger: GitHub webhook on workflow_run.completed
  where conclusion == "failure"
Schedule: also run at 6am daily as a sweep
  for any failures missed by the webhook

On each trigger:
1. Pull the failing log
2. Run the full triage graph
3. Update STATE.md
4. Open draft PR or escalate

Alerts:
- Ping #dev-alerts only for escalations
- Weekly summary every Monday 9am to #ci-health

Limits:
- Max 3 retries per failure
- Never touch src/auth/ or src/billing/
- If token spend exceeds $5 in one run, stop and alert

先手动跑稳,再排期,最后提升为例程

顺序很重要:先把手动跑做稳,看着它工作、读它的 diff、信任它的闸门,再排期,最后升级成 Routine。跳过步骤,就是图在生产环境里悄悄又昂贵地失败的方式。

十、现在就能试:一个对话里搭一张图

不需要 Claude Code,也不需要任何特殊工具,就能感受这套机制。把下面这段粘进任何一个 LLM,看它在一个对话里搭出一张图:

You will work as a graph of specialized agents.

TASK: review this code for bugs, security issues,
and style problems.

GRAPH:
You will simulate 3 specialist agents, one at a time.
Each agent sees only its own instructions and the code.
Each agent produces a typed report.

AGENT 1 - Bug Hunter:
Read the code. Find logic errors, edge cases,
off-by-one mistakes. Output a JSON list of findings
with file, line, severity, description.

AGENT 2 - Security Reviewer:
Read the code. Find hardcoded secrets, injection
vulnerabilities, missing input validation.
Output same JSON format.

AGENT 3 - Style Checker:
Read the code. Find naming inconsistencies,
dead code, missing error handling.
Output same JSON format.

GATE:
After all 3 agents finish, merge their findings.
Remove duplicates. Rank by severity.
Output a final report with:
- total findings by severity
- top 3 issues to fix first
- overall verdict: "ship", "fix first", or "rewrite"

RULES:
- Each agent works independently.
- No agent sees another agent's findings.
- The gate sees all findings but doesn't add its own.
- Be specific. File names, line numbers, examples.

CODE TO REVIEW:
[paste your code here]

Begin. Run each agent in order, then the gate.

这是一张搭在单个对话里的图,有局限:触发器还是你,没有持久状态,也没有真正的并行。但它展示了核心机制——三个专科,各自指令范围收窄,路由到一个负责合并与裁决的闸门。

从这里走到一张完整的自主图,只需要补上这个玩具版缺的四样:持久状态(STATE.md)、真正的并行(子 agent)、自动化(/loop、Routines)、硬验证(测试套件而不是模型的意见)。

十一、图会带来的两种债

图越好,有两个问题会越尖锐,不会越轻松。两个都随成功而放大。

理解债与认知投降

理解债。 图每次给你交付你没写过的代码,这份债就涨一点。一张跑得顺的图很安静,安静感觉像进步。但「仓库里存在的东西」和「你真正理解的东西」之间的差距在按复利计息。等到某天你必须 debug 一个团队里没人读过的系统,那一天的成本会超过你之前省下的所有 token。

认知投降。 更微妙。图自己跑起来之后,人很容易停止形成自己的判断,来什么接受什么。你带着判断去设计图,它是解药;你为了逃避思考去设计图,它是催化剂。同样一个动作,结果相反。两个工程师可以搭出一模一样的图:一个用它更快地推进自己懂的工作,另一个用它来逃避理解工作。图不知道两者的区别。

缓解办法都不是技术性的,没有哪个框架能替你解决:

  • 读 diff,每一个都读。 不读图交付的东西,你就是在按复利租借理解债。
  • 抽查闸门。 挑几个图批准过的 PR,人工确认测试真的抓到了你在意的失败模式。闸门会随时间腐坏。
  • 把图限制在小而可机检的任务上。 CI 分诊、依赖升级、lint 修复、复现 flaky 测试。一旦图开始碰架构决策、鉴权逻辑,或任何「完成」需要人判断的事,理解债就开始加速。
  • 和同事一起设计图。 设计阶段多一双眼睛,能抓到图跑起来之后会永久利用的盲区。

结尾:杠杆往上挪了一层

Karpathy 让我们看到上下文窗口就是编程界面;Cherny 让我们看到循环把上下文工程自动化了;Steinberger 问的那个问题把框架又抬了一层:许多个循环连成一个系统之后会怎样?

但这个故事诚实的那一面,是这个领域每篇文章最后都会说的:大多数开发者现在还不需要图。 除非循环撞到天花板、任务能拆成并行子任务、写者与检查者的分离需要结构性强制。还没搭出一个能用的循环,就先去搭循环。底下没有一个能跑的循环,那张图只是被整理过的复杂度。

循环能用、任务简单,就留着循环。 循环成了瓶颈,现在你知道图怎么搭了。

从草图开始,建节点,接边,加闸门,手动跑,读 diff,慢慢建立信任,然后让它自己跑。

设计节点,接好边,设好闸门,让系统交付。

资料来源