
我做过一个判断失误的方案在 Agent 项目里让模型直接调用财务对账工具。上线第二周一个日期参数被解析成超大范围Agent 在十五分钟内循环调用了四千多次查询接口把下游服务的配额直接打满。事后我拉复盘会问了一个核心问题“允许它直接调这个工具的决策是谁在什么场景下做的为什么不是走审批队列”会议室安静了五秒钟。有人记得周会上聊过有人翻出来一条聊天记录但没有任何一份文档能回答。这个场景我后来在好几个团队都遇到过——Agent 的自主执行能力越强这种“决策失忆”带来的代价就越大。于是在接下来一个月里我系统性读了两类材料Anthropic 关于 Agent 自主执行的工程描述以及 AWS 工程团队在架构决策记录ADR上的实践总结。前者告诉我模型在什么条件下才应该自己决定下一步后者告诉我怎么把这些决定沉淀成别人三个月后还能快速检索到的文件。这篇文章就把我合并阅读的收获和之后的落地经验完整写出来。1. 同一起事故的两种视角自主执行为什么离不开决策记录1.1 事故是怎么发生的那次事故的背景并不复杂。我的 Agent 需要每天处理前一天的订单对账核心操作是调用内部财务系统的“按区间查询流水”接口然后根据返回结果生成差异报告。第一次灰度测试很顺利没有发现问题。第二周某天模型把日期参数从“2025-03-11”解析成了“2025-03-01 到 2025-03-11”接口返回的数据量比预期大了一百倍。后续的时间线大概是这样的09:12 Agent 开始处理前一天对账任务09:13 首次查询返回超大结果集进程内存明显上升09:14 Agent 判断“数据量过大需要按天拆分”开始逐日查询09:16 查询次数开始指数级增长因为 Agent 对“已完成”的判断在极端数据下始终不满足09:18 下游财务系统触发配额告警09:21 人工杀停任务但已经产生四千多次额外调用09:47 配额恢复账单显示该时段费用是正常情况的 32 倍所有人第一条反应都是“模型不听话”。但再往下问一层就不是这样了。谁决定让 Agent 直接访问这个查询接口没有人能说得清。当时讨论过的备选方案比如“生成 SQL 让数据分析师审核后执行”“先拉取日汇总再决定是否细查”“给单次任务设置调用上限”散落在不同人的聊天记录和会议笔记里没有一份落地的决策文档。1.2 代码评审拦不住这种问题我在事后反思了一个问题这种隐患能不能靠代码评审拦下来答案是不能完全拦住。代码评审看的是“当前代码做了什么”而 Agent 行为是模型在运行时根据工具返回结果动态生成的。工具 Schema 里只写了“按区间查询流水”看起来人畜无害但模型在特定输入下会做出什么判断只通过静态代码评审根本推演不出来。那次事故真正的问题不是“模型把日期格式搞错了”而是团队从第一天起就没有把“允许 Agent 自主执行到什么程度”这个决策当成一等公民来对待。它既没有被评审也没有被记录所有约束都散落在 prompt 的一句“请谨慎调用”里。后来我形成的习惯是Agent 项目里任何和“自主程度”相关的变化都必须对应一份记录。记录里至少要写清楚管还是不管、管到哪一步、为什么这样管。这份记录就是工程决策记录ADR。你说它是文档也好说它是流程也罢没有它Agent 越聪明系统越不可解释。1.3 为什么把 Anthropic 和 AWS 放在一起读单独看 Anthropic 的材料你学到的是“Agent 什么时候该自主行动”的行为设计单独看 AWS 的 ADR 实践你学到的是“怎么把技术决策文档化”的工程方法。但这两者其实是咬合的。Anthropic 给的是行为层的操作手册workflow 和 Agent 怎么区分、工具怎么设计、模型怎么在循环中做决策。AWS 给的是决策层的容器每次边界变化、每次工具授权、每次终止条件调整都用一种轻量、可检索、能审阅的方式记录下来。行为层让你把 Agent 做出来决策层让你在三个月后还敢改它。2. Anthropic 教会我的第一件事区分工作流与自主执行2.1 workflow 是固定线路Agent 是自由行导游Anthropic 的材料里最常被引用也最容易被误解的概念就是 workflow 和 Agent 的边界。它的原意很简单如果任务的执行路径是预先编排好的只是某些参数需要动态填入这是 workflow如果执行路径本身不能预先确定需要模型根据中间结果不断调整“下一步做什么”这才是 Agent。我用一个生活化的类比来记这句话workflow 是固定线路的旅行团每天去哪、吃什么、住哪里都是定死的Agent 是一个只给了预算上限和必去景点的自由行导游它会根据当天天气、路况、用户体力动态调整路线。这个区分在工程上的意义非常大。我见过很多团队把本来可以写成五个步骤 workflow 的事情硬做成了“万能 Agent”结果模型在一个本来不需要探索的领域里东试西试既慢又贵还容易出错。反过来也有团队把需要 Agent 探索的开放任务硬拆成固定工作流一旦遇到列表里没有的情况就彻底卡死。下面是我自己经常拿来对照的表格判断维度适合 workflow适合 Agent步骤数量固定通常少于十个不固定取决于中间结果分支逻辑可枚举有限几种不可枚举需要动态判断工具返回结果结构稳定模式已知结构多变需要模型理解语义失败恢复重试同一个步骤即可可能需要换一种策略典型例子数据清洗流水线、定时报表复杂故障排查、自主研究、多源信息整合如果你拿这张表对照之后仍然拿不准我的经验是先用 workflow不要一上来就上 Agent。Anthropic 的原话里也有一条类似的建议只给解决问题所需的最简单方案。很多团队失败不是因为 Agent 能力不够而是用了比问题更复杂的方案。2.2 自主执行需要三个基本要素在 Agent 模式里模型的角色从“语言生成器”变成了“任务决策者”。要让它能真正执行任务至少需要三个东西。第一是工具访问能力。没有工具模型只能输出文字建议不能改变任何外部状态。这里说的工具不只是函数调用也包括访问数据库、调用内部 API、读写文件、控制浏览器等等。工具描述写得越清楚模型越容易在正确的时候使用它。我见过很多工具 Schema 写得极其敷衍导致模型要么不用要么乱用。第二是反馈循环。Agent 不是一次 prompt 生成一个结果就结束而是在多次迭代里“观察到结果 → 判断是否成功 → 如果不是再决定下一步”。这个循环既可以是模型自己调用接口来获取反馈也可以由外部系统把结果注入上下文。没有反馈循环Agent 只是在单轮里做一次预测谈不上自主执行。第三是终止条件。这一点最容易被忽视。人类在做事的时候“什么时候停止”和“怎么做”同样重要模型也一样。一个没有终止条件的 Agent在一个达不到目标的场景里会无限循环或者在成功之后继续画蛇添足。我后来所有 Agent 项目里至少都会设置三样东西最大循环步数、单任务预算上限、达到目标或确认失败时立即停下的判定逻辑。2.3 安全边界三件套终止条件、权限最小化、人工审批点Anthropic 的材料里对自主执行的边界反复强调我在落地时把它整理成了安全边界三件套。第一件是终止条件上面已经提到。这里的细节是不要只设一个“最大步数”就完事还要根据工具类型设置差异化限制。比如只读查询接口可以允许的次数多一些写操作接口的次数要严格控制每一次写操作之间还要设置冷却时间。第二件是权限最小化。模型能调用的所有工具在定义阶段就要裁剪到“完成当前任务所必需的最小集合”。不要为了省事把所有公司内部 API 都接入 Agent接入了就等于把权限交给了一个概率模型。我个人的做法是Agent 默认只能访问只读接口任何写操作必须经过一个独立的网关代理通过网关再去决定要不要人工审批。第三件是人工审批点。不是每一次调用都需要人看但高影响动作必须设置暂停点。比如“发送对外邮件”“删除数据”“推送配置到生产环境”“转账超过一定金额”这些都应该进入审批队列模型负责生成请求人工负责批准或拒绝。设计审批点时不要放在循环内部太频繁的位置否则人工会陷入大量低价值请求里应该把审批点集中在真正需要判断的操作类型上。3. AWS 的 ADR 实践把工程决策从人脑搬进仓库3.1 ADR 是什么三句话说明白ADR 全称是 Architecture Decision Record最初由 Michael Nygard 提出后来被大量工程团队采用AWS 的工程实践里也在持续推广这套思路。它本质上是一种短小的文档用来记录一次架构决策的上下文、结论和影响。每份 ADR 应该回答三个问题当时面临什么问题我们决定怎么做这个决定带来了什么正面和负面影响它不像需求文档那样描述“系统应该有什么功能”而是记录“这个系统为什么长成现在这个样子”。一句话概括需求文档写给将来要写代码的人ADR 写给将来要改架构的人。Agent 项目里这个“将来要改架构的人”很可能就是三个月后的你自己。我第一次接触这套方法论时以为它不过是另一种文档形式。真正用起来才发现它解决的痛点非常具体团队会议里讨论得热火朝天最后定的方案是什么、为什么放弃另一个方案如果不写下来一周之后就会开始失真。两个人就会有两个版本的记忆而 ADR 是唯一能回到“当时事实”的地方。3.2 为什么 Agent 项目比普通项目更需要 ADR普通后端项目的代码是确定性的同样的输入永远走同样的分支代码评审能够把大部分设计缺陷挡下来。Agent 项目完全不同模型的行为一部分由参数和 prompt 决定一部分由运行时工具返回的结果决定还有一部分是概率性涌现。这意味着你无法通过读代码来准确推断“Agent 在某个条件下会做什么”。正因为行为不可静态推断设计意图就显得更加重要。比如有一条决策是“Agent 不能直接访问生产数据库必须通过一个只返回聚合结果的中间层”。如果这条决策没有被记录后续接手的人看到中间层第一反应可能是“这么简单的查询为什么要绕一层”然后顺手把中间层删掉直接让 Agent 连数据库事故就来了。ADR 把这类决策的半衰期从几天延长到几年。AWS 的工程实践里还有一个很好的习惯ADR 和代码一起提交评审。每条 ADR 都跟着一个 Pull Request评审人不仅要看代码还要看决策本身是否合理。对于 Agent 项目来说这相当于把“意图评审”前置到了“行为发生”之前。代码评审是在检查“写出来的代码对不对”ADR 评审是在检查“让 Agent 做这件事本身合不合适”后者的层级更高。3.3 ADR 的标准结构和生命周期一份成熟团队的 ADR 通常包括这么几个部分标题包含编号和简短描述例如ADR-0012Agent 写操作接入审批网关状态Proposed / Accepted / Deprecated / Superseded背景为什么会有这个决策当前痛点是什么决策我们最终选了什么方案后果正面影响、负面影响以及后续要处理的事项生命周期也不复杂。刚开始写的时候是 Proposed评审通过后变成 Accepted如果后来发现某条决策不再适用不要直接删除而是在新 ADR 里把它标记为 Superseded并指向新的 ADR。这样整个架构脉络是一条可以追溯的链而不是一堆孤立的文档。文件存放我建议和代码放在同一个仓库里通常建一个adr/目录用NNNN-title.md的格式命名。这样每次代码变更都会触发对相关 ADR 的重新关注不会出现“文档在知识库里吃灰”的情况。3.4 一个简单的 ADR 长什么样拿一个最常见的 Agent 场景举例要不要用 Step Functions 来编排多步骤 Agent 任务# ADR-0007采用 Step Functions 编排多步骤 Agent 任务 ## 状态 已采纳 ## 背景 Agent 需要对账任务进行多步骤处理拉取流水、分析差异、生成报告、发送通知。 最初实现是 Python 代码里的 while 循环状态都保存在进程内存中。 线上出现几次问题后团队质疑这种方式的可靠性和可观测性。 ## 决策 采用 AWS Step Functions 作为编排层每个步骤对应一个 Lambda 函数。 Agent 不再自持循环状态而是由状态机驱动。 ## 后果 正面状态持久化失败后可以从最近一个稳定步骤重试每一步都有执行记录 IAM 权限可以精细到“某个步骤只能访问某些资源”。 负面引入额外成本状态机有执行历史长度限制团队需要学习 ASL 语法。 后续还需要设计好超时和重试策略避免状态机任务卡死。这份 ADR 不长但它把最重要的信息保存下来了。三个月后如果有人问“为什么这里是 Step Functions 而不是自建循环”不需要把当时参与讨论的人找齐只需要打开这个文件。4. 合流之后我整理的一份“Agent 自主执行决策记录”模板4.1 七个字段对应七个必须回答的问题把 Anthropic 的自主执行思路和 AWS 的 ADR 框架放在一起之后我整理了一份专门用于 Agent 项目的决策记录模板。和通用 ADR 相比它更关注“行为边界”和“风险约束”。字段要回答的问题填写要点决策编号与标题这条决策在改什么一句话说清楚比如“限制 Agent 单日调用预算”状态决策处在什么阶段Proposed / Accepted / Superseded背景为什么需要这个决策写出当前 Agent 行为中存在的具体风险或痛点约束有什么不可改变的限制成本上限、合规要求、响应延迟、可用性目标候选方案我们考虑过哪些方案至少写两个并说明每个方案的优缺点决策最终选择了什么必须具体到可执行比如“写操作走网关50% 人工抽检”后果与验证决策落地后怎么确认有效列出可量化指标比如调用次数、失败率、审批延迟第七个字段是我后来加的。很多决策记录写完就结束了没有人去验证这个决策是否真的解决了问题。Agent 场景下模型行为和工程决策之间的因果关系往往不直观如果不设定验证指标你根本不知道当初的决定对不对。4.2 完整示例给高影响工具调用加审批网关下面是我一份在实际项目中用过的 ADR脱敏后放出来作为模板参考。# ADR-0021为高影响工具调用引入审批网关 ## 状态 已采纳 ## 背景 Agent 可调用预算系统的写接口生成付款申请。虽然 prompt 里声明了 “未经授权不得提交付款”但实际运行中模型仍可能生成提交操作。 一旦误提交将直接影响真实账务数据。 ## 约束 - 端到端延迟必须控制在 30 秒以内 - 每一次写操作必须有完整审计记录 - 不能引入新的内网隔离架构不能依赖特殊网络手段 - 人工审批流程必须简单避免增加运维团队负担 ## 候选方案 方案一在 prompt 中严格禁止不增加额外控制。 被否不可验证模型在复杂上下文里可能遗忘或误解规则。 方案二在 Agent 代码层做关键词拦截。 被否只能拦截固定的表达形式无法应对模型自然语言变体。 方案三所有高影响写操作统一走审批网关Agent 提交请求后进入 pending 状态 审批人通过控制台批准或拒绝超时默认拒绝。 被采用。 ## 决策 采用方案三新增高影响操作审批网关。Agent 的写工具调用统一通过 POST /gateway/request 提交不透出目标系统地址。 未在 20 分钟内审批的请求默认拒绝并回传 Agent 一条 friendly 失败消息。 同时为 Agent 增加审批中状态轮询逻辑避免在等待时反复重试同一请求。 ## 后果与验证 正面写操作全部可审计超时自动拒绝避免灰产审批记录可反查。 负面端到端延迟增加数秒需要协调审批人排班Agent 开发量额外增加。 验证指标灰度两周内审批事件平均延迟约 8 秒无一条超时被拒 误提交次数从每月 3 次降为 0。这个 ADR 写完之后团队评审时最重要的讨论点不是“该不该审批”而是“20 分钟超时是否太长”。后来我们把超时改成 10 分钟这个变更也走了一次 ADR 更新。你会发现好的 ADR 会引导团队把注意力放到真正需要讨论的边界参数上而不是在整体方案层面反复拉扯。4.3 模板的使用节奏初始、更新、取代有了模板之后更重要的是使用节奏。我的建议是三条。第一Agent 上线前至少有一份 ADR描述“当前允许 Agent 自主执行哪些动作以及为什么不许它做其他动作”。这份初始记录不需要面面俱到但必须有候选方案对比不能只写结论。第二每当工具权限、终止条件、模型策略、审批流程发生任何变化都要走一次 ADR 更新。这里说的更新不是修改旧文档而是新增一条 ADR把旧的标记为 Superseded。这样做看起来会多出不少文件但它能保留完整的决策演化轨迹。半年后你想知道“为什么这个 Agent 的写操作要先经过校验再发邮件”顺着 ADR 链就能找到答案。第三ADR 的评审节奏和代码评审同步。每一条新的 ADR 都要作为 Pull Request 的一部分提交至少经过一个熟悉 Agent 整体架构的人确认。如果团队人数少哪怕只是口头上对齐一遍再合入也比没人评审强得多。5. 落地一年后我踩过的四个坑和现在的处理方式5.1 坑一把 ADR 写成了需求清单我最早写的几份 ADR现在看来就是穿上 ADR 衣服的需求文档。“Agent 需要支持多轮对话”“Agent 需要能调用 CRM 系统”这种话没有任何决策含量。需求文档回答的是“what”ADR 回答的是“decision”。现在的判断标准很简单如果一句话去掉之后后续实现不会发生任何不同那它就不该出现在 ADR 里。真正值得写的是“我们决定用 XX 而不是 YY因为 ZZ”。比如“Agent 有权限调用 CRM 的只读接口但写操作必须经过网关”就是一个合格的决策因为它明确排除了另一个方案。5.2 坑二只写结论不写被否决的候选方案这个坑比第一个更隐蔽。ADR 里有背景、有决策、有后果看起来完整但“候选方案”一栏空空如也。后来有人问“为什么当初不用向量检索而直接用关键词搜索”没人能答上来才知道不写候选方案会毁掉 ADR 一半的价值。被否决的方案是决策的锚点。没有锚点后人看到现有方案可能会产生“为什么要这么绕”的疑问甚至在毫无风险意识的情况下重新引入被否决的方案。我现在要求每一份 ADR 都必须至少写两个候选方案并给出淘汰原因。哪怕原因是“开发时间不足先上轻量方案”也比不写强因为这个原因本身就是未来推进的线索。5.3 坑三把安全边界全压在 prompt 里这是我踩得最重的一个坑也是事故的根源之一。早期我天真地以为只要在 prompt 里写清楚“你不可以删除任何数据”“你不可以调用这个接口”模型就会遵守。实际上 prompt 是最不稳定的约束层它可能被用户输入中的注入内容影响也可能在长对话里被稀释还可能在模型版本升级后突然改变行为。正确的做法是能落到工具 Schema 就落到工具 Schema能落到 IAM 权限就落到 IAM 权限能落到网关拦截就落到网关拦截。prompt 只是最后一道提醒不是第一道防线。这个认知写在 ADR 里之后团队再也不会出现“prompt 里没有禁止所以 Agent 可以做”的错觉了。另外提醒一句工具描述本身也会成为约束的一部分。给模型看的工具说明里不要写出实际代码里没有实现的权限比如描述写“可读取所有项目数据”但后面又靠 prompt 说“只允许读当前项目”模型有很大概率会尝试越界。与其这样不如直接把工具 Schema 里的数据范围限定死。5.4 坑四用 git 改历史来“修正”决策有一种情况很常见ADR 写完之后没过多久团队发现当时的决策方向不对于是有人直接把原来的 ADR 文件改了改成“结论正确”的样子。这种操作短期内看起来很干净实际上破坏了一个非常重要的资产——决策演化路径。保留一条“我们曾经做过一个错误决策后来因为什么原因改掉”的记录比维护一个“永远正确”的文档有价值得多。错误决策和后来修正的过程恰恰是团队认知升级的证明。新的决策应该体现在新的 ADR 里旧的 ADR 标记为 Superseded 并指向新的编号这样任何人都能沿着这条链看到思想的演变。我现在对团队的要求是ADR 文件一旦合入就视为不可变历史。要修正就新建一条 ADR不要修改旧文件。这个规则比想象中更好执行因为 git 本身支持这种模式配合 CI 要求文件命名递增很难绕过去。这套组合打法的实际效果已经体现出来了。现在我的团队接到 Agent 异常告警时第一步不是翻日志而是看最近三个月的 ADR 变更新的工具权限申请必须在 ADR 审批通过后才会被加入 allowlist。维护成本大概是每周花半小时更新决策记录但它省下来的排查时间远远不止半小时。如果你手头的 Agent 项目还没有任何决策记录今天就可以把第一个 ADR 补上记录一个最重要的决策“当前允许该 Agent 自主执行哪些动作以及为什么不许它做其他动作。”这个片段越早写下来未来的你越会感谢现在的你。