Day 15 / 共 20 天 · 第 3 周收官

Agent 全家福 + 元 Agent

昨天(D14)把旗舰 sre-rca 逐文件读透了。今天(D15)横向扫全部 21 个 agent:各干什么、结构上有哪些变体,再钻进最精彩的"用 Agent 造 Agent"——两个元 Agent 的 builder.py 真代码,以及平台"自己开发自己"的 spec 四段流水线和它的安全护栏。这是第 3 周收官——之后进入第 4 周"平台化"(门户/部署/CI/起新 agent/OpenSpec)。

📍 你在 20 天里的位置(可信底座 → 运行时 → 精读)
D09 记忆 D10 评测 D11 安全底座 D12 server 运行时 D13 CLI/MCP D14 精读 sre-rca D15 全家福+元 Agent
💡 用一个类比先兜住今天(延续「盖楼/物业」世界观) 如果说 sre-rca 是一支"满配的维修班",那今天看的就是整个小区的各类服务队全家福:修水电的、查安全的、审文档的……每支队按需配人(有的配质检员 critic,有的不配;有的 4 个师傅,有的 0 个)。最妙的是还有两支"教练队"(元 Agent)——agent-creator 专门照着标准模板培训新服务队(一次成型),skiller-creator 培训完还自己出题考核、不合格回炉重训(RLHF 循环)。再往上,物业甚至有一条"自己给自己搞装修改造"的流水线(发现需求→出方案→施工→验收),每一环都被限权死死框住、关键决策留给人。今天 10 讲就是这张全家福 + 两支教练队 + 一条自装修流水线,全部落到 builder.py 真代码。
L01

21 个 agent 分类速览

apps/ 下实际有 21 个 agentls apps/ | wc -l = 21,文档常说 9/12,Day 01 讲过的漂移)。按用途大致分四类:

业务/运维执行类(≈14 个)

sre-rca-agent

故障根因分析(旗舰,D14 精读)

risk-reviewer

PR 变更风险评审

alert-triage

告警治理,高频在线

doc-checker

研发文档要点检查

security-audit

安全审计,多专家扫 secrets/SAST/依赖

faq-miner

脱敏聊天/工单 → 带出处 FAQ 草稿

arch-compliance

架构合规:文档分析 + 代码扫描

biz-link-gov-agent

业务链路治理:周期巡检 + 治理清单

gateway-qa-agent

网关 FAQ 问答

prom-metric-setup-agent

生成 Prometheus 接入 5 件套

frontend-dockerize-agent

前端容器化:dry-run 生成 Dockerfile+k8s

productivity-tracker-agent

扫 Jira/GitLab/Prom 推导人月月报

bmc-agent

自动接入/升级 Java bmc-boot-parent

元 Agent(造东西的 Agent,2 个)

agent-creator(造 agent)、skiller-creator(造 skill)。L03-L05 精读。

自我开发流水线(4 个)

requirement-discovererspec-authorspec-executorrelease-coordinator,让平台"自己开发自己"。L06-L09 精读。

调度/载体类

supervisor-router-agent(跨 agent 路由)、bmc-agent-mcp(MCP 形态载体,D13 讲过)。

怎么快速读懂任意一个? 用 D05/D14 的方法:打开它的 builder.py 看图骨架 → 数它有几个专家、有没有 Critic → 看 prompts。因为结构统一,10 分钟就能摸清一个陌生 agent。今天就用这招连读 6 个 builder。
L02

结构变体谱系:一套框架,多种裁剪

所有 agent 共用同一套 toolkit,但按需裁剪出不同"形态"。理解这些变体,能体会框架的弹性:

维度取值 → 代表 agent为什么这么裁
Critic 强度L1+L2三态 → sre-rca / 仅L1不调LLM → alert-triage / L1不重试 → requirement-discoverer / 无 → risk-reviewer责任重多审、延迟敏感用轻量、有的刻意做减法
专家数量0(纯路由 supervisor-router)/ 2(risk-reviewer)/ 3(requirement-discoverer)/ 4(sre-rca)/ 5(security-audit)make_specialist_node 让加减专家几乎零成本
拓扑并行取证(sre-rca)/ 串行流水(agent-creator)/ 带循环(skiller-creator RLHF、spec-author repair)/ 5 门并行(release-coordinator)任务性质决定,见 L04-L08
载体FastAPI pod(默认)/ CLI / MCP(bmc-agent-mcp)D12/D13 讲过
🔀 设计取舍①:为什么故意保留"无 Critic""仅 L1""critic 不重试"这些变体?一方面真实场景确实需要(延迟/成本/噪音权衡——比如 requirement-discoverer 是 cron 定时跑的,critic 不过就等下次,不必当场重试制造噪音);另一方面这些变体是对 toolkit 的压力测试——如果 toolkit 只在"满配"下好用、一裁剪就散架,说明抽象没做好。能优雅支持各种裁剪,才证明底座真的通用。这是"渐进抽象"(D06)的验收标准。
L03

什么是"元 Agent"

🤔 痛点:造一个新 agent 要抄一堆样板,还容易抄错一个新 agent 要有 state.py / builder.py / server.py / critic / 记忆接线……90% 是样板。手抄既慢又容易漏。于是——为什么不让一个 agent 来造 agent?

元 Agent(meta-agent)= 造 Agent/组件的 Agent。它的输出不是"业务分析结果",而是"另一段代码/一个新组件"。它们本身也是标准的 LangGraph agent(有 State、builder、节点、Critic),只是节点干的活是"分析需求、渲染模板、生成文件"而非"查日志、算指标"。本框架有两个:

  • agent-creator:一句自然语言描述 → 生成一个符合规范、可直接跑的新 agent 代码骨架(一次成型)。
  • skiller-creator:生成并注册新的 Skill 组件,带自动评测-修订的迭代闭环(RLHF 思想)。
📝 举个例子:一句话让 agent-creator 造一个新 agent 输入:{"description":"做一个巡检 K8s Pod 重启次数、超阈值就出告警清单的 agent","mode":"dry_run"}
→ 需求分析(解析要几个专家、要不要 critic) → 架构设计 → scaffolder 渲染模板 → critic_l1 查硬错 → assemble
输出(mode="dry_run" 只预览不落盘):一份完整、能直接跑的新 agent 代码骨架。mode 还能取 write(真写盘)/ bundle(打 zip)/ sandbox——用状态控制"副作用"大小(agent_creator/state.py:7Mode = Literal["dry_run","write","bundle","sandbox"])。
L04

agent-creator:一条串行流水线(看真代码)

agent-creator 的图是纯串行——需求→架构→脚手架→评测设计→查错→组装,没有分支没有循环(apps/agent-creator/agent_creator/builder.py:40-59):

走读 1 agent-creator/agent_creator/builder.py:40-59
def build_agent_creator_graph():               # builder.py:40
    builder = StateGraph(AgentCreatorState)
    builder.add_node("requirements_analyst", requirements_analyst_node)
    builder.add_node("architect", architect_node)
    builder.add_node("scaffolder", scaffolder_node)
    builder.add_node("evals_designer", evals_designer_node)
    builder.add_node("critic_l1", build_critic_l1())       # ← 复用 D07 business_layer1!
    builder.add_node("warn_check", check_eval_count_warning)
    builder.add_node("assemble_outcome", assemble_outcome)
    # 一条直线,无分支
    builder.add_edge(START, "requirements_analyst")
    builder.add_edge("requirements_analyst", "architect")
    builder.add_edge("architect", "scaffolder")
    builder.add_edge("scaffolder", "evals_designer")
    builder.add_edge("evals_designer", "critic_l1")
    builder.add_edge("critic_l1", "warn_check")
    builder.add_edge("warn_check", "assemble_outcome")
    builder.add_edge("assemble_outcome", END)
    return builder.compile()
build_critic_l1()它的 critic 复用 D07 讲过的 build_business_critic_l1!打开 agent_creator/critic.py:162 能看到 R1-R5 是 error 级规则、R6 是 warn 级——用纯规则校验生成的 agent 骨架合不合格,0 成本不调 LLM。
scaffolder_node渲染模板生成文件。用白名单模板 + fragments(预写好的 critic_l1/memory 等标准件)拼装,而不是让 LLM 自由发挥——因为标准件有固定写法,让 LLM 编反而易错。
warn_check把 R6 那条 warn 级规则的结果物化成 state.warnings(比如"评测样本太少"),提示但不阻断。
控制流:agent-creator 串行 7 节点(一次成型) 需求分析analyst 架构architect 脚手架scaffolder 评测设计evals critic_l1规则查错 warn_check assemble→END
图注:agent-creator 是一条直线——无循环、无分支,"造完就交付"。对比下一讲 skiller-creator 的循环。
防编造的巧思:生成 Critic、记忆这些"标准件"时用白名单 fragments(预先写好的正确片段),只在"需求解析/架构设计"这种真需要理解力的地方用 LLM。又一个"能用确定性手段就别用 LLM"的例子。
L05

skiller-creator:带 RLHF 自迭代的造 Skill(两个路由函数是精髓)

skiller-creator 比 agent-creator 多了一个自我评测-修订循环。它的图有两条路径,靠两个条件函数控制。先看图组装(apps/skiller-creator/skiller_creator/builder.py:83-117):

走读 2 skiller-creator/skiller_creator/builder.py:93-116
g.add_edge(START, "skill_writer")
g.add_edge("skill_writer", "scaffolder")
g.add_edge("scaffolder", "critic_l1")
g.add_conditional_edges("critic_l1", _after_critic, {   # ← 路由①
    "assemble": "assemble",
    "test_query_generator": "test_query_generator",
    "evaluator": "evaluator"})
g.add_edge("test_query_generator", "evaluator")
g.add_conditional_edges("evaluator", _after_eval, {     # ← 路由②
    "assemble": "assemble",
    "reviser": "reviser"})
g.add_edge("reviser", "scaffolder")                     # ← 修订后回炉!循环边
g.add_edge("assemble", END)

真正的精髓是评测后的路由 _after_evalbuilder.py:65-80)——它决定"达标交付 or 回炉重写":

走读 3 builder.py:65-80
def _after_eval(state) -> str:                 # builder.py:65
    eval_report = state.get("eval_report") or {}
    passes = bool(eval_report.get("passes_threshold", False))
    current_iter = int(state.get("iteration") or 0)
    max_iter = int(state.get("max_iterations") or 0)
    if passes:
        return "assemble"                      # 达标 → 交付
    if current_iter >= max_iter:
        return "assemble"                      # ← 预算用尽也交付(不死循环)
    return "reviser"                           # 不达标 + 还有预算 → 修订后回炉
reviser → scaffolder这条边是"循环"的关键:修订完不是结束,而是回到 scaffolder 重新组装、再评测,形成"生成→考核→改→再考"的闭环。
current_iter >= max_iter边界/防死循环:哪怕一直不达标,跑满 max_iterations 也会 return "assemble" 交付(附带"未达标"标记)。绝不无限重写烧钱。
_after_critic 三态另一个路由(builder.py:40):critic 失败/skip_eval/max_iter=0 → 直接 assemble;已有 test_queries → 去 evaluator;首次进 RLHF → 先生成测试问句。
🔀 设计取舍②:为什么 skiller-creator 有 RLHF 循环,agent-creator 却"一次成型"?因为两者产物的"可自动评估性"不同。skill 的好坏可以用"拿测试问句去问、看命中率"自动打分evaluator 节点,复用 D10 评测能力),所以能做"不达标就自己改"的闭环。而一个完整 agent 的骨架,质量很难用一个分数自动判定(要不要人看、要不要跑真实业务),强行循环意义不大。只在"输出能被自动量化"时才值得上自迭代循环——这是把 D10 的评测用在了"生成质量把关"上的精准判断。
生成 skill evaluator 自己出题考 达标/预算尽 assemble 产出 不达标且有预算 → reviser 修订后回炉
图注:skiller-creator 的自迭代循环——生成的东西自己先考一遍,不及格自己改了再考,用 iteration vs max_iterations 卡上限。
L06

spec 四段流水线:平台开发自己

最精彩的部分——4 个 agent 串成一条"平台自己给自己提需求、写规格、改代码、评审发布"的流水线(就是 Day 04 route_chain 那条链):

requirement-
discoverer
发现需求
spec-author写 OpenSpec 规格 spec-executor改代码+测试 release-
coordinator
5 道 gate 评审
agent做什么(真实结构)安全约束(scope guard)
requirement-discoverer3 专家(cases/prom/静态)并行 → synthesizer → critic → writebackcritic 不 retry,产出 IssueDraft,不自动起 spec
spec-author8 节点 + repair loop,issue → OpenSpec change 文件(L07)safe_change_write 只能写本次 change 目录(L09)
spec-executorM0 计划 / M1 执行 双模,读 change → 改代码 → 跑测 → commit(L08)只 commit 到 agent/<change-id> 分支,不碰 main
release-coordinator5 道 gate 并行 → merge_decisions → reporter(L08)只出 PR decision,不自动合并

先看 requirement-discoverer 的 critic 路由——它体现了"cron 场景不该重试"(apps/requirement-discoverer/requirement_discoverer/builder.py:33-37):

走读 4 requirement-discoverer/.../builder.py:33-37
def _route_after_critic(state) -> str:         # builder.py:33
    """critic 三态:pass → writeback / fail → __end__(等下次 cron · 不 retry · 防 noise)。"""
    if state.get("critique_passed", False):
        return "writeback"
    return "end"                               # ← 不过就结束,不回炉重试
🔀 设计取舍③:同样带 critic,为什么 sre-rca 不过要重试、requirement-discoverer 不过就直接结束?因为运行场景不同。sre-rca 是用户实时等结果,值得当场回炉再试 2 次给个好答案。而 requirement-discoverer 是定时 cron 跑的"发现需求"——这次没发现出高质量需求,等下次 cron 再跑就行,当场硬重试只会制造噪音、烧钱。注释里那句"等下次 cron · 不 retry · 防 noise"把这个权衡讲得很直白。同一个 toolkit 组件(critic),在不同运行节律下配不同的失败策略。
L07

spec-author:带 repair loop 的自修复图

spec-author 把一段需求文本变成 OpenSpec change 的 4 个文件(proposal/tasks/design/spec-delta),写完还会自校验、失败自修复apps/spec-author/spec_author/builder.py)。它的图骨架(文件头 docstring 画得很清楚):

走读 5 spec-author/spec_author/builder.py:1-40(docstring 图)
# builder.py:1 docstring 描述的图:
#  classify → scan_related_specs → generate_proposal → generate_tasks →
#  generate_design → generate_spec_delta → write_files → validate
#                                                          │
#                            ┌───── validate.status ───────┤
#                          valid                          failed
#                            │              self_repair_count < 2 ?
#                            │                ┌────────────┴──────────┐
#                            │              yes(repair→write→loop)   no(fail_2x)
#                            └────────────────┴────────────────────────┘
#                                          ↓  reporter → END
REPAIR_LIMIT = 2                           # builder.py:40 · 最多自修复 2 次
...
def build_spec_author_graph():             # builder.py:55
write_files → validate写完文件立刻自校验(OpenSpec 结构合不合法)。这是"生成完先自己检查一遍"的思路,和 skiller 的 evaluator 异曲同工。
failed 且 count < 2校验失败且没到上限 → 进 repair 节点修 → 重新 write → 再 validate,构成 repair loop。
REPAIR_LIMIT = 2又是"受控循环 + 硬上限":修 2 次还不合法就带着 fail_2x 状态走 reporter,绝不无限修。跟 sre-rca 的 max_retries、skiller 的 max_iterations 同一个模式。
💡 本质:三个"造东西"的 agent 都是同一套"生成→自检→(修/退)"骨架sre-rca 的 synthesizer→critic→回炉、skiller 的 生成→evaluator→reviser、spec-author 的 write→validate→repair——形态一模一样:产出后自检,不合格在预算内修,超预算就诚实退出。看懂一个,其余都是换皮。这就是"读懂旗舰,其它都是变体"的真正含义。
L08

spec-executor 双模 + release-coordinator 五门并行

spec-executor 用一张图跑两种模式(apps/spec-executor/spec_executor/builder.py:1-20 docstring):m0_plan 只做计划就 reporter 收尾;m1_execute 才真正 prepare_worktree → patch_files → run_quick_test → commit_branch。而 commit 的约束写在节点里:

走读 6 spec-executor/.../nodes/commit_branch.py:1
# commit_branch.py:1
"""commit_branch · M1 only · 测试 pass 才 commit 到 agent/<change-id>。"""
# → 只提到临时分支 agent/*,永不碰 main;测试不过不 commit

release-coordinator 则是"5 道 gate 并行"的裁判(apps/release-coordinator/release_coordinator/builder.py:38-63)。它并行跑 ruff/mypy/pytest/arch/risk/doc 多道闸,用一个 reducer 合并各闸结果:

走读 7 release-coordinator/.../builder.py:38-40
def _merge_gate_results(left: dict, right: dict) -> dict:   # builder.py:38
    """gate_results 字段的 reducer · 并行 gate 写各自 key · LangGraph 合并 dict。"""
    return {**(left or {}), **(right or {})}       # ← 各 gate 写不同 key,字典合并零冲突
m0 / m1 双模同一张图用 mode 字段走不同分支——"只出计划"和"真改代码"复用大部分节点,避免维护两套图。
_merge_gate_results和 D14 sre-rca 的 4 专家一个道理:多个并行节点写同一 state 字段(gate_results),用 reducer 做字典合并,各 gate 写自己的 key 互不覆盖。这是并行安全的通用招式。
只出 decision跑完 5 门只给"建议合/不合",绝不自动按合并键——最终拍板留给人。

👶 小白:让 AI 自己给自己改代码,不会哪天失控把主干搞崩吗?

👨‍🏫 老师:这正是这条流水线最克制的地方——每一环都被"限权"死死框住。spec-author 的 safe_change_write 只准写本次 change 目录(L09);spec-executor 的 commit 只到 agent/* 临时分支、永不碰 main,测试不过就不提;release-coordinator 跑完 5 门也只出 decision、不自动合并;连最上游的 requirement-discoverer 都只产 IssueDraft、不自动起 spec。就像装修队能施工,但"拆承重墙"和"最终验收签字"必须人来。

L09

safe_change_write:元 Agent 会写文件,护栏怎么写

元 Agent 和 spec 流水线都会往磁盘写文件——这是最危险的副作用。spec-author 的写文件全走 safe_change_write 这道闸(apps/spec-author/spec_author/tools/safe_change_write.py)。核心判定函数:

走读 8 spec-author/.../tools/safe_change_write.py:14-66
_DENY_PREFIXES = (                             # safe_change_write.py:14 · 黑名单
    "openspec/specs/",    # capability spec 是 ground truth,走 archive 流程,禁直改
    "apps/spec-author/",  # 禁改自己(防自递归污染)
    ".env", ".git/", "secrets/", "deploy/",
)

def is_change_path_allowed(rel_path: str, change_id: str) -> tuple[bool, str]:  # :41
    rp = _normalize(rel_path)                  # 统一斜杠 / 拒绝绝对路径
    if ".." in rp.split("/"):
        return False, "path 含 `..` parent traversal 拒"      # ① 防目录穿越
    for deny in _DENY_PREFIXES:
        if rp.startswith(deny):
            return False, f"黑名单前缀 {deny!r}"               # ② 黑名单
    expected_prefix = f"openspec/changes/{change_id}/"
    if not rp.startswith(expected_prefix):
        return False, f"必须在 {expected_prefix!r} 子树"       # ③ 白名单:只准写本次 change
    if not re.match(r"^\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*$", change_id):
        return False, f"change_id 格式不规范:{change_id!r}"    # ④ change_id 格式校验
    return True, ""
白名单唯一只准写 openspec/changes/<change_id>/ 这一棵子树。哪怕 LLM 想写别处,路径一对不上就被拒。默认拒绝,显式放行
黑名单强制就算落在白名单,碰到 .git//.env/secrets//改自己 也一律拒。双保险。
".." 穿越检查openspec/changes/x/../../etc/passwd 这种用 .. 逃出白名单的经典攻击。
不覆盖已存在文件safe_change_write_file(:69)默认 overwrite=False,还会 resolve() 后校验没逃出 repo_root。层层设防。
⚠️ 边界/易错点:给 AI 开"写文件"权限,必须假设它会犯错/被诱导元 Agent 的写盘能力若不设闸,一次幻觉就可能覆盖 .env、改坏 .git、甚至改自己的代码造成自递归灾难。safe_change_write 的做法是把"能写哪"缩到一棵最小子树,再叠加黑名单、路径穿越检查、格式校验、禁覆盖、resolve 越界检查——五层防护,任一层不过就 SafeWriteRejected。这是"让 AI 自我开发"能安全落地的技术底座:能力越大,闸门越要提前收窄
处处 scope guard(呼应 D04):发现需求的不自动起 spec、写 spec 的只能写自己那块 change、改代码的只能提到临时分支不碰 main、评审的只出决策不自动合。每一环都"只做自己该做的、把关键决策留给人"——这是让"AI 自我开发"变得安全可控的关键。D20 会结合 OpenSpec 再讲这条闭环。
L10

今日小结 + 动手

🧠 第 3 周收官自测

  • 21 个 agent 大致分哪四类?结构变体有哪几个维度?
  • 为什么故意保留"无 Critic""仅 L1""critic 不重试"变体?(真实需求 + 压测 toolkit 通用性)
  • 元 Agent 是什么?agent-creator 怎么防止 LLM 编造标准件?(白名单 fragments + 规则 critic_l1)
  • 为什么 skiller-creator 有 RLHF 循环、agent-creator 一次成型?(产物能否自动评分)
  • sre-rca / skiller / spec-author 的"生成→自检→修/退"骨架为什么都长一样?
  • 为什么 requirement-discoverer 的 critic 不重试、sre-rca 要重试?(cron vs 实时)
  • 自我开发流水线 4 环各有什么 scope guard?safe_change_write 有几层防护?

✋ 动手

# 1. 扫一遍所有 agent(数一下确实 21 个)
ls apps/ | wc -l && ls apps/
# 2. 元 Agent 的图:串行 vs RLHF 循环
sed -n '40,59p'  apps/agent-creator/agent_creator/builder.py
sed -n '40,117p' apps/skiller-creator/skiller_creator/builder.py
# 3. spec 四段流水线的 4 个 builder(读 docstring 图)
sed -n '1,40p' apps/spec-author/spec_author/builder.py
sed -n '1,40p' apps/spec-executor/spec_executor/builder.py
sed -n '1,40p' apps/release-coordinator/release_coordinator/builder.py
sed -n '1,45p' apps/requirement-discoverer/requirement_discoverer/builder.py
# 4. 安全护栏(元 Agent 写盘的底座)
cat apps/spec-author/spec_author/tools/safe_change_write.py
# 5. 复用 D07 的规则 critic 造 agent
sed -n '160,175p' apps/agent-creator/agent_creator/critic.py
第 4 周预告 · Day 16 起:代码层面基本讲完!最后一周讲"平台化"——Day 16 Web 管理门户(看 agent 资产/成本/闸门/回放/评测),Day 17 部署,Day 18 CI 治理闸门,Day 19 从零起新 agent 实操,Day 20 OpenSpec + 全书收官。
← Day 14 精读 sre-rca Day 16 · Web 管理门户 →