
有一段时间我维护的Agent代码几乎就是“一个大类套所有逻辑”模型调用、工具注册、上下文记忆、格式化输出、重试策略全揉在一起。单个类能从300行一路长到3000行改一个prompt要全局搜索三遍加一个工具要做好久冒烟测试。不是某一行代码写错了而是这个“Agent”在被当作单一对象对待的时候已经承担了太多本来该分工的职责。这次CodeAgent V0.6.1发布让我看到另一种组织方式。它没有把智能体当成一块整体而是把骨架收敛成五样东西原子、编排、能力路由、门控、事件账本。原子提供能力编排组织流程路由做意图分发门控做执行审批账本留下完整轨迹。单看每个概念都很简单合在一起你会发现一个跑在真实任务里的Agent最难的那部分其实不是“把prompt写聪明”而是让这五个部分各自守住自己的边界。1. 为什么“骨架”非是这五样不可拆完一个Agent之后我彻底想通了1.1 为什么是“骨架”而不是“框架”框架这个词容易让人误会以为框架应该把所有东西都替你写好。骨架不一样骨架定的是“哪些骨头在、骨头之间怎么连接”至于肌肉和血液长什么样是外面的事情。五件套起的就是骨头的作用它定义原子和编排之间的调用关系定义路由与门控的先后顺序定义账本如何被其他四样共同追加。每一块都是可替换的但接缝是固定好的。我过去犯的错误恰好相反把流程规则写在提示词里让模型自己在每一步去猜。提示词的好处是灵活坏处是不可测。同样一句话在不同的模型、不同的温度下可能会走出完全不同的路径。当你需要稳定交付时灵活就变成了一种摇摆。骨架的价值就是把这层摇摆限制住让可变的只发生在编排层的一个小范围内。1.2 五件套各回答一个运行时问题如果给五件套各配一个问题事情会变得非常清楚骨架回答的问题原子某个具体动作由谁来执行编排这些动作按什么顺序、什么节奏执行能力路由当前请求应该交给哪个能力处理门控这个能力此时此刻真的允许被调用吗事件账本之前发生过什么为什么发生我曾经用这个表格逼着自己去检查代码如果一段逻辑回答不了这些问题它就不该出现在自己的位置。比如把“如果用户没有权限就换一个能力”写在路由里表面看是路由在纠偏实际上这是门控的职责被挪到了路由。短期能跑长期一定会乱。谁负责选择谁负责否决必须一开始就分开。1.3 五件套真正改掉的东西和传统的大而全框架对比CodeAgent V0.6.1最有意义的变化是把“模型自主决策”和“工程约束”分离了。路由可以由模型参与门控却必须由代码把住编排可以由模型设计高级路径执行却必须有原子层兜底事件账本不依赖模型日志而是由每一次真实调用自动追加。你没有把决策权全部收走也没有把决策权全部交给模型——两边各自做擅长的事。这个思路我在自己的项目里试了一段时间后最大的体感是新需求进来改动的位置通常只有一个。要加一种能力去原子层加去路由注册一下要加一个审批评审在门控层加一条规则要调流程顺序只看编排层。不再出现“为了加一个工具把整个Agent类翻了一遍”的情况。2. 原子最小能力单元不是把工具函数换了个说法很多框架都会提供工具注册往一个列表里塞函数Agent就知道它可以调用这些函数。工具这个概念太习惯了以至于我们经常忘了它其实只回答了“能做什么”。原子在工具之上加了一层契约回答的是“在什么条件下能做什么、做了之后产生什么影响、成本是多少、会留下什么事件”。2.1 原子和工具的差别在“契约”一个典型原子声明出来的东西远不止一个函数的签名。我做过一段时间的整理最小契约至少包括这些字段作用输入Schema调用方必须给出哪些参数类型和约束是什么输出Schema成功和失败分别返回什么结构前置条件原子在什么状态下才可用比如“工作区已初始化”副作用声明会不会改文件、会不会发起网络请求、会不会产生外部影响成本与时限预计消耗多少token、多少时间超出怎么办事件描述执行后会向账本写入哪种事件事件里包含哪些关键字段工具函数可能只需要输入输出原子则把这些额外的信息变成一等公民。为什么这么重要因为在编排层做决策时“这个原子贵不贵”“失败会不会损坏现场”“有没有副作用”是和“能不能完成”同等重要的信息。如果这些信息都藏在原子内部编排只能靠猜路由和门控也就失去了判断依据。2.2 粒度控制在什么位置才算“原子”粒度是设计原子最纠结的一件事太大会变成服务太小会变成token。我比较认可的一个标准是原子应当是一次“一个动作、一轮调用”能完成的单元。在代码类智能体里典型原子集大概是read_file读文件指定范围返回内容与元数据search_symbol在代码库里搜符号或引用关系返回检索结果run_test运行指定测试用例或测试集返回通过/失败详情apply_patch把补丁打到工作区支持回滚git_commit把当前改动提交到临时分支方便后续恢复web_search只在授权场景下开放返回已经过过滤的摘要execute_command执行少量受控的shell命令默认不开放自由终端。这些原子都符合“一个动作”的标准。如果把“实现某个功能”当成原子编排层要等很长一轮而且中间没有可观测点失败时不知道该从哪一步回退。反过来如果把“读取某一行字符”当成原子编排层会被大量细碎调用刷屏光调度开销就压过了收益。经验是让编排层“以动作而不是以目标”为单位思考粒度自然会收敛。2.3 原子层最容易犯的“隐式状态”错误原子之间共享状态是最隐蔽的坑。我看到过很多实现原子A把结果写入一个临时文件或全局变量原子B去读看起来省事实际上一旦并发、重试、异步调度状态就全乱了。原子应该是无状态或显式状态的要么通过参数把上下文传入要么通过返回值和账本把结果交给编排层再由编排层决定是否传给下一个原子。在代码Agent的场景里最典型的是工作区状态。多个原子操作同一份代码时必须在原子参数上明确传入“操作哪个路径”“基于哪个版本”而不是原子内部自己猜。V0.6.1的骨架设计对这个问题其实留了个很好的口子事件账本会把每次原子操作的输入输出都记录下来回退的时候可以直接根据账本恢复现场而不是靠全局变量里的“上一次结果”碰运气。3. 编排把原子拼成流程也把“失控”锁在边界里原子是零件编排是把零件装成产线的过程。很多项目把编排简单理解成“让LLM一步步调用工具”这在Demo里没问题一旦任务复杂编排层就要开始回答更严肃的问题并行还是串行失败重试还是降级超时怎么处理子任务要不要拆出去3.1 编排层必须支持的三种基本结构最基础的编排就是顺序执行前一个原子成功后一个原子才启动。这个结构最简单但性能差一步慢步步慢。第二种是条件分支编排层根据当前上下文和中间结果决定走哪条路径这时路由会被编排调用。第三种是图结构节点是原子边是依赖关系它可以天然表达并行和汇合。成熟点的Agent编排基本都是图结构的变体。不管用哪种结构我建议编排层至少内置三件事超时、重试、降级。超时永远要有LLM调用尤其需要不然一个原子挂住整个任务就卡死。重试要区分幂等和不幂等read_file幂等可以重试apply_patch不幂等重试前必须回滚。降级的意思是一个原子失败时编排层要有一个替代路径比如主力测试执行器失败就用保守的测试方式重跑。3.2 动态场景下编排应该给“坐标”而不是“每一步”另一个极端是把编排做成一个死板的工作流每个步骤都固定好这在实际任务面前很快崩掉。真实需求往往是任务开始前不知道要改几个文件也不知道会不会需要调用网络搜索。这时候比较有用的做法是让LLM在编排层生成一个高级任务坐标比如“先摸清现状再定位改动点然后逐个修改最后回归验证”而坐标内部的细化执行交给原子层去完成。你可能会问这个高级坐标和“让LLM自己选工具”有什么区别。区别在于高级坐标被当成编排结构落到了代码里它可以被记录、被评估、被修正。如果LLM说这一步要“先摸清现状”它到底选了哪个原子、是否真的先执行了read_file事件账本里都看得见。而如果只是让LLM自由调用你只能看到“调用了工具A、B、C”至于整体计划是什么模型自己可能都说不清。3.3 多Agent编排分工方式和路由是两码事CodeAgent新版本里反复提到编排很多人会拿它和多Agent编排对比。多Agent场景下多了一个问题多个Agent之间怎么协作。常见的主管模式是主Agent把子任务分给子Agent子Agent完成后把结果汇总回来群聊模式则是多个Agent共享上下文谁接话谁干活。主管模式的控制力更强群聊模式更灵活适用场景不一样。我踩过的一个坑是容易把多Agent里的“谁干活”和单Agent里的“能力路由”混在一起。其实它们在不同层级多Agent编排解决的是“哪个Agent接下来参与”能力路由解决的是“Agent内部哪个能力处理”。两层可以串起来但在代码结构里最好分开否则你会在一个Agent框架里同时维护两套路由和两套门控复杂度翻倍。4. 能力路由与门控一个管“选谁”一个管“能不能”这一章我想认真聊路由和门控因为它们是五件套里最容易混淆的一对。很多项目把它们合并成一个“给模型候选工具列表”的步骤缺失了所有代码。看CodeAgent V0.6.1把这两个概念单独提出来我认为是非常正确的取舍。4.1 路由的本质是意图到能力坐标的映射能力路由的工作是给定当前请求和上下文决定应该调用哪个原子或哪条原子链。最简单的实现是基于规则的元数据匹配比如关键词命中、环境信息匹配再往上是用向量检索做语义路由把意图和原子描述做相似度匹配最灵活的是让LLM来选模型从候选原子列表中挑选最合适的。在代码Agent这种场景里我更愿意用混合路由先用规则把高度确定的情况过滤掉比如git diff相关请求直接路由到仓库分析原子剩下的开放任务交给模型选但候选列表要由路由层维护不是把全部原子都塞给模型。原因很现实模型能看到的候选越多选错的概率越大而且很多原子是内部能力、不应该暴露给模型。路由层还要处理一个很容易被忽略的事情——兜底。路由不是百分之百能命中选错原子很常见。兜底方案至少要有两种一种是选错后允许路由重新选另一种是选不到合适能力时把状况如实写成一条未知意图事件而不是硬选一个。我见过太多Agent在路由层硬选选一个不合适的原子硬跑然后拿着失败结果继续凑整个链路越来越不可信。4.2 门控的核心是“不可绕过”的执行策略如果路由负责选门控就负责审批。门控不一定非要有人的参与它更应该是一种策略执行点。常见的门控类型包括门控类型典型规则权限门控当前用户/密钥是否有权调用该原子敏感操作门控删除、覆盖、支付等高风险操作要求二次确认成本门控单次执行预算耗尽时阻止继续调用上下文门控只有满足某个状态时原子才可用比如从未初始化时禁止部署合规门控特定数据不允许被发送到指定外部能力门控最关键的性质是“不可绕过”。它不能只存在于提示词里因为提示词建议可以被模型忽略它也不应该放在原子内部因为原子可能被多种编排路径调用每个路径都要各自写一遍。更好的位置是一个独立中间件夹在路由和实际执行之间路由选出候选原子之后门控先做一次执行前检查检查不通过就返回带原因的门控拒绝事件由编排层决定是换一个原子还是请求人介入。我在实际项目里被教育过一次曾把敏感操作门控写成路由规则的一部分模型在路由阶段被提示“删除操作不可用”于是模型换了一种绕过方式照样把目录清了。问题不是模型多坏而是我把否决权和选择权放在了一起。选择权可以被模型视角影响否决权必须独立存在。4.3 路由和门控为什么要分开一次事故复盘分开设计和分开实现是两件不同的事。你可以把它们各自设计得很清楚但实现时图省事写在一个函数里还是会在接线处出事。我的建议是至少在事件账本里用不同的事件类型区分“路由决策”和“门控决策”这样复盘时一眼就能看出是选择错了还是不该放行却被放行了。用事故复盘来说明。有一次我的Agent跑“清理临时文件”任务路由正确选中了delete_temp原子但门控没有判断当前工作区状态是“带有未提交代码的仓库”直接放行了。原子执行完代码里几个没提交的调试文件被一并清掉。回看事件账本路由事件是干净的门控事件显示“无可用规则”问题出在我压根没给这个场景配门控规则。如果路由和门控没有分开记这次事故会归结为“路由选错了”方向就带偏了。分开之后问题非常清晰新增一条“未提交工作区禁止执行清理类原子”的门控规则事情就结束了。5. 事件账本Agent每一步都留下可以回放的轨迹五件套里事件账本是最容易被当成“日志系统”忽略的一环。但它其实是整个骨架里最值钱的部分。没有账本原子写得再干净、编排设计得再合理你也没法证明它是按预期跑的。5.1 账本和日志是两码事日志是给人看系统故障的账本是给程序和人都能回放的运行事实。典型的代码Agent日志长这样“调用read_file耗时0.3秒返回200行”。账本里的记录则更结构化它不但记录“调用了read_file”还会记录“为什么调用它”“路由的置信度多少”“门控检查了几条规则命中哪一条”“输入参数解析到哪个文件哪个范围”“输出摘要是什么”“这次调用消耗了多少token”。这两者最大的区别是日志是流水账账本是可追溯的事实链。日志用来排查“系统哪里崩了”账本用来回答“Agent刚才为什么这么做”。后者在Agent开发里更重要因为Agent的失败往往不是崩溃而是“做一个看起来合理但实际错误的决策”。这种失败只有靠追溯决策依据才能发现。账本还应该可以被重放把一次运行的事件序列重新执行一遍检验不同的路由策略或门控规则会带来什么不同结果。5.2 事件账本最小字段集不需要一开始就把事件模型设计得很复杂。我给出一个最小可用字段集够Cover大部分场景字段说明event_id全局递增的事件编号用于排序和定位parent_id父事件编号用于还原调用链event_typeatomic_executed / route_decided / gate_decided / orchestration_step / system_error 等agent_id / run_id一次任务运行内的唯一IDactor事件的来源哪个原子、路由、门控或编排节点input_summary关键输入的摘要避免记录过多敏感原文output_summary关键输出的摘要或是状态码decision_reason路由选择该能力的原因、门控拒绝/放行的原因cost / latencytoken消耗、耗时、重试次数timestamp精确到毫秒的UTC时间当你发现一个字段很难在原子执行完时填上这往往说明事件发生点设计得不对。比如“决策理由”不在原子执行时填它是路由事件里的字段门控事件里填“规则命中列表”。如果原子里出现了本来应该由编排填的内容说明职责边界又破了。5.3 用账本复盘一次失败的代码Agent任务举一个我最近实际复盘过的例子。任务很简单“给项目加一个命令然后跑一下测试确认不破坏现有功能”。Agent执行后测试失败。如果只有最终日志我只知道“测试失败”四个字。有账本之后我看到了完整链路路由事件显示第一轮意图被路由到“改动定位原子”输出了一个改动点建议编排层下一步进入“apply_patch原子”门控事件显示apply_patch的“工作区干净”检查没有命中是放行的原因是代码改动在临时分支上原子执行的输出摘要显示补丁合并时出现一个冲突更早一条路由事件显示这个改动点建议来自一个相似度只有0.61的检索结果而路由层没有把置信度太低当回事。这个复盘给了我三个明确的修改点第一检索置信度低于0.7时应该触发二次路由而不是直接执行第二apply_patch门控应要求工作区干净否则必须先走git_commit原子第三编排层应该在原子返回值标记“冲突”时停止而不是继续尝试无关测试。如果没有账本这一切只能靠猜。这大概也是CodeAgent V0.6.1把事件账本当作骨架重要一块的原因。6. V0.6.1开源的边界与我的实操经验聊完五件套最后说点更贴近使用的感想。CodeAgent V0.6.1这次把骨架开源出去社区里马上会有人问我是不是每次都要用齐五件套我的答案是骨架像一副积木它不是让你一定件件都上而是让你知道什么时候该用哪几件、什么时候不上哪些件。6.1 骨架不是搭出来的是从需求里长出来的如果你正在从零搭一个Agent我强烈建议不要第一天就铺开五件套的完整目录结构至少等需求把每一层逼出来再动手。先写一个原子比如能读文件再写一个最简单的顺序编排读文件→把内容交给模型→模型产出结果等你要支持多个操作时再引入路由等你要对外提供服务、要控制权限和成本时再上门控账本反而可以从第一天就带因为回放能看到每轮迭代的真实变化。骨架的价值是告诉你每块砖的位置不是逼你把所有砖一次砌完。6.2 什么时候不该用这套骨架有几种情况直接用简单框架更快。第一是场景极度固定天天跑同一个脚本原子集不会扩张编排写死即可不需要路由和门控。第二是纯研究型玩法只想验证模型在某个prompt下的表现直接调模型API就行五件套是冗余的。第三是纯单机个人工具没有权限区分、没有预算审计、没有对外服务门控和账本可以先降级为日志。别把架构复杂度当成成熟度简单问题用一个函数解决是对工程量的尊重。反过来如果你在做一个要交付给多角色使用的代码Agent或者它会被接进一个有版本、有审计、有成本核算的平台那五件套几乎绕不开。原子保证能力可扩展编排保证流程可观察路由保证请求可分发门控保证边界可执行账本保证回溯可依据。这五样一旦缺位后期补的成本会远高于一开始就按骨架搭。6.3 我实际使用中的几个习惯和教训写在最后算是一点个人经验沉淀。第一原子命名和事件类型命名一开始就要统一规范不要“run”和“execute_atom”混着用。账本好不好用取决于事件类型是否一致。我用了一段时间之后还把原子按read_only、mutation、external三类做前缀区分这样路由和门控在过滤时一眼就能判断风险等级。第二门控拒绝时要返回结构化的拒绝原因而不是简单返回一个false。拒绝原因会被编排层拿去决定下一步。如果只拿到false编排层不知道是权限问题还是成本问题只能笼统地换个原子很可能接着踩同一个门控。第三给事件账本留一个“经验标记”的口子。当一次任务跑成功但结果不理想时从账本里挑出关键决策事件做标记日积月累就是一个非常宝贵的评估集。这个集子能直接用来测试新版本的路由策略是不是比改一版prompt然后盲测靠谱得多。CodeAgent V0.6.1把骨架收敛到五样等于把一套可复用的工程结构摊开了。如果你也在梳理自己的Agent框架我建议拿这五样当一张检查表去对着自己现在的代码问一遍原子的边界清楚吗编排能回退吗路由兜底了吗门控没法绕过吗账本可回放吗这些问题答完你的Agent骨架大概也就立住了。