Unified Agent + reusable Go agent core.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: clarify config defaults authority and config path semantics

Lyric 260ef5fa fa057a4b

+81 -15
+24 -1
docs/feat/feat_20260418_config_defaults_sources.md
··· 8 8 9 9 ## 1) Goal 10 10 11 - This note explains where configuration defaults come from today, why there are two defaulting entrypoints, and where each one is used. 11 + This note explains where configuration defaults come from today, why there are two defaulting entrypoints, where each one is used, and which one should become the shared authority. 12 12 13 13 This is intentionally documented outside the current PowerShell PR scope. 14 14 ··· 36 36 ## 3) `internal/configdefaults.Apply` 37 37 38 38 This is the main program default source for the global CLI/runtime `viper`. 39 + 40 + It should be treated as the repo-wide authority for shared defaults. 39 41 40 42 Primary uses: 41 43 ··· 63 65 64 66 This is used when code creates a fresh temporary `viper` instance and wants a self-contained config reader. 65 67 68 + It is better understood as a construction helper for isolated readers, not as an independent authority. 69 + 66 70 Primary uses: 67 71 68 72 - `integration` runtime snapshot loading ··· 100 104 The current issue is not that there are two call paths. 101 105 The current issue is that both paths each define defaults directly. 102 106 107 + This is already causing real drift, not just theoretical duplication. 108 + 109 + Examples: 110 + 111 + - `internal/configdefaults.Apply` contains keys that are absent from `integration.ApplyViperDefaults`, such as: 112 + - `tasks.*` 113 + - `multimodal.image.sources` 114 + - `console.*` 115 + - `line.*` 116 + - `lark.*` 117 + - `telegram.serve_listen` 118 + - `slack.serve_listen` 119 + - `integration.ApplyViperDefaults` contains `logging.*`, while `internal/configdefaults.Apply` does not 120 + - at least one shared key already disagrees: 121 + - `telegram.addressing_interject_threshold` 122 + - `internal/configdefaults.Apply` sets `0.6` 123 + - `integration.ApplyViperDefaults` sets `0.3` 124 + 103 125 That means whenever a new config key is added, especially under `tools.*`, there is a risk of: 104 126 105 127 - updating one defaults function but not the other ··· 120 142 - `internal/configdefaults.Apply` remains the shared source of truth 121 143 - `integration.ApplyViperDefaults` calls into it first 122 144 - `integration.ApplyViperDefaults` only adds integration-specific defaults if it truly owns any 145 + - shared `logging.*` defaults should also be moved under the same authority instead of living only in the isolated-reader path 123 146 124 147 This keeps the useful construction split without duplicating the actual default values. 125 148
+57 -14
docs/feat/feat_20260418_console_config_runtime_snapshots.md
··· 157 157 158 158 不需要一上来做成通用事件总线。 159 159 160 + 不过这里有两层语义不能只让 Console 自己定义,而应按 repo 级统一: 161 + 162 + - shared defaults 的 authority 163 + - config path 的解析顺序 164 + 160 165 建议新增一层,例如: 161 166 162 167 - `internal/configruntime` ··· 164 169 165 170 它负责: 166 171 167 - 1. 解析配置路径。 172 + 1. 按 repo 级统一规则解析配置路径。 168 173 2. 用独立的临时 `viper` 读取并展开 `config.yaml`。 169 174 3. 应用 defaults、环境变量展开、必要的 normalize。 170 175 4. 生成一份不可变的 `ProcessConfigSnapshot`。 171 176 5. 给这份快照分配 `generation`、`loaded_at`、`source_path`、`content_hash`。 172 177 6. 在配置无效时保留“最后一份有效快照”,并记录最近一次加载失败。 173 178 179 + 其中第 1 条建议明确固定为: 180 + 181 + - 先看显式 `--config` / `config` 182 + - 再看当前工作目录下的 `config.yaml` 183 + - 再看 `~/.morph/config.yaml` 184 + 185 + 并且要区分两种 mode: 186 + 187 + - read mode:三者都不存在时返回空路径,由启动装配层决定是否允许“无配置启动” 188 + - write mode:三者都不存在时回落到当前工作目录下的 `config.yaml`,作为新文件落点 189 + 190 + 其中第 3 条也建议明确 authority: 191 + 192 + - shared defaults 的唯一 authority 应该是 `internal/configdefaults.Apply` 193 + - `integration.ApplyViperDefaults` 如果保留,应只作为 isolated reader 的薄包装,并先调用 `internal/configdefaults.Apply` 194 + - Console 内部不应再额外发明一套 defaults 语义 195 + 174 196 建议接口形态: 175 197 176 198 ```go ··· 207 229 208 230 否则只是“把全局 `viper` 换成 snapshot 内部 reader”,复杂度降了,但边界还是不干净。 209 231 232 + 也就是说,Console 里的 manager 可以先只服务 `console serve`,但它不应该再自己定义另一套 config path/defaults 规则;这两层应与 repo 其他入口共享。 233 + 210 234 当前需求下,manager 的分发语义也可以简单一点: 211 235 212 236 - manager 持有固定的一组 consumer ··· 221 245 222 246 建议在 `console serve` 内部引入文件变更监听: 223 247 224 - 1. 监听当前 config path。 225 - 2. 使用 debounce + content hash 去抖。 226 - 3. 文件变化后重新加载 snapshot。 227 - 4. 解析成功则发布新 generation。 228 - 5. 解析失败则保留旧 generation,并暴露错误状态。 248 + 1. watcher 绑定的是 canonical config resolver 语义,而不是某一次启动时碰巧命中的单一路径。 249 + 2. 如果显式指定了 `--config`,就监听该路径。 250 + 3. 如果没有显式路径,就按 `./config.yaml` -> `~/.morph/config.yaml` 的候选顺序解析当前 source,并在 source 切换时重新绑定 watcher。 251 + 4. 使用 debounce + content hash 去抖。 252 + 5. 文件变化后重新加载 snapshot。 253 + 6. 解析成功则发布新 generation。 254 + 7. 解析失败则保留旧 generation,并暴露错误状态。 229 255 230 256 实现上可以用: 231 257 ··· 236 262 237 263 - Console Web API 改配置只需要写文件 238 264 - 用户手工编辑 `config.yaml` 也能触发同样的效果 265 + - 启动时没有配置文件、之后再创建 `config.yaml` 或 `~/.morph/config.yaml` 的场景也不会漏掉 239 266 240 267 ## 6.3 每个 runtime 自己持有快照 241 268 ··· 393 420 建议在 health/overview 中先暴露: 394 421 395 422 - `config_generation` 396 - - `applied_generation` 397 423 - `config_loaded_at` 398 424 - `config_source_path` 399 425 - `config_last_error` 400 426 - `config_last_error_at` 427 + - `consumers.local.applied_generation` 428 + - `consumers.local.last_error` 429 + - `consumers.managed.applied_generation` 430 + - `consumers.managed.last_error` 431 + 432 + 如果后续需要更细粒度,再继续扩展到 per-kind 状态,例如 `telegram/slack/line/lark` 各自的 generation。 433 + 434 + 这里不建议继续保留单一的 `applied_generation` 字段。 435 + 436 + 原因很直接: 437 + 438 + - manager 是顺序 fan-out 给多个 consumer 439 + - local runtime 和 managed runtime 可能不会在同一时刻完成切换 440 + - 一旦出现部分 apply 成功、部分 apply 失败,单一字段就无法表达真实状态 401 441 402 442 这样前端可以区分三种状态: 403 443 ··· 413 453 414 454 ### Phase 1:引入快照管理器,但先不改外部行为 415 455 416 - 1. 新增 `ConfigManager` 和 `ProcessConfigSnapshot`。 417 - 2. 启动时加载第一版 snapshot。 418 - 3. manager 内先挂 `consoleLocalRuntime` 与 `managedRuntimeSupervisor` 两个 consumer。 419 - 4. 把 `cmd/mistermorph/registry.go` 那条 `configdefaults.Apply(viper.GetViper())` 调用链移回主启动路径,只在进程初始化阶段执行一次。 420 - 5. runtime 路径后续彻底不再碰 `SetDefault(...)` 或其他全局 defaults 写入。 421 - 6. 加入 watcher,但先只做日志和状态记录。 422 - 7. 在此基础上,暂时保留现有 handler 中的 `viper.Set + reload` 逻辑,确保行为不变。 456 + 1. 新增 repo 级共享的 config path resolver,明确 read/write 两种 mode 和 `--config` -> `./config.yaml` -> `~/.morph/config.yaml` 的顺序。 457 + 2. 新增 `ConfigManager` 和 `ProcessConfigSnapshot`。 458 + 3. 启动时加载第一版 snapshot。 459 + 4. 把 shared defaults authority 收敛到 `internal/configdefaults.Apply`;`integration.ApplyViperDefaults` 若保留,则先调用前者。 460 + 5. manager 内先挂 `consoleLocalRuntime` 与 `managedRuntimeSupervisor` 两个 consumer。 461 + 6. 把 `cmd/mistermorph/registry.go` 那条 `configdefaults.Apply(viper.GetViper())` 调用链移回主启动路径,只在进程初始化阶段执行一次。 462 + 7. runtime 路径后续彻底不再碰 `SetDefault(...)` 或其他全局 defaults 写入。 463 + 8. 加入 watcher,但先只做日志和状态记录。 464 + 9. 在此基础上,暂时保留现有 handler 中的 `viper.Set + reload` 逻辑,确保行为不变。 423 465 424 466 目标: 425 467 426 468 - 先把“统一快照生成”做出来 469 + - 先统一 defaults authority 和 config path 语义 427 470 - 先消掉已知的全局 `viper` 并发写 panic 428 471 - phase 1 的最小可测成果是:运行期不再存在 `configdefaults.Apply(viper.GetViper())` 429 472 - 避免一上来同时改配置加载和 runtime 生命周期