Understand-Anything · agents/*.md · 多 Agent 编排

七专家混合路由 · 多 Agent 接力流水线

Understand-Anything 没有用一个"全能 agent"包揽所有分析工作,而是把整条流水线拆成七个职责单一的专家 agent,各自由一个 YAML frontmatter 定义角色描述,通过路由器按语义类型分发任务。本页基于 agents/ 目录下的 markdown 定义与 compute-batches.mjs 的路由逻辑,深度拆解这套混合系统的设计原理与工程细节。

7
专家 Agent
1:7
路由粒度(专家/任务)
串+并
执行拓扑
5
file-analyzer 并行度

七专家职责图

每个 agent 由一个 markdown 文件定义(agents/*.md),包含 YAML frontmatter(角色名、输入 schema、输出 schema、适用条件)和一段大白话指令给 LLM。以下是七专家的完整职责映射:

🔍
project-scanner

确定性脚本枚举文件、识别语言、过滤 .understandignore;LLM 读 manifest 提取项目名/框架。零并行,纯顺序。

📄
file-analyzer ×N(并行 5)

每批 20–30 文件,tree-sitter 抽结构 + LLM 产出摘要/标签/语义边;输出过大时自动切片成 batch-i-part-k.json。最耗时的阶段。

🏗️
architecture-analyzer

计算目录分组与 import 邻接,LLM 归纳 3–10 个逻辑分层,每个文件归属唯一一层。是后续所有路由的"地图像限"。

🧭
tour-builder

算 fan-in/fan-out 排名与依赖链,LLM 设计 5–15 步"按正确顺序学代码"的导览路径。

✅
graph-reviewer

纯确定性校验:schema 合规、引用完整性、分层覆盖、唯一性约束。问题分三级:已修正 / 已丢弃 / 致命。

🏢
domain-analyzer

可选阶段:从代码中抽取业务域 / 流程 / 步骤,产出 domain-graph.json,叠加在结构图之上。

🔗
synthesizer(隐式专家)

不单独存在——它是一个逻辑角色,由 file-analyzer 与 architecture-analyzer 的协作结果隐式构成。两者的输出合并后自动形成"跨文件语义边",即 synthesizer 的工作成果。

执行拓扑:串行为骨架,并行填血肉

七个 agent 的执行顺序不是平面的,而是有明确的数据依赖拓扑:

01
project-scanner扫描文件树,输出文件列表 + 语言指纹
↓
02
architecture-analyzer先于 file-analyzer 运行——必须先知道"分层结构"才能决定文件归属哪个语义批
↓
03
file-analyzer ×N(并行 5)对每个语义批并发执行,每批 20–35 个文件
↓
04
domain-analyzer依赖 file-analyzer 输出的语义标签,抽取业务域
↓
05
tour-builder依赖 architecture-analyzer 的分层 + file-analyzer 的 fan-in/fan-out
↓
06
graph-reviewer最后校验器,对整张图做 schema + 引用完整性检查

路由器决策逻辑:分发任务给谁

Understand-Anything 的路由器不是一张查表,而是一套基于语义类型的启发式分发规则。它出现在两个地方:

  • 任务分发阶段:决定新任务(如"分析这批文件")应该路由到哪个 agent。
  • 社区批计算阶段(compute-batches.mjs):对 import 图跑 Louvain 社区发现,把语义耦合的文件归到同一批,决定哪些文件该一起发给同一个 file-analyzer。
agents/file-analyzer.md — frontmatter 示意
---
name: file-analyzer
type: agent
trigger: semantic-file-batch    # 路由器识别的触发类型
input:
  files: string[]
  lang: string
  community: string          # 来自 Louvain 的社区 ID
output:
  summaries: Summary[]
  crossEdges: CrossFileEdge[]
retry: 2
---

# 分析这批文件,产出大白话摘要、语义标签、跨文件引用边。
# community context 帮助 LLM 理解同批文件之间的相互依赖关系。

路由决策不是神经网络的 softmax,而是基于 frontmatter 里 trigger 字段的字符串匹配。编排器(src/orchestrator.ts 或类似)按任务类型查表找对应 agent,触发条件在 frontmatter 里声明,确定性、可审计、可进 git——这正是 Understand-Anything "确定性结构 × LLM 语义"哲学的体现。

与"学出来的路由"(如 SWARM、HiveMind 等学习型多 agent 系统)的区别:这里没有训练过程。路由规则是人工设计的,每条规则的适用范围由 trigger 和 agent 的描述共同约束。好处是完全可控,坏处是无法自动适应全新场景——需要人工加新的 agent markdown。

协作推理流:expert → synthesizer → output

单个任务的协作推理遵循三轮迭代模式:

①
Expert 独立分析file-analyzer 对每批文件独立产出结构解析 + 语义摘要,不依赖其他批
↓
②
边界检查每个 file-analyzer 实例检查 neighborMap——跨批邻居导出符号——判断是否需要引用其他批的节点
↓
③
Synthesizer 合并所有批的输出汇总后,architecture-analyzer 注入分层结构,domain-analyzer 注入业务域标签,形成统一语义图

neighborMap 是这个协作模式的关键粘合剂。每个文件在批分配阶段就拿到了"如果我的符号引用了本批没有的节点,该查谁"的映射——这是在 Louvain 社区发现阶段计算的,跨批的 import 依赖在批划分时就已知。这样 file-analyzer 不需要跨网络请求就能判断引用完整性。

另一个协作细节:输出截断自愈。当 LLM 的输出超过模型的 token 上限时(如 Bedrock Opus 4096–8192 token),file-analyzer 会检测到截断,把输出自动切成 batch-i-part-1.json、batch-i-part-2.json……后续的 synthesizer 阶段会按序号合并。这让系统在"输出受限"的后端上也能运行。

协作的核心约束:每个 expert 必须是有状态的局部决策者——它只需要知道自己的输入和邻居的接口,不需要知道全局图。graph-reviewer 在最后统一检查全局完整性。这种"局部决策 + 全局校验"的模式,避免了全图同步的开销,同时保证了端到端正确性。

语义覆盖:七专家如何覆盖全代码空间

七专家的设计逻辑是按代码理解的需求维度切分,而不是按语言或文件类型切分:

专家覆盖的语义维度典型任务
project-scanner项目结构、框架指纹"这是什么框架?React 还是 Next.js?"
file-analyzer函数/类语义、跨文件引用"这个函数做什么?它调用了哪些服务?"
architecture-analyzer模块边界、层次结构"应该按什么顺序学习这个代码库?"
tour-builder学习路径、依赖链"fan-out 最高的入口点在哪?"
graph-reviewer完整性、正确性"有没有悬空引用?分层覆盖是否完整?"
domain-analyzer业务逻辑、业务流程"这个模块属于哪个业务域?"

这种切分方式保证了每个代码理解需求都恰好有一个专家负责——没有两个专家抢同一个问题,也没有一个问题没有任何专家覆盖。七专家的语义覆盖是正交的:project-scanner 管"是什么项目",file-analyzer 管"文件里有什么语义",architecture-analyzer 管"文件之间怎么组织",tour-builder 管"人应该按什么顺序学",graph-reviewer 管"图是否完整正确",domain-analyzer 管"代码背后的业务意图"。

与"一个 LLM 分析全代码库"的 naive 方案相比,混合路由的优势在于:每轮 LLM 调用只需要处理语义相关的文件子集,而不是把整个代码库塞进 context。这在 20 万行级别的代码库上是生死之别。

专家边界:职责边界如何定义与执行

每个专家的职责边界通过两个机制双重约束:

  • frontmatter 的 trigger 字段:声明"什么样的任务语义类型会路由到我"。路由器按字符串匹配分发。
  • agent markdown 里的描述:LLM 读取的是自然语言指令,而不是 JSON schema——这让它能在边界模糊时自行判断是否在职责范围内。

冗余规避:如何防止两个专家做同一件事

七专家之间有三类潜在重叠,系统用不同策略规避:

  • file-analyzer × architecture-analyzer:都有"理解文件内容"的成分,但 file-analyzer 处理单文件内部语义,architecture-analyzer 处理跨文件的分组结构。执行顺序上,architecture-analyzer 先运行,file-analyzer 后运行——后者可以参考前者的分层结果,但不会重复前者的跨文件分析。
  • file-analyzer × domain-analyzer:domain-analyzer 依赖 file-analyzer 的输出(语义标签),不会独立再跑一遍文件解析。
  • tour-builder × graph-reviewer:tour-builder 产出学习路径(面向人),graph-reviewer 校验完整性(面向图)。两者输入有重叠,但输出正交。

这种"数据依赖强制排序"是防止冗余的主要机制——如果 B 依赖 A 的输出,那么 B 不可能重复做 A 的工作,因为 B 的输入已经是 A 的结果。

关键工程约束:每个 agent 都是幂等的——对同样的输入文件列表,无论运行多少次,输出应该一致(允许 LLM 摘要有微小措辞差异,但节点和边的集合应该相同)。这让流水线可以安全地重试失败阶段,而不用担心累积副作用。

触发示例:什么代码触发什么专家

用三个具体场景说明路由器如何工作:

场景 A:React + TypeScript Monorepo 的入口分析
// project-scanner 检测到:
// - package.json 存在 + workspace 配置
// - tsconfig.json 存在
// - apps/web/src/App.tsx 存在
// → 识别为:TypeScript monorepo (workspace)

// router 决策:
// task = "scan project structure"
// trigger = "project-structure-scan"
// → 路由到: project-scanner
场景 B:Louvain 批划分后的一批 API 路由文件
// compute-batches.mjs 运行 Louvain 后:
// 社区 "api-routes" = [
//   "apps/api/src/routes/users.ts",
//   "apps/api/src/routes/orders.ts",
//   "packages/auth/src/jwt.ts"       ← 跨包依赖,被导出到这个社区
// ]

// router 决策:
// task = "analyze semantic batch"
// trigger = "semantic-file-batch"
// context.community = "api-routes"
// → 路由到: file-analyzer (part 1 of N)

// file-analyzer 在 neighborMap 中看到:
// jwt.ts 的公开接口被这批文件引用
// → 它在生成跨文件边时,会引用 jwt.ts 中的符号
场景 C:批量截断后的 part 文件合并
// file-analyzer 输出检测到截断:
// "Output exceeded model limit, generating batch-i-part-1.json"

// router 检测到 part 文件存在:
// task = "merge partial outputs"
// trigger = "output-synthesis"
// → synthesizer 收集 batch-i-part-*.json
//   按 part 编号排序,合并 summaries 和 crossEdges

// graph-reviewer 最终校验:
// 所有引用完整性检查通过后,流水线完成

生态意义:从"一个 agent 全做"到"混合专家"

Understand-Anything 的七专家混合路由代表了一种正在成为主流的coding agent 架构范式:

  • 可审计性:每条边、每个摘要都能追溯到是哪个 agent、哪段 prompt 生成的。没有"黑箱 LLM 内部推理"。
  • 可组合性:新增一个专家只需要写一个 markdown 文件,声明 trigger、input schema、output schema,无需修改编排器代码。
  • 可选择性:domain-analyzer 是可选的——如果用户不需要业务域叠加,可以跳过这条流水线阶段。这意味着流水线是可裁剪的。
  • 可重试性:每个阶段独立重试,失败的 agent 可以从 checkpoint 恢复,不影响其他阶段。

与 SWARM(OpenAI)、HiveMind、AutoGen 等"学出来的多 agent"系统相比,Understand-Anything 的路由是人工设计的、确定性的、可进 git 的。这在"代码库理解"这个相对稳定的场景下是合理的——我们不需要 agent 自动发现"应该用几个专家",因为代码分析的任务类型本身就是已知的。

下一步方向:路由器从"字符串匹配"升级为"语义匹配"——用一个小型 embedding 模型,对任务描述编码后在 agent 描述向量空间里做最近邻查找。这会让"新场景无需手动注册新 agent"成为可能,同时保持路由的可审计性。