type
Post
status
Published
date
May 2, 2026
slug
agent27
summary
tags
Agent
category
icon
password
一、最困难的挑战:是让Agent“稳定、可控、可验证”地调工具
我做过最困难的 AI 项目,是一个面向研发团队的 AI Coding + RAG Agent 平台。
它是一个能在真实代码仓库中完成以下任务的 Agent:
理解需求 → 检索代码与文档 → 制定修改计划 → 调用工具读写文件 → 执行测试 → 修复失败 → 生成变更说明 → 提交 PR 草稿。
一开始我以为难点是:
如何让 LLM 调用工具?如何让 RAG 检索更准?如何让模型写出更好的代码?
后来发现真正困难的是:
当 Agent 进入真实工程环境后,它会面对长上下文、多工具、多轮失败重试、脏数据、权限风险、工具输出噪声、子 Agent 失控、历史记忆污染等问题。难点不再是“会不会调用工具”,而是“调用工具之后系统是否仍然可控”。
Claude Code 的 Agent SDK 官方文档里明确把读文件、运行命令、编辑代码、搜索代码库、Hooks、Subagents、MCP、Permissions、Sessions 等能力作为 Agent SDK 的核心组成部分,这说明成熟 Coding Agent 的工程重心已经从“单次问答”转向了完整的工具执行与上下文管理系统。(Claude API Docs)
OpenCode 也采用了类似思路:它把工具调用、权限控制、内置工具、Agent 类型、子 Agent 和会话机制作为核心能力,其中工具包括 bash、edit、write、read、grep、glob、lsp、apply_patch、webfetch、websearch 等。(OpenCode) OpenClaw 则进一步把 Agent 放到 Gateway、WebSocket、多通道、节点、沙箱、技能、插件和长期会话体系里,说明生产级 Agent 的难点本质上是一个 分布式控制系统问题,而不是简单 prompt 工程问题。(OpenClaw)
二、项目背景:我们要做的不是 Copilot,而是“半自动研发执行器”
项目目标是构建一个内部研发 Agent,类似 Claude Code / OpenCode / Devin 类产品的核心能力,但针对公司自己的代码库、研发规范、测试环境和知识库做定制。
核心场景包括:
- 根据 Jira / GitHub Issue 自动理解需求。
- 检索相关代码、历史 PR、架构文档、接口文档。
- 生成变更计划。
- 自动修改多文件代码。
- 自动运行单测、类型检查、lint、构建命令。
- 根据失败日志二次修复。
- 输出 PR 描述、风险点、回滚方案。
最初版本非常简单:
这个版本在 Demo 环境里效果很好,但一进真实项目就开始暴露问题。
三、第一版为什么失败?
1. RAG 检索到的内容“语义相关但工程无效”
比如用户说:
给订单退款流程增加风控校验。
向量检索经常能召回这些内容:
refund、risk、order、payment、settlement、audit
语义上都相关,但工程上不一定可用。常见问题有:
检索到旧版本接口。检索到废弃模块。检索到测试 mock,不是真实调用链。检索到业务文档,但缺少代码入口。检索到相似文件,但不是当前服务的实现。
所以 Agent 会出现一种非常危险的行为:
它看起来引用了上下文,实际上引用的是“错误但看似合理”的上下文。
这比完全没有 RAG 更危险。
2. 工具输出把上下文撑爆
真实代码库里,一个
grep、ripgrep、npm test、pytest、mvn test 的输出都可能非常大。第一版 Agent 会直接把工具输出塞回 LLM:
问题是:
失败日志很长,真正有用的只有前几个 error stack。LLM 会被重复 warning、无关日志、依赖安装信息干扰。上下文窗口被快速消耗。后续重要代码片段被挤出去。
OpenCode 早期文档里就包含 auto compact 能力,会在对话接近上下文窗口限制时触发总结,以避免长会话失控;当前 OpenCode 文档也把 hidden compaction / summary agent 放在内置 Agent 体系中。(GitHub) (OpenCode) Claude Code 的 Skills 文档也强调,Skill 主体只有在使用时才加载,从而降低长参考材料的上下文成本。(Claude API Docs)
3. 工具权限不是“有没有权限”这么简单
第一版只有简单开关:
这在生产里完全不够。
因为 bash 不是一个工具,而是一个 能力入口:
同一个 bash 工具,既可以安全执行
npm test,也可以执行危险命令。OpenCode 的权限模型将动作分成
allow、ask、deny,并支持对 bash、edit 等工具做更细粒度的通配符规则;例如 git *、npm * 可以 allow,而 rm * 可以 deny。(OpenCode) Claude Code 的 hooks 文档也强调 hooks 可以在 Claude Code 生命周期的关键点运行确定性逻辑,用来格式化文件、阻止命令、注入上下文、发送通知等,而不是完全依赖 LLM 自觉。(Claude API Docs)4. 子 Agent 让问题变成“递归系统问题”
我们后来引入了多 Agent:
Planner:负责计划。Explorer:负责代码检索。Coder:负责修改。Reviewer:负责审查。Verifier:负责运行测试。
这看起来很优雅,但新问题出现了:
主 Agent 有权限控制,子 Agent 是否继承?子 Agent 能否继续创建子 Agent?子 Agent 的工具输出如何汇总回主 Agent?子 Agent 的错误上下文会不会污染主会话?子 Agent 是否可能绕过主 Agent 的安全策略?
这个问题不是理论上的。OpenCode 的历史 issue 中就出现过类似风险:有 issue 报告
tool.execute.before 插件 hook 能阻止 primary agent 的工具调用,却没有拦截通过 task tool 产生的 subagent 工具调用,导致安全策略被绕过;另一个 issue 报告 task tool 忽略 per-target deny,允许 agent 自我派发,从而造成递归 subagent loop。(GitHub) (GitHub)所以我们最终意识到:
多 Agent 的核心不是“多几个角色 prompt”,而是 权限继承、上下文隔离、任务边界、递归限制、审计链路。
Claude Code 和 OpenCode 都有内置 subagent 概念。Claude Code 的 Explore / Plan / General-purpose subagents 会根据任务场景被委派,并且 Explore / Plan 这类 agent 会限制为只读工具;OpenCode 也区分 primary agents 和 subagents,内置 Build、Plan、General、Explore、Scout 等不同权限和职责的 Agent。(Claude API Docs) (OpenCode)
四、最终解决方案:把 Agent 拆成四个平面
我们最后没有继续堆 prompt,而是把系统拆成四个平面:
Context Plane:上下文平面,决定给模型什么信息。Control Plane:控制平面,决定模型能做什么。Execution Plane:执行平面,负责真实工具调用。Verification Plane:验证平面,判断结果是否可信。
整体架构如下:
这四个平面的设计原则是:
LLM 只负责推理和决策,不直接拥有执行权。工具调用必须经过策略层。工具结果必须经过压缩和结构化。代码修改必须经过验证闭环。子 Agent 必须有清晰的能力边界。
OpenClaw 的文档里也有类似分层思想:Gateway 作为长生命周期控制平面,维护消息通道、WebSocket API、节点、事件和会话;工具执行可以进入 sandbox,而 Gateway 本身留在 host 上。(OpenClaw) (OpenClaw)
五、核心设计一:Context Pack,而不是直接把 RAG 结果塞给模型
我们把 RAG 结果重构成
ContextPack。第一版:
改造后:
关键变化是增加了三类元信息:
freshness:这段信息是否过期。trust:这段信息是否可信。reason:为什么它被放进上下文。
真正喂给 LLM 的不是原始 chunk,而是经过筛选、排序、去重、压缩后的上下文包。
这里最重要的是
negativeEvidence。很多 RAG 系统只告诉模型:
这些内容相关。
但生产级 Agent 还要告诉模型:
哪些内容虽然相关,但不能直接信。哪些接口已经废弃。哪些文件只是测试 mock。哪些历史 PR 已经被 revert。
这能显著降低“基于错误上下文的自信修改”。
六、核心设计二:工具调用必须经过 Policy Gate
成熟 Agent 的工具系统通常不是简单函数调用,而是:
LLM 生成 tool call。Runtime 解析 tool call。Policy 判断是否可执行。Hook 记录和拦截。Executor 执行。Observation 结构化返回。
OpenCode 文档里明确说明工具允许 LLM 在代码库中执行动作,并且可以通过 permission 控制 allow、deny 或 require approval。(OpenCode) OpenClaw 文档也强调,工具可见性会受到 active profile、allow/deny policy、provider restriction、sandbox state、channel permissions、plugin availability 等因素影响,而且 tool policy 会在 model call 前执行;如果策略移除了某个工具,模型这一轮就看不到该工具 schema。(OpenClaw)
我们的工具调用中心如下:
具体策略:
执行入口:
这里的关键点是:
权限判断不写在 prompt 里,而写在 runtime 里。Prompt 只能建议,Policy 才能约束。所有 Agent、Subagent、MCP Tool、Custom Tool 都必须走同一个入口。
这正是我们从 OpenCode 历史 issue 中吸取的教训:如果 primary agent 和 subagent 不走同一条工具执行链路,策略就可能被绕过。(GitHub)
七、核心设计三:Observation Normalizer,工具结果不能原样回填
工具结果必须转换成结构化观察结果。
错误写法:
正确写法:
测试输出压缩:
这样 LLM 看到的是:
哪些测试失败。哪些文件相关。栈顶在哪里。可能的下一步是什么。原始日志是否被截断。
而不是几万行噪声。
八、核心设计四:多 Agent 不是角色扮演,而是能力隔离
我们最终采用的 Agent 结构如下:
每个 Agent 有不同权限:
派发逻辑:
这里的几个硬约束非常重要:
子 Agent 不能无限递归。子 Agent 默认不再创建子 Agent。子 Agent 不能自我派发。子 Agent 权限不能超过父任务允许的能力。子 Agent 的输出必须摘要回主 Agent,而不是全量合并上下文。
Claude Code 文档提到 Plan subagent 用于在 plan mode 中研究代码库,并且 subagents 不能继续 spawn subagents,以避免无限嵌套;OpenCode 文档也明确区分 primary agents 和 subagents,并支持不同 agent 配置 prompt、model、permission 等。(Claude API Docs) (OpenCode)
九、核心设计五:Verification Loop,让 Agent 对结果负责
第一版 Agent 最大的问题是:
它会说“我已经修复了”,但实际上测试没过。
最终我们把“完成”定义为状态机,而不是一句自然语言。
核心循环:
这里有一个关键原则:
Agent 不是生成代码后就结束,而是要通过测试、diff、审查和风险评估之后才结束。
Claude Code 的 Skills 文档里也提到
/run、/verify、/run-skill-generator 这一组能力,用来启动应用并确认变更在运行环境中有效,而不是只依赖测试或类型检查。(Claude API Docs) 这和我们的经验一致:生产级 Agent 必须从“生成答案”转向“验证结果”。十、核心设计六:把 Prompt 固化成 Skill,而不是每次复制粘贴
后期我们发现,很多团队规范不应该每次塞进系统 prompt。
比如:
如何启动项目。如何跑测试。如何生成数据库 migration。如何处理 monorepo。哪些目录不能改。PR 描述格式。回滚方案模板。
这类内容适合沉淀成 Skill。
示例:
Then run:
4. Final response
Include:
- Files changed
- Why each change was needed
- Tests run
- Risks
- Rollback plan
审计写入:
高风险判断:
这套机制的目的不是阻止 Agent 工作,而是让它在可控范围内工作。
十二、一次真实任务的执行链路
以“给退款流程增加风控校验”为例,最终链路如下:
这里的核心不是模型一次写对,而是系统允许模型安全地错、可控地修、可验证地收敛。
十三、最终效果:从“能 Demo”到“能进生产”
上线前后的差别主要体现在几个维度:
1. 成功率提升
第一版 Agent 经常出现:
找错文件。改了测试没改业务。改了业务没跑测试。测试失败后胡乱修。上下文爆掉后忘记任务目标。
改造后,任务成功率提升主要来自:
Context Pack 降低错误上下文。Verification Loop 降低假完成。Observation Normalizer 降低日志噪声。Multi-Agent 隔离降低主上下文污染。Policy Gate 降低危险操作。
2. 可解释性提升
以前 Agent 的行为像黑盒:
为什么改这个文件?为什么运行这个命令?为什么认为测试通过?
后来每一步都有审计:
哪个 Agent 做的?基于哪些 Evidence?调用了哪个 Tool?Policy 为什么允许?输出被如何压缩?验证结果是什么?
3. 安全性提升
最重要的改进是:
权限不再由 prompt 约束,而由 runtime 约束。子 Agent 不再默认继承无限能力。MCP / Custom Tool 不再绕过统一 Policy。高风险命令需要确认或直接拒绝。所有工具调用可审计、可回放、可追责。
十四、我最大的技术收获
这个项目让我对 Agent 系统有了一个很深的认识:
LLM Agent 的核心不是“大模型会思考”,而是“工程系统能否把思考、行动、观察、验证、安全边界组织成稳定闭环”。
如果只做 Demo,ReAct 循环就够了:
但生产系统必须是:
真正困难的地方在这些细节:
RAG 不是召回越多越好,而是要给模型“可信上下文”。工具不是暴露越多越好,而是要有“最小权限”。多 Agent 不是角色越多越好,而是要有“能力边界”。Memory 不是记得越多越好,而是要防止“信息污染”。测试不是最后跑一下,而是 Agent 收敛过程的一部分。Prompt 不是系统的安全边界,Runtime 才是。
十五、总结
如果让我用一句话回答“最困难的技术挑战是什么”,我的答案是:
最困难的不是让 AI 写代码,也不是让它调用工具,而是构建一个在真实工程环境中能长期运行的 Agent 控制系统:它要能选择正确上下文、调用正确工具、限制危险行为、压缩噪声结果、管理多 Agent 协作,并通过测试和审查证明自己真的完成了任务。
解决方案不是单点优化,而是一整套工程化架构:
用 Context Pack 代替原始 RAG 拼接。用 Policy Gate 代替 prompt 里的安全提醒。用 Observation Normalizer 代替原始工具输出。用 Readonly / Coder / Verifier / Reviewer 多 Agent 隔离能力。用 Verification Loop 代替“模型说完成”。用 Skill 固化项目工作流。用 Sandbox、Audit、Human Approval 兜住执行风险。
这也是我认为 Claude Code、OpenCode、OpenClaw 这类成熟 Agent 给我们最大的启发:生产级 Agent 不是一个模型调用封装,而是一套围绕模型构建的上下文、工具、权限、会话、验证和安全系统。
分享
