# DESIGN Evolution

本日志记录 `DESIGN.md` 当前契约之外仍需保留的约束、分析与变更原因；更新时先压缩历史，再追加新的变更单元。

## 2026-09-04 · 确立最小可验证的调度模型

- 发生：系统需要验证多个持久职责 Shadow 能否稳定补充 Main。
- 分析：复杂语义调度会同时改变过多实验变量，基础机制尚未产生足够收益、误报、成本和延迟数据。
- 改变：采用随机 heartbeat 与确定性 final-response review；语义 Gate、Expected Value of Thinking、学习型调度和自动策略优化保留为获得运行数据后的候选。

## 2026-09-04 · 分离持久定义与一次运行

- 发生：Shadow 既需要跨项目复用职责，又需要避免运行历史在任务间隐式泄漏。
- 分析：全局定义适合表达持久身份，一次性 AgentSession 适合隔离状态；Main 可见上下文足以支持独立核查，Main thinking、完整工具结果和未上报 Shadow 推理会污染证据链。
- 改变：Shadow 定义进入全局 registry，运行实例保持一次性；上下文使用 Main 可见内容的净化子集。项目 registry、跨次记忆、Shadow 间通信及独立摘要/截断留待真实需求出现后评估。

## 2026-09-04 · 建立完成检查的 generation 边界

- 发生：多个 completion Shadow 可能晚于新输入或修订返回。
- 分析：旧检查若继续介入，会把过期判断投递到新的任务线；同期结果也需要一次性聚合，而不是逐条打断 Main。
- 改变：每轮完成检查由独立 generation 管理并整批交付，新输入或修订使旧 generation 失效；模型二次总结或去重暂不进入公共流程。

## 2026-09-04 · 明确工具授权边界

- 发生：Shadow 需要显式工具能力，广泛授权的静态列表又会随 Main Session registry 漂移。
- 分析：Pi 的通用 `ToolDefinition` 不能声明只读性，运行时也无法预知第三方工具副作用，因此不能承诺普适文件锁；全工具授权应继承实时 registry，而非复制名称。
- 改变：普通 Shadow 使用用户白名单，`tools: ["*"]` 动态解析当前工具 registry。协作式目标文件锁、未知工具协调和凭据脱敏仅作为未来可选协议。

## 2026-09-04 · 保持最小报告协议

- 发生：公共运行时需要可靠接收不同职责 Shadow 的结论。
- 分析：严重度、类别和表达结构属于职责定义；把它们固化进协议会限制新 Shadow，并增加无必要的运行时分支。
- 改变：`report_to_main` 保持单字段、单次上报，负责终止当前 loop 和可靠投递；固定严重度、类别、模板及同期报告的模型总结均不进入公共协议。

## 2026-09-04 · 收敛设计语言与扩展范围

- 发生：早期讨论包含产品隐喻、内置编辑体验和可观测性扩展等非运行时主线内容。
- 分析：“多核”“章鱼”等隐喻适合产品表达但不拥有架构语义；Markdown 编辑器和日志轮转需要先由真实使用证明价值。
- 改变：`DESIGN.md` 只保留当前运行时契约；隐喻退出技术定义，编辑体验、日志轮转及其他尚未验证的扩展保留在本变更历史中。

## 2026-09-07 · 对齐 ACP 持久压缩视图

- 发生：PR #6 报告 ACP 已压缩的 Main 会话仍向 Shadow 提供原始历史，触发上下文超限；审查进一步复现多工具调用、自定义报告漏压缩和 `null` 状态文件异常。
- 分析：ACP 将状态写入独立文件，多工具调用使用 `entryId#toolCallId`；仅按原始会话 entry ID 替换会漏掉覆盖内容，并可能在非当前分支或原生 compaction 之前消耗摘要锚点。Pi 原生摘要使用 `summary` 字段，旧序列化器只读取 `content` 也会丢失摘要正文。
- 改变：将状态校验与上下文投影分离，在当前原生上下文上折叠 ACP 覆盖内容，保留部分工具调用并清理孤立工具对；补齐分支、解压、嵌套块、异常文件及原生摘要回归。实现参考 billion-context-pi `55afd556` 的消息 ID 映射和 acp-kernel `4216937a` 的首条用户消息保护及工具配对规则。当前适配不执行 ACP 的动态 nudge、紧急截断或其他扩展 hooks，也不承诺复现任意扩展的最终请求载荷。

## 2026-09-07 · 在配置刷新后执行 heartbeat 工具过滤

- 发生：PR #7 增加全局 `heartbeat_tools` 和单个 Shadow 的 `activation_tools`，用于减少不相关工具轮次上的唤醒。
- 分析：初版先按内存中的旧过滤器判断，再刷新配置；放宽限制时可能持续被旧配置拦截，收紧限制时则会多进入一次抽选。管理工具只写入配置文件，同样受此顺序影响。
- 改变：事件入口只跳过无工具活动的轮次，heartbeat 编排刷新一次配置后统一执行工具过滤和概率判断。新增真实配置文件回归，覆盖放宽、收紧、清空、连续过滤、无工具轮次和无效配置回退；确认 final-response 资格保持独立。

## 2026-09-07 · 分离报告历史与常驻状态面板

- 发生：PR #9 将报告正文挂到最近运行下；审计复现报告被后续沉默运行挤出视图、长报告生成超高常驻组件，以及面板关闭时显示命令只提示成功。
- 分析：最近运行与最近交付报告是不同的保留口径；常驻摘要面板也不适合承载完整正文。继续在 Runtime 中叠加可见性状态、命令解析和正文排版会扩大入口职责。
- 改变：ReportHistory 独立按交付保留 5 份报告，ReportBrowser 管理命令与会话生命周期，ReportViewer 提供限高滚动快照。定向回归经过实际交付、命令和组件渲染，验证 200 行正文完整可达、终端缩放、关闭重开、会话重置及非 TUI 模式。

## 2026-09-12 · 为完成检查增加按 Shadow 的轮次上限

- 发生：PR #11 与 issue #10 记录到同一用户任务内的反复整改：终检报告触发 Main 修订后，新的最终回复再次触发同一 Shadow，缺少停止条件；heartbeat 报告接力也会持续唤醒整改轮。
- 分析：跳过由 shadow-report 触发的修复轮会失去对修订结果的复核，统一预算又涉及整个调度层、不适合放进单个 Shadow 定义。因此停止条件放在单个 Shadow 的完成检查上：整批审查进入终态后才提交轮次，被新输入、新报告或会话切换失效的检查不消耗配额；超时或错误的运行同样提交一次尝试，避免对同一用户任务重复重试。存在待交付的 heartbeat 报告时先交付已知问题，避免在旧文本上启动终检并浪费有限的验收轮次。缺省与 0 都表示不限制，维持现有默认行为。
- 改变：新增 `final_response_rounds` frontmatter 字段与调度过滤；独立的 FinalResponseBudget 拥有轮次语义、计数与重置，Runtime 只接线 Session 生命周期与整批完成回调；CompletionReview 增加完成回调，FinalResponseQueue 增加启动门禁，batcher 有待交付报告时优先刷新。heartbeat 接力、跨 Shadow 统一预算与 stop 后不自动 follow-up 仍留在 issue #10。

## 2026-09-15 · active_for_models 支持排除语法与模型短名

- 发生：用户希望仅对特定主力模型（如 `gpt-6-astra`）关闭某 Shadow，而对其余模型保持启用。
- 分析：此前仅支持全量 `*` 或显式枚举白名单，针对特定模型禁用必须穷举所有其他合法模型，配置维护成本高；应支持以 `!` 开头的排除规则，支持模型短名与 glob 通配，并在纯排除配置时默认全量模型候选。
- 改变：`matchesModel` 区分正向与否定规则；命中任一排除规则立即过滤；纯排除列表默认继承 `*`；补充短名、通配、格式破坏与调度过滤回归。

## 2026-09-21 · Shadow 会话不加载按会话记录的扩展

- 发生：同时安装 Run 归档扩展 pi-experiencev2 时，用户归档的 1098 个 Run 里有 381 个（35%）来自 Shadow 会话，缺摘要率 21.0%，高于用户会话的 9.3%；摘要成功的 301 条也按 Main 的目标撰写，再因为 Shadow 只回 `NOT_RELEVANT` 判成任务未完成，在归档列表里与用户 Run 混排。Shadow 会话不走 CLI 的 `session_start` / `session_shutdown`，这类扩展的关闭路径也不触发，归档 `writers` 表残留 26 条记录。
- 分析：Shadow 的结论本来就通过 `report_to_main` 进入 Main 会话记录，381 次运行只有 8 次产出报告，单独归档价值接近零。整体关闭扩展不可行，provider 扩展要提供模型。因此按名单排除，默认排除 `pi-experiencev2`，写 `[]` 恢复原状。匹配只能基于扩展文件路径：`DefaultResourceLoader` 先调用 `extensionsOverride`，再调用 `applyExtensionSourceInfo`，回调拿到的 `sourceInfo` 还是 `source: "local"` / `origin: "top-level"` 的占位值，`baseDir` 指向文件所在目录而非包根。包名改从文件向上最近的 `package.json` 读取，遇到 `node_modules` 或 home 目录即停，`index.js` 这类入口文件名不作为标识，避免一个通用词排除掉所有打包扩展。
- 改变：新增 `excluded_extensions` 配置与 `src/extension-filter.ts`（`isSelfExtension` 一并迁入），`bootstrapSession` 的 `extensionsOverride` 同时应用两个判据。实测对照：`excluded_extensions: []` 时归档 2 条 Run 且残留 1 条 writer，默认值下归档 1 条、writer 0 条，同一次运行的 Shadow debug 日志与 usage 证明 Shadow 确实执行过。
