From d4471b02fca999ef1976cef2b36bdc9034e026d0 Mon Sep 17 00:00:00 2001 From: lili Date: Thu, 2 Jul 2026 20:52:42 -0700 Subject: [PATCH] =?UTF-8?q?docs(agent-specs):=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E9=9D=A2=E9=98=B6=E6=AE=B5=E4=BA=8C=20yudao?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=B8=AD=E5=BF=83=20=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E6=88=90=E7=A8=BF(=E5=88=9B=E5=A7=8B=E4=BA=BA=E5=B7=B2?= =?UTF-8?q?=E6=8B=8D=20B)=E2=80=94=E2=80=94=E5=8F=8C=E8=AF=84=E5=AE=A1?= =?UTF-8?q?=E5=B7=B2=E5=9B=9E(Codex=202B+5M/Opus=200B+2M+4m),=E5=85=88?= =?UTF-8?q?=E8=90=BD=E5=9F=BA=E7=BA=BF=E7=89=88=E4=BE=BF=E4=BA=8E=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2=E5=AF=B9=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- ...-02-配置控制面阶段二-yudao配置中心-设计.md | 259 ++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 docs/agent-specs/2026-07-02-配置控制面阶段二-yudao配置中心-设计.md diff --git a/docs/agent-specs/2026-07-02-配置控制面阶段二-yudao配置中心-设计.md b/docs/agent-specs/2026-07-02-配置控制面阶段二-yudao配置中心-设计.md new file mode 100644 index 00000000..af4b6c53 --- /dev/null +++ b/docs/agent-specs/2026-07-02-配置控制面阶段二-yudao配置中心-设计.md @@ -0,0 +1,259 @@ +--- +date: 2026-07-02 +topic: 配置控制面-阶段二 +status: 草稿 · 创始人已拍方向 B(维持 yudao⊕Nacos;prompt 移出 Nacos→MySQL 版本行,2026-07-02)· 待 Codex+Opus 双评审 → 创始人评审 +sot-impact: 修订 生成引擎运行时(SoT §5.2 管理面配置管理 / §5.8 配置三类"受治理发版" / ADR-4 配置热取 / A13 配置注册表:把 GitOps/Langfuse 式口径收敛为 yudao 配置中心版本化(版本账本落 MySQL 版本行)+ 双路激活,与上级设计 §6 pending 同一收口面,收口时回写;对上级设计两处口径的回调挂账见本档 §7) +上级: docs/agent-specs/2026-06-30-配置控制面一次性按序实现-设计.md +关联: + - 阶段前置(已落地):阶段〇 生产基建(Nacos 2.4.3 / RocketMQ 5.3.1 / Sentinel 自托管 mini-infra + game-cloud 接入,`docs/plans/2026-07-01-配置控制面-spike与阶段〇生产基建-plan.md`)· 阶段一① 护城河 middleware(`07100eef`)· 阶段一② cheap-worker 归并 Service `/chat`(`526e9b3d`) + - tier2/gen-worker/worker/genconfig.py(进程内配置门面 `get(area,key,default)`,现读本地 `tier2/config/generation.yaml`;本阶段把其读取后端提升到 Nacos) + - tier2/gen-worker/worker/nacos_hotconfig.py(阶段〇 D2 已建、未接线;本阶段接进 genconfig 作热源) + - tier2/gen-worker/worker/middleware.py:173(CircuitBreakerMiddleware 软预算,预算/阈值经 genconfig 读)· worker/gate_judge.py(门判 GateJudgment) + - tier2/gen-worker/worker/store.py(MySQL manifest + MinIO 源文件的成品归档 —— MinIO 在本项目的既有角色参照,§3.1 prompt 存储选型佐证)· cheap-worker/cheap_roles.py ≈30KB / tier2 worker/roles.py ≈24KB(prompt 尺寸画像依据) + - cheap-worker/cheap_service_app.py:213(`_cheap_middlewares_factory` 每回合工厂)· :300(`build_cheap_app`) + - tier2/gen-worker/service/app.py(AgentScope Service `/chat` + `/agent` + `/session`) + - agentscope==2.0.2:app/_router/_agent.py:138(`update_agent` PATCH)· app/_router/_session.py:291(`update_session` PATCH,部分字段更新语义) + - game-cloud huijing-module-bpm(审批,放量后)· huijing-module-system(操作日志 + 权限,yudao 原生)· game-module-aigc(生成配置治理落点)· huijing-module-infra(Nacos SCA 通路 + infra_config KV 参照) + - game-admin/src/views/wanxiang(配置编辑面,阶段三消费) + - commit hash:待收口填 +图清单: [图0 一图看懂, 图1 分工边界与四方职责, 图2 配置生命周期与双路激活, 图3 失败模式与兜底] +--- + +# 配置控制面 · 阶段二:yudao 配置中心 · 功能设计 + +## 0 一图看懂 + +(图0 概览 SVG 待补) + +- **核心思想**:阶段一已经让生成配置能热改——prompt/模型/参数经 `/agent`+`/session` 由 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`+`/session` 每 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 边界 + +(图1 分工边界 SVG 待补) + +进控制面的判据沿用 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 源就有几十 KB(`cheap_roles.py` ≈30KB、tier2 `roles.py` ≈24KB),配置集版本再带 few-shot 与参数,内容会向上限爬。把最大、改得最勤的旋钮放在一条 100KB 的钢丝上,不如一开始就换地方。 +- **定版要原子**。版本行(元数据 + 全量内容)同库同事务一次落;内容若在库外,定版就成了跨系统写,要补超时/重试/补偿——治理层最高频的动作不该是分布式事务。而 prompt 一旦出 Nacos,版本若劈成"prompt 在 MySQL、小参在 Nacos"两处,一次定版跨两系统、必漂移,所以账本整体落 MySQL。 +- **diff 与检索要就地**。版本 diff、按内容查历史都是版本行上的本地操作;内容在外部系统则每次 diff 都要来回取对象。 + +**prompt 内容列定 MySQL `MEDIUMTEXT`,不选 MinIO**——在此定死、不留开放项。MinIO 在本项目的既有角色是成品源工程归档(`store.py`:MySQL manifest + MinIO 源文件、内容哈希、id+version 寻址),那是"多文件源树"的形状;prompt 是"一段文本字段"的形状,几 KB 到几十 KB,MinIO 的价值(大对象、二进制、流式)一样都用不上,却把定版原子性与就地 diff 全破掉,还把备份从"一份 MySQL dump = 治理+内容一致快照"劈成两个系统。列型取 `MEDIUMTEXT`(16MB)而非 `TEXT`(64KB):30KB 级 prompt 加 few-shot 有越过 64KB 的现实可能,MEDIUMTEXT 给足余量且无额外成本。 + +对上级设计"自建版本表 = 重造 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 配旧模型"的半拉子状态。 + +一个配置集走这样一条生命周期: + +```mermaid +stateDiagram-v2 + [*] --> 草稿: 创始人在 yudao 编辑一组旋钮 + 草稿 --> 待审: 定版(版本行落库,creator/时间随行) + 待审 --> 已激活: 审核通过(MVP 创始人角色直接激活 · 放量后走 BPM 审批) + 已激活 --> 待审: 编辑出新版本 + 已激活 --> 已激活: 回滚(取历史版本行,重放激活) + 已激活 --> 已归档: 被更新版本取代 + 待审 --> 草稿: 驳回 +``` + +几个关键点。**激活之前 Nacos 完全不被触碰**:草稿与版本行都是 yudao 库内数据,配置内容到"激活"才第一次离开治理库——这天然堵死了"草稿提前生效"的口子(Nacos 的配置是 publish 即被监听者读到的,预算/阈值若提前进 Nacos 就绕过了激活闸)。**激活是独立的闸**:它把选定版本的内容双路下发(见 §3.3),并把"当前激活版指针"指过去。**回滚就是重放激活**:取历史版本行的内容,走一遍激活动作,幂等。**归因与审计随生命周期自然沉淀**:定版记 creator 与时间,激活/回滚是一条 yudao 操作日志,diff 是两条版本行的就地比对——全是 yudao 原生能力,不新造。 + +### 3.3 双路激活:PATCH 路 ⊕ Nacos 热读路 + +(图2 配置生命周期与双路激活 SVG 待补) + +激活一个配置集,编排层从选定的版本行取内容,按旋钮性质分两路下发: + +```mermaid +flowchart LR + ACT["yudao 激活编排
(取选定版内容)"] + ACT -- "路A:prompt/context/max_iters" --> PA["PATCH /agent"] + ACT -- "路A:模型/参数/thinking/temp" --> PS["PATCH /session"] + PA & PS --> REDIS[("AgentScope Redis
当前配置")] + REDIS -- "每 POST 现装配(热)" --> GEN["/chat 生成回合"] + ACT -- "路B:软预算目标/门阈值" --> PUB["publish Nacos 生效 dataId"] + PUB --> HC["worker NacosHotConfig
长轮询推送热读"] + HC --> GENC["genconfig 门面"] + GENC -- "middleware 构造时读" --> GEN + ACT -- "记当前激活版 + 操作日志" --> YU[("yudao 治理表")] +``` + +- **路 A(prompt/模型/参数)**:激活编排把内容 PATCH 进 AgentScope Service 的 `/agent`(system_prompt / 压缩比例 / tool_result 截断 / max_iters)与 `/session`(模型 / 协议 type / credential / max_tokens / thinking / reasoning_effort / temperature)。`/session` 的 PATCH 是部分字段更新语义,只改本次带上的字段。写进 Service 的 Redis 后,下一个 POST `/chat` 现装配即生效。 +- **路 B(软预算目标 / 门阈值)**:这些参数不在 `/agent`+`/session` 里,它们是 worker 的 middleware 在构造时从 genconfig 读的。激活编排把内容 publish 进 worker 监听的 Nacos 生效 dataId,NacosHotConfig 经长轮询推送热读进进程内,genconfig 从它取到新值,下一次 middleware 构造时读到。 + +**为什么静默期语义在这个模型下几乎是自然的**:两路生效都发生在"下一次生成"的边界上——路 A 的 Redis 在 `/chat` 进 session 锁之前读、路 B 的 genconfig 在 middleware 构造时读,正在跑的那一局早已经读过配置了,新配置只影响此后新起的 POST。所以真正的撕裂窗口只剩一个:一个配置集的多次 PATCH/publish 之间非原子,理论上并发一局能读到"路 A 新、路 B 旧"的组合。MVP 单操作者 + 创作页置灰把这个窗口的影响面压到零,取静默期语义即可;真机制级原子留放量后(上级 §3.3)。激活编排对此的硬要求是:一批下发须**幂等**、**失败即整批判未生效可重试**、不留半生效态(见 §3.6 失败模式)。 + +### 3.4 NacosHotConfig 接进 genconfig(阶段〇 follow-up 落地) + +预算/门阈值那一路的热读,是把 `nacos_hotconfig.py` 接进既有的 genconfig 门面,而不是改 middleware。现状链条是 `middleware → genconfig.get("budget", "rmb_hard_limit") → generation.yaml`;genconfig 已经是一个带缓存、带 best-effort 兜底、带内置默认的成熟门面,middleware 到处在调它。阶段二只换 genconfig 的**读取后端**:让它优先读 NacosHotConfig(配置中心激活投影),读不到回落本地 `generation.yaml`,再回落内置默认。 + +这样接线有三处好处。其一,**middleware 一行不改**:`genconfig.get(...)` 的调用面完全不动,CircuitBreakerMiddleware 的软预算目标、门判的阈值都自动吃上 Nacos 热读。其二,**回落链天然是 fail-safe**:Nacos 不可达时 NacosHotConfig 自带本地磁盘快照兜底,再不行 genconfig 回落本地 YAML 与内置默认,生成主链不被配置中心连累。其三,**尊重既有 working code**:genconfig 是雏形阶段就在的进程内配置层(SoT §5.2 提到的那个雏形),阶段二是把它的源从本地文件提升到集中版本化,不是推翻重造。 + +命名空间上有一个已知的坑必须钉死:**Nacos 默认公共空间的 namespaceId 是空串,不是字面量 "public"**(阶段〇 follow-up 明确)。业务生成配置的 dataId 要么显式放进一个专门的命名空间(用真实 namespaceId、不是 "public" 三个字母),要么明确用空串走默认空间;写侧(yudao publish)与读侧(worker NacosHotConfig 订阅、game-cloud SCA)必须对齐同一个真实 namespaceId,否则激活写进一个空间、worker 在另一个空间读、配置永远推不到。 + +### 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 失败模式与兜底 + +(图3 失败模式与兜底 SVG 待补) + +激活是一批跨系统的非原子写(PATCH Service + publish Nacos + 更新 yudao 指针),这里的失败模式要正面处理,不能让配置中心把生成链路带崩。 + +- **激活失败可重试、不留半生效态**。一批下发里任一步失败(某个 PATCH 返错、Nacos publish 超时),整批判**未激活**:yudao 的当前激活版指针**不前移**,保留上一激活版为权威,并把这次激活标为失败可重试。下发动作须幂等(同一版本重放不产生副作用),重试就是把整批重放一遍。绝不出现"指针已前移、但 Service 只 PATCH 了一半"的状态。 +- **Service 不可达**。路 A 依赖 Service 在线才能 PATCH。Service 不可达时,激活判失败(同上),保留上一激活版;此时 Service 上跑的还是它 Redis 里的旧配置,生成不中断,只是新配置没生效。Service 恢复后重放激活即可。 +- **配置漂移检测**。Service 的 Redis 投影可能与"当前激活版"漂移——Service 重启若 Redis 未持久化会丢配置回落缺省、或有人绕过治理直接改了 Redis/Nacos、或某次激活 PATCH 部分失败没被发现。配置中心提供一个对账:激活后与定期,拉 Service 的 `/agent`+`/session` 当前配置、比对当前激活版的路 A 内容;拉 worker 的生效投影、比对路 B 内容;对不上即告警,并支持一键重推(重放激活把投影拉回激活版)。这道对账是"配置中心为权威、Service 与 worker 为投影"这个定位的兜底闸。 +- **命名空间错配**。见 §3.4:写读两侧的 namespaceId 必须是同一个真实值,默认空间是空串非 "public"。这条在接线时一次性核对钉死,避免配置静默推不到。 + +## 4 步骤计划 + +(阶段序 SVG 待补) + +阶段二自身按依赖分五步串行;阶段三(UI)、阶段四(观测)作为后续阶段划界在后,各自后续出 plan。 + +```mermaid +flowchart LR + S1[步骤1
配置集建模
+ yudao 治理骨架] --> S2[步骤2
版本行与历史 MySQL
回滚/diff] + S2 --> S3[步骤3
双路激活编排] + S1 --> S4[步骤4
NacosHotConfig
接进 genconfig] + S3 --> S5[步骤5
失败兜底
+ 漂移对账] + S4 --> S5 + S5 --> N3[阶段三 UI
后续出 plan] + N3 --> N4[阶段四 观测
后续出 plan] +``` + +**步骤 1 · 配置集建模 + yudao 治理骨架** +- 交付:在 game-module-aigc 落一个生成配置治理子域(复用 yudao 的鉴权/creator/审计框架,不塞进 infra_config 的 KV 表——生成配置是结构化配置集、不是键值对)。定义配置集的数据模型(一个配置集含哪些旋钮、映射到哪些 `/agent`+`/session` 字段与哪些 Nacos 生效 key)、版本与当前激活版指针、生命周期状态机、creator 与操作审计。 +- 验证:能建/改/定版一个配置集,creator 与时间随版本落库;权限门挡住无权写;状态机流转符合 §3.2。 +- 依赖:yudao(game-cloud 已在)。 +- 风险:配置集与 AgentScope agent/session 的字段映射要对齐 `/agent`+`/session` 的真实 schema(消费 `/agent/schema`),映射错则激活 PATCH 无效。 + +**步骤 2 · 版本行与历史(MySQL)** +- 交付:定版落版本行——配置集全量内容(prompt 存 `MEDIUMTEXT` + 模型/参数/预算/阈值)与元数据同库同事务一次写成一行;历史列表、任一历史版取回、两版 diff;当前激活版指针只由激活动作前移(定版不动它)。 +- 验证:定多版后能列历史、取任一版、diff 两版;定版是单库事务、无跨系统写;30KB 级 prompt 正文完整存取无截断。 +- 依赖:步骤 1。 +- 风险:版本行只增不删,量级 = 人工定版频次(每天几次),多年不清理也只是几千行——留一个归档策略位即可,不做提前优化。 + +**步骤 3 · 双路激活编排** +- 交付:激活动作把选定版双路下发——路 A PATCH `/agent`+`/session`,路 B publish 进 worker 监听的生效 dataId;更新当前激活版指针 + 记操作日志;成组下发幂等、失败整批判未生效可重试(见 §3.6);静默期语义(只对下一批新生成生效)。 +- 验证:改任一旋钮 → 定版 → 激活 → 下一次生成生效;并发两局不读到路 A 新/路 B 旧的撕裂组合(静默期);回滚旧版即恢复;一批下发中途失败时指针不前移、可重试。 +- 依赖:步骤 1、2;阶段一②(两档已归并 Service,PATCH 有落点)。 +- 风险:激活一致性是本阶段最重的风险(§3.6);Service PATCH 与 Nacos publish 跨系统,须幂等 + 判未生效可重试。 + +**步骤 4 · NacosHotConfig 接进 genconfig** +- 交付:genconfig 的读取后端提升到 Nacos——优先 NacosHotConfig、回落本地 `generation.yaml`、回落内置默认;NacosHotConfig 作进程级单例接进 worker 启动。middleware 调用面不改。落地阶段〇 D2 建好未接线的 `nacos_hotconfig.py`。 +- 验证:改 Nacos 生效 dataId → worker 数秒内经 genconfig 读到新预算/阈值(不重启);Nacos 不可达时回落本地 YAML 与默认、生成不受影响;cheap 与 tier2 两档的软预算目标、门阈值都吃上热读。 +- 依赖:阶段〇 Nacos + NacosHotConfig(已建);阶段一①(middleware 经 genconfig 读预算/阈值)。 +- 风险:genconfig 现有缓存与 NacosHotConfig 的推送时序要理顺(别让本地 YAML 缓存盖住 Nacos 热值);回落链的优先级要测到位。 + +**步骤 5 · 失败兜底 + 漂移对账** +- 交付:激活失败整批判未生效 + 保留上一激活版 + 可重试;Service 不可达兜底;配置漂移对账(拉 Service/worker 投影比对当前激活版)+ 一键重推。 +- 验证:模拟 PATCH 中途失败/Service 不可达 → 指针不前移、生成用旧配置不中断、重放激活恢复;篡改 Redis/生效 dataId 后对账能检出漂移并重推拉回。 +- 依赖:步骤 3、4。 +- 风险:漂移对账要区分"真漂移"与"正在激活的中间态",避免误报。 + +**后续阶段(划界,各自出 plan)**:阶段三 = game-admin 配置编辑面(浏览/改/定版/激活/回滚/历史/diff,表单消费 `/agent/schema`),依赖阶段二;阶段四 = 观测(Studio + genMonitor,trace 回放/成本对账/在跑监控),依赖 trace 事件流。两者不在本设计展开。 + +## 5 验证方式 + +1. **配置即数据、版本可管**:改 model/budget/max_tokens/thinking/prompt 任一 → 定版 → 激活 → 下一次生成生效;回滚即恢复;逐项覆盖本会话手改过的硬编码点。 +2. **归因与审计**:每次定版记 creator/时间,激活/回滚有 yudao 操作日志,任意两版可 diff。 +3. **双路激活、静默期无撕裂**:路 A PATCH、路 B Nacos 热读都生效;一组旋钮作一个配置集激活;并发两局不读到跨路撕裂组合。 +4. **NacosHotConfig 接线**:改 Nacos 生效 dataId,cheap 与 tier2 的软预算/门阈值经 genconfig 数秒内热读到新值、不重启;Nacos 不可达回落本地 YAML 与默认、生成不受影响。 +5. **分工边界**:基建配置仍走 SCA 原生通路、不经治理层;业务生成配置的写入只由 yudao 治理层独占。 +6. **失败兜底**:激活中途失败/Service 不可达时指针不前移、生成用旧配置不中断、重放可恢复;漂移可检出并重推。 + +## 6 风险与回滚 + +- **激活一致性(本阶段最重)**:一批 PATCH+publish 跨系统非原子。缓解 = 幂等 + 失败即整批判未生效可重试 + 当前激活版指针只在整批成功后前移 + 保留上一激活版为权威;并发撕裂 MVP 由静默期规避,机制级原子(epoch 戳)留放量后。回滚 = 配置中心永远能重放上一激活版。 +- **Service 不可达 / Redis 未持久化丢配置**:激活判失败保留旧版;Service 恢复后由漂移对账检出并一键重推。Service Redis 的持久化策略需与运维确认(丢配置是否需 Service 启动即从配置中心拉激活版自愈,可作后续增强)。 +- **NacosHotConfig 接线破坏 middleware 读参**:回落链(Nacos → 本地 YAML → 内置默认)是保命绳,先在两档单测里锁死三级回落再上;真接线灰度——先只把一个低风险旋钮(如门阈值)切到 Nacos 热读、验稳再推预算目标。回退 = genconfig 后端切回纯本地 YAML(改一处开关)。 +- **命名空间错配**:默认空间是空串非 "public";写读两侧 namespaceId 一次性核对钉死,否则配置静默推不到(阶段〇同款坑)。 +- **配置集字段映射漂移**:配置集到 `/agent`+`/session` 字段的映射依赖 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`+`/session` · publish 生效 dataId);图随文改 | +| 4 | SoT 回写口径(架构图说 §5.2 / §5.8 / ADR-4 / A13;上级 §6 已挂"GitOps 口径"收口项) | GitOps / Langfuse 式热取叙述 | "配置中心版本化"的落点 = yudao MySQL 版本行 + 双路激活(本档 frontmatter sot-impact 申报);与上级挂的收口单合并执行 | + +## 附 图清单与状态 + +| 图 | 角色 | 状态 | +|---|---|---| +| 图0 一图看懂 | §0 门面 SVG | 待 opus 子代理出图 | +| 图1 分工边界与四方职责 | §2/§3.5 SVG | 待出图 | +| 图2 配置生命周期与双路激活 | §3.2/§3.3 SVG(内联 Mermaid 事实源已在文) | 待出图 | +| 图3 失败模式与兜底 | §3.6 SVG(内联 Mermaid 事实源已在文) | 待出图 | + +文字 + Mermaid 为事实源,SVG 为派生视觉。