
深度技术方案 · Deep Dive
先从一个真实场景说起。业务用户在使用 finance(财资)模块时反馈:“资金日报中会存在流水重复,请排查。”在传统研发链路里,这句话要在「提单 → 排期 → 复现 → 读日志 → 读源码 → 改码 → 编译 → 测试 → 验收 → 发布」十个环节之间流转,每一环都是一次上下文切换,平均以“天”为单位计价。
而在本文描述的体系里,这句话进入的是另一条链路:用户在 chatnext 会话中对业务智能体说一句话,devAgent 随即接管研发侧全过程——先到沙箱读日志取证,再定位源码、生成补丁、编译打包,回到沙箱跑测试验证,最后把改动与证据一起交还给用户验收。全程无需人工搬运上下文,用户只在起点提需求、在终点做验收。
听起来很美,但让 LLM 自由发挥去做这件事,我们踩过一个很典型的坑。以真实历史会话(us1-drive-2026-10-16)的 SSE dump 复盘为例:智能体读了很多次日志、检索了很多次源码,却从未改码、从未编译、从未测试、从未请用户验收——会话在“看起来很忙”中自然结束。这不是模型能力问题,而是流程治理问题:缺少一个确定性的“裁判”,把“看起来做完了”和“真的做完了”区分开。
THESIS 01
LLM 出灵活性,状态机出确定性
阶段推进权在服务端(决策智能体),LLM 只负责选工具。推进、回环、终止由状态机裁决,而非模型自觉。
THESIS 02
事件即证据,门禁即门槛
每一步都产生 SSE 事件;门禁脚本消费 dump,把 11 项断言变成可机检的完成标准。
THESIS 03
验收权还给用户
human_confirm 是唯一终态入口。验收不过自动 REOPENED 回环,不通过就不算完成。
THESIS 04
devAgent 是护航层,不是替代者
业务智能体保持领域专注;研发自治由 devAgent 三体护航,形成业务与研发的 AI 自助协作。
本文以 finance 源码工程为标的物,完整拆解这条「用户自助修改—发布」闭环的设计与实现:三层智能体协作架构(§2)、需求受理与验收回环(§3)、八段闭环状态机(§4)、SSE 事件流与 chatnext 前端(§5)、门禁与证据链(§6)、安全护栏(§7)、沙箱入网与灰度回归的实战难点(§8),最后给出落地状态与演进路线(§9)。
整套体系的核心命题是:让每个智能体只做它最擅长的事,并把它们放进一个互相制衡的协作拓扑里。架构上分为三层——业务智能体在领域里交付价值;devAgent 三体(对话、沙箱、代码)为研发过程护航;决策智能体作为确定性裁判凌驾于编排之上,决定流程走向。

图 1:三层智能体协作架构 —— 业务智能体被护航,devAgent 三体分工,决策智能体裁决
角色 | 形态 | 职责 | 关键落点 |
|---|---|---|---|
对话智能体(devAgent 主控) | LLM 编排 | 受理诉求、选择工具、生成说明与阶段报告;不做流程裁决 | FunctionCallingLoopExecutor |
决策智能体 | 确定性状态机(非对话、无 LLM) | 判定阶段推进 / 回环 / 终止;向提示词注入阶段契约;执行权限与门禁 | DevLoopService+ policy.v2 + dump 门禁 |
沙箱智能体 | 执行体(8081) | 日志取证、源码检索、编译、测试、证据归档、验收回环 API | AgentDevController/api/test/agent/dev/** |
代码智能体 | code agent | 源码理解、补丁生成、静态检查与编译 | apply_patch/write_source/compile |
业务智能体 | 领域 agent | 交付业务能力(财资、专利、浏览器等),保持领域专注 | ooder-biz-*各业务包 |
chatnext 前端 | 事件消费者 | 阶段可视化、工具卡渲染、验收卡片交互 | PhaseDomain /Panels.js/events.contract.json |
设计要点:决策智能体为什么“不是 LLM”
流程裁决必须是可复现、可审计的。同样的事件序列必须得到同样的阶段判定——这是 LLM 无法保证的。所以决策智能体被实现为纯服务端状态机:它消费工具事件,输出阶段事件;LLM 在它面前只有“选哪个工具”的自由,没有“我做到哪一步了”的解释权。
值得强调的是,devAgent 不侵入业务智能体的领域逻辑。finance 的账务口径、patent 的流程规则,仍由各自业务包承载;devAgent 三体只在研发过程维度上护航——业务智能体遇到“需要改代码才能解决的问题”时,问题被移交给 devAgent 闭环,产出物(补丁 + 测试 + 证据)再回流到沙箱业务包。业务专注业务,研发自治研发,两者以沙箱为交界、以验收为闭环。
闭环的起点不是“开始干活”,而是立案。沙箱侧以 AgentDevController 为唯一写入方,维护 .agent-dev/requests/ 目录下的需求台账,形成一个小而完整的状态机:

图 2:需求受理状态机 —— OPEN → CLAIMED → CLOSED / REOPENED,reopenCount 记录返工次数
四组工具把这套台账暴露给智能体侧:
工具 | 作用 | 权限 |
|---|---|---|
dev_request_create | 立案:rawText / expectedBehavior 至少一项,生成带 ID 的需求单 | WRITE |
dev_request_list | 按状态过滤查询(≤200 条) | READ |
dev_request_claim | 领单:OPEN / REOPENED → CLAIMED | WRITE |
dev_request_verify | 验收回写:通过则 CLOSED,不通过则 REOPENED,并记录 verifications 历史与 lastEvidenceId | WRITE |
这个设计解决了两个此前含糊的问题。其一,“改什么”有契约了:立案时必须写明现象(rawText)与预期行为(expectedBehavior),后续每个阶段的事件都可以回指需求单。其二,“算修好”有凭据了:verify 必须携带证据 ID(lastEvidenceId)与通过与否的结论,验收不通过的需求单会带着 reopenCount 历史回到 CLAIMED,返工成本被显性化。
工程细节:唯一写入方与原子落盘
需求台账由 AgentDevController 独占写入,采用「临时文件 + 原子 move」落盘,读取侧对 UTF-8 BOM 做容错——避免沙箱高频读写下的半截 JSON。ID 白名单校验([A-Za-z0-9_-]{1,64})在入口处挡住路径穿越。
整个闭环的骨架是一条 8 + 1 的阶段流水线。服务端 DevLoopService(决策智能体的主体)为每个会话维护阶段状态,按工具事件前向推进,按工具结果回环:

图 3:八段闭环状态机 —— 实线前向推进,虚线为失败回环;DONE 仅可由用户验收通过到达
阶段 | 名称 | 推进工具 | 失败去向 |
|---|---|---|---|
INTAKE | 需求受理 | dev_request_create | — |
ENV_CHECK | 环境预检 | dev_env_check(policy.v2 清单校验) | — |
COLLECT_LOGS | 日志取证 | sandbox_read_logs/sandbox_trace | — |
LOCATE | 源码定位 | sandbox_search_code/read_source | 验证失败 / 验收驳回时回到这里 |
PATCH | 修改源码 | apply_patch/write_source | 编译失败时回到这里 |
BUILD | 编译打包 | compile(agent-host 沙箱包) | → PATCH |
VERIFY | 测试验证 | run_tests/agent_execute | → LOCATE |
ACCEPT | 用户验收 | human_confirm(devConfirmType=DEV_ACCEPT) | 不通过 → REOPENED 回 LOCATE |
DONE | 完成 | 仅 ACCEPT 通过后到达 | — |
工具开始时,状态机只前向推进;工具结束时,按结果裁决回环:
// 工具 → 阶段映射:服务端判定,LLM 只选工具(DevLoopService)
sandbox_read_logs / sandbox_trace → COLLECT_LOGS // 先取证,后定位
sandbox_search_code / read_source → LOCATE
apply_patch / write_source → PATCH
compile → BUILD
run_tests / agent_execute → VERIFY
human_confirm(devConfirmType=DEV_ACCEPT) → ACCEPT // 唯一终态入口// FunctionCallingLoopExecutor:工具结果 → 回环裁决
Transition t = devLoopService.onToolResult(convId, toolName, status, args, output);
if (t.changed) pushDevPhaseEvent(convId, t);
// BUILD 失败 → 回 PATCH;VERIFY 失败 → 回 LOCATE;
// 验收不通过 → REOPENED 并回 LOCATE;同一回环最多 3 次,超限转人工状态机不只是裁判,还是提示词的供给方。PromptEngine 在组装提示词时优先注入 buildPromptBlock 生成的阶段上下文(当前阶段 / 第几次尝试 / 已有证据 / 阶段契约与可用工具 / 硬规则),注入成功则跳过常规沙箱上下文:
// PromptEngine:dev 闭环上下文优先,常规沙箱上下文兜底
boolean devLoopInjected = false;
if (devLoopService != null && context != null) {
String devBlock = devLoopService.buildPromptBlock(context);
if (devBlock != null && !devBlock.isEmpty()) {
prompt = prompt + "\n\n" + devBlock;
devLoopInjected = true;
}
}
if (!devLoopInjected && sandboxContextProvider != null) { /* 常规沙箱上下文 */ }注入有两条边界保护:入口画像(EntryProfiles.DEV_AGENT)判定当前是否 dev 会话;意图过滤(isDevTaskRequest)识别“排查 / 修复 / 报错 / 编译 / bug / fix”类研发诉求。纯设计类会话与 finance 等业务入口完全不受影响——这是“护航不侵扰”在实现层的落点,并有单测(financeEntryIsNotAffected)钉死。
同时,沙箱侧流程定义(sandbox-devops/definition.json v2)与状态机保持同构:七个 dev_* 阶段 + BACKWARD 回环迁移(buildOk==false → dev_patch、verifyOk==false → dev_locate、accepted==false → dev_locate),并有契约测试校验“代码里的工具”与“流程定义里的活动”一一对应。
闭环的每一步都被翻译成 SSE 事件。两类事件构成观察面:阶段事件(phase_*)讲“流程走到哪”,工具事件(flow_tool_*)讲“正在做什么”。chatnext 前端的 PhaseDomain 与工具卡各自消费,用户看到的不再是黑盒转圈,而是一张可以回放的流程地图。

图 4:一次闭环的运行时时序 —— 阶段事件由状态机发出,工具事件由执行侧发出
工具事件在原有字段之上新增了 dev 维度字段(events.contract.json 同步登记),前端无需解析事件正文即可渲染阶段语义:
字段 | 含义 | 消费方 |
|---|---|---|
devStage | 阶段枚举(如PATCH) | 前端阶段分组与统计 |
devStageLabel | 中文标签(如“修改源码”) | 工具卡卡头 |
devAttempt/devMaxAttempts | 第几次尝试 / 回环上限 | 回环可视化(“第 2 次”) |
devLoopId | 本次闭环标识 | 关联 phase_* 与 flow_tool_* 事件 |
// 工具事件示例(flow_tool_call)
{ "event": "flow_tool_call",
"data": {
"toolName": "apply_patch", "displayName": "修改源码", "status": "running",
"devStage": "PATCH", "devStageLabel": "修改源码",
"devAttempt": 1, "devMaxAttempts": 3, "devLoopId": "loop-20261020-0930"
} }
// 阶段事件示例(phase_advanced)
{ "event": "phase_advanced",
"data": {
"phaseId": "dev_verify", "fromPhaseId": "dev_build", "toPhaseId": "dev_verify",
"displayName": "测试验证", "classification": "DEV_LOOP", "order": 6, "attempt": 1
} }阶段事件共四种:phase_started / phase_advanced / phase_rolled_back(回环)/ phase_closed(终态)。前端 Panels.js 在工具卡卡头拼接阶段标签——[日志取证] 读取 3 份系统日志、[修改源码·第2次] 生成补丁 patch-0007.diff——回环次数直接可见,返工不再被藏进黑盒。
为什么选择“双轨事件”而非单一事件流
工具事件由执行侧产生,粒度细但语义散;阶段事件由状态机产生,语义强但粒度粗。两者以 devLoopId 关联后,dump 既能还原“做了什么”,也能还原“做到哪一步”——这正是 §6 门禁能够机检的前提。
有了双轨事件流,就可以对整场会话做事后机检。sse-devloop-gate.ps1 消费 SSE dump,执行 11 项断言,退出码 0 = PASS / 1 = FAIL,可直接挂进 CI 或 Trae hook:
# | 检查项 | 断言 |
|---|---|---|
1 | 有工具调用 | 会话内至少出现一次flow_tool_call |
2 | 阶段齐全 | 由devStage/phaseId/toPhaseId三来源统计,覆盖全部研发阶段 |
3 | 先读日志 | COLLECT_LOGS事件早于改码事件(先取证后动手) |
4 | 有改码 | apply_patch/write_source≥ 1 |
5 | 有编译 | compile≥ 1 |
6 | 有验证 | run_tests/agent_execute≥ 1 |
7 | 有证据产物 | flow_artifact> 0 |
8 | 有用户验收 | human_confirm(DEV_ACCEPT)> 0 |
9 | 验收后完成 | completeAt > confirmAt(先验收、后收尾) |
10 | 无孤儿 running | 无未闭合的 running 事件 |
11 | 摘要可读 | 无“已完成一步操作”式通用字幕 |
门禁的价值要用反面样本来验证。把前文提到的真实历史会话 dump 喂给门禁脚本,结果绝大多数检查项不通过——日志读了很多次,但改码、编译、测试、验收四项关键断言全部为零:

图 5:历史会话 vs 闭环目标的关键工具调用形态(示意,基于真实 dump us1-drive-2026-10-16 复盘;单位:次)
而按 §4 的阶段契约构造正确事件序列后,同一脚本输出 PASS。门禁不是摆设:它把“闭环”从一句口号变成了 11 条可机检的断言。第 11 项“摘要可读”尤其值得一提——它针对的是另一类隐性失败:每一步都有事件、但最终字幕是一句放之四海皆准的“已完成一步操作”,用户依然不知道发生了什么。
证据产物(diff、构建日志、测试报告)以 flow_artifact 事件落档,并回写需求单的 lastEvidenceId。验收卡片展示的不只是“是否通过”,而是证据引用链:需求单 → 补丁 → 构建 → 测试 → 验收结论,逐环可点开复查。
把改码、编译、发布交给智能体,权限设计必须先行。沙箱调试面 /api/test/agent/dev/** 按 policy.v2 四清单组织,每个工具登记三级风险之一,并由策略上限(policyLevel ceiling)取交集约束:
风险级 | 含义 | 典型工具 |
|---|---|---|
READ | 只读探查,随时放行 | sandbox_read_logs/sandbox_search_code/read_source/dev_request_list |
WRITE | 改变沙箱状态,需 write 级策略 | apply_patch/compile/run_tests/dev_request_create/dev_request_claim/dev_request_verify |
DANGER | 影响面外溢(发布 / 外呼),full 级且人工在场 | 发布类操作 |
三条收口原则:
其一,写路径唯一化。需求台账只有 AgentDevController 一个写入方,阶段事件只有状态机一个发出方——任何绕过状态机的“自我宣告完成”都会在第 9 项门禁(验收后完成)上现形。
其二,上限取交集。会话策略(read-only / write / full)与工具风险级取交集,read-only 会话里即使 LLM 幻觉出 apply_patch,也会被沙箱侧拒绝而非静默执行。
其三,验收面留给人。终态只有一个入口——human_confirm。决策智能体可以把流程推进到 ACCEPT,但永远不能代替用户按下“通过”。
前七章搭建的是闭环的“骨架”;把骨架放进真实生产环境时,有两块最硬的骨头。它们指向同一个母题:沙箱必须既隔离、又与生产“同网共生”——孤立测试不可信,直接替换生产不可控。
agent 化之后的生产环境不是单体应用,而是一张agent 网络:业务智能体、平台服务、B 端接入层、调试面彼此发现与调用。沙箱要验证的不是“补丁能不能编译”,而是补丁在真实拓扑里的行为——路由、鉴权、依赖、并发,缺一样,验证结论就是“测了个寂寞”。三个冲突点:
冲突点 | 矛盾 | 后果(若无解法) |
|---|---|---|
可达性 | 沙箱要被生产侧的 agent 发现与调用,反之沙箱也要能触达生产依赖 | 沙箱成了“孤岛”,只能验证代码逻辑,验证不了集成行为 |
隔离性 | 沙箱的写操作、脏数据、未验证补丁不能外溢到生产 | 测试流量污染生产数据,故障面从沙箱扩大到全网 |
真实性 | 网络拓扑、域名、鉴权链路必须与生产一致 | 旁路环境测通 ≠ 生产可用,灰度前心里没底 |
解法回到 A / B / C 三方案里做取舍:
方案 | 思路 | 评价 |
|---|---|---|
A · 整包镜像替换 | 沙箱与生产对齐同基线,整包替换某一实例 | 弃重、慢、风险高;替换期间该实例容量受损,回滚只能靠整包 |
B · B端 domain 路由 + 细颗粒切流 | 沙箱以「灰度域身份」(如finance#gray)向 B 端路由注册:domain 一级分流,再按用户 / 会话 / 功能开关做微小颗粒细分 | 选 B沙箱成为生产 agent 网络的“一等公民”——并行浮现、白名单隔离、秒级上下线 |
C · 旁路直连 | 沙箱独立端点,测试工具直连 | 弃隔离最彻底,但永远“浮不出”生产网络,验证不到路由与集成 |

图 6:B 端 domain 路由 —— 沙箱灰度域并行浮现,细颗粒切流回用户,验收通过即切全量
八段闭环的 ACCEPT 阶段原本只是一张“验收卡片”;但真实发布要求用户在真实流量下测试。解法是把 ACCEPT 与 B 端灰度路由焊在一起——验收阶段即灰度阶段:
devAgent 闭环动作 | B 端路由动作 | 用户侧所见 |
|---|---|---|
VERIFY通过 +dev_request_verify(passed=true) | 注册灰度路由finance#gray(含细颗粒白名单) | — |
ACCEPT= 灰度实测 | 提需人 / 白名单会话被路由到灰度实例 | 用户在真实入口使用“带补丁的服务” |
human_confirm(passed=true) | 灰度域切全量(发布) | 全量用户无感升级;需求单置 CLOSED |
验收驳回 | 注销灰度路由(秒级回滚) | 需求单 REOPENED,闭环回LOCATE |
这一步为什么关键
用户验收的不再是截图与口述,而是带补丁的真实服务;切流颗粒度到会话级;回滚等价于“注销一条路由”。发布的最小单元从“整包”降到了“灰度域”,风险面随之从全网收缩到白名单。
回头看,B 端 domain 路由与细颗粒切分本质上是架构预先留出的接缝,而 devAgent 八段闭环恰好按这条接缝生长:证据链挂在需求单(§3)、阶段推进挂在状态机(§4)、灰度挂在路由表(本节)、验收挂 human_confirm。研发链路与架构不是两套体系,而是同一张拓扑上的两种视角。这也是沙箱基线必须对齐生产(JDK25 / jakarta 升级,见 §9)的根本原因——灰度实例要成为生产网络的公民,字节码与依赖基线必须一致。
模块 | 交付物 | 验证方式 |
|---|---|---|
决策智能体 | DevLoopService状态机 + 阶段契约注入(PromptEngine) | 单测:happyPath / buildFailure / verifyFailure / acceptanceRejected / financeEntryIsNotAffected 等 |
事件面 | SSEphase_*事件 + 工具事件 dev 字段(events.contract.json) | 契约测试 + dump 回放 |
验收回环 | requests台账 API +dev_request_*四工具 +human_confirm(DEV_ACCEPT) | requestLifecycle状态机单测 |
前端 | chatnext 工具卡阶段标签 + dev-agent 入口卡片 | 契约对齐(payloadKeys 登记) |
门禁 | sse-devloop-gate.ps111 项断言 | 真实历史 dump 复盘(暴露差距)+ 构造序列(PASS) |
回归状态:代码侧测试全绿(ooder-pro 15 + ooder-test 5)。沙箱 8081 的 JDK25 基线升级(A 路线)正在收尾——新沙箱包已完整构建,仅余 A2UI 模板仓库的 JPA 装配适配;部署完成前,线上 8081 仍以旧基线健康运行,新调试面暂未对外(404)。
P1:四类 flow_artifact(日志摘录 / diff / 构建 / 测试)由执行侧自动生产;全部写路径统一收口到 policy.v2;线上日志只读适配器(不依赖沙箱部署即可取证);B 端灰度路由落地——沙箱灰度域注册 / 细颗粒切流 / 切全量发布 API(§8 方案 B 的工程化)。
P2:harness 渲染统一(多前端同一套阶段组件);断线重连后按 devLoopId 补发阶段快照;门禁从“事后审计”前移为“事中拦截”(关键断言不满足时阻止进入下一阶段)。
回头看 §1 的那个问题——“资金日报流水重复”——在这套体系里,它的完整旅程是:一句话立案 → 状态机护航下的取证 / 定位 / 改码 / 编译 / 验证 → 证据链交付 → 用户一键验收。决策智能体确保每个阶段真实发生,devAgent 三体确保每个动作有人执行,业务智能体确保领域逻辑不被惊扰。
更长远看,这套“状态机 + 事件 + 门禁”的护栏设计,同样适用于其他需要自治的智能体场景:凡是让 LLM 自主行动的地方,都需要一个不带 LLM 的裁判。当护栏足够可靠,护航就可以逐步放手——这才是“AI 自助协作”从演示走向生产的路径。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。