Record the decoupling tiers and LLM layering design for the Agent-Squad adoption.
ci / vet-and-test (push) Failing after 2s
ci / docker-checkout-hostgateway (push) Successful in 3s

This commit is contained in:
weichen
2026-09-29 15:45:37 +00:00
parent 40c33d5edc
commit 55950f6414
@@ -0,0 +1,99 @@
# 意图栈解耦档位与 LLM 分层设计(含 Agent-Squad 接入方案 v2)
状态:待审;owner:weichen;主任务:本文件。授权与交付终点:本方案获批后实施 Agent-Squad 接入(v1 不含 LLM 层)。
关联:[2026-09-29-agent-intent-design.md](2026-09-29-agent-intent-design.md)、[2026-09-29-implementation-verification.md](2026-09-29-implementation-verification.md)。
## 问题与证据
1. Agent-Native 已定为"不接入"(无自然语言入口,闸门属于调用方)。Agent-Squad 是否也能与意图识别完全解耦?
2. 意图栈中存在需要 LLM 的层(歧义重排、复杂分句、澄清生成、目标改写)。这部分 LLM 依赖如何接:宿主直连 LLM?保持解耦?调用 Agent-Native 执行?还是其他设计?
证据(现状):Agent-Squad 核心包已直接 import agent-native(`squad/backend.go` 的 `adapters/native`、`exec`、`runtime/llm`),其 LLM 规划器复用 `runtime/llm.Client` 传输层而非跑完整 agent 执行(`squad/planner_llm.go`、`cli/cli.go:114`)——即"库需要 LLM 时复用 Native 的传输层、不开工作区不跑循环"在本生态已有先例。
## 一、解耦的四个档位
「完全解耦」不是布尔值。可操作的分解:
| 档位 | 含义 | 删除/回退成本 | 适用 |
|---|---|---|---|
| T0 行为解耦 | 未配置意图 ⇒ 行为与原系统逐字节一致(gate 为 nil,或快照零 spec ⇒ 每次 Decide 返回 nil ⇒ 引擎走原代码路径) | 无需删除 | **所有档位共同底线,必须测试证明** |
| T1 结构解耦(编译期可选) | 核心包零 import agent-intent;唯一接入点 = 一个适配器包 + 组合根约 20 行接线 | 删 1 个包 + 接线块 + go.mod/submodule(引擎零改动) | **推荐**:Squad 自带 CLI/serve 是一等入口,单一二进制要保留意图能力 |
| T2 仓库级零依赖 | Squad 仓完全不引用 agent-intent(只定义 IntentGate 端口);桥与意图化二进制放独立组合仓(或产品仓 Agent-System) | 删组合仓即可;Squad 仓从未被触碰 | Squad 定位为纯库、永远不单独带意图运行 |
| T3 进程解耦 | 意图闸门做成 sidecar 服务,Squad 经 HTTP 调用 | 下线服务 + 删 HTTP 客户端 | 多语言宿主、独立部署节奏、跨团队所有权 |
判断依据:**桥必须存在于某个组合根**——Native 零接入是因为它的入口是 Go API 而非自然语言;Squad 的 `run <goal>` / `POST /v1/runs` 是自然语言入口,桥无处可藏,只能选择放在哪个仓、以什么粒度删。
- T1 比生态既有模式更松:现状 `squad` 核心包直接 import agent-native,T1 把意图依赖隔离在 `squadintent/` 适配器 + cli 接线两处,`go list -deps ./squad ./server` 不含 agent-intent(可脚本断言)。
- T2 的代价要说清:Squad 自带 CLI 永远没有意图能力,需要第二个二进制(组合仓提供 main);若 Squad 还会独立使用(当前事实如此),T2 把能力从主入口挪走。
- T3 不推荐:单人生态引入常驻服务、网络故障面与配置面;重新考虑的条件是出现多语言宿主或独立发布节奏。
**不变式(T0,任何档位都必须成立并验证)**:`gate == nil` 与 `gate 配置了但快照零 spec` 两种引擎,对同一输入集产出与"从未有过此功能"的系统一致的运行结果。验证方式:现有 conformance 套件跑在无 gate 引擎上(等价回归)+ 新增"零配置等价"对比测试。
## 二、意图栈的 LLM 分层
### 2.1 盘点:哪些层可能用 LLM
| # | 层 | 归属 | 默认实现 | LLM 增强形态 | 失败契约 |
|---|---|---|---|---|---|
| 1 | 守卫 / 规则 / 置信度裁决 | agent-intent | 纯规则确定性 | **永不引入** | n/a |
| 2 | Ambiguous 重排(Classifier 接口) | 宿主实现 | 不启用 | LLM 对候选重打分 | 失败保持 Ambiguous(agent-intent 已内建,识别不失败) |
| 3 | 多意图分句(Segmenter 接口) | 宿主实现 | 规则分句(确定性) | LLM 分句 | 失败回退规则分句 |
| 4 | 澄清提问生成 / 多轮对话 | 宿主(对话面) | 无 | LLM 生成 | 失败退固定话术 |
| 5 | 目标改写 / 任务简报结构化 | 宿主 | 规则 | LLM(Agent-System RewriteQuery 先例) | 失败用原文 |
| 6 | 离线:spec 草案生成、评测集扩充 | intentctl(未来) | 无 | LLM 辅助 | 人在环审批,不入库不生效 |
### 2.2 LLM 依赖的接法:不是单选,是按层分派
**agent-intent 本体:永不直接接 LLM(既定硬边界,不变)。** LLM 层全部以接口注入宿主实现。任何 LLM 适配器都不进 agent-intent 仓——否则引入 LLM 传输依赖与配置面,并破坏"禁止依赖 agent-native"的防循环边界。
**同步轻量层(#2、#3):宿主适配器复用 `agent-native/runtime/llm.Client` 传输层,不跑 agent 执行。** 这就是 Squad LLMPlanner 的既有模式:同一个在组合根构造的 client 实例,包装成 Classifier/Segmenter 实现。约束:严格 JSON 输出 + 显式超时 + fail-open(失败降级到 Ambiguous/规则分句,绝不使识别失败)。理由:这两层在请求热路径上,需要亚秒延迟与低开销;走完整 agent 执行(工作区、会话落盘、多轮循环)是秒级成本,且让路由决策的可用性耦合到执行内核。
**异步重层(#4、#5 及未来的语料挖掘):走 Agent-Native `exec.Request`(reasoning 档)做一次真正的受治理执行。** 这些层本质是"小任务"而非"路由判断":产出是生成文本(提问、改写、spec 草案),有交付物语义,应当享受 exec.Result、稳定错误码、trace、会话审计的统一治理,而不是在路由热路径里私接 LLM 调用。
**热路径可用性原则:LLM 不可用时,意图栈退化为纯规则,永远不阻断原路径。** 规则能判则不调 LLM(Agent-System intentflow 的 `RulesCanDecide` 门控同一逻辑);LLM 层全部是增强项而非依赖项。
### 2.3 确定性与审计契约
- 规则层确定性承诺不变(同输入+同快照+同分支语义);LLM 参与的结果显式标注(`Reason` 含 `refined_by=classifier` 等),确定性作用域随之收窄为"含该 LLM 层的行为契约",不算破坏承诺。
- 每次裁决记录:分支、top、快照 revision(可复现)、命中的层。LLM 层额外记录 prompt 摘要与模型名(不记原始输入全文,沿用脱敏纪律)。
### 2.4 对 Agent-Squad v1 的直接结论
**Squad v1 接入不需要任何 LLM 层。** 闸门只消费三类结果:GuardHit→拒绝、Matched→蓝图定向、其余(Ambiguous/Fallback)→透传原路径(`Blueprints.Match` 关键词计分保持原行为,意图只增不删)。Squad 没有对话面,#2–#5 均不适用;LLM 分层策略约束的是未来的对话型宿主(Agent-System 或产品仓)。
## 三、Agent-Squad 接入设计(T1 具体化,v1 范围)
### 3.1 Squad 仓内变更
| 位置 | 变更 | 规模 |
|---|---|---|
| `squad/intent.go`(新) | `IntentGate` 窄接口 + `IntentDecision`(`Reject{Reason,Guidance}` / `Blueprint{Key}` / nil=透传)+ `Options.IntentGate` 字段;`Run()` 入口调用一次(入口语义;澄清续跑不重复过闸) | ~60 行,零外部依赖 |
| `squadintent/`(新包) | **全仓唯一 import agent-intent 处**。`Gate` 构造(宿主 Go 内置守卫 spec + JSON root → Registry/Recognizer)与 `Decide` 映射:GuardHit→Reject;Matched→`Metadata["blueprint"]`→Blueprint key,映射的蓝图不存在则 fail-closed(同"未知 backend 拒绝"先例);Ambiguous/Fallback→nil;空快照→恒 nil | 适配器 + 单测 |
| `cli/cli.go` assemble | `-intent-root` flag + `AGENT_SQUAD_INTENT_ROOT` env(双通道,同 planner 先例);未设置→gate=nil;配置存在但构建失败→启动失败(fail-closed) | ~20 行 |
| `scripts/submodule-consistency.sh` | 加 `deps/agent-intent` 及其嵌套三子模块与外层同仓 gitlink 比对 | 数行 |
| 文档 | AGENTS 硬边界补一行(意图闸门可选,唯 squadintent 与组合根可 import agent-intent);PROJECT 依赖表、policy 采纳记录、docs/records 接入记录 | 文档 |
依赖方向:`squadintent → agent-intent → (errcode/logsys 轻接入)`;`squad / server / store → 无 agent-intent`。
### 3.2 解耦证明(随实施交付的测试)
1. **零配置等价**:同一输入集在「无 gate 引擎」与「空配置 gate 引擎」上 RunResult 关键字段一致。
2. **既有 conformance 全量跑在无 gate 引擎上**:等价回归,证明引擎路径未被改写。
3. **透传保持**:配置了意图但输入 Fallback 时,结果与无 gate 引擎一致(意图只增不删)。
4. **结构断言**:`go list -deps ./squad/... ./server/...` 输出不含 `agent-intent`(进一致性脚本)。
5. squadintent 适配器跑 agent-intent `conformance.Run` + 自有语料。
### 3.3 回退程序(精确到文件)
删 `squadintent/`、`squad/intent.go`(或仅置 Options.IntentGate 为 nil 亦即行为回退)、cli 接线块、go.mod 的 require/replace 三行、`.gitmodules` 一条、submodule、一致性脚本对应行。`squad` 引擎与其余包零改动。
## 决策与剩余项
- **D1 档位**:T1(推荐,理由见上)vs T2(Squad 永不单独带意图时选)。
- **D2 蓝图映射缺失**:fail-closed 报错(推荐,同未知 backend 先例)vs 透传原路径。
- **D3 Guard 拒绝错误码**:Error-Code-System 新增 `squad.intent_refused`(推荐,400/warn/never/none,需上游 PR)vs 复用 `squad.plan_rejected`。
- **D4 配置来源**:flag + env 双通道(推荐)。
- **D5 Squad v1 不接 LLM 层**(推荐);#2/#3 的 llm.Client 适配器留给需要的宿主仓实现。
- **D6 结构断言**进 `submodule-consistency.sh`(推荐)还是仅文档约定。
剩余项:Agent-System 未来以本模块替换 `internal/intent` 时,其 intentflow/对话组件按 2.2 的分层归位(同步层复用 llm.Client、异步层走 exec);`intentctl` 的 LLM 辅助草案生成(#6)另立任务。