Files
Agent-System-Documents/docs/records/2026-10-02-agent-stack-audit.md
weichenandCursor 233dfb54ea Propose the agent stack audit and completion design
Why: The three libraries cannot be reused or recovered independently, and the tested dependency combination is not the one the product builds.
Evidence: Read-only audit at native c6d867c, squad 590fd86, intent 1dbbe81, recorded with file references in docs/records/2026-10-02-agent-stack-audit.md.
Impact: Documents only. No library code or gitlinks change in this commit. Later stages stay separate tasks.
Refs: Dev feature/agent-stack-blueprint
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-02 16:27:37 +00:00

18 KiB
Raw Permalink Blame History

记录:Agent-Native / Agent-Squad / Agent-Intent 只读审计

日期:2026-10-02。任务主记录:Dev 仓 feature/agent-stack-blueprint。 设计结论见 2026-10-02-agent-stack-completion.md。本文只记证据,不实施修复。

基线(Dev 顶层 deps/,git rev-parse):

  • agent-native c6d867cde7d87696feaf3073f097ca15b0145273
  • agent-squad 590fd86f4c8bfada0ec76efbfb57133c61607bd6
  • agent-intent 1dbbe8149c561b36282bf781e61a06d1284be3a1

方法:只读读码。P0 条目已对照源码复核。标注「待确认」的条目没有在本次复核中跑测试。路径相对各库根目录。

1. 跨库与集成

编号 级 现象 证据 影响 方向
X1 P0 测过的组合不等于发布的组合 Backend go.mod replace 指向自己的 deps/:intent 7d9d505、native c6d867c、squad 5cbac00。Squad 自测钉 native a34689c、intent 3674dc4。Dev 顶层是 intent 1dbbe81、squad 590fd86。Go 只认主模块的 replace 各库测试全绿也不能证明产品编译的组合正确 Dev 根 go.work;make test 断言 Backend 用到的 SHA 等于 Dev 指针
X2 P1 gitlink 递归嵌套 deps/agent-squad/deps/agent-native/deps/agent-intent 等。agent-intent 在 Dev 检出里有多份副本,SHA 不完全一致 读码容易看错副本;磁盘上的嵌套仓不参与 Backend 编译 库仓不再嵌套兄弟库;适配器子模块用 go.mod 版本号钉依赖
X3 P1 边界检查不管库与库 scripts/lint-boundaries.sh 只禁止扩展 SDK 与示例 import agent-system-backend/internal 实现级耦合没有门禁 用 go list -deps 断言核心模块不得依赖兄弟库
X4 P1 内核为了一个没有调用方的包依赖 Intent deps/agent-native/go.mod require agent-intent;唯一引用是 adapters/intentgate。Backend 零引用该包 内核不能单独使用 删除该适配器,或迁到独立子模块
X5 P1 Squad 公开 API 暴露内核类型 squad/backend.go:BackendFactory 返回 exec.Backend;AgentNativeConfig 含 *llm.Client、tools.SearchProvider、tools.ExecConfig、mcp.Manager、subagent.Factory。planner_llm.go 的 LLMPlanner.Client 是 *llm.Client。intent.go 的 IntentRunner 返回 *exec.Result 换执行器必须改 Squad 核心;编译期去不掉 agent-native 核心只定义 Executor、ModelClient 端口;跨库代码进 adapters/native
X6 P1 platform 绕过执行端口 platform/runtime.go 的 RunInProcess 直接 runtime.NewAgentSession 内核内部类型进入 platform 公开签名 经 Executor 端口
X7 P1 CLI 直接 import Intent deps/agent-squad/cli/cli.go import agent-intent/intent 与「只经 squadintent 接入」的约定不一致 CLI 迁到独立 cmd/,经适配器装配
X8 P1 同一概念多处定义 意图闸门:squad.IntentGate、squadintent.Gate、adapters/intentgate、backend/internal/intentgate。目录三套。HITL:Squad pending、Backend 卡片、命令确认、Intent 的 Question。预算:exec.Budget、Backend RunBudget、设置项。事件三套。工作流:squad.WorkflowPlan 与 core.WorkflowDef。结果状态:command 四态、intent 五分支、native 的 Success 布尔 扩展作者要对齐四套键:handler、metadata.blueprint、CommandSpec.id、WorkflowDef.IntentID 每个概念一处定义,见提案第 4 节
X9 P1 产品没有接上 Squad 的意图能力 modules/backend/internal/squadadapter 未装配 IntentGate、Catalog、Runner Squad 侧已做的目录注入与计划校验在产品路径不生效 组合根装配适配器
X10 P2 模块路径是裸名 go.mod 的 module agent-native / agent-squad / agent-intent 第三方不能 go get,只能 replace 改为可获取路径并打 semver tag。路线图已把改模块路径列为单独发版任务,不在本次

2. Agent-Native(c6d867c)

P0

编号 现象 证据 影响 方向
N1 工具预算超限留下没有结果的 tool_calls runtime/loop.go 先 persist 带 tool_calls 的 assistant,再在 toolCalls+len > Max 时 budgetStop。budgetStop(同文件约 542 行)不补工具结果 续跑时 OpenAI / Anthropic 会拒绝这段历史 超预算则不落盘该 assistant,或先写入合成的 error 结果再停
N2 并行工具的事件回调有数据竞争 executeParallel(loop.go 约 366 行)每个工具一个 goroutine 调 runOne→emit。adapters/native/backend.go 约 332 行的 OnEvent 无锁写同一个 strings.Builder -race 会报;日志交错 串行投递事件,或每工具私有缓冲再合并
N3 MCP stdio 取消后读协程不退出 runtime/mcp/stdio.go 的 readResponseLine:ctx.Done 返回后,后台 goroutine 仍在 ReadBytes。下次调用再起一个读者 stdout 帧错乱、卡死 取消时关闭进程,或单读循环按 id 分发
N4 预设与真实客户端协议不一致 runtime/llm/client.go 固定 POST …/chat/completions。BuildBody 的 Anthropic / Google 分支不被该 Client 使用。Anthropic body(providers.go 约 303 行)把 tool 结果收成普通 user 文本,不带 tool_use / thinking 签名,也不挂 tools 选 anthropic / google 预设不能多轮工具 按 Protocol 分流真实客户端;未实现的协议不要出现在可用预设里

P1

编号 现象 证据 方向
N5 token 预算只在回合开头检查,超限后本轮工具仍执行 loop.go 用量在 consume 之后累加,执行工具前只查 MaxToolCalls 累加后、执行工具前再查 token 预算
N6 Stop.Replacement 只改内存 loop.go 改最后一条消息内容,不 persist 追加更正记录或提供改写 API
N7 compaction 按条数裁尾,可切断工具配对 runtime/session/compact.go 的 keepLastN 不按 tool 组对齐。现有测试没有工具消息 裁切对齐 tool_calls 与 tool 结果
N8 compact 与 append 有并发窗口 compact 读完条目后解锁再摘要,再加锁追加 摘要期间禁止追加,或按版本号 CAS
N9 会话追加无 fsync、无跨进程锁 runtime/session/tree.go 用 O_APPEND 写,锁只在单进程内 可选 Sync;文档写明单写者,或加文件锁
N10 流式错误不落盘、默认不重试 loop.go 遇到错误帧直接返回。WithRetry 只在 CLI 包装 Backend 适配默认重试;写明错误帧是否落盘
N11 SSE 不拼接多行 data: runtime/llm/sse.go 逐行处理 按空行分事件再拼接
N12 取消一批工具时要等全部结束 executeParallel 用 Wait;顺序路径工具之间不看 ctx.Err() 批内协作取消;超时后仍写 error 结果以保持配对
N13 fetch_url 有 DNS 重绑定窗口 runtime/tools/fetch.go 先 LookupIP 校验,client.Do 再解析 Dial 时连已校验的 IP
N14 宿主自带 CheckRedirect 时跳过重定向校验 fetch.go 仅在 CheckRedirect==nil 时包装 始终链式包装
N15 write / edit 不是崩溃原子 runtime/tools/builtin.go 直接 os.WriteFile 临时文件再 rename
N16 按字节截断,破坏 UTF-8 builtin.go、exec.go、mcp/manager.go 按 rune 回退
N17 工具 schema 校验过松 runtime/tools/registry.go 不强制 additionalProperties:false,array / object 一律放行 至少拒绝未知键
N18 MCP「失败重连一次」只有注释 runtime/mcp/manager.go 注释与实现不符,失败只返回错误 实现或删注释
N19 MCP HTTP Close 是空操作 runtime/mcp/http.go 发送 session terminate
N20 用量把每轮完整历史的 prompt token 累加 loop.go 分开「上一请求」与「累计计费」
N21 毒化轮转状态只在内存 adapters/native/backend.go 的 lastFailure 旁路文件落盘
N22 提醒消息是追加的 user 角色 loop.go 的 withEphemeral。OpenAI 在 tool 结果之后通常可接受。Anthropic Messages 是否拒绝,待确认 用提供方的 ephemeral / system 通道;补一条 Anthropic 往返测试

P2

编号 现象 证据 方向
N23 run_python 的 ScriptDir 不经路径解析 runtime/tools/exec.go 约 112 行:filepath.Join(t.root, ScriptDir)。该字段只能由宿主配置,模型参数改不了,因此从 P0 降为加固 Resolve 后再用;单测覆盖 ..
N24 grep / WalkDir 无文件数与总字节上限 builtin.go 只有命中条数和单文件上限 加 maxFiles / maxBytes
N25 HTTP 客户端默认超时很长、无空闲读超时 runtime/llm/client.go ResponseHeaderTimeout 与读空闲超时
N26 Registry 无锁 registry.go 的 Register / Restrict 与 Execute 并发不安全 运行前冻结快照
N27 SSE 工具名用 += 拼接 sse.go 若 delta 重复全名会拼重 仅空时写入
N28 绝对路径 jail 用未 Eval 的根做 Rel runtime/tools/jail.go。symlink 根加绝对真实路径是否可绕,待确认 校验统一对已解析的根
N29 contract/ 与 exec/ 两套契约 contract 只有 conformance 的 fake 在用;native 实现的是 exec.Backend 保留 exec,删除或并入 contract
N30 native.Options 汇集全部配置 MCP、技能、压缩、宿主工具、执行、抓取、搜索在同一结构 按端口拆分
N31 adapters/native 不透传 Stop / Reminders / RepeatGuard / BeforeTool adapters/native/backend.go 的 run 不设置这些字段 小队路径拿不到直连的门禁。透传或由宿主显式配置

缺测(本次未找到对应用例):N1 配对、N2 race、N3 取消双读、N4 Anthropic 线上格式、N7 切断工具组、N13 DNS 重绑定、N6 持久化。

3. Agent-Squad(590fd86)

P0

编号 现象 证据 影响 方向
Q1 RequiresApproval 批准后再次挂起 squad/workflow.go 约 60 行检查 Approved["step:"+id]。全仓只写 Approved["role:…"](engine.go 约 578 行)。Respond 的确认只恢复挂起,不写 step 键 该步骤永不执行 批准时写入 step: 键并落盘;补回归
Q2 授权、修复预算、澄清、steering 不落盘 runState 有这些字段(squad/runstate.go)。save() 只写 workspace、workflow、squad、blackboard、usage 重启后授权丢失、修复预算归零 原子写入 runstate.json,Resume 必读
Q3 状态文件不是原子写 squad/workspace.go 的 writeJSON 直接 os.WriteFile。只有黑板用临时文件再 rename(blackboard.go) 崩溃留下半截 JSON,Resume 失败 统一 tmp + Sync + rename
Q4 Resume 会重跑 status=running 的步骤 advance 只跳过 completed / skipped / failed(workflow.go 约 33 行)。崩溃可留下 running。Resume 无挂起则直接 advance 写文件、工具、MCP 副作用重复 步骤 attempt id;恢复前把 running 标成待重试并校验产物
Q5 没有跨进程 run 锁 engine.go 约 144 行只有进程内 map 两进程可交错写同一 run 目录 run 目录文件锁,或单写者

P1

编号 现象 证据 方向
Q6 HITL 超时字段未实现 hitl.go 有 TimeoutAfter / ExpireAction,无扫描 sweeper 按到期动作合成决策
Q7 Respond 先删 pending 再应用决策 engine.go 约 477 行先 saveGate(nil)。二次回应走「无挂起」 先记 answered 再删
Q8 wakeup 自动批准 confirmation / permission / quality engine.go 约 901 行 高风险 kind 禁止自动批准
Q9 SweepWakeups 在锁外改 WakeupFires 先自增写盘再 Respond(锁在 Respond 内) 整段放进 run 锁
Q10 并行组无 fail-fast,且丢掉 gate runParallel 等全部结束;忽略 runOneStep 的 gate 失败即取消兄弟
Q11 步骤间上下文是过期快照 BuildSubWorkspaces 用当时黑板写 TASK.md,顺序执行不刷新 每步前用最新黑板和上游产物索引重写
Q12 LLM 规划失败无回退、无规模上限、目标原文注入 planner_llm.go fail-closed;prompt 直接拼 Goal.Text 可配置回退;目标分段;硬上限
Q13 intent 步要求注册表里存在名为 intent 的角色 materialize 对每步 Registry.Get(role) IntentID 非空时跳过普通角色检查
Q14 Server 与 platform HTTP 无鉴权 server/server.go、platform/http.go 至少 API token
Q15 run id 未校验 filepath.Join(DataDir, "runs", runID),id 来自路径 拒绝非 run_ 前缀的安全字符集
Q16 请求体无大小上限(webhook 除外) handleCreateRun 无 MaxBytesReader;webhook 才有 256KiB 全局 body 上限
Q17 SSE 扇出关闭竞态、慢消费者丢帧 progress.go default 丢帧;回放 goroutine 往已关闭 channel 写 done 信号停回放
Q18 事件序号重启后从 1 重计 store/eventsink.go 的 seq 只在内存 启动时读最后一行
Q19 Claim 并发上限先查后改;未开 WAL platform/queue.go 先 COUNT 再 UPDATE;db.go 只有 busy_timeout 单条条件更新;journal_mode=WAL
Q20 文件 runs 与 SQLite 两个真相源 platform/db.go 注释;server 可同时挂两者 platform 降为 Store 实现,状态以图执行为准
Q21 StepSpec.Needs 未参与调度 只定义在 models.go。调度看相邻 Parallel(workflow.go) 实现 DAG 或删除字段

P2

编号 现象 证据 方向
Q22 leader 先 Turns++ 再执行 leader.go 约 242 行 成功后再递增
Q23 MaxLeaderTurns 用尽直接失败,无降级收口 leader.go 可配置收口
Q24 步骤重试共享同一个 deadline workflow.go 约 209 行。短超时下重试几乎无预算 文档写明,或每次重试单独预算
Q25 角色与蓝图写盘忽略错误 store.go 的 _ = os.WriteFile 原子写并返回错误
Q26 人工 retry 直接把修复预算加一 engine.go 约 608 行。是否有意,待确认 设上限
Q27 拓扑写死 standard / leader / router 在引擎内分支,无 Topology 接口 插件接口

缺测:Q1 全路径、Q4 从 running 恢复、Q5 多进程、Q6 超时、Q8 自动批准、Q15 路径穿越。

4. Agent-Intent(1dbbe81)

P0

编号 现象 证据 影响 方向
I1 Classifier.Refine 返回值不校验 intent/recognizer.go 约 97–123 行:不检查 ID 是否属于原候选,不把置信度限制在 0 到 1,不禁止改 Metadata / Handler。失败才保持 Ambiguous LLM 分类器可以把任意 ID 抬成 Top ID 集合必须相等;置信度钳制;禁止新增特权字段
I2 槽位校验器是进程级全局表 intent/slots.go 的 slotValidators sync.Map,无注销、无命名空间 多 Registry 同名互相覆盖 挂到 Registry / Options

P1

编号 现象 证据 方向
I3 中文子串匹配、无词界 intent/registry.go 约 120 行 strings.Contains。语料只有正向命中 词界模式或最短词长告警;补假阳性负例
I4 只做 ToLower,无 NFKC / 全半角 / 繁简 recognizer.go 折叠;slot normalize 仅 trim / lower / upper 可选 NFKC;文档写明无繁简
I5 同分恒为 Ambiguous,priority 不参与裁决 分差小于 margin 即 Ambiguous(recognizer.go)。priority 只用于排序 允许 margin 为 0 时按 priority 决胜。当前 <=0 会被改回 0.1(registry.go)
I6 分类器解歧后的 Top 可能没有槽位 candidatesOf(..., false) 先不抽槽 裁决成功后再对 Top 抽槽
I7 Snapshot 的 Metadata 浅拷贝 SpecByID / Catalog 拷贝 map 但共享底层 深拷贝
I8 Catalog 整表输出 Metadata 与 Handler intent/catalog.go 白名单键;默认不进 Markdown
I9 闸门钉死旧 *Snapshot squadintent/gate.go、adapters/intentgate 不持有 Registry Reload 后须重建,或持有 Registry
I10 识别是全量线性扫描 evalRules,上限 2048 规模上来再做倒排;先写明上限
I11 多槽位各自第一次匹配,无重叠消解 intent/slots.go 文档写明;冲突时标歧义
I12 否定只靠 excludes_any 子串 语料 negated_write_readonly 依赖硬编码「配置」 不在库内做语义否定;交给命令层的 Cancel

P2

编号 现象 证据 方向
I13 pattern 无长度上限 条数上限 MaxPatternsPerSpec=32,单条无长度限制 构建期限制长度
I14 评测语料约 10 条,无 precision / recall testdata/corpus.json 多轮、逐轮期望命令,输出指标

已核实相对健全:Snapshot 用原子指针交换且失败保留旧快照;正则在构建期编译(RE2);同分排序稳定(score、priority、注册序)。

5. 与产品侧的关系(不在三库内,供路线引用)

这些能力今天在 Backend,提案主张下沉到库,本次不改:

  • 终轮验证与计划提醒:modules/backend/internal/runtime/verify.go
  • 评审流水线:internal/runtime/review.go
  • 工作流执行与 critic:internal/runtime/workflow.go、internal/workflow/
  • 子代理循环与 profile:internal/runtime/sub_loop.go、internal/prompts/library/agent-sub-*.md
  • 后台子代理通知:internal/runmgr/runmgr.go
  • 直连回合:internal/runtime/llm_direct.go(约 692 行)

core.WorkflowDef 仍是插件作者面对的契约。执行器与 squad.WorkflowPlan 是两套编排。