前言一个残酷的判断我最近在github上或是社媒上看到过很多 Agent 项目有一个规律几乎从不例外Demo 靠功能生产靠失败模式。Demo 的评判标准是它能做什么生产的评判标准是它出错的时候会怎样。这个区别不是程度上的差异而是设计出发点的差异。一个围绕功能设计的 Agent你后面每加一个生产级能力可观测、可回放、可评测、权限控制都要改一遍循环而一个围绕失败模式设计的 Agent这些能力是架构的自然推论。下面从一起真实事故说起。一、先看一起真实事故Replit 的 Agent 在一次操作中删除了客户的生产数据库并且在事后谎报了这件事。很多人看到这个新闻的第一反应是“prompt 没写好”。这个判断是错的。问题不在于它没被告诉不要删数据库而在于系统里根本没有任何机制能阻止它删。区别在这里做法结果Demo 思路在 system prompt 里写删除前请确认模型今天听话明天不听话生产思路delete操作被标记为 destructive运行时强制要求人类审批模型再怎么想删也删不掉这就是本文的全部主题机制mechanism而不是提示prompt。二、Demo Agent 与生产级 Agent 的八项对比先给一张全景对照表维度Demo Agent生产级 Agent循环隐藏的 while 循环显式、可中断、有硬预算上下文当缓冲区用塞满就截断当预算管理可出具上下文收据工具能调通就行为模型设计的 API错误信息可教学安全靠 prompt 请求运行时强制默认拒绝状态存在内存里可序列化、可恢复、可 fork非确定性接受它、“反正结果随机”收缩到最小边界并全部记录测试手动跑几次看看不调真实 LLM 就能测主循环可观测print()打日志结构化事件流作为唯一事实来源接下来逐条展开每条都给反面写法和生产写法。三、机制 1日志即本体The Log Is the Agent这是我认为最重要的一条架构决策。有一篇论文直接把这个论点写进了标题《The Log Is the Agent: Event-Sourced Reactive Graphs for Auditable, Forkable Agentic Systems》。它的核心主张是Agent 的本质是日志事件流而不是内存里的状态对象。一旦接受这个前提「可审计」「可 fork」「可回放」「可评测」就不再是需要额外开发的功能而是架构的直接推论。❌ Demo 写法defrun_agent(task:str):messages[{role:user,content:task}]whileTrue:respllm.chat(messages)ifresp.tool_calls:resultcall_tool(resp.tool_calls)print(fcalled tool:{result})# 日志 用完即弃messages.append(result)else:returnresp.content问题print出来的东西不是数据。你无法对它做聚合、归因、回放——它只是一串给人看的文本。✅ 生产级写法fromdataclassesimportdataclass,field,asdictfromtypingimportLiteralimporttime,jsondataclassclassAgentEvent:seq:int# 单调递增序号type:Literal[run_start,llm_call,tool_call,tool_result,policy_denied,budget_exceeded,stagnation_detected,run_end,]ts:float# 时间戳payload:dict# 结构化载荷tokens_in:int0tokens_out:int0cost_usd:float0.0classEventSink:事件流是 Agent 的唯一事实来源single source of truth。def__init__(self,path:str):self._fopen(path,a,encodingutf-8)self._seq0defemit(self,type_:str,payload:dict,**metrics)-AgentEvent:self._seq1evAgentEvent(seqself._seq,typetype_,tstime.time(),payloadpayload,**metrics)self._f.write(json.dumps(asdict(ev),ensure_asciiFalse)\n)self._f.flush()returnev关键点Agent 的运行时状态可以从事件流重建。这意味着——崩溃后能从事件流恢复而不是从内存快照能从任意历史事件 fork 出一条新分支能对历史事件做聚合分析成本、延迟、失败率能拿真实事件流直接生成测试用例四、机制 2硬预算 停滞检测失控循环是 Agent 最常见的生产事故没有之一。一个没有预算的 Agent 在遇到工具持续报错时会一直重试到你的账单爆炸。❌ Demo 写法whileTrue:respllm.chat(messages)# 祈祷它能自己停下来✅ 生产级写法fromdataclassesimportdataclassclassBudgetExceeded(Exception):...dataclassclassBudget:max_steps:int20max_tokens:int200_000max_wall_clock_s:float120.0max_cost_usd:float1.0defcheck(self,*,steps:int,tokens:int,elapsed:float,cost:float)-None:必须是硬限制不是建议。ifstepsself.max_steps:raiseBudgetExceeded(f步数超限:{steps}/{self.max_steps})iftokensself.max_tokens:raiseBudgetExceeded(ftoken 超限:{tokens}/{self.max_tokens})ifelapsedself.max_wall_clock_s:raiseBudgetExceeded(f耗时超限:{elapsed:.1f}s)ifcostself.max_cost_usd:raiseBudgetExceeded(f花费超限: ${cost:.4f})别漏掉停滞检测。预算只能防烧钱防止不了原地打转 20 步然后失败defis_stagnant(recent:list[Action],window:int3)-bool:连续 N 步调用同一个工具且参数完全相同 - 判定为停滞。iflen(recent)window:returnFalsefingerprints{a.fingerprint()forainrecent[-window:]}returnlen(fingerprints)1停滞检测的价值不只是省钱——它把一个沉默烧钱 5 分钟然后失败的体验变成3 秒内明确告诉你卡在哪。五、机制 3上下文是「预算」不是「缓冲区」绝大多数 Agent 项目把上下文当垃圾桶什么都往里塞塞满了就静默截断。静默截断是 bug不是优化。它会让 Agent 神秘地丢失关键信息而你完全不知道发生了。✅ 生产级做法出具「上下文收据」fromdataclassesimportdataclassdataclassclassSection:name:str# 例如 system_prompt / tool_schemas / history / retrieved_docstokens:intsource:str# 这段内容从哪来文件路径 / 事件 seq / 检索 idtruncated:boolFalsedataclassclassContextReceipt:sections:list[Section]total:intbudget:intdefexplain(self)-str:lines[f上下文预算{self.total}/{self.budget}tokens]forsinself.sections:flag ⚠️ 已截断ifs.truncatedelselines.append(f{s.name:20}{s.tokens:7}tok -{s.source}{flag})return\n.join(lines)配上输出上下文预算 48210/64000 tokens system_prompt 1204 tok - prompts/v3/system.md tool_schemas 18320 tok - 42 tools ⚠️ 已截断 history 24100 tok - 事件 seq 12..87 retrieved_docs 4586 tok - kb://policies/refund这一张表能立刻暴露大部分上下文问题。上面这个例子一眼就能看出42 个工具的 schema 占了 38% 的预算——这正对应工具渐进披露按需加载工具而不是一次性全塞进去的需求。核心认知上下文组装应该是一个显式、可测试、可观测的函数而不是散落在各处的messages.append()。六、机制 4工具的错误信息要「教模型怎么恢复」工具是给模型设计的 API不是给人用的函数。它的命名、描述、错误信息都是设计对象。而这里有一个极其普遍、又极少被修的缺陷❌ 大多数项目的工具错误raiseToolError(invalid argument)模型看到这句话只能瞎猜。于是它重试、重试、再重试——直接喂给了你的失控循环。✅ 生产级写法raiseToolError(参数 start_date 格式错误收到 2024/13/01。\n要求ISO 8601 格式YYYY-MM-DD且必须是过去的日期。\n示例2024-12-01\n请修正该参数后重试。)记住这句话错误信息本身就是一段 prompt。你花在打磨错误信息上的每一分钟都会直接转化为 Agent 的自恢复能力。另外三条工具设计原则# 1. 返回值默认截断/分页——一个能往上下文倒 10 万 token 的工具是 bugtool(max_output_tokens2000)defsearch_logs(query:str,page:int1)-str:...# 2. 写操作必须幂等或提供 dry-runtool(idempotentTrue)defcreate_ticket(title:str,idempotency_key:str)-str:...tool(dry_run_supportedTrue)defdelete_records(filter_:str,dry_run:boolTrue)-str:...# 3. 显式声明能力等级——这是机制 5 的前提CAPABILITIES{read_file:{read},write_file:{write},delete_file:{write,destructive},run_shell:{execute},}七、机制 5运行时权限默认拒绝回到开头的事故。永远不要用 prompt 实现安全。✅ 能力制权限 默认拒绝classPolicyDenied(Exception):...dataclassclassPolicy:allowed:set[str]# 例如 {read, write}auto_approve_destructive:boolFalsedefenforce(policy:Policy,tool_name:str,args:dict)-None:requiredCAPABILITIES[tool_name]# 1) 默认拒绝能力不在白名单里直接拦下ifnotrequiredpolicy.allowed:raisePolicyDenied(f工具{tool_name}需要能力{required}f当前策略仅允许{policy.allowed})# 2) 不可逆操作必须有人类审批闸门ifdestructiveinrequiredandnotpolicy.auto_approve_destructive:raiseNeedsHumanApproval(tooltool_name,argsargs,reason不可逆操作)配套要求代码执行必须在沙箱里容器 / 受限文件系统 / 无网络有审计轨迹能回答它到底做了什么——这又回到了机制 1审批队列要有超时策略不能无限等人核心原则默认安全deny by default。白名单而不是黑名单——因为你永远列不全危险操作。八、机制 6持久化与时间旅行Demo 的 Agent 状态在内存里进程一死全没了。生产级要求能力价值状态可序列化崩溃后能恢复长任务不丢进度可从任意检查点 fork调试时能从出错点分叉反复试不同策略区分持久状态与临时上下文前者是事实后者是每次重建的视图dataclassclassAgentState:必须是可序列化的纯数据——不要塞进不可序列化的对象。run_id:strevents:list[AgentEvent]cursor:intbudget_used:dictdeffork(self)-AgentState:从当前状态分叉出一条独立分支用于调试/对比。returnAgentState(run_idf{self.run_id}-fork-{uuid4().hex[:6]},eventslist(self.events),cursorself.cursor,budget_useddict(self.budget_used),)时间旅行调试是 Agent 领域目前最缺的能力之一。传统调试器有gdbAgent 却没有对应物——而这恰恰是生产环境最需要的错误往往在第 5 步显现根因在第 1 步。九、机制 7把非确定性收缩到最小边界这是架构层面最核心的一条原则。Agent 项目的根本困难就是非确定性。业余做法是接受它专业做法是把它围起来# 1) 显式 pin 住模型版本与参数MODELyour-model-2024-11-01# 不要用会漂移的 latest 别名TEMPERATURE0.0SEED42# 2) 所有模型 I/O 全部记录dataclassclassModelCall:model:strparams:dictmessages:list[dict]response:dict# 完整记录用于回放defreplay(self)-dict:returnself.response这样做的收益是决定性的你能复现 bug 了。线上出问题 → 拿到事件流 → 本地回放 → 精确定位。没有这条你的 Agent 线上出问题就只能靠猜。判断标准把你项目里非确定的部分圈出来如果它占了超过一小块或者没有被完整记录那就是架构问题。十、机制 8不调真实 LLM 就能测试主循环我拿这条当作架构正确性的终极检验如果你的架构做不到无 LLM 测试那是架构错了不是测试难写。理由很朴素如果测试一次要花钱、且结果随机你就不会测试不测试的 Agent 项目一定会退化。而且这条能倒逼架构——它会强迫你把模型调用抽象成一个可替换的接缝fromtypingimportProtocolclassModelClient(Protocol):defchat(self,messages:list[dict],**kw)-dict:...classFakeModel:按剧本返回完全确定零成本。def__init__(self,script:list[dict]):self.script,self.iscript,0defchat(self,messages,**kw)-dict:respself.script[self.i]self.i1returnresp# 现在可以廉价地测试那些真正重要的机制deftest_agent_stops_on_budget():agentAgent(modelFakeModel([{tool_calls:[{name:read_file,args:{path:a}}]}]*100),budgetBudget(max_steps3),)withpytest.raises(BudgetExceeded):agent.run(随便什么任务)deftest_destructive_tool_needs_approval():agentAgent(modelFakeModel([{tool_calls:[{name:delete_file,args:{path:/db}}]}]),policyPolicy(allowed{read,write}))withpytest.raises(NeedsHumanApproval):agent.run(清理一下)这两个测试跑起来只花毫秒、不花一分钱却覆盖了本文最重要的两条机制。这就是架构正确带来的复利。结语回到最开始那句话Demo 靠功能生产靠失败模式。优秀的 Agent 项目不是功能更多的项目而是**“出错方式更可控”**的项目。它们的设计出发点是机制 提示—— 能用运行时保证的不要用 prompt 请求非确定性收缩到最小边界并全部记录—— 这是可复现、可测试的前提默认安全—— 白名单不是黑名单日志即本体—— 事件流是事实来源内存状态不是为失败设计不为演示设计必须能在无 LLM 的情况下测试最后补一句题外话这六条同时也是面试里最常被追问的点。能写出炫酷 Demo 的人很多能把这六条答清楚的人很少——而后者的稀缺性才是真正的护城河。参考资料The Log Is the Agent: Event-Sourced Reactive Graphs for Auditable, Forkable Agentic SystemsAnthropic,Agent Harness Design: 3 Patterns for Harnessing Claude’s Intelligence— https://claude.com/blog/harnessing-claudes-intelligenceAnthropic,The anatomy of effective commerce agents— https://claude.com/blog/the-anatomy-of-effective-commerce-agents12-Factor AgentshumanlayerInngest,Building Durable AI Agents: A Guide to Context Engineering— https://inngest.vercel.app/blog/building-durable-agentsTemporal,LangGraph Plugin adds Durable Execution— https://temporal.io/blog/temporal-langgraph-plugin-durable-executionAgent-Governed Lossless Context Folding — https://zenodo.org/records/21856874/files/pi-fold-context-folding.pdfOpenTelemetry GenAI Semantic ConventionsReplit Agent 数据库删除事故复盘 — https://safeguard.sh/resources/blog/replit-agent-database-deletion-vibe-coding-2025