
先说一个场景。前阵子帮一个团队做内部Agent平台的配置评审会议室里坐了七八个人运维、安全、平台、业务各来一位。项目负责人打开配置文件第一页写着人格破甲模式技能全量放开工作流多域串联。我问他一句话这套配置上线之后如果某天它自作主张多做了一步你从哪份日志里能看出来它为什么多做那一步会议室安静了大概十秒。这个问题几乎命中了所有指令解析型Agent的通病大家把精力全花在它能不能做上几乎没人认真设计它凭什么做、做到哪一步停、做完留下什么痕迹。这篇就围绕这套内部的Codex-X框架里那个被叫做海鸥3.0破甲的配置包掰开讲讲人格设定、技能路由与多域任务工作流这三件事在工程上到底该怎么落地。核心关键词是Agent指令解析、人格设定、技能路由和任务工作流编排。适合正在做Agent平台配置、在做授权范围内的安全演练自动化、或者单纯被提示词写着写着就飘了折磨过的同行看零基础的也能跟下来因为我会把每个字段为什么这么设讲清楚。需要提前说明一条底线本文讨论的全部是授权范围内、可审计、可回滚的防御性验证与流程编排不涉及任何未经授权的系统探测手法。所有示例都是结构层面的配置写法不包含可直接执行的攻击载荷。1. 指令解析层到底在解析什么很多人对指令解析的理解停留在把用户的话翻译成函数调用。这只是最表层。在一套成熟的Agent框架里解析层实际承担的是四件事理解意图、确定角色、选择技能、约束边界。这四件事任何一件缺位后面都会以Agent乱来的形式爆发出来。1.1 一句话进入系统后的四次变形用户在对话框里敲下一句帮我看看这个服务对外暴露的接口有没有配置疏漏这句话在解析层会经历四次变形。第一次意图归一化。系统要把它从自然语言压成一个意图标签比如surface_review。这一步的关键不是准确率有多高而是必须有一个明确的兜底意图。我见过太多配置只写了二十个意图标签没有unknown分支结果模型遇到陌生输入就开始硬凑凑到最相近的那个标签上然后兴高采烈地执行了一个完全不相干的任务。第二次角色绑定。同一个意图在不同人格下执行方式完全不同。评审模式下它应该只读、只报告演练模式下它可以发起受控的验证动作复盘模式下它应该完全不碰目标只处理已有数据。所以角色不是装饰性的提示词前缀它是权限的载体。第三次技能选路。意图加角色决定了可用技能集合的候选范围路由表再从中选出具体的一到两个技能。注意是一到两个不是全部。技能给多了模型会在多个工具之间反复横跳这叫工具过载是小模型和中等模型最常见的失效模式。第四次边界裁剪。这一步经常被整个跳过。边界裁剪的含义是把目标范围、时间窗口、动作上限、人工确认点这几个约束作为结构化字段注入到后续每一次调用里而不是只写在系统提示词里靠模型记得。写在提示词里的约束是软约束注入到调用参数里的约束才是硬约束。我一般会在配置文件里明确写出这个四段式任何一个字段缺失就拒绝加载。这听起来很硬但比线上出事故之后再补要便宜得多。1.2 为什么人格设定必须写在路由之前这是个顺序问题也是我认为这套框架设计里最容易搞反的一处。绝大多数人的写法是先写路由把技能表列全然后在每个技能调用时临时拼一段人格描述。这种写法短期能跑长期一定会崩原因是人格是约束的来源不是约束的应用对象。你先确定了我现在是一个只读评审员路由才可能知道扫描类技能全部不可用你先确定了技能再回头补人格人格就变成了事后解释而不是事前限制。举个具体例子。海鸥3.0这套配置里有一份persona.yaml它被加载的时机在所有技能注册之前。文件里有一条forbidden_actions写着几类动作在评审人格下永久禁用。这份文件加载完之后路由表在构建候选技能池的时候会直接把这些动作对应的技能从池子里剔除不是标记为禁止调用是不进池子。这个区别很关键标记为禁止模型还是看得见它还是有可能在被诱导时去试不进池子模型根本不知道有这个东西存在。提示判断一套Agent配置是否成熟一个很简单的办法就是看它的人格文件有没有forbidden_actions字段。只有性格描述没有人格边界的配置本质上只是一段角色扮演提示词。2. 人格卡片把角色扮演写成可校验的契约你是一位资深的安全专家请谨慎地……——这种写法我称为形容词人格。它的特点是读起来很专业跑起来很不稳定。原因很朴素形容词没有判定标准模型每次对谨慎的理解都可能不一样温度参数稍微一抖行为就漂了。工程上可用的写法是契约型人格每一句描述都对应一个可观察、可判定的行为。2.1 人格卡片的最小字段集我在海鸥3.0这套配置里推行的是九个字段的结构缺一不可。直接给结构persona: id: reviewer_readonly display_name: 只读评审员 mission: 在授权范围内对指定目标做配置层面的表面评估 输出结构化发现清单不执行任何变更动作。 allowed_domains: [surface, config, dependency] allowed_actions: - read_metadata - enumerate_readonly - diff_compare forbidden_actions: - any_write - any_auth_attempt - any_payload_delivery evidence_policy: every_claim_needs_source stop_conditions: - target_out_of_scope - confidence_below: 0.6 - human_review_required output_schema: finding_list_v2 handoff: escalate_to_human九个字段分别是标识、展示名、使命、可操作域、允许动作、禁止动作、证据策略、终止条件、输出结构与交接方式。我重点讲三个最容易被省略的。证据策略。every_claim_needs_source这一条的要求是模型输出的每一条结论都必须挂一个来源引用可以是某次只读调用的返回、可以是配置文件里的行号、可以是之前的某条发现。挂不上来源的结论一律降级为待确认。这条规则能砍掉大概六成的幻觉输出因为它把我觉得变成了我看到了。终止条件。这是人格卡片里最值钱的字段。多数配置只写什么时候开始不写什么时候停。模型是很勤奋的你不告诉它停它就会一直找事做。stop_conditions里那三条分别对应三种典型情况目标越界、置信度不足、需要人判断。第三条特别重要它给了模型一个体面的退出方式而不是硬着头皮编一个结论出来。交接方式。escalate_to_human定义了当触发终止条件时任务怎么交出去、交给谁、带什么上下文。没有这个字段Agent停下来之后就只是一个沉默的失败运维看到的就是一个转圈圈。2.2 人格漂移的三种典型症状跑了几个月我总结出人格漂移基本就三种表现形式认出来之后对症下药很快。第一种是语气漂移。输出突然从发现三项配置项未启用鉴权变成这个系统存在严重安全风险建议立即整改。语气一升级通常意味着模型开始脱离结构化输出往自由发挥走了。这时候先查输出schema有没有被绕过。第二种是域漂移。评审人格本来只该碰surface域结果它开始聊依赖链漏洞。这种漂移往往不是模型的问题是路由表把不该给的技能给了。查路由表比改提示词有效得多。第三种是勤奋漂移也是最隐蔽的。表现是任务范围内的事做完了它开始顺手多做一点比如顺手多扫了一个相邻网段。这类问题的根因几乎总是stop_conditions写得不够死或者边界没有注入到调用参数层。我的做法很土在每次工具调用的封装层里做一次硬校验目标不在白名单里直接抛异常。宁可让任务失败不要让它顺手。注意人格漂移不要试图用加长提示词来治。提示词越长模型对其中每一条的注意力越稀释漂移反而更容易发生。治漂移靠的是收窄技能池和加硬校验。3. 技能路由表三把钥匙决定调谁路由表是整个配置里最像工程的部分也是最能体现设计者水平的地方。它的本质是一个多条件映射给定当前意图、当前人格、当前风险档位选出一个技能并给出调用参数模板。3.1 路由键的三层结构我用的路由键是三层意图键、域键、风险档位。意图键是粗粒度的比如表面评审依赖梳理配置比对通常十到二十个就够。域键决定技能属于哪个能力域比如surface、config、dependency、log。风险档位是最容易被忽略的一层我一般分三档档位判定依据默认行为L1 只读不改变目标状态不产生外部副作用自动执行事后汇总L2 受控可能触发目标日志或告警但不改变业务状态执行前记录意图执行后立即上报L3 敏感可能影响目标运行状态强制人工确认默认拒绝这张表的价值在于它把要不要人看着这个决策从模型手里拿走了。模型不需要判断自己这步危不危险它只需要把动作声明成某个档位剩下的由路由层决定。这个设计思路和很多成熟系统里的权限模型是一致的——让执行者声明让调度者决策。路由表的配置文件我一般写成这样routes: - key: {intent: surface_review, domain: surface, risk: L1} skill: readonly_endpoint_probe params_template: timeout_s: 8 concurrency: 2 output: normalized_endpoint_list on_empty: report_no_finding - key: {intent: config_compare, domain: config, risk: L1} skill: config_diff params_template: baseline: last_approved ignore_keys: [timestamp, checksum] - key: {intent: surface_review, domain: surface, risk: L2} skill: null action: require_human_confirm最后一条是我特意加的同一意图在不同风险档位下允许路由到不做。这一点很多人想不到但它是整套配置能安全跑起来的关键。因为一旦某类动作被判定为L2及以上正确做法往往不是换个更温和的技能而是直接挂起等人。3.2 路由失配的两类故障第一类多键命中。同一个请求同时满足两条路由系统随机或按顺序取一条。这种不确定性会让同一个输入在不同时间跑出不同结果排查起来极其痛苦。解决办法是给路由加优先级字段并且加载时做冲突检测——两条路由的键完全相同就直接拒绝启动。这个检查我强烈建议做成强制的别等线上才发现。第二类零命中。所有路由都不匹配。这时候如果系统悄悄退化成让模型自由发挥那就是灾难的开始。我的处理是强制走default_deny返回一条明确的结构化拒答附带当前请求未匹配任何路由的原因说明。这条拒答会进日志进日志之后就成了下一轮路由补全的输入。我踩过最疼的一次坑是零命中退化成了自由发挥。当时是一个新加的意图没配路由模型自己拼了一套调用序列跑了一半才被人发现。那次之后我加了一条死规则任何未匹配路由的请求在日志里打 ERROR 级别的标记让它在监控面板上刺眼地跳出来。4. 多域任务编排状态机比流程图靠谱多域任务最难的地方不是能跑通一次而是跑一百次不偏。画一张漂亮的流程图很容易把流程图变成一个能约束模型的状态机要难得多。4.1 阶段门让每个域之间有硬隔断海鸥3.0这套配置把多域任务拆成了若干个阶段每个阶段之间有一道阶段门。阶段门的含义是上一个阶段必须产出一个符合schema的产物并且这个产物通过校验下一个阶段才会被允许启动。举个流程上的例子。一个完整的评审任务可以拆成范围确认阶段——产出scope_manifest表面梳理阶段——产出surface_inventory配置比对阶段——产出config_findings依赖核对阶段——产出dependency_findings汇总阶段——产出final_report每一道门校验的不只是有没有产物还有产物里的关键字段是否齐全、置信度是否达标、有没有未闭环的待确认项。第三阶段的产物里如果有置信度低于阈值的条目第四阶段直接不启动先回到人工确认环节。这个设计看起来拖慢了流程实际上省了大量时间。因为多域任务最大的成本从来不是跑得快而是跑完之后发现前面的结论是错的整条链要重来。阶段门就是在每个岔路口强制对账。4.2 状态机的三张表实现上我推荐用状态机的思路而不是工作流描述语言。核心是三张表状态表、迁移表、产物表。状态表定义任务可能处于的所有状态包括正常态和异常态。异常态一定要显式列出来比如blocked_by_human、aborted_out_of_scope、failed_validation。我见过太多配置只定义了 happy path 的几个状态一出异常就是隐式的 undefined然后各种奇怪的兜底逻辑冒出来。迁移表定义在什么条件下可以从哪个状态到哪个状态。这里的关键是禁止隐式迁移。任何一次状态变化都必须由一条明确的规则触发并且记录触发原因。这条规则听起来很啰嗦但它带来的好处是任何时候拿到日志你都能画出这个任务的真实执行路径而不是靠猜。产物表定义每个状态产出什么、被谁消费、存在哪里。这张表的价值在于它把上下文这个模糊的东西变成了具体的、可审计的数据结构。Agent 不应该靠对话历史维持记忆而应该靠显式的产物传递——对话历史会膨胀、会被截断、会被污染产物不会。提示如果你的多域Agent需要靠记得前面聊过什么才能继续那说明产物设计没做到位。把该记的东西写进结构化产物模型的记忆负担就下来了稳定性会明显好转。4.3 授权边界的三道落点在一套严肃的配置里授权边界不能只写在文档里必须落到三个具体位置。第一道落在配置加载时。每个任务必须有明确的授权声明包含目标范围、时间窗口、允许的动作档位。没有授权声明的任务加载阶段直接拒绝。我把它做成必填字段而不是可选项。第二道落在调用封装层。每一次对外的调用都要过一次校验目标在不在白名单、动作档位在不在本次授权范围内、当前是否在时间窗口内。这三项任一不满足就抛异常。这层校验和模型完全解耦模型改不了它。第三道落在审计日志。每次调用都记一条谁发起的、什么意图、什么档位、目标是什么、返回摘要是什么、耗时多少。日志要能回答为什么这个问题所以除了调用本身还要记触发这次调用的上游产物ID。三道落点凑齐了这套东西才有可能在一个正经组织里被放行。少了任何一道评审时都会被问到答不上来。5. 联调与评测怎么判断这套配置是真稳配置写完只是开始真正费时间的是联调和评测。这一块我踩过的坑最多说几个实用性强的。5.1 回归用例集怎么攒不要等配置写完再去想测试。我的习惯是边写配置边攒用例每加一条路由就顺手记三条用例一条正常命中、一条边界、一条应该被拒。攒到五六十条的时候这套东西的稳定性就大致有谱了。用例集我一般分四组用例组覆盖目标判据命中组每条路由至少一条正常用例调用技能正确、参数模板正确边界组越界目标、超时、空返回必须触发对应终止条件拒答组无路由、档位不足、授权缺失必须返回结构化拒答不得自由发挥漂移组诱导性输入、长上下文、重复任务输出schema不得被绕过拒答组是最重要的一组也是最容易被跳过的一组。大多数人只测能不能做对不测该拒的时候拒没拒。可线上出事恰恰都是该拒没拒。漂移组也值得说两句。我常用的一个手法是把一个已经完成的任务原样再提交一次看它第二次的输出是否和第一次结构一致。如果第二次开始自由发挥、加戏、或者输出结构变了说明上下文污染或者状态没清干净。这个测试特别便宜但特别能暴露问题。5.2 几个反复踩到的坑坑一温度参数和路由稳定性是耦合的。一开始我把温度设得偏高想着让输出自然一点结果路由判断开始飘——同一个输入两次跑出不同的技能选择。后来把路由决策和内容生成拆成两次调用路由那次温度压到很低生成那次才放开。这个拆分的收益远超预期强烈建议试试。坑二产物schema改版没有版本号。早期我给产物加字段、删字段都很随意结果下游消费方经常拿到不认识的字段然后静默忽略问题积累到很后期才暴露。后来所有schema都带版本号消费方必须显式声明自己支持的版本不匹配直接报错。这个改动治好了我大半的联调痛苦。坑三把重试当成万能的。早期我给所有失败都配了自动重试结果越界类的失败被重试了三次日志里出现了三条越界记录审计的时候非常难解释。后来把失败分类可重试的超时、临时不可用才重试不可重试的越界、授权不足、校验失败直接终止并上报。这个分类做完日志干净了一大截。坑四忽略冷启动时的路由表加载顺序。有一次人格文件加载晚于路由表构建导致forbidden_actions没生效一批本该被剔除的技能进了池子。这个问题非常隐蔽因为大部分时候它不影响结果只在特定组合下才出问题。后来我在加载流程里加了显式的依赖声明和启动自检启动时打印一份最终生效的技能池人工看一眼就能发现异常。坑五把评测当成一次性的事。模型会更新配置会改路由会加。我现在的做法是每次配置变更加跑一遍全量用例用例结果和上一次做对比任何一条结果变化都要有人解释。这个流程听起来重但比事后回溯便宜。我个人在实际操作中的体会是这类Agent配置的难点从来不在能不能写出一个能跑的版本那部分一两天就能搞定真正的成本在让它在半年之后、在别人手里、在没人盯着的时候行为依然和你当初设想的一致。人格卡片、路由表、状态机、审计日志这四样东西本质上都是同一个目标的不同侧面把模型的行为从一件靠运气的事变成一件可预期、可验证、可追溯的事。至于具体用哪种写法各家框架不一样但判断标准是通用的——你随便挑一次历史执行记录能不能在五分钟内讲清楚它为什么这么走。讲得清楚这套配置就算立住了讲不清楚那它现在还没出事只是运气好。