Drop the unconsumed `llm/adapter-change` event
移除未被消费的 `llm/adapter-change` 事件
`LlmService.registerAdapter()` emits `llm/adapter-change` on registration and disposal ([packages/llm/llm/src/index.ts](../../../../packages/llm/llm/src/index.ts)). Grepping `llm/adapter-change` across `packages/*/src` and `examples/*/src` finds only the declaration, emit sites, docs, and tests; no production listener subscribes to it. This differs from `tools/change` and `system-prompt/change`. Those two events are
English
Problem
LlmService.registerAdapter() emits llm/adapter-change on registration and disposal (packages/llm/llm/src/index.ts). Grepping llm/adapter-change across packages/*/src and examples/*/src finds only the declaration, emit sites, docs, and tests; no production listener subscribes to it.
This differs from tools/change and system-prompt/change. Those two events are also unconsumed today, but they are plausible registry-change signals for future live tool/prompt UIs. LLM adapter registration is more of a boot-time implementation detail: adapters are not a user-visible palette and the real model-call interception seam is llm/stream. Keeping an adapter-change event with no listener repeats the drop-the-dead-summary pattern at a smaller scale.
The event is not free. registerAdapter() yields its rollback disposer before emitting llm/adapter-change so a throwing listener unwinds the mutation instead of leaking an adapter entry, and the package carries tests for that listener-throw path. That defensive ordering protects a failure mode only tests can trigger.
Decision
Only llm/adapter-change is removed: the declaration in dsh-llm's interface Events, the ctx.emit('llm/adapter-change') calls, and the "Emits llm/adapter-change on registration and disposal" sentence in LlmService.registerAdapter's JSDoc. registerAdapter()'s effect generator keeps the mutation and rollback disposer for HMR/disposal but sheds the listener-throw rollback ordering that existed only for the removed event. The adapter-disposer test asserts the returned disposer removes the adapter without subscribing to the event; the listener-throw rollback test is gone with its subject. The event taxonomy in docs/architecture.md and packages/llm/llm/README.md is updated in the same change.
Alternatives considered
Why not remove every registry change event?
A microkernel where registries announce mutations is a coherent convention. tools/change and system-prompt/change may become useful when a UI can live-refresh available tools or prompt sections. This Agent Note leaves that convention intact where it has a plausible user-facing consumer and cuts only the adapter-change event whose current and likely future consumer is unclear.
If an LLM adapter browser or dynamic model-picker needs this signal later, reintroduce it with that consumer and a clearer payload than "something changed."
Verification
llm/adapter-change and its emits are gone and the regenerated cordis catalog is fresh; HMR-safety holds (disposing a contributing fiber removes the adapter); tools/change and system-prompt/change remain documented and tested; and the ACP snapshots plus the keyless Headless Loader smoke pin the unchanged production paths.
Consequences
- Removing a documented emit event is a public-surface change. It is in the taxonomy table, so it reads as deliberate API. But "declared and emitted" is not "consumed" — the same distinction that justified dropping the mutable summary. The taxonomy table is updated in the same change, so the docs do not drift.
- The registry-change convention becomes uneven. That is acceptable because LLM adapter registration is not the same user-facing concept as tools or prompt sections. Uneven but honest beats uniform but dead.
This is a small cut, but it retires a standing correctness invariant that guards a consumer that does not exist.
中文
问题
LlmService.registerAdapter() 在注册和 dispose(资源释放)时发出 llm/adapter-change 事件(packages/llm/llm/src/index.ts)。在 packages/*/src 和 examples/*/src 中搜索 llm/adapter-change,只能找到声明、emit 站点、文档和测试;没有任何生产环境的监听器订阅它。
这与 tools/change 和 system-prompt/change 不同。如今这两个事件同样没有消费方,但它们有望成为未来实时工具/提示词 UI 的注册表变更信号。LLM(大语言模型)适配器注册更像是启动时的实现细节:适配器不是用户可见的选项面板,真正的模型调用拦截 seam 是 llm/stream。保留一个没有监听器的适配器变更事件,只是在更小范围内重复删除无用 summary 的模式。
这个事件并非零成本。registerAdapter() 在发出 llm/adapter-change 之前先 yield 回滚 disposer,这样抛出异常的监听器会回退变更而非泄漏适配器条目;包内还有针对该监听器抛出路径的测试。这种防御性排序保护的是一个只有测试才能触发的失败模式。
决策
只移除 llm/adapter-change:包括 dsh-llm 的 interface Events 中的声明、ctx.emit('llm/adapter-change') 调用,以及 LlmService.registerAdapter JSDoc 中“在注册和释放时发出 llm/adapter-change”的句子。registerAdapter() 的效应生成器为 HMR(热模块替换)/释放保留变更与回滚 disposer,但移除仅因该事件而存在的监听器抛错回滚顺序。适配器 disposer 测试断言返回的 disposer 会移除适配器,不再订阅事件;监听器抛错回滚测试则随其测试对象一起消失。docs/architecture.md 和 packages/llm/llm/README.md 中的事件分类也在同一变更中更新。
曾考虑的替代方案
为什么不移除所有注册表变更事件?
由注册表通告变更的微内核是一种一致的约定。当 UI 能够实时刷新可用工具或提示词章节时,tools/change 和 system-prompt/change 可能会有用。本 Agent Note(agent 决策记录)在存在合理用户侧消费方的位置保留该约定,只删除当前及可能的未来消费方都不明确的适配器变更事件。
如果将来需要 LLM 适配器浏览器或动态模型选择器用到此信号,届时再连同消费方一起重新引入,并提供比「something changed」更清晰的 payload。
验证
llm/adapter-change 及其 emit 已消失,重新生成的 Cordis 目录保持新鲜;HMR 安全性仍成立(释放贡献该适配器的 fiber 会移除它);tools/change 和 system-prompt/change 仍有文档与测试;ACP(Agent Client Protocol)快照和无密钥 Headless Loader 冒烟则固定了未变的生产路径。
后果
- 移除一个已文档化的 emit 事件属于公开接口变更。 它出现在分类体系表中,读起来像有意设计的 API。但「已声明且已发出」不等于「已被消费」——这与移除可变 summary 时的判断依据相同。分类体系表在同一个变更中更新,因此文档不会漂移。
- 注册表变更约定变得不均匀。 这是可接受的,因为 LLM 适配器注册与工具或提示词段落不是同一层面的面向用户概念。不均匀但诚实,胜过统一但无用。
这是一个小裁剪,但它退役了一条守护着并不存在的消费方的正确性不变式。