12-factor-agents是一份观点 教材 可运行样例的合集。和OpenHarness对比两者都反对“模型想做什么就直接执行什么”的粗糙 Agent loop但前者更像一套判断 Agent 是否可靠的工程原则后者更像一个把这些原则扩展到 Coding Agent 场景的完整运行时工程样板。如果把 OpenHarness 看成一座功能完整的建筑包含 CLI/TUI、工具注册、权限、MCP、压缩、记忆、插件、多 Agent、沙箱和 UI那么 12-Factor Agents 更像检查这座建筑承重边界的尺子。它关心的不是“模块够不够多”而是 prompt 是否可控、上下文是否由应用构造、工具调用是否只是结构化输出、状态是否可恢复、选择工具和执行工具之间能否暂停。换句话说OpenHarness 回答“一个 Coding Agent Runtime 应该有哪些模块”12-Factor Agents 回答“无论模块多少哪些控制权不能交给黑盒”。它的作者 DexHumanLayer 创始人想回答一个很具体的问题怎样才能构建出可靠到能交付给生产客户的 LLM 应用这个问题背后是他反复见到的一个失败模式。很多团队想给产品加上 Agent 能力于是抓一个流行框架很快做到 70–80% 的体验然后卡住——因为剩下的 20% 才是决定能不能给真实用户用的部分而这部分往往要求你回过头去逆向框架的 prompt、控制流和状态管理最后不得不推倒重来README.md:153-161。作者由此得出一个和主流叙事相反的判断真正跑在生产里、面向客户的好 Agent大部分其实是普通软件只在少数关键节点插入 LLM 决策而不是给个目标、塞一袋工具、循环到完成那种模式README.md:39-45。所以这个项目的名字致敬了经典的 12-Factor App。当年那份文档讲的是云原生应用该守哪些工程约定这份文档讲的是构建可靠 LLM Agent 该守哪些工程约定。这里的12 个因素不是 12 个参数也不是必须全部打勾的清单而是12 条设计原则——你可以只挑其中几条塞进你现有的产品里。仓库里到底有什么打开仓库你会看到三样东西它们分别对应讲道理、给样例、教怎么搭理念内容content/目录下的 12 篇 factor 文章加一篇附录是这个项目的主体和灵魂。可运行样例packages/create-12-factor-agent/template/一个用 TypeScript BAML Express HumanLayer 写的小 Agent能真的跑起来做计算、找人审批。教学构建系统workshops/和packages/walkthroughgen/把上面那个样例拆成一步步的 workshop并用一个小工具从 YAML 生成教程文档和分章节代码。需要说清楚的是这个样例会真的执行任务但任务本身是教学性的——一个计算器加上人类审批流程不是通用的生产 Agent。它的价值在于用最小的 domain 把机制讲透而不是覆盖真实业务的复杂度。12 个因素一句话一条先把 12 条快速过一遍建立全景后面再讲它们如何在代码里落地自然语言转工具调用——把帮我退 750 美元翻译成结构化对象{ intent: refund, amount: 750 }再交给确定性代码处理。掌控你的 prompt——prompt 是一等公民代码要能测试、能版本化、能随时替换别塞进框架黑盒。掌控你的上下文窗口——喂给模型什么、按什么格式喂由你决定不被 SDK 的标准 message 历史绑死。工具只是结构化输出——调用工具本质只是模型吐了段 JSON是否执行、何时执行、怎么执行全由你的代码说了算。统一执行状态和业务状态——尽量用一条事件历史同时表达发生了什么和进行到哪一步别维护两套状态。用简单 API 启动/暂停/恢复——Agent 是程序就该能被启动、能暂停等待、能靠外部信号恢复。用工具调用联系人类——找人确认也是一种结构化事件request_approval、done_for_now和普通工具走同一条事件流。掌控你的控制流——哪些工具直接执行、哪些要中断等审批由你的代码决定而不是框架的循环。把错误压缩进上下文——出错时不要把原始堆栈无限塞回上下文要压缩成模型能读懂、能恢复的形式。小而专注的 Agent——别造无所不能的巨型 Agent上下文一膨胀模型就容易走偏。从任何地方触发——Agent 不只从聊天框启动也可以来自 Slack、邮件、cron、webhook、报警。让 Agent 成为无状态 reducer——理想形态是输入事件历史 → 输出下一步状态外置可恢复、可重放。这 12 条并不是彼此独立的它们其实收敛到同一条主线。一条主线串起所有因素如果只记一句话那就是把 LLM 关进一个很窄的职责里其余的控制权全部留在你自己的代码手上。顺着这条主线12 个因素会自然分成几个层次LLM 只负责一件事——看着到目前为止发生了什么输出下一步该干什么的结构化意图factor 1、2、3、4。执行、暂停、恢复、联系人类全是确定性代码的活儿factor 6、7、8、9。状态用一条事件线程统一承载既是上下文也是业务历史也是恢复锚点factor 5、12。整个 Agent 保持小而专注靠外层确定性外壳控制风险边界factor 10、11。这条主线也解释了作者的产品立场不是要你换一个更强的框架而是要你想清楚——哪些控制权无论如何都不能交出去。样例代码如何印证这条主线光讲原则容易空。这个项目的高明之处在于它把原则压进了一个几十行的小 Agent 里让你能一眼看到原则长什么样。LLM 只决定不执行。样例里那个被 LLM 调用的函数DetermineNextStep返回值是一个联合类型要么是计算工具加减乘除要么是联系人类的工具要澄清、要审批、报告完成template/baml_src/agent.baml:26-32。模型做的全部事情就是根据当前线程输出其中一种意图。真正的执行权在 TypeScript 的agentLoop里它把模型的输出记为一次tool_call然后用 switch 分流——加减乘可以当场算但divide被当成危险操作和所有联系人类的意图都会直接return出去交给外层处理template/src/agent.ts:89-113。这正是 factor 4 和 factor 8 的核心模型选了某个工具不等于代码必须立刻、以同样方式执行它。能在选择工具和真正调用之间插一刀正是审批、回滚、人类介入得以存在的前提。一条事件线程就是全部状态。样例里的Thread类只有一个核心字段events数组template/src/agent.ts:8-14。它有个serializeForLLM()方法把所有事件拼成一段 XML 风格的文本喂给模型——这就是 factor 3 说的LLM 的输入本质就是’发生了什么 下一步是什么’“。存储层也没有额外维护当前第几步、在等什么、重试了几次这类编排状态而是直接把整条线程序列化成 JSON 落盘顺便再写一份人类可读的 txttemplate/src/state.ts:15-31。这就是 factor 5 和 factor 12状态是单一事实源天然可序列化、可恢复、可分叉、可观察。你想知道 Agent 当时看到了什么、选了什么、人怎么回的”打开那个.threads/id.txt就行。内循环负责推进外循环负责等待。agentLoop是内循环——不停问模型下一步能同步做的当场做做不了的就退出来。外循环则有两个化身命令行版本创建线程、跑内循环、用命令行提示接收人类的审批或补充再把回复追加回线程template/src/cli.ts:10-36HTTP 版本则从 HumanLayer 的 webhook 事件里恢复线程处理审批结果或人类回复再继续跑内循环template/src/server.ts:69-110。这两者一起演示了 factor 6 和 factor 7Agent 能用简单 API 暂停、靠 webhook 恢复而联系人类本身就是事件流里的一环靠把thread_id存进外部交互状态来实现跨请求的接力。跟着样例跑一遍理解这套东西最快的方式是想象几个真实场景做一道多步计算。你在命令行敲npx tsx src/index.ts can you multiply 3 and 4, then divide by 2 and add 12。整句话被当作一条user_input放进线程模型开始一轮轮地选下一步乘法当场算、结果追加回上下文、再问模型……直到它输出完成并给出最终答案。除法这一步会触发审批见下。全程线程被不断写进.threads/留下完整轨迹。除法需要人类点头。模型可以选择divide但样例故意把它当成危险操作内循环一选中它就退出绝不先算。外循环转而请求人类审批——命令行里按回车批准输入任何文字则视为拒绝并把反馈写回线程让模型带着反馈重试template/src/agent.ts:105-107,template/src/cli.ts:69-80。这里的产品意义很关键高风险操作不是靠相信模型不会算错来兜底而是靠控制流边界来兜底。用 webhook 恢复一个长流程。外部系统创建会话HumanLayer 回调进来server 建线程、跑内循环如果最后一步需要人类澄清或完成就创建一个 human contact如果是危险操作就创建一个 function call并把thread_id塞进 HumanLayer 的状态里template/src/server.ts:112-149。等人类在邮件或 Slack 里回复后下一个 webhook 带着thread_id回来server 找回原线程、追加回复、继续推进。整个过程跨越了多个 HTTP 请求但对 Agent 来说只是同一条线程被恢复了。维护教程本身。这是个元用例维护者把教学步骤写成 YAML每步含说明、文件复制、命令、预期输出walkthroughgen读了之后生成 markdown、逐章节的工作目录和最终项目packages/walkthroughgen/src/index.ts:75-145。它不属于 Agent 运行时而是保证教程文字、示例代码、最终产物三者不漂移的基础设施。谁会用它为什么值得读它的目标读者是那些正在把已有 SaaS 或内部系统变得更 agentic的技术创始人、AI 工程师和产品团队——正是 README 里描述的、走到 80% 就卡住的那群人。它给这群人的价值不是又一个框架而是一个和主流相反、却更贴近生产经验的判断框架不要从选哪个框架开始而要从哪些控制权不能交出去开始。而且这些原则不绑定任何技术栈——文档里 BAML 只是举例factor 2 明确说你可以用任意 prompt 工具甚至手写模板content/factor-02-own-your-prompts.md:69-75。一句话概括它的定位一套把 Agent 从黑盒框架拉回生产软件工程的原则和模板。它的代价与边界作为一份诚实的分析也要说清楚它没做什么、哪里不能照搬样例不是生产就绪的。它缺鉴权、并发锁、幂等、webhook 签名校验、持久化数据库、可观测性和严格的错误恢复。它是用来讲机制的不是用来上线的。示例 domain 太窄。计算器足以说明选择与执行分离但真实业务工具的权限、回滚、审计和数据依赖复杂度它一概没碰。有教学草稿的痕迹。比如server.ts里若干 async 的存储调用缺了awaittemplate/src/server.ts:83,template/src/server.ts:116-119BAML 里定义了ProcessRefund意图但 TypeScript 侧的handleNextStep并没有覆盖它template/baml_src/agent.baml:19-32,template/src/agent.ts:49-87。当教材读没问题当代码抄要小心。文件命名有历史包袱。content/下既有factor-2-own-your-prompts.md又有factor-02-own-your-prompts.md前者只是指向后者的迁移指针。阅读时以 README 里 01–12 的链接为准。如果从零重建该怎么推进顺着主线重建这个样例的合理顺序是这样的——每一步都对应一个要解决的压力而不只是实现某个模块先让模型能输出结构化的下一步。压力是自然语言不能直接驱动副作用。此时只读用户输入、不写任何外部状态不变量是模型输出必须落进 schema 联合类型。验收标准输入add 3 and 4能得到{ intent: add, a: 3, b: 4 }。加一个确定性的工具分发器。压力是模型负责选但执行必须由代码掌控。它负责追加tool_call和tool_response不变量是未知意图要 fail-fast高风险意图不能自动执行。验收标准多步计算能在线程里留下完整轨迹。把线程做成唯一的状态锚点。压力是多轮、暂停、恢复都需要可序列化的历史。events 是主状态thread id 是外部引用不变量是执行状态尽量从 events 推导不另建隐式状态机。验收标准存盘再加载serializeForLLM()的结果一致。拆开内循环和外循环。压力是人类审批和长任务不能阻塞内存里的循环。外部的联系/审批状态要携带 thread id不变量是选择工具和调用工具之间必须能暂停。验收标准除法先生成审批请求批准后才执行。接上 HTTP / webhook 触发。压力是Agent 不该只能在命令行里活着。从 webhook body 恢复线程、追加回复、继续内循环不变量是所有恢复路径都要校验线程存在和事件类型合法。验收标准会话创建 → 审批完成 → 报告完成能跨请求走完。最后才做生产化补齐。压力是教学 demo 扛不住真实客户数据。把文件存储换成数据库加上幂等键、锁、审计、trace、metrics不变量是人类审批、权限校验、业务副作用和 LLM trace 要分层记录。验收标准重复的 webhook 不会重复执行工具并发回复不会互相覆盖线程高风险工具都留有审批证据。它给如何分析一个 Agent带来的启发读完这个项目最大的收获或许不是那 12 条本身而是它提醒我们分析一个 Agent不能只数有没有工具、有没有 memory、有没有 planner。真正决定它能不能上生产的是控制权的归属prompt 是否可测试、可版本化、可替换上下文是应用主动构造的还是被 SDK 的 message 历史绑死选择工具和调用工具能不能分离状态能否从业务事件推导而不是藏在框架运行时里人类是不是一种结构化事件而不是聊天 UI 的特例Agent 是否足够小能靠确定性外壳把风险圈住。换句话说判断一个 Agent 系统是否可靠可以看这样一组边界是否清晰LLM 决策边界 上下文构造权 工具执行权 状态归属 暂停/恢复粒度 人类 / 外部触发的接入方式 小 Agent 的范围控制如果一个系统在这些边界上不可见、不可插入、不可恢复那么无论它的框架抽象多漂亮都很难真正达到生产级可靠性。这就是 12-Factor Agents 想让你记住的判断框架。
12-Factor Agents:构建Agent应用的12个因素
12-factor-agents是一份观点 教材 可运行样例的合集。和OpenHarness对比两者都反对“模型想做什么就直接执行什么”的粗糙 Agent loop但前者更像一套判断 Agent 是否可靠的工程原则后者更像一个把这些原则扩展到 Coding Agent 场景的完整运行时工程样板。如果把 OpenHarness 看成一座功能完整的建筑包含 CLI/TUI、工具注册、权限、MCP、压缩、记忆、插件、多 Agent、沙箱和 UI那么 12-Factor Agents 更像检查这座建筑承重边界的尺子。它关心的不是“模块够不够多”而是 prompt 是否可控、上下文是否由应用构造、工具调用是否只是结构化输出、状态是否可恢复、选择工具和执行工具之间能否暂停。换句话说OpenHarness 回答“一个 Coding Agent Runtime 应该有哪些模块”12-Factor Agents 回答“无论模块多少哪些控制权不能交给黑盒”。它的作者 DexHumanLayer 创始人想回答一个很具体的问题怎样才能构建出可靠到能交付给生产客户的 LLM 应用这个问题背后是他反复见到的一个失败模式。很多团队想给产品加上 Agent 能力于是抓一个流行框架很快做到 70–80% 的体验然后卡住——因为剩下的 20% 才是决定能不能给真实用户用的部分而这部分往往要求你回过头去逆向框架的 prompt、控制流和状态管理最后不得不推倒重来README.md:153-161。作者由此得出一个和主流叙事相反的判断真正跑在生产里、面向客户的好 Agent大部分其实是普通软件只在少数关键节点插入 LLM 决策而不是给个目标、塞一袋工具、循环到完成那种模式README.md:39-45。所以这个项目的名字致敬了经典的 12-Factor App。当年那份文档讲的是云原生应用该守哪些工程约定这份文档讲的是构建可靠 LLM Agent 该守哪些工程约定。这里的12 个因素不是 12 个参数也不是必须全部打勾的清单而是12 条设计原则——你可以只挑其中几条塞进你现有的产品里。仓库里到底有什么打开仓库你会看到三样东西它们分别对应讲道理、给样例、教怎么搭理念内容content/目录下的 12 篇 factor 文章加一篇附录是这个项目的主体和灵魂。可运行样例packages/create-12-factor-agent/template/一个用 TypeScript BAML Express HumanLayer 写的小 Agent能真的跑起来做计算、找人审批。教学构建系统workshops/和packages/walkthroughgen/把上面那个样例拆成一步步的 workshop并用一个小工具从 YAML 生成教程文档和分章节代码。需要说清楚的是这个样例会真的执行任务但任务本身是教学性的——一个计算器加上人类审批流程不是通用的生产 Agent。它的价值在于用最小的 domain 把机制讲透而不是覆盖真实业务的复杂度。12 个因素一句话一条先把 12 条快速过一遍建立全景后面再讲它们如何在代码里落地自然语言转工具调用——把帮我退 750 美元翻译成结构化对象{ intent: refund, amount: 750 }再交给确定性代码处理。掌控你的 prompt——prompt 是一等公民代码要能测试、能版本化、能随时替换别塞进框架黑盒。掌控你的上下文窗口——喂给模型什么、按什么格式喂由你决定不被 SDK 的标准 message 历史绑死。工具只是结构化输出——调用工具本质只是模型吐了段 JSON是否执行、何时执行、怎么执行全由你的代码说了算。统一执行状态和业务状态——尽量用一条事件历史同时表达发生了什么和进行到哪一步别维护两套状态。用简单 API 启动/暂停/恢复——Agent 是程序就该能被启动、能暂停等待、能靠外部信号恢复。用工具调用联系人类——找人确认也是一种结构化事件request_approval、done_for_now和普通工具走同一条事件流。掌控你的控制流——哪些工具直接执行、哪些要中断等审批由你的代码决定而不是框架的循环。把错误压缩进上下文——出错时不要把原始堆栈无限塞回上下文要压缩成模型能读懂、能恢复的形式。小而专注的 Agent——别造无所不能的巨型 Agent上下文一膨胀模型就容易走偏。从任何地方触发——Agent 不只从聊天框启动也可以来自 Slack、邮件、cron、webhook、报警。让 Agent 成为无状态 reducer——理想形态是输入事件历史 → 输出下一步状态外置可恢复、可重放。这 12 条并不是彼此独立的它们其实收敛到同一条主线。一条主线串起所有因素如果只记一句话那就是把 LLM 关进一个很窄的职责里其余的控制权全部留在你自己的代码手上。顺着这条主线12 个因素会自然分成几个层次LLM 只负责一件事——看着到目前为止发生了什么输出下一步该干什么的结构化意图factor 1、2、3、4。执行、暂停、恢复、联系人类全是确定性代码的活儿factor 6、7、8、9。状态用一条事件线程统一承载既是上下文也是业务历史也是恢复锚点factor 5、12。整个 Agent 保持小而专注靠外层确定性外壳控制风险边界factor 10、11。这条主线也解释了作者的产品立场不是要你换一个更强的框架而是要你想清楚——哪些控制权无论如何都不能交出去。样例代码如何印证这条主线光讲原则容易空。这个项目的高明之处在于它把原则压进了一个几十行的小 Agent 里让你能一眼看到原则长什么样。LLM 只决定不执行。样例里那个被 LLM 调用的函数DetermineNextStep返回值是一个联合类型要么是计算工具加减乘除要么是联系人类的工具要澄清、要审批、报告完成template/baml_src/agent.baml:26-32。模型做的全部事情就是根据当前线程输出其中一种意图。真正的执行权在 TypeScript 的agentLoop里它把模型的输出记为一次tool_call然后用 switch 分流——加减乘可以当场算但divide被当成危险操作和所有联系人类的意图都会直接return出去交给外层处理template/src/agent.ts:89-113。这正是 factor 4 和 factor 8 的核心模型选了某个工具不等于代码必须立刻、以同样方式执行它。能在选择工具和真正调用之间插一刀正是审批、回滚、人类介入得以存在的前提。一条事件线程就是全部状态。样例里的Thread类只有一个核心字段events数组template/src/agent.ts:8-14。它有个serializeForLLM()方法把所有事件拼成一段 XML 风格的文本喂给模型——这就是 factor 3 说的LLM 的输入本质就是’发生了什么 下一步是什么’“。存储层也没有额外维护当前第几步、在等什么、重试了几次这类编排状态而是直接把整条线程序列化成 JSON 落盘顺便再写一份人类可读的 txttemplate/src/state.ts:15-31。这就是 factor 5 和 factor 12状态是单一事实源天然可序列化、可恢复、可分叉、可观察。你想知道 Agent 当时看到了什么、选了什么、人怎么回的”打开那个.threads/id.txt就行。内循环负责推进外循环负责等待。agentLoop是内循环——不停问模型下一步能同步做的当场做做不了的就退出来。外循环则有两个化身命令行版本创建线程、跑内循环、用命令行提示接收人类的审批或补充再把回复追加回线程template/src/cli.ts:10-36HTTP 版本则从 HumanLayer 的 webhook 事件里恢复线程处理审批结果或人类回复再继续跑内循环template/src/server.ts:69-110。这两者一起演示了 factor 6 和 factor 7Agent 能用简单 API 暂停、靠 webhook 恢复而联系人类本身就是事件流里的一环靠把thread_id存进外部交互状态来实现跨请求的接力。跟着样例跑一遍理解这套东西最快的方式是想象几个真实场景做一道多步计算。你在命令行敲npx tsx src/index.ts can you multiply 3 and 4, then divide by 2 and add 12。整句话被当作一条user_input放进线程模型开始一轮轮地选下一步乘法当场算、结果追加回上下文、再问模型……直到它输出完成并给出最终答案。除法这一步会触发审批见下。全程线程被不断写进.threads/留下完整轨迹。除法需要人类点头。模型可以选择divide但样例故意把它当成危险操作内循环一选中它就退出绝不先算。外循环转而请求人类审批——命令行里按回车批准输入任何文字则视为拒绝并把反馈写回线程让模型带着反馈重试template/src/agent.ts:105-107,template/src/cli.ts:69-80。这里的产品意义很关键高风险操作不是靠相信模型不会算错来兜底而是靠控制流边界来兜底。用 webhook 恢复一个长流程。外部系统创建会话HumanLayer 回调进来server 建线程、跑内循环如果最后一步需要人类澄清或完成就创建一个 human contact如果是危险操作就创建一个 function call并把thread_id塞进 HumanLayer 的状态里template/src/server.ts:112-149。等人类在邮件或 Slack 里回复后下一个 webhook 带着thread_id回来server 找回原线程、追加回复、继续推进。整个过程跨越了多个 HTTP 请求但对 Agent 来说只是同一条线程被恢复了。维护教程本身。这是个元用例维护者把教学步骤写成 YAML每步含说明、文件复制、命令、预期输出walkthroughgen读了之后生成 markdown、逐章节的工作目录和最终项目packages/walkthroughgen/src/index.ts:75-145。它不属于 Agent 运行时而是保证教程文字、示例代码、最终产物三者不漂移的基础设施。谁会用它为什么值得读它的目标读者是那些正在把已有 SaaS 或内部系统变得更 agentic的技术创始人、AI 工程师和产品团队——正是 README 里描述的、走到 80% 就卡住的那群人。它给这群人的价值不是又一个框架而是一个和主流相反、却更贴近生产经验的判断框架不要从选哪个框架开始而要从哪些控制权不能交出去开始。而且这些原则不绑定任何技术栈——文档里 BAML 只是举例factor 2 明确说你可以用任意 prompt 工具甚至手写模板content/factor-02-own-your-prompts.md:69-75。一句话概括它的定位一套把 Agent 从黑盒框架拉回生产软件工程的原则和模板。它的代价与边界作为一份诚实的分析也要说清楚它没做什么、哪里不能照搬样例不是生产就绪的。它缺鉴权、并发锁、幂等、webhook 签名校验、持久化数据库、可观测性和严格的错误恢复。它是用来讲机制的不是用来上线的。示例 domain 太窄。计算器足以说明选择与执行分离但真实业务工具的权限、回滚、审计和数据依赖复杂度它一概没碰。有教学草稿的痕迹。比如server.ts里若干 async 的存储调用缺了awaittemplate/src/server.ts:83,template/src/server.ts:116-119BAML 里定义了ProcessRefund意图但 TypeScript 侧的handleNextStep并没有覆盖它template/baml_src/agent.baml:19-32,template/src/agent.ts:49-87。当教材读没问题当代码抄要小心。文件命名有历史包袱。content/下既有factor-2-own-your-prompts.md又有factor-02-own-your-prompts.md前者只是指向后者的迁移指针。阅读时以 README 里 01–12 的链接为准。如果从零重建该怎么推进顺着主线重建这个样例的合理顺序是这样的——每一步都对应一个要解决的压力而不只是实现某个模块先让模型能输出结构化的下一步。压力是自然语言不能直接驱动副作用。此时只读用户输入、不写任何外部状态不变量是模型输出必须落进 schema 联合类型。验收标准输入add 3 and 4能得到{ intent: add, a: 3, b: 4 }。加一个确定性的工具分发器。压力是模型负责选但执行必须由代码掌控。它负责追加tool_call和tool_response不变量是未知意图要 fail-fast高风险意图不能自动执行。验收标准多步计算能在线程里留下完整轨迹。把线程做成唯一的状态锚点。压力是多轮、暂停、恢复都需要可序列化的历史。events 是主状态thread id 是外部引用不变量是执行状态尽量从 events 推导不另建隐式状态机。验收标准存盘再加载serializeForLLM()的结果一致。拆开内循环和外循环。压力是人类审批和长任务不能阻塞内存里的循环。外部的联系/审批状态要携带 thread id不变量是选择工具和调用工具之间必须能暂停。验收标准除法先生成审批请求批准后才执行。接上 HTTP / webhook 触发。压力是Agent 不该只能在命令行里活着。从 webhook body 恢复线程、追加回复、继续内循环不变量是所有恢复路径都要校验线程存在和事件类型合法。验收标准会话创建 → 审批完成 → 报告完成能跨请求走完。最后才做生产化补齐。压力是教学 demo 扛不住真实客户数据。把文件存储换成数据库加上幂等键、锁、审计、trace、metrics不变量是人类审批、权限校验、业务副作用和 LLM trace 要分层记录。验收标准重复的 webhook 不会重复执行工具并发回复不会互相覆盖线程高风险工具都留有审批证据。它给如何分析一个 Agent带来的启发读完这个项目最大的收获或许不是那 12 条本身而是它提醒我们分析一个 Agent不能只数有没有工具、有没有 memory、有没有 planner。真正决定它能不能上生产的是控制权的归属prompt 是否可测试、可版本化、可替换上下文是应用主动构造的还是被 SDK 的 message 历史绑死选择工具和调用工具能不能分离状态能否从业务事件推导而不是藏在框架运行时里人类是不是一种结构化事件而不是聊天 UI 的特例Agent 是否足够小能靠确定性外壳把风险圈住。换句话说判断一个 Agent 系统是否可靠可以看这样一组边界是否清晰LLM 决策边界 上下文构造权 工具执行权 状态归属 暂停/恢复粒度 人类 / 外部触发的接入方式 小 Agent 的范围控制如果一个系统在这些边界上不可见、不可插入、不可恢复那么无论它的框架抽象多漂亮都很难真正达到生产级可靠性。这就是 12-Factor Agents 想让你记住的判断框架。