opus 按 atlas 标准产 4 图落 docs/agent-specs/assets/,fable 终审(结构良构+viewBox 自适应/最复杂的双路激活图 qlmanage 渲 PNG 亲看无溢出配色准/设计档 diff 零 prose 改): - cfgctl2-01 配置中心分层与分工(yudao 治理⊕Nacos 路B、基建vs业务分工、prompt 出 Nacos 进 MySQL) - cfgctl2-02 双路激活编排(sanity 前置→beginActivating 快照→路A PATCH∥路B publish→markVersionActivated→补偿) - cfgctl2-03 激活一致性状态机(草稿→待审→ACTIVATING V30 持久态→已激活;recoverActivating 幂等/双恢复语义) - cfgctl2-04 漂移对账三态(IN_SYNC/DRIFT/不可达;8s 容忍窗;篡改→检出→重推→收敛闭环) 接入阶段二设计档 §2/§3.2/§3.3/§3.6 四处 SVG 占位为真实内嵌+图清单同步(11增8删,零 prose 改)。 atlas 复验全过(界内/良构/脚注/转义)、docs-gate 绿。W-DSGN 只剩旧档 SVG(plan①顶图/quality/重推演,非阻塞)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 KiB
date, topic, status, sot-impact, 上级, 关联, 图清单
| date | topic | status | sot-impact | 上级 | 关联 | 图清单 | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-07-02 | 配置控制面-阶段二 | 已实施(2026-07-04,fable 编排·opus 执行·逐波终审)· 创始人已拍方向 B(维持 yudao⊕Nacos;prompt 移出 Nacos→MySQL 版本行,2026-07-02)· Codex+Opus 双评审 11 条已回并全修收口(2026-07-02) | 修订 生成引擎运行时(SoT §5.2 管理面配置管理 / §5.8 配置三类"受治理发版" / ADR-4 配置热取 / A13 配置注册表:把 GitOps/Langfuse 式口径收敛为 yudao 配置中心版本化(版本账本落 MySQL 版本行)+ 双路激活,与上级设计 §6 pending 同一收口面,已回写(2026-07-03,W-DSGN doc-sync);对上级设计四处口径 + SoT 回写的回调挂账见本档 §7) | docs/agent-specs/2026-06-30-配置控制面一次性按序实现-设计.md |
|
|
配置控制面 · 阶段二:yudao 配置中心 · 功能设计
0 一图看懂
(图0 概览 SVG 待补)
- 核心思想:阶段一已经让生成配置能热改——prompt/模型/参数经
/agent+/sessions由 AgentScope 每 POST 现装配、下一次生成即生效,预算/门阈值经 genconfig 从本地 YAML 热取。但热改缺一层治理:改动没有版本历史、没有归因、不能一键回滚、没有激活闸。阶段二在这层热配之上补一个版本化治理层:创始人在 game-cloud 的 yudao 侧编辑一组生成配置,走"改 → 定版 → 审核挂点 → 激活",激活时双路下发——prompt/模型/参数这一路 PATCH 进 AgentScope Service(写它的 Redis,下一次 POST 现装配即生效),预算/门阈值这一路 publish 进 Nacos、由 worker 的 genconfig 经 Nacos 热读投影。版本历史、回滚、diff 落 yudao 侧的 MySQL 版本行(prompt 正文随行存MEDIUMTEXT、不进 Nacos——Nacos 单配置默认上限 100KB,扛不住会长大的 prompt,创始人已拍);Nacos 收窄为预算/门阈值那一路的生效下发通道;鉴权、creator 归属、审计、审批全部复用 yudao 原生。 - 边界:管业务生成配置的版本化治理(prompt/context/max_iters/模型/参数/预算/门阈值);基建配置(game-cloud 的 Spring 配置、Sentinel 流控规则、RocketMQ 参数)不进本治理层——那条是阶段〇已接的 Nacos SCA 原生通路,运维改、即时热生效。BPM 审批放量后启用(架构预留挂点,MVP 由创始人本人直接激活);配置编辑 UI 属阶段三;观测属阶段四;机制级激活原子性留放量后(MVP 取静默期语义)。
- 成功:本会话手改过的每个旋钮(模型 M3↔glm-5.2、预算 ¥10↔¥49、max_tokens、prompt)都能在配置中心改一版、激活、下一次生成即生效、一键回滚,并可查谁在何时改了什么、diff 是什么;一组协同旋钮作一个配置集一次激活;激活失败、Service 不可达、配置漂移都有明确兜底。
1 意图与目标
1.1 意图
配置控制面这条线的痛点在上级设计里已经讲透:本会话调一款便宜档游戏,四个旋钮里三个要改代码重启,而且是在 live worktree 上 SSH 手敲。阶段〇到阶段一②把"能不能热改"这半解决了——生产基建三件套自托管进 mini-infra,护城河续修/软预算/门判做成 AgentScope 洋葱里的 middleware,cheap 与 tier2 两档都归并到同一套 Service /chat,prompt/模型/参数经 /agent+/sessions 每 POST 现装配、预算/门阈值经 genconfig 从 generation.yaml 热取。
剩下的是"改动能不能管"这半。今天改一个旋钮,要么直接 PATCH Service、要么 scp 一份新的 generation.yaml 上去,两条路都没有版本账本:改错了没有上一版可回滚,改过了查不出是谁在什么时候改的、和上一版差在哪,更没有一个"激活"的闸把"编辑中"和"已生效"分开。放量后这些配置要过审批才能上生产,现在连挂审批的地方都没有。阶段二就是把这层治理补上,让配置从"能热改的散落旋钮"变成"可版本、可回滚、可归因、可审批的受治理资产"。
还有一个阶段〇留下的 follow-up 一直悬着:nacos_hotconfig.py 在阶段〇 D2 已经写好并单测绿了,但没有接进任何 middleware——预算/门阈值现在还是 genconfig 从本地文件读。这条线不接上,配置中心对预算/阈值那一路的"激活"就落不了地。阶段二把它接上,让这些参数真正从 Nacos 热读,从"本地文件热取"升级为"配置中心版本化 + 集中热推"。
1.2 目标
- 配置即数据、版本可管:每一组生成配置进配置中心,能改一版、激活、回滚、看 diff,逐项覆盖本会话手改过的全部硬编码点。
- 改动可审计、归因到人:编辑者即 yudao 登录用户、落进
creator,写操作受权限门约束,每次改动与激活随版本可查,审计复用 yudao 原生操作日志。 - 双路激活、以激活版为权威:prompt/模型/参数经 PATCH、预算/门阈值经 Nacos 热读,两路由同一个激活编排统一驱动;配置中心记"当前激活版"为权威,Service 与 worker 是它的生效投影。
- NacosHotConfig 接线落地:genconfig 的读取后端从本地
generation.yaml提升到 Nacos(NacosHotConfig 作热源、本地 YAML 与内置默认作回落),middleware 的调用面一行不改。 - 分工边界清晰:基建配置走 Nacos SCA 原生通路、不经本治理层;业务生成配置以 yudao 为治理与版本账本、只以 Nacos 作路 B 生效下发;两者在 Nacos 上以命名空间/分组隔离。
- 失败模式有兜底:激活是一批非原子写,须幂等、失败即整批判未生效可重试;Service 不可达时保留上一激活版;配置漂移可检测并一键重推。
2 边界
进控制面的判据沿用 SoT §5.8 的配置三类;本阶段只碰其中"热配置策略类"里属于生成业务的那部分,并给它加治理。
| 进阶段二 | 不进 | |
|---|---|---|
| 业务生成配置(受治理版本化) | prompt / system_prompt、压缩比例、tool_result 截断、max_iters、模型、协议 type、credential 引用、max_tokens、thinking、reasoning_effort、temperature、软预算目标、门阈值档 | — |
| 基建配置 | — | game-cloud Spring 配置、Sentinel 流控规则、RocketMQ 参数、服务发现——阶段〇已接 Nacos SCA 原生通路,运维改、即时热生效 |
| 硬代码机制类 | — | 门断言逻辑、熔断机制本体、续修机制、基线硬门强制、那几条铁律 |
几条明确的划界:
- BPM 审批放量后启用。上级设计已定:放量前创作页置灰、配置改动者是创始人本人、属非关键即时改。所以阶段二把审核落成状态机里的一个挂点(见 §3.2),放量前创始人角色从"草稿"直接到"激活"、审批自动通过;放量后打开
huijing-module-bpm审批流,同一状态机不改。不在本阶段验证真实审批流。 - 配置编辑 UI 属阶段三。阶段二只交付后端治理层、版本化、激活编排与 NacosHotConfig 接线;编辑表单由阶段三在 game-admin 落地,消费 AgentScope 的
/agent/schema。 - 观测属阶段四(Studio + genMonitor)。
- 机制级激活原子性留放量后。一个配置集的多次 PATCH/publish 机制上非原子,MVP 取静默期语义规避(单操作者 + 创作页置灰,blast radius 小);真机制级原子(epoch 戳)留放量后。这条沿用上级 §3.3,不在本阶段重推。
3 方案
3.1 配置中心的两半:yudao 治理与版本账本 ⊕ Nacos 生效下发
配置中心把职责劈成两半,各用现货、不自建下发机制。yudao 管治理与版本账本:编辑的鉴权、creator 归因、放量后的 BPM 审批、激活状态机、操作审计,连同每次定版落下的版本行——权限、审计、CRUD 全是 yudao 作为成熟后台框架的原生件,game-cloud 是它的 fork,直接复用。Nacos 管生效下发:长轮询推送、客户端磁盘快照兜底、命名空间隔离——这些是 Nacos 成熟的下发通道,阶段〇已自托管进 mini-infra,预算/门阈值那一路的热推全靠它(路 B,见 §3.3)。
版本账本落 MySQL 而不落 Nacos,基于三个约束确定,第一个就是创始人拍 B 时点名要解的:
- prompt 正文放不进 Nacos。Nacos 单配置内容默认上限 100KB(容量保护
defaultMaxSize)。prompt 的实际规模有两个量级:Python 角色源文件(cheap_roles.py30499B、tier2roles.py24539B,含多角色内置 prompt 与配套代码,非单条运行时 prompt)与外置 prompt 文件(contracts/prompts/04-config/cheap-system.md14695B 为当前最大单条、tier2 最大单条writer-system.md4970B)。单条运行时 prompt 现在离 100KB 尚有余量,但配置集版本要把一档的 system_prompt 连同 few-shot、多角色配置一起收进一行,内容会随 few-shot 与角色规模增长向上限爬。把最大、改得最勤的旋钮放在接近 Nacos 单配置上限的位置,不如一开始就换地方。 - 定版要原子。版本行(元数据 + 全量内容)同库同事务一次落;内容若在库外,定版就成了跨系统写,要补超时/重试/补偿——治理层最高频的动作不该是分布式事务。而 prompt 一旦出 Nacos,版本若劈成"prompt 在 MySQL、小参在 Nacos"两处,一次定版跨两系统、必漂移,所以账本整体落 MySQL。
- diff 与检索要就地。版本 diff、按内容查历史都是版本行上的本地操作;内容在外部系统则每次 diff 都要来回取对象。
prompt 内容列定 MySQL MEDIUMTEXT,不选 MinIO——在此确定、不留开放项。MinIO 在本项目的设计定位是成品源工程归档的后端 seam(store.py 的 BackendStore 声明 MySQL manifest + MinIO 源文件、内容哈希、id+version 寻址,目前是接口占位,真正在用的是 LocalFsStore 本地文件实现),那是"多文件源树"的形状;prompt 是"一段文本字段"的形状,几 KB 到几十 KB,MinIO 的价值(大对象、二进制、流式)一样都用不上,却把定版原子性与就地 diff 全破掉,还把备份从"一份 MySQL dump = 治理+内容一致快照"劈成两个系统。列型取 MEDIUMTEXT(16MB)而非 TEXT(64KB):30KB 级 prompt 加 few-shot 有越过 64KB 的现实可能,MEDIUMTEXT 给足余量且无额外成本。版本表的实现约束一并明确:表用 InnoDB + DEFAULT CHARSET=utf8mb4;prompt 正文列不建全文或普通索引(只做整行读写、不参与检索),另存 content_hash 与 content_bytes 两列供快速比对与容量核对(content_bytes 按 utf8mb4 字节数计,防混淆字符数与字节数);备份恢复以包含版本行正文的 MySQL dump / binlog 为口径,而非只抽结构不抽内容;两版 diff 在应用层做。
对上级设计"自建版本表 = 重造 Nacos"的判定要诚实交代:那条针对的是版本化下发机制(推送/快照/回滚/灰度),这部分仍然全买——路 B 热推是 Nacos、基建配置通路是 Nacos SCA;挪回 MySQL 的只是版本行这张普通 yudao 业务表,借的全是 yudao CRUD/分页/审计原生能力,没有一行自建下发机制。此口径回调上级 §3.3 等处,挂账见 §7。
于是四方各就各位:
- yudao(game-cloud):配置集元数据 + 版本行(全量内容含 prompt,
MEDIUMTEXT)+ 当前激活版指针 + creator + 状态机 + 审批挂点 + 操作审计 + 激活编排。治理与账本权威。 - Nacos:路 B 生效下发通道——激活时 publish 预算/门阈值到生效 dataId,worker 长轮询热读;不再承版本账本(生效 dataId 上的配置历史只是运维排障的顺带留痕,不是权威)。
- AgentScope Service 的 Redis:路 A(prompt/模型/参数)生效投影(激活时 PATCH 写入,每 POST 现装配读出;Service 只认自己的 Redis、不认 Nacos,所以路 A 生效只能靠 PATCH)。
- worker 进程内 genconfig:路 B 生效投影(经 NacosHotConfig 从 Nacos 热读)。
账本单点(MySQL 版本行)、投影两处(Redis / Nacos 生效 dataId)、消费两路(Service per-POST / middleware 构造)——权威与投影的关系清晰,§3.6 的漂移对账就建在这上面。
3.2 配置对象与生命周期:改 → 版本 → 审核 → 激活 → 回滚
治理的单元是配置集,不是单个旋钮。一款档位的生成行为由一组协同旋钮共同决定——便宜档配置集 = 便宜档 agent 的 system_prompt + M3 模型 + 参数(max_tokens/thinking)+ 软预算目标 + 门阈值;tier2 富游戏配置集 = tier2 agent 的 prompt + glm-5.2 + 参数 + 预算 + 门阈值。以配置集为激活单元,才能让一组本该一起变的旋钮一次激活、不出现"新 prompt 配旧模型"的半拉子状态。
一个配置集走这样一条生命周期:
stateDiagram-v2
[*] --> 草稿: 创始人在 yudao 编辑一组旋钮
草稿 --> 待审: 定版(版本行落库,creator/时间随行)
待审 --> 已激活: 审核通过(MVP 创始人角色直接激活 · 放量后走 BPM 审批)
已激活 --> 待审: 编辑出新版本
已激活 --> 已激活: 回滚(取历史版本行,重放激活)
已激活 --> 已归档: 被更新版本取代
待审 --> 草稿: 驳回
几个关键点。激活之前 Nacos 完全不被触碰:草稿与版本行都是 yudao 库内数据,配置内容到"激活"才第一次离开治理库——这天然堵死了"草稿提前生效"的口子(Nacos 的配置是 publish 即被监听者读到的,预算/阈值若提前进 Nacos 就绕过了激活闸)。激活是独立的闸:它把选定版本的内容双路下发(见 §3.3),并把"当前激活版指针"指过去。回滚就是重放激活:取历史版本行的内容,走一遍激活动作,幂等。归因与审计随生命周期自然沉淀:定版记 creator 与时间,激活/回滚是一条 yudao 操作日志,diff 是两条版本行的就地比对——全是 yudao 原生能力,不新造。
3.3 双路激活:PATCH 路 ⊕ Nacos 热读路
激活一个配置集,编排层从选定的版本行取内容,按旋钮性质分两路下发:
flowchart LR
ACT["yudao 激活编排<br/>(取选定版内容)"]
ACT -- "路A:prompt/context/max_iters" --> PA["PATCH /agent"]
ACT -- "路A:模型/参数/thinking/temp" --> PS["PATCH /sessions"]
PA & PS --> REDIS[("AgentScope Redis<br/>当前配置")]
REDIS -- "每 POST 现装配(热)" --> GEN["/chat 生成回合"]
ACT -- "路B:软预算目标/门阈值" --> PUB["publish Nacos 生效 dataId"]
PUB --> HC["worker NacosHotConfig<br/>长轮询推送热读"]
HC --> GENC["genconfig 门面"]
GENC -- "middleware 构造时读" --> GEN
ACT -- "记当前激活版 + 操作日志" --> YU[("yudao 治理表")]
- 路 A(prompt/模型/参数):激活编排把内容 PATCH 进 AgentScope Service 的
/agent(system_prompt / 压缩比例 / tool_result 截断 / max_iters)与/sessions(模型 / 协议 type / credential / max_tokens / thinking / reasoning_effort / temperature)。/sessions的 PATCH 在顶层是部分字段更新语义(exclude_unset,只改本次带上的顶层字段),但模型与参数都封在chat_model_config这一个子对象里,而该子对象一旦出现即被整体替换——顶层exclude_unset不深合并它内部的parameters,且type/model是必填字段。所以激活编排改任一模型参数(哪怕只动 max_tokens 或 thinking)都必须从版本行组装出完整的chat_model_config,把type/credential_id/model/parameters全带齐,不能只发局部对象,否则未带上的字段会被清空。写进 Service 的 Redis 后,下一个 POST/chat现装配即生效。 - 路 B(软预算目标 / 门阈值):这些参数不在
/agent+/sessions里,它们是 worker 的 middleware 在构造时从 genconfig 读的。激活编排把内容 publish 进 worker 监听的 Nacos 生效 dataId,NacosHotConfig 经长轮询推送热读进进程内,genconfig 从它取到新值,下一次 middleware 构造时读到。这里有一处 dataId 粒度要定死:cheap-worker 与 tier2 gen-worker 是两个独立进程、各接一个 NacosHotConfig,两档 key 命名本就不同(cheap 用cheap_rmb_hard_limit、tier2 用rmb_hard_limit)。路 B 生效 dataId 按档隔离——cheap 与 tier2 各用一个专属 dataId,而不是共用一个gen-hot-params整体替换;否则激活一档的配置集会把另一档的 key 从该 dataId 上冲掉(publish 是整内容替换,worker 初读/重启即缺 key)。激活哪一档,就只 publish 那一档的 dataId。
为什么静默期语义在这个模型下几乎是自然的:两路生效都发生在"下一次生成"的边界上——路 A 的 Redis 在 /chat 进 session 锁之前读、路 B 的 genconfig 在 middleware 构造时读,正在跑的那一局早已经读过配置了,新配置只影响此后新起的 POST。所以并发引起的撕裂窗口只剩一个:一个配置集的多次 PATCH/publish 之间非原子,理论上并发一局能读到"路 A 新、路 B 旧"的组合。MVP 单操作者 + 创作页置灰把这个并发窗口的影响面压到零,取静默期语义即可;真机制级原子留放量后(上级 §3.3)。
还有一个撕裂窗口不是并发引起、静默期也兜不住,要如实交代:激活自身中途失败。双路投影是"写即被读"——PATCH 写进 Redis 即被下一 POST 装配,publish 进 Nacos 即被 worker 长轮询推送读到,两路之间没有分布式事务。若激活先成功下发一路、再在另一路失败,已发出的那一路在投影层已经生效、不会自动回退;此刻"当前激活版指针不前移"只保住账本语义(见 §3.6),保不住投影。所以本设计不写"绝不留半生效态"这种投影层做不到的强承诺,而是要求激活编排在任一路失败时立即补偿:把已成功下发的那一路重推回上一激活版的内容(配置中心持有上一版,可做),不等定期对账。激活编排对此的硬要求是:一批下发须幂等、任一步失败即整批判未生效、立即把已成功路补偿重推回上一激活版、再标为可重试(见 §3.6 失败模式)。
3.4 NacosHotConfig 接进 genconfig(阶段〇 follow-up 落地)
预算/门阈值那一路的热读,是把 nacos_hotconfig.py 接进既有的 genconfig 门面,而不是改 middleware。现状链条是 middleware → genconfig.get("budget", "rmb_hard_limit"),genconfig 现有取值优先级是 env 覆盖 > 外部 generation.yaml > 内置默认 > 调用方 default;它已经是一个带缓存、带 best-effort 兜底、带内置默认、支持 env 单点覆盖的进程内配置门面,middleware 到处在调它。阶段二只换 genconfig 的读取后端:把 NacosHotConfig(配置中心激活投影)接成新的热源。
接入后 Nacos 热源与 env 的相对优先级必须定死、不能留空:Nacos 热源 > env > 外部 YAML > 内置默认 > 调用方 default。理由是配置中心才是权威,机器上遗留的实验 env 不应静默压过激活值——否则会出现"配置中心已激活、机器上却不生效"的难查故障(操作者在配置中心看不到机器上的 env)。env 由现状的最高优先级降为"Nacos 取不到时"的回落层之一,保留其一次性实验逃生舱的作用;同时把 env 覆盖纳入 §3.6 漂移对账口径:对账发现某旋钮实际生效值来自 env 而非当前激活版时,一并告警。
这样接线有三处好处。其一,middleware 一行不改:genconfig.get(...) 的调用面完全不动,CircuitBreakerMiddleware 的软预算目标、门判的阈值都自动吃上 Nacos 热读。其二,回落链天然是 fail-safe:Nacos 不可达时 NacosHotConfig 自带本地磁盘快照兜底,再不行 genconfig 回落 env、本地 YAML 与内置默认,生成主链不被配置中心连累。此外 NacosHotConfig 侧要补一道值 fail-safe:它现在只挡空/脏 JSON、不校验业务范围,一个合法 JSON 但语义非法的值(负预算、超小超时)会直接 update 进热参;要改成遇语义非法值时保留旧值 + 告警、不覆盖当前热参,把错值挡在生成链之外(激活前的正向值域校验见 §3.6)。其三,尊重既有 working code:genconfig 是雏形阶段就在的进程内配置层(SoT §5.2 提到的那个雏形),阶段二是把它的源从本地文件提升到集中版本化,不是推翻重造。
命名空间上有一个已知的坑必须先解:Nacos 默认公共空间的 namespaceId 是空串,不是字面量 "public"(阶段〇 follow-up 明确)。业务生成配置的 dataId 要么显式放进一个专门的命名空间(用真实 namespaceId、不是 "public" 三个字母),要么明确用空串走默认空间;写侧(yudao publish)与读侧(worker NacosHotConfig 订阅、game-cloud SCA)必须对齐同一个真实 namespaceId,否则激活写进一个空间、worker 在另一个空间读、配置永远推不到。
这不止是一句提醒,而是一项阶段二必修前置任务:现状代码里 namespace 口径已经与"空串"这一事实不一致——Java game-cloud/huijing-server/.../application.yaml 两处写 namespace: public、Python tier2/gen-worker/service/nacos_registry.py 默认 os.environ.get("NACOS_NAMESPACE", "public")、test_nacos_registry.py 还断言 "public"。若阶段二写侧按空串、读侧沿用 public,路 B 会静默推不到。所以在接线动作之前先统一口径:走默认公共空间就把该配置项置空/删除、而不是填字面量 "public",并同步改 Java 配置、Python 默认值、对应测试断言与凭据档(docs/内网凭据与端点.md)。这一步排在步骤 4 接线之前(见 §4)。
3.5 分工边界:基建配置 vs 业务生成配置
配置控制面用了 Nacos,阶段〇的基建也用了 Nacos,两者必须划清,否则治理层会误伸手去管运维配置、或运维改配置时绕过了该有的审计。判据是一句话:谁改、改了要不要治理。
| 基建配置 | 业务生成配置 | |
|---|---|---|
| 例子 | game-cloud Spring 配置、Sentinel 流控规则、RocketMQ 参数、服务发现 | prompt / 模型 / 参数 / 软预算 / 门阈值 |
| 谁改 | 运维 / 开发 | 创始人 / 运营 |
| 要不要治理 | 不要——即时热生效即可 | 要——版本 / 审计 / 审批 / 可回滚 |
| 通路 | Nacos SCA 原生(spring-cloud-starter-alibaba-nacos-config,dataId 按应用名,@RefreshScope 或重启) |
yudao 治理层(MySQL 版本行为账本)→ 激活时 PATCH Service + publish 生效 dataId |
| 消费方 | game-cloud 各 Java 服务 | AgentScope Service(PATCH)+ Python worker(NacosHotConfig) |
两者共用 Nacos 这套下发基础设施,但治理路径分开、命名空间或分组隔离。基建配置那条阶段〇已经接好、跑在生产,阶段二不碰它;阶段二只在 Nacos 上开一块属于业务生成配置的地(路 B 生效 dataId),由 yudao 激活编排独占写入。
3.6 失败模式与兜底
激活是一批跨系统的非原子写(PATCH Service + publish Nacos + 更新 yudao 指针),这里的失败模式要正面处理,不能让配置中心把生成链路带崩。
- 激活失败即时补偿(账本语义,不作投影强承诺)。一批下发里任一步失败(某个 PATCH 返错、Nacos publish 超时),整批判未激活:yudao 当前激活版指针不前移,保留上一激活版为账本权威。但双路投影是"写即被读"、两路间无事务,已成功下发的那一路在投影层已经生效——指针不前移只保住账本、保不住投影。所以失败处置不能停在"判未激活",必须立即补偿:把本批已成功下发的那一路(PATCH 或 publish)重推回上一激活版的内容,再标为可重试。补偿与重放都要幂等(同一版本重推无副作用)。这样账本与投影一起收敛回上一激活版,不留"路 A 新、路 B 旧"的持续撕裂;而不是把"绝不留半生效态"写成投影层做不到的强承诺、再靠定期对账慢慢兜。
- MySQL 不可达 / 指针写失败。激活还含"读版本行、写激活尝试、前移当前激活版指针、记审计"这几步 yudao 库内写,两种情形分开处置:(a) 起步读版本行时 MySQL 不可达——直接拒绝激活、不发起任何下发,生成继续用旧投影。(b) 三路投影(PATCH
/agent、PATCH/sessions、publish Nacos)都成功、最后前移指针或写审计时 MySQL 失败——此刻投影已是新版、账本仍指旧版,是确定漂移。处置:激活状态先持久化为ACTIVATING(下发前落、全部成功后才转ACTIVE),恢复任务按激活尝试幂等地二选一执行到底——要么补提交指针(投影既已是新版,对齐账本),要么补偿重推旧版回投影并回滚该尝试,不停在中间态。 - 配置值本身非法(合法但语义错)。激活链是"改 → 定版 → 激活",对预算写错单位(元/分混)、预算填成天文数、超时给到过小、
max_tokens ≤ thinking_budget、模型协议 type 与 credential type 不匹配这类类型正确但语义错的值,若无校验会一路激活、下一局生效——预算是成本红线旋钮,配错直接让软预算失效、成本超支。所以激活前加一道值域 sanity 校验:预算为正数且落在合理区间、单位固定;超时有下限;max_tokens > thinking_budget;模型协议 type 与 credential type 匹配;prompt 非空。校验不过直接拒绝激活。NacosHotConfig 读侧再兜一道(§3.4:语义非法值保旧值 + 告警);仍漏过的错值挂阶段四观测(成本异常告警)事后发现。 - Service 不可达。路 A 依赖 Service 在线才能 PATCH。Service 不可达时激活判失败,按上面的补偿口径处置(路 A 尚未成功、无需补偿路 A;若路 B 已 publish 成功则补偿重推路 B 回旧版),保留上一激活版;Service 上跑的还是它 Redis 里的旧配置,生成不中断,只是新配置没生效。Service 恢复后重放激活即可。
- 配置漂移检测。Service 的 Redis 投影可能与"当前激活版"漂移——Service 重启若 Redis 未持久化会丢配置回落缺省、或有人绕过治理直接改了 Redis/Nacos、或某次激活部分失败没补偿干净、或机器上遗留 env 压过了本该生效的激活值。配置中心提供一个对账:激活后与定期,拉 Service 的
/agent(AgentScope 无单个 GET/agent/{id},用列表端点按 id 过滤)+/sessions当前配置比对当前激活版的路 A 内容;拉 worker 的生效投影比对路 B 内容(含 env 覆盖口径:实际生效值来自 env 而非激活版时也算漂移;Nacos publish→read 最终一致,读带 8s 容忍窗、避开刚激活的收敛中间态);对不上即告警,并支持一键重推(重放激活把投影拉回激活版)。这道对账是"配置中心为权威、Service 与 worker 为投影"这个定位的兜底闸,但不是失败的第一道防线——第一道是上面的即时补偿。 - 命名空间错配。见 §3.4:写读两侧的 namespaceId 必须是同一个真实值,默认空间是空串非 "public",且现状代码的
public字面量偏差要作前置任务先修。这条在接线时一次性核对确认,避免配置静默推不到。
4 步骤计划
(阶段序 SVG 待补)
阶段二自身按依赖分五步串行,另有一项 namespace 口径统一作为接线前置;阶段三(UI)、阶段四(观测)作为后续阶段划界在后,各自后续出 plan。
flowchart LR
S1[步骤1<br/>配置集建模<br/>+ yudao 治理骨架] --> S2[步骤2<br/>版本行与历史 MySQL<br/>回滚/diff]
S2 --> S3[步骤3<br/>双路激活编排]
S1 --> S4[步骤4<br/>NacosHotConfig<br/>接进 genconfig]
S3 --> S5[步骤5<br/>失败兜底<br/>+ 漂移对账]
S4 --> S5
S5 --> N3[阶段三 UI<br/>后续出 plan]
N3 --> N4[阶段四 观测<br/>后续出 plan]
前置 · namespace 口径统一(排在步骤 4 接线之前)
- 交付:把现状代码里的
namespace: public字面量偏差统一成同一个真实 namespaceId(走默认公共空间就置空、而非填 "public"),覆盖 Javaapplication.yaml(两处)、Pythonnacos_registry.py默认值、test_nacos_registry.py断言与凭据档docs/内网凭据与端点.md(见 §3.4)。 - 验证:写侧(yudao publish)与读侧(worker NacosHotConfig、game-cloud SCA)落在同一真实 namespaceId;既有服务发现/注册不回归。
- 风险:不先修这项,路 B 写读会静默错空间、步骤 4 接线无从验证。
- 落地(
b2b4d389):namespace 口径统一,Java/Python/测试/凭据档已对齐同一真实值。
步骤 1 · 配置集建模 + yudao 治理骨架
- 交付:在 game-module-aigc 落一个生成配置治理子域(复用 yudao 的鉴权/creator/审计框架,不塞进 infra_config 的 KV 表——生成配置是结构化配置集、不是键值对)。定义配置集的数据模型(一个配置集含哪些旋钮、映射到哪些
/agent+/sessions字段与哪些 Nacos 生效 key)、版本与当前激活版指针、生命周期状态机、creator 与操作审计。 - 验证:能建/改/定版一个配置集,creator 与时间随版本落库;权限门挡住无权写;状态机流转符合 §3.2。
- 依赖:yudao(game-cloud 已在)。
- 风险:配置集与 AgentScope agent/session 的字段映射要对齐
/agent+/sessions的真实 schema(消费/agent/schema),映射错则激活 PATCH 无效。 - 落地(
3ae34de1):配置集建模 + yudao 治理骨架 + 版本行(步骤 1、2 同提交,MySQL 治理子域 Flyway V29/V30)。
步骤 2 · 版本行与历史(MySQL)
- 交付:定版落版本行——配置集全量内容(prompt 存
MEDIUMTEXT+ 模型/参数/预算/阈值)与元数据同库同事务一次写成一行;历史列表、任一历史版取回、两版 diff;当前激活版指针只由激活动作前移(定版不动它)。 - 验证:定多版后能列历史、取任一版、diff 两版;定版是单库事务、无跨系统写;30KB 级 prompt 正文完整存取无截断,并加一个 64KB 边界样本(越过
TEXT上限、验证MEDIUMTEXT存取无截断)与 utf8mb4 字节数校验(content_bytes按字节而非字符计,含多字节中文/emoji 时口径正确)。 - 依赖:步骤 1。
- 风险:版本行只增不删,量级 = 人工定版频次(每天几次),多年不清理也只是几千行——留一个归档策略位即可,不做提前优化。
- 落地(
3ae34de1):版本行与历史/回滚/diff(步骤 1、2 同提交)。
步骤 3 · 双路激活编排
- 交付:激活动作先做值域 sanity 校验(§3.6),通过后把选定版双路下发——路 A PATCH
/agent+/sessions(模型/参数从版本行组装完整chat_model_config,§3.3),路 B publish 进 worker 监听的按档隔离生效 dataId;更新当前激活版指针 + 记操作日志;成组下发幂等、任一步失败即整批判未生效并立即补偿重推已成功路回上一激活版、再判可重试(见 §3.6);静默期语义(只对下一批新生成生效)。 - 验证:改任一旋钮 → 定版 → 激活 → 下一次生成生效;并发两局不读到路 A 新/路 B 旧的撕裂组合(静默期);回滚旧版即恢复;一批下发中途失败时指针不前移、已成功路被补偿重推回旧版、可重试;非法值(负预算等)在激活前被拒。
- 依赖:步骤 1、2;阶段一②(两档已归并 Service,PATCH 有落点)。
- 风险:激活一致性是本阶段最重的风险(§3.6);Service PATCH 与 Nacos publish 跨系统,须幂等 + 判未生效可重试。
- 落地(
72973532):双路激活编排;路 A/sessions复数端点修复22444fd5(早稿单数/session与真实路由不符)。
步骤 4 · NacosHotConfig 接进 genconfig(依赖上方 namespace 口径统一前置)
- 交付:genconfig 的读取后端提升到 Nacos,落地阶段〇 D2 建好未接线的
nacos_hotconfig.py,拆两件。(1) 新增 worker 启动期的 HotConfig 单例工厂:nacos_hotconfig.py明确只消费已建好的 v1NacosClient、不自建 client,所以工厂负责把 client 构造齐——server 地址、鉴权(账号密码)、NO_PROXY(内网直连绕系统代理,阶段〇同款坑)、真实namespaceId、group、按档隔离的dataId,再注入进 NacosHotConfig 并接进 genconfig。(2) genconfig.get 的取值链前端接热源:优先查 NacosHotConfig,再依次回落 env、本地generation.yaml、内置默认(优先级见 §3.4)。middleware 调用面一行不改。 - 验证:改 Nacos 生效 dataId → worker 数秒内经 genconfig 读到新预算/阈值(不重启);单测覆盖四条异常/回落路径——未 start(工厂没起 NacosHotConfig → genconfig 回落 env/YAML/默认)、Nacos 不可达(NacosHotConfig 磁盘快照兜底,再回落本地)、namespace 配错(写读不同空间时读不到,佐证前置任务的必要)、YAML 回落(热源无值时取本地 YAML 与默认);cheap 与 tier2 两档各读各的 dataId、软预算目标与门阈值都吃上热读。
- 依赖:阶段〇 Nacos + NacosHotConfig(已建);阶段一①(middleware 经 genconfig 读预算/阈值);namespace 口径统一前置。
- 风险:genconfig 现有缓存与 NacosHotConfig 的推送时序要理顺(别让本地 YAML 缓存盖住 Nacos 热值);回落链优先级(Nacos > env > YAML > 默认)要测到位。
- 落地(
344330a4):NacosHotConfig 接进 genconfig 作热源;worker 启动期 HotConfig 单例工厂入口装配d9def7d9。
步骤 5 · 失败兜底 + 漂移对账
- 交付:激活失败整批判未生效 + 保留上一激活版 + 可重试;Service 不可达兜底;配置漂移对账(拉 Service/worker 投影比对当前激活版)+ 一键重推。
- 验证:模拟 PATCH 中途失败/Service 不可达 → 指针不前移、生成用旧配置不中断、重放激活恢复;篡改 Redis/生效 dataId 后对账能检出漂移并重推拉回。
- 依赖:步骤 3、4。
- 风险:漂移对账要区分"真漂移"与"正在激活的中间态",避免误报。
- 落地(
a8dd5e50):失败兜底 + 漂移对账(对账读带 8s 容忍窗避开激活收敛中间态)。
后续阶段(划界,各自出 plan):阶段三 = game-admin 配置编辑面(浏览/改/定版/激活/回滚/历史/diff,表单消费 /agent/schema),依赖阶段二;阶段四 = 观测(Studio + genMonitor,trace 回放/成本对账/在跑监控),依赖 trace 事件流。两者不在本设计展开。
5 验证方式
- 配置即数据、版本可管:改 model/budget/max_tokens/thinking/prompt 任一 → 定版 → 激活 → 下一次生成生效;回滚即恢复;逐项覆盖本会话手改过的硬编码点。
- 归因与审计:每次定版记 creator/时间,激活/回滚有 yudao 操作日志,任意两版可 diff。
- 双路激活、静默期无撕裂:路 A PATCH、路 B Nacos 热读都生效;一组旋钮作一个配置集激活;并发两局不读到跨路撕裂组合。
- NacosHotConfig 接线:改 Nacos 生效 dataId,cheap 与 tier2 的软预算/门阈值经 genconfig 数秒内热读到新值、不重启;Nacos 不可达回落本地 YAML 与默认、生成不受影响。
- 分工边界:基建配置仍走 SCA 原生通路、不经治理层;业务生成配置的写入只由 yudao 治理层独占。
- 失败兜底:激活中途失败/Service 不可达时指针不前移、生成用旧配置不中断、重放可恢复;漂移可检出并重推。
6 风险与回滚
- 激活一致性(本阶段最重):一批 PATCH+publish 跨系统非原子、投影写即被读。缓解 = 值域 sanity 校验前置 + 幂等 + 任一步失败即整批判未生效并立即补偿重推已成功路回上一激活版 + 当前激活版指针只在整批成功后前移 + 保留上一激活版为账本权威(投影层撕裂由即时补偿收敛,不靠"绝不半生效"这种投影层做不到的强承诺);并发撕裂 MVP 由静默期规避,机制级原子(epoch 戳)留放量后。MySQL 指针写失败而投影已成功时,以
ACTIVATING持久态 + 幂等恢复对齐(§3.6)。回滚 = 配置中心永远能重放上一激活版。 - Service 不可达 / Redis 未持久化丢配置:激活判失败保留旧版;Service 恢复后由漂移对账检出并一键重推。Service Redis 的持久化策略需与运维确认(丢配置是否需 Service 启动即从配置中心拉激活版自愈,可作后续增强)。
- NacosHotConfig 接线破坏 middleware 读参:回落链(Nacos → env → 本地 YAML → 内置默认,优先级见 §3.4)是保命绳,先在两档单测里锁死整条回落链再上;真接线灰度——先只把一个低风险旋钮(如门阈值)切到 Nacos 热读、验稳再推预算目标。回退 = genconfig 后端切回纯本地 YAML(改一处开关)。
- 命名空间错配:默认空间是空串非 "public";现状代码的
public字面量偏差作前置任务先修(§3.4 / §4 前置),写读两侧 namespaceId 一次性核对对齐,否则配置静默推不到(阶段〇同款坑)。 - 配置集字段映射漂移:配置集到
/agent+/sessions字段的映射依赖 AgentScope schema,框架升版可能变;映射以/agent/schema为准、加一致性校验,别硬编码字段名。 - SoT 收口:本阶段落地后,须把 SoT 里 GitOps/Langfuse 式的旧口径收敛为 yudao 配置中心版本化(账本落 MySQL 版本行)——§5.2 管理面配置管理、§5.8 配置三类的"受治理发版"、ADR-4 配置热取、A13 配置注册表四处,与上级设计 §6 pending 是同一收口面,收口时一并回写,避免两份自称权威触 canonical 门;对上级设计本身的口径回调逐条挂账在 §7,由编排层另单收口、本档不动上级档。
7 收口涟漪清单(挂账,由编排层另单收口)
版本账本从 Nacos 挪进 yudao MySQL 版本行(§3.1)回调了上级设计已评审的几处口径。按创始人指令,本档不改上级档,在此逐条挂账;收口时随上级 §6 已挂的 SoT 收口单一并执行、不双开。
| # | 位置 | 现口径 | 应回调为 |
|---|---|---|---|
| 1 | 上级设计 §0 / §3.3 / §3.7"互补"行 | "Nacos 管版本化存储与下发(版本历史/回滚/diff/灰度/推送),取代自建 Flyway 版本表 + 薄读接口" | 版本账本落 yudao MySQL 版本行(prompt 100KB 上限 + 定版原子性所迫,§3.1);Nacos 收窄为路 B 生效下发;"消灭薄读接口"结论不变(NacosHotConfig 仍替它);"灰度发布"收窄为路 B 生效 dataId 层能力、MVP 不启用 |
| 2 | 上级设计 §4 阶段二节(交付/验证/风险) | "激活时把 active 版写进 Nacos 并 PATCH""Nacos 承版本/回滚/灰度/推送(取代自建 Flyway 版本表 + 薄读接口)" | 交付 = yudao 治理子域(版本行 + 状态机 + 激活编排)+ 双路激活 + NacosHotConfig 接 genconfig;验证/风险按本档 §4/§6 |
| 3 | 上级设计 §3.6 mermaid(UI→yudao→NACOS 写版本→激活 PATCH) | 版本写进 Nacos、激活自 Nacos 侧发出 | yudao(MySQL 账本)→ 激活编排双路下发(PATCH /agent+/sessions · publish 生效 dataId);图随文改 |
| 4 | 上级设计 §3.3(middleware 用 nacos-sdk-python add_listener 订阅)/ §3.7 生产图(Python middleware 直读预算/阈值)/ §4 阶段〇(middleware 接 nacos-sdk-python listener 读预算/阈值) |
middleware 经 nacos-sdk-python add_listener 直读 Nacos |
middleware 经既有 genconfig 门面读、NacosHotConfig(v1 add_config_watcher 长轮询推送)作 genconfig 热源(§3.4);middleware 调用面不变,底层由"直读 Nacos"改为"genconfig 门面 + 热源"。上级核心结论(middleware 从 Nacos 热读预算/阈值、fail-safe 靠 SDK 快照兜底)不变,回调的是实现路径口径;此偏差实由阶段〇引入、阶段二继承 |
| 5 | SoT 回写口径(架构图说 §5.2 / §5.8 / ADR-4 / A13;上级 §6 已挂"GitOps 口径"收口项) | GitOps / Langfuse 式热取叙述 | "配置中心版本化"的落点 = yudao MySQL 版本行 + 双路激活(本档 frontmatter sot-impact 申报);与上级挂的收口单合并执行 |
另挂一条知识面治理归属(2026-07-04 知识面审计 → 创始人 2026-07-04 已裁「进配置集」):知识件——品类评分尺 fixture(cheap-worker/fixtures/genre-rubrics/)、品类设计 skill、few-shot 金标——此前既不在本档「配置集」范围内、也不在其它治理面上(变更无版本定版、无审计回滚,fixture↔skill 内联副本双写只靠 rubric-sync-gate 人肉对账)。创始人 2026-07-04 决策6 裁定:纳入配置集受版本治理。这是本阶段的一个范围增量,须另出设计/plan 落地——关键设计点:① 三类知识件的配置对象建模(rubric fixture=结构化 JSON、skill=markdown、few-shot 金标=样本集),映射进配置集版本行;② 配置中心成为单一真源后,cheap-worker/fixtures/ 与 skill 内联副本降为投影,现行 rubric-sync-gate 双写对账门相应改造为「配置中心→投影」的单向下发校验;③ 生效通路(知识件不走 /agent PATCH、也非预算/门阈值那类小参 Nacos 下发,而是随 worker 读取的 fixture/skill 文件——生效形态需另定,可能经版本行导出到 worker 可读路径或 RAG 索引)。裁前落地按现范围(知识件不入),此增量排在阶段二五步之后、与阶段三 UI 同批或紧随。
8 实施工单(六要素 · 2026-07-04 排期,opus 自治领单)
目标:按 §4 交付「namespace 口径统一」前置 + 步骤 1–5,把生成配置升级为「yudao 定版 → 双路激活 → 数秒热生效」的受治理配置中心;完成即逐条兑现 §5 六条验证。阶段三 UI、阶段四观测不在本单。
事实指针:设计 = 本档(方向 B 已拍、双评审 11 条全修);上级 = 2026-06-30 总设计(阶段〇/一①/一② 已落地)。代码基线全在 frontmatter 关联段:genconfig.py(env→YAML→内置三级 + mtime 缓存)、nacos_hotconfig.py(阶段〇已建未接线)、middleware.py:173(软预算经 genconfig 读)、cheap_service_app.py / service/app.py(PATCH 落点)、game-module-aigc(治理子域落点)。部署面:Nacos 在 mini-infra(ssh 真 IP 100.64.0.8),worker/Service 在 mini-desktop(100.64.0.7);凭据一律读 docs/内网凭据与端点.md,不问、不设 env。近期阈值批(writer 60 / step 100 / 墙钟 2700,bfeaea2d)已在 generation.yaml,接线以现值为准。
边界红线:基建配置通路(SCA 原生)不动(§3.5);middleware 调用面一行不改(只换 genconfig 底层热源);不改上级设计档(涟漪已按 §7 挂账);知识件(品类 rubric fixture / skill / 金标)在 §7 末创始人裁定前不入配置集、按现范围实施;live 变更逐次报(决策包⑥ 授权边界)。
验收门:§5 六条逐条真验 + §4 各步骤「验证」全绿;Python 单测绿——回落链四条异常路径(未 start / Nacos 不可达 / namespace 错配 / YAML 回落)先在单测锁死才许真接线;Java 单测 + Flyway replay 绿;灰度顺序 = 先门阈值、验稳再切预算目标;docs-gate 绿。
坑(前车之鉴,本仓全部真栽过):① Nacos 默认空间 namespaceId = 空串而非 "public",前置任务不先修则写读静默错空间;② 内网直连必须绕系统代理(NO_PROXY / ProxyHandler({}),阶段〇 worker 回调 502 同款);③ genconfig 的 mtime 缓存别盖住热值,回落优先级(Nacos > env > YAML > 内置)要测到位;④ 新旋钮禁止在调用点写死默认值——双源默认值影蔽刚在收敛环栽过(DoD = 调用链无第二默认值,tech-decisions §13);⑤ jar 轮换 = env 依赖面随升(NACOS_PASSWORD 双杀教训),部署/重启脚本同步改;⑥ 若触队列参数,rocketmq consumeThreadMax 是死参数,有效并发 = consumeThreadNumber 且 min=max。
自审:每步交付附真实 commit hash + 真跑验证输出(hash 会被 rev-parse 核验,伪造必露);没全绿报 BLOCKED、不谎报 DONE;激活一致性(§6 首条)是本单最重风险——实施中若发现投影层补偿语义做不到,停下挂问题、不许绕;收口时随上级 §6 收口单一并回写 §7 涟漪清单,不双开。
执行终局(2026-07-04):§4 五步连同 namespace 前置全部代码落地(前置 b2b4d389、步骤 1+2 3ae34de1、步骤 3 72973532、步骤 4 344330a4+入口 d9def7d9、步骤 5 a8dd5e50),cheap 与 tier2 两档的 Service 入口都已接上双路激活的 PATCH 落点与 NacosHotConfig 热源;启用旗默认关,生产灰度与路 A 对真 Service 的 PATCH 排部署窗口(先门阈值、验稳再切预算目标)。实施中坐实三处口径修正、已就地回写正文:① AgentScope 没有单个 GET /agent/{id},漂移对账改用 /agent 列表端点按 id 过滤(§3.6 早稿"拉 /agent"口径随此改真);② Nacos 是 publish→read 最终一致、非即时,对账读带 8s 容忍窗,避开刚激活尚未收敛的中间态被误报成漂移;③ 机器上遗留 env 覆盖压过激活值这类漂移从 game-cloud 侧看不见(env 只活在 worker 进程内),当前对账探不到,挂阶段四观测补——由 worker 暴露一个 effective 配置端点、把真正生效值连同来源吐出来供对账。§7 涟漪清单第 1–4 行已回写上级设计档(§0 / §3.3 / §3.6 图 / §3.7 / §4),收口标记见上级 §6;第 5 行 SoT 回写已随架构图说 doc-sync(2026-07-03)完成。余下待办承 §4 后续阶段:阶段三管理面 UI、阶段四观测、部署窗口灰度。
附 图清单与状态
| 图 | 角色 | 状态 |
|---|---|---|
| 图0 一图看懂 | §0 门面 SVG | 待出图 |
| 图1 配置中心分层与分工 | §2 / §3.1 / §3.5 分工边界与四方职责 | 已出图 assets/cfgctl2-01-配置中心分层与分工.svg |
| 图2 双路激活编排 | §3.3 双路下发编排(内联 Mermaid 事实源已在文) | 已出图 assets/cfgctl2-02-双路激活编排.svg |
| 图3 激活一致性状态机 | §3.2 生命周期 + §3.6 ACTIVATING 恢复(内联 Mermaid 事实源已在文) | 已出图 assets/cfgctl2-03-激活一致性状态机.svg |
| 图4 漂移对账三态 | §3.6 配置漂移检测三态 | 已出图 assets/cfgctl2-04-漂移对账三态.svg |
文字 + Mermaid 为事实源,SVG 为派生视觉。