mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
refactor: remove runtime invariant plugins
Delete @deepseek-ai/dsh-invariants, every package's ./invariant companion and its tests, the invariant gates and Vitest setup, the scoped-event resolver generator, and the INVARIANT-coded listener rethrow paths. Update docs, READMEs, Agent Notes, and add an upgrade guide.
This commit is contained in:
1 parent
3dce1c93d0
commit
f028f25667
1355 files changed
+1487
-15409
No files matched your search
+6
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/archived/architecture/2026-07-19-package-invariant-runtime-contracts.md
|
||||
2026-07-19-package-invariant-runtime-contracts.md: d0559b0379ce05aa9a5a0c30df4808e3088c840b
|
||||
2026-07-19-package-invariant-runtime-contracts.zh.md: 0cfe61ee6344a4f6da73ffff22bb8a1b30f9f3a9
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: Meaningful package invariant contracts
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-09-30
|
||||
|
||||
English | [中文](2026-07-19-package-invariant-runtime-contracts.zh.md)
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: 有意义的包不变量约定
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-09-30
|
||||
|
||||
[English](2026-07-19-package-invariant-runtime-contracts.md) | 中文
|
||||
|
||||
@@ -73,6 +73,9 @@
|
||||
"architecture/2026-07-19-gui-layering-and-rpc-protocol.i18n.yaml": "sha256:855477999c84236430dc9308e16797eb21658a67a72b8537315c6964ff0c0c0a",
|
||||
"architecture/2026-07-19-gui-layering-and-rpc-protocol.md": "sha256:3517f37e98e74865dced37d5e1559d443e8fa827031c8335e99a1e910586e9ac",
|
||||
"architecture/2026-07-19-gui-layering-and-rpc-protocol.zh.md": "sha256:8181386d957fa6d6b3eb9b05d29adb10804b5a926853425415d368cc7fceaefa",
|
||||
"architecture/2026-07-19-package-invariant-runtime-contracts.i18n.yaml": "sha256:a232db6670ea51521112ad54bcc0cc4ad43c5b11dfa0df2963a6f04e79434c7f",
|
||||
"architecture/2026-07-19-package-invariant-runtime-contracts.md": "sha256:8f4a84b310d5ad8398cc272201c8c0ed259496050fad8cb4d3634ee5bbb1dc1e",
|
||||
"architecture/2026-07-19-package-invariant-runtime-contracts.zh.md": "sha256:6b43c6437523e2715cdb8d75fd286a1eebd0d17f5a35f6c9afddb3f15cdfb945",
|
||||
"architecture/2026-07-19-package-owned-invariant-service.i18n.yaml": "sha256:9ea696b84c3af215eb3640e8acfe75a73b0f16109cbdffd4ce18cbf616221231",
|
||||
"architecture/2026-07-19-package-owned-invariant-service.md": "sha256:514f93f2261fecab9eb7efa8613340b1f01b729b411ac4adce903c50935fa95e",
|
||||
"architecture/2026-07-19-package-owned-invariant-service.zh.md": "sha256:cbcc5550b68bc6699ac7411c46c5da67db3fc4fe8330873f1d190ef8141f1280",
|
||||
@@ -1849,6 +1852,9 @@
|
||||
"simplification/2026-08-27-persistence-export-and-pre-release-trims.i18n.yaml": "sha256:1c6779c70d8d327844d69390a449b0c4f0603364666b7d30c93cbeb01899d3ec",
|
||||
"simplification/2026-08-27-persistence-export-and-pre-release-trims.md": "sha256:715ecdf9be8eda840c8ad37e56569d91aeac8128a171cbc783aba898cea240fc",
|
||||
"simplification/2026-08-27-persistence-export-and-pre-release-trims.zh.md": "sha256:244a2225316ad54edbeaeef558cba8a01c68f6f366e22abd5688553c77b71629",
|
||||
"simplification/2026-08-28-omit-unneeded-invariant-companions.i18n.yaml": "sha256:49a8f569a6b8b04596c846e6ef27b19d5a9a0e6f04bf0559580927966b0747a1",
|
||||
"simplification/2026-08-28-omit-unneeded-invariant-companions.md": "sha256:d981d9b813f7b317c41cc636e44d6bf0cd79dd71971285db3843ef2f27be47a3",
|
||||
"simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md": "sha256:c6a18d0be3bf03fcd4febc33d863d3c993dea24e5e2e2b3a93fda4ba646eb5e7",
|
||||
"simplification/2026-08-28-remove-hero-input-glow.i18n.yaml": "sha256:68af9690c3463de8e658dbc14194edb438ebf75c8788bba740332d11b0420819",
|
||||
"simplification/2026-08-28-remove-hero-input-glow.md": "sha256:1d492977342fae8542c2781b0b3df8a95388ce97bedf9dc69db74868167b782c",
|
||||
"simplification/2026-08-28-remove-hero-input-glow.zh.md": "sha256:b5e52a51adddee66a7f738e0345203cc339f0aaf2dbdfd9ad7524592a300837c",
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/archived/simplification/2026-08-28-omit-unneeded-invariant-companions.md
|
||||
2026-08-28-omit-unneeded-invariant-companions.md: db6e74752add7f7f78384987a55ea77f8a47c4ac
|
||||
2026-08-28-omit-unneeded-invariant-companions.zh.md: ecd3eec023ee18ee0ef3b638230b0d64ceed1d94
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: Omit invariant companions without independent observations
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-09-30
|
||||
|
||||
English | [中文](2026-08-28-omit-unneeded-invariant-companions.zh.md)
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
# Agent Note: 没有独立观察时省略不变量伴生入口
|
||||
|
||||
Status: implemented
|
||||
Archived: 2026-09-30
|
||||
|
||||
[English](2026-08-28-omit-unneeded-invariant-companions.md) | 中文
|
||||
|
||||
+22
-25
@@ -2,36 +2,33 @@
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants:
|
||||
en: 052aa271e87074c0
|
||||
zh: 3ccffc50fc1a28d4
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/problem:
|
||||
en: 6ea85a4531d5e59f
|
||||
zh: b4b568eb347f016a
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision:
|
||||
en: 6e701ec64c3bbfae
|
||||
zh: b8ed0f1cb7a0477a
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/session-owns-immutable-history:
|
||||
en: ff47e58c377a8dc0
|
||||
zh: 5edbe4f3642a3fe6
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/derived-requests-remain-detached:
|
||||
/agent-note-source-owned-session-immutability:
|
||||
en: cf33ffdd07df7257
|
||||
zh: 3d6895c9c80b0b1f
|
||||
/agent-note-source-owned-session-immutability/problem:
|
||||
en: e5d87f6c5aef2dad
|
||||
zh: 9f7f1459d99c523c
|
||||
/agent-note-source-owned-session-immutability/decision:
|
||||
en: 821a886d365a2271
|
||||
zh: 4ba5d74a7772150d
|
||||
/agent-note-source-owned-session-immutability/decision/session-owns-immutable-history:
|
||||
en: 76fc0d8548d192c1
|
||||
zh: 34c610fff5beb455
|
||||
/agent-note-source-owned-session-immutability/decision/derived-requests-remain-detached:
|
||||
en: 975df574ee6e6df8
|
||||
zh: 96d765058482835a
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/decision/package-owned-invariant-companions-check-relationships:
|
||||
en: a88e3a896eedca91
|
||||
zh: 03e96c7870cba932
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered:
|
||||
/agent-note-source-owned-session-immutability/alternatives-considered:
|
||||
en: 6119e2aff3b6be5a
|
||||
zh: 76136ef5249c23eb
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/pervasive-deep-readonly-types:
|
||||
/agent-note-source-owned-session-immutability/alternatives-considered/pervasive-deep-readonly-types:
|
||||
en: ec54af2931616616
|
||||
zh: 5fd9bc469a6fd3a5
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/development-only-freezing:
|
||||
en: 85591197bb62eabf
|
||||
zh: ba69b9ccf596a6d7
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/alternatives-considered/clone-only-when-deriving-messages:
|
||||
/agent-note-source-owned-session-immutability/alternatives-considered/development-only-freezing:
|
||||
en: 1857fb245ec3f086
|
||||
zh: 6d3324c872627bf6
|
||||
/agent-note-source-owned-session-immutability/alternatives-considered/clone-only-when-deriving-messages:
|
||||
en: a9848dec50664b92
|
||||
zh: 7c558550b054627b
|
||||
/agent-note-source-owned-session-immutability-and-dev-mode-invariants/consequences:
|
||||
en: bcccf7d464a070db
|
||||
zh: eccd442a21e2c99e
|
||||
/agent-note-source-owned-session-immutability/consequences:
|
||||
en: d26609b76b9eea01
|
||||
zh: cf046d5c7173b738
|
||||
+6
-14
@@ -1,4 +1,4 @@
|
||||
# Agent Note: Source-owned session immutability and dev-mode invariants
|
||||
# Agent Note: Source-owned session immutability
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,17 +6,17 @@ English | [中文](2026-06-11-dev-invariants-over-deep-readonly.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The session log needs two different protections: immutable ownership of each stored fact, and checks for relationships among facts across time and service contracts. Conflating them in an optional development plugin would leave production history vulnerable; trying to express both through TypeScript readonly types would not create a runtime boundary or describe relational rules.
|
||||
The session log needs immutable ownership of each stored fact. Making that protection an optional development plugin would leave production history vulnerable; expressing it through TypeScript readonly types would not create a runtime boundary.
|
||||
|
||||
The session log is the durable source of truth for replay, request reconstruction, persistence, and user-visible history. Code outside the session package must be able to inspect that history without retaining a reference that can rewrite it later, and inputs accepted from callers must not remain connected to caller-owned mutable objects.
|
||||
|
||||
Immutability of individual values is only half of the contract. A log can contain perfectly immutable records whose sequence, turn/step nesting, tool-call pairing, scoped delivery, or reconstructed model request is wrong. Those rules relate multiple records or services and cannot be established by freezing one object.
|
||||
Immutability of individual values does not establish relationships among records. A log can contain perfectly immutable records whose sequence, turn/step nesting, tool-call pairing, scoped delivery, or reconstructed model request is wrong. Those rules relate multiple records or services, cannot be established by freezing one object, and are outside this decision.
|
||||
|
||||
TypeScript readonly types are not a sufficient runtime boundary. They disappear when the program runs, a cast can bypass them, and a recursive `DeepReadonly<T>` would spread through every log and message consumer even though some downstream request-processing APIs intentionally work with mutable values.
|
||||
|
||||
## Decision
|
||||
|
||||
Responsibility is split between an always-on storage boundary and optional development assertions.
|
||||
`Session` owns an always-on storage boundary.
|
||||
|
||||
### Session owns immutable history
|
||||
|
||||
@@ -24,18 +24,12 @@ Responsibility is split between an always-on storage boundary and optional devel
|
||||
|
||||
The accepted event and all of its descendants are deep-frozen before publication. `append()` returns that owned frozen event, and `session/event` observers and `eventAt(seq)` receive the same record. `snapshotEvents(fromSeq?, toSeqExclusive?)` returns a frozen array snapshot; a previously returned array does not grow after a later append. `seq` reads the current length without materializing an array. Synchronous historical readers are deprecated under the [event-read policy](2026-09-09-deprecate-synchronous-session-event-reads.md). Seed records pass through the same validation, snapshot, and freeze boundary before construction succeeds.
|
||||
|
||||
This guarantee belongs in `Session`, not in an optional listener, because every composition relies on trustworthy history. A production deployment, a focused test, or a custom embedding receives the same storage semantics whether or not development support plugins are registered.
|
||||
This guarantee belongs in `Session`, not in an optional listener, because every composition relies on trustworthy history. A production deployment, a focused test, or a custom embedding receives the same storage semantics regardless of which plugins are registered.
|
||||
|
||||
### Derived requests remain detached
|
||||
|
||||
`deriveMessages()` projects logged surface events into detached, deep-frozen `Message` objects and returns a fresh array snapshot. Request assembly can therefore combine derived history with other inputs without exposing a path back into the log. The cache reuses safe immutable projections rather than recloning the complete history for each model call.
|
||||
|
||||
### Package-owned invariant companions check relationships
|
||||
|
||||
`dsh-invariants` registers the configurable `ctx.invariants` service and contains no product checks. A package publishes a `./invariant` ownership companion only for an independently observable runtime relationship; packages without one omit the companion and record the reason in their README. `dsh-session`, `dsh-agent`, `dsh-scope`, and `dsh-agent-loop` provide the initial rules that require trace state or observation of another seam: monotonic sequence numbers, turn and step nesting, tool-call/result pairing, legal agent-status transitions, subject-correct scoped dispatch, and equality between a loop-built request and the request reconstructed from its session-log prefix. Global enablement and package-name regex filters belong to the service ([package-owned invariant service](../../archived/architecture/2026-07-19-package-owned-invariant-service.md); [omission decision](../simplification/2026-08-28-omit-unneeded-invariant-companions.md)).
|
||||
|
||||
When the session companion attaches to an existing or seeded session, it replays the immutable log to rebuild trace state. The service gives each contribution a disposable child fiber, so hot reload is safe in the middle of a turn without giving diagnostics ownership of session storage.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### Pervasive deep-readonly types
|
||||
@@ -44,7 +38,7 @@ A rejected companion proposal would apply a recursive `DeepReadonly<T>` type acr
|
||||
|
||||
### Development-only freezing
|
||||
|
||||
Freezing history only when an invariants plugin is installed would make the core guarantee composition-dependent. Code could pass development tests and still corrupt history in production or in a focused composition that omits the plugin. Storage immutability is therefore always on, while the more expensive relational checks remain opt-in development support.
|
||||
Freezing history only when a development plugin is installed would make the core guarantee composition-dependent. Code could pass development tests and still corrupt history in production or in a focused composition that omits the plugin. Storage immutability is therefore always on.
|
||||
|
||||
### Clone only when deriving messages
|
||||
|
||||
@@ -55,6 +49,4 @@ Detaching `deriveMessages()` would protect the most common request path but leav
|
||||
- Every accepted live or seeded session event is detached from caller-owned inputs and deeply immutable before any observer can receive it.
|
||||
- Existing `snapshotEvents()` and `eventAt()` callers retain immutable read results while their migration is deferred; `seq` reads the log length without copying the array.
|
||||
- Request-side mutation cannot reach stored history through derived messages.
|
||||
- Development builds can enable relational assertions without changing storage behavior, and disposing or filtering a companion does not weaken log immutability.
|
||||
- `dsh-invariants` configures global enablement plus package allow/block regex lists; each check remains owned and tested by its product package.
|
||||
- The runtime boundary carries a recursive snapshot-and-freeze cost once per accepted event; later readers and cached projections reuse the owned immutable records.
|
||||
+6
-14
@@ -1,4 +1,4 @@
|
||||
# Agent Note: 源端拥有的会话不可变性与开发模式不变式
|
||||
# Agent Note: 源端拥有的会话不可变性
|
||||
|
||||
Status: implemented
|
||||
|
||||
@@ -6,17 +6,17 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
会话日志需要两种不同的保护:对每条已存储事实的不可变所有权,以及对跨时间和服务约定的事实之间关系的检查。如果将二者混为一个可选的开发插件,生产环境的历史记录将失去保护;如果试图通过 TypeScript readonly 类型同时表达两者,既无法建立运行时边界,也无法描述关系规则。
|
||||
会话日志需要对每条已存储事实的不可变所有权。如果将这项保护做成可选的开发插件,生产环境的历史记录将失去保护;如果通过 TypeScript readonly 类型表达它,则无法建立运行时边界。
|
||||
|
||||
会话日志是回放、请求重建、持久化与用户可见历史的持久真源。会话包外部的代码必须能检视历史,但不能保留一个可在之后改写历史的引用;从调用方接受的输入也不能继续连接到调用方拥有的可变对象。
|
||||
|
||||
单个值的不可变性只是约定的一半。一份日志可以包含完全不可变的记录,但其序列、轮次/步骤嵌套、工具调用配对、作用域分发或重建的模型请求是错误的。这些规则涉及多条记录或多个服务,无法通过冻结单个对象来建立。
|
||||
单个值的不可变性不能建立记录之间的关系。一份日志可以包含完全不可变的记录,但其序列、轮次/步骤嵌套、工具调用配对、作用域分发或重建的模型请求是错误的。这些规则涉及多条记录或多个服务,无法通过冻结单个对象来建立,也不属于本决策。
|
||||
|
||||
TypeScript readonly 类型不是充分的运行时边界。它们在程序运行时消失,类型转换可以绕过它们,而递归的 `DeepReadonly<T>` 会扩散到每个日志和消息消费方,尽管某些下游请求处理 API 有意使用可变值。
|
||||
|
||||
## 决策
|
||||
|
||||
职责划分给始终启用的存储边界和可选的开发断言。
|
||||
`Session` 拥有始终启用的存储边界。
|
||||
|
||||
### Session 拥有不可变历史
|
||||
|
||||
@@ -24,18 +24,12 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行
|
||||
|
||||
被接受的事件及其所有后代在发布前被深度冻结。`append()` 返回由 Session 拥有的冻结事件,`session/event` 观察者和 `eventAt(seq)` 接收同一记录。`snapshotEvents(fromSeq?, toSeqExclusive?)` 返回冻结的数组快照;先前返回的数组不会因后续 append 而增长。`seq` 无需物化数组即可读取当前长度。同步历史读取方法按[事件读取策略](2026-09-09-deprecate-synchronous-session-event-reads.zh.md)弃用。种子记录在构造成功前经过相同的验证、快照与冻结边界。
|
||||
|
||||
此保证属于 `Session` 而非可选监听器,因为每种组合都依赖可信的历史。无论是否注册了开发支持插件,生产部署、聚焦测试或自定义嵌入都获得相同的存储语义。
|
||||
此保证属于 `Session` 而非可选监听器,因为每种组合都依赖可信的历史。无论注册了哪些插件,生产部署、聚焦测试或自定义嵌入都获得相同的存储语义。
|
||||
|
||||
### 派生请求保持分离
|
||||
|
||||
`deriveMessages()` 将已记录的表面事件投影为分离的、深度冻结的 `Message` 对象,并返回一份新的数组快照。因此请求组装可以将派生历史与其他输入组合,而不会暴露一条回到日志的路径。缓存复用安全的不可变投影,而非为每次模型调用重新克隆完整历史。
|
||||
|
||||
### 包拥有的不变式配套插件检查关系
|
||||
|
||||
`dsh-invariants` 注册可配置的 `ctx.invariants` 服务,本身不包含产品检查。只有拥有可独立观察的运行时关系时,包才发布 `./invariant` 所有权配套插件;没有该关系的包会省略 companion 并在 README 中记录原因。`dsh-session`、`dsh-agent`、`dsh-scope` 和 `dsh-agent-loop` 提供首批需要跟踪状态或观察另一个 seam 的规则:单调递增的序列号、轮次与步骤嵌套、工具调用/结果配对、合法的 agent(智能体)状态转换、主体正确的作用域分发,以及循环构建的请求与从其会话日志前缀重建的请求之间的相等性。全局启用和包名 regex 过滤器归该服务所有(见[包拥有的不变式服务](../../archived/architecture/2026-07-19-package-owned-invariant-service.md)与[省略决策](../simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md))。
|
||||
|
||||
当会话配套插件附加到已有会话或以种子记录初始化的会话时,它回放不可变日志以重建跟踪状态。服务为每项贡献提供一个可 dispose(资源释放)的子 fiber,因此轮次中途热重载是安全的,同时不赋予诊断逻辑对会话存储的所有权。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
### 全面的 deep-readonly 类型
|
||||
@@ -44,7 +38,7 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行
|
||||
|
||||
### 仅在开发模式冻结
|
||||
|
||||
仅当不变式插件安装时才冻结历史,会使核心保证依赖于组合方式。代码可能通过开发测试,却在生产环境或省略了该插件的聚焦组合中破坏历史。因此存储不可变性始终启用,而开销更大的关系检查则保持为可选的开发支持。
|
||||
仅当开发插件安装时才冻结历史,会使核心保证依赖于组合方式。代码可能通过开发测试,却在生产环境或省略了该插件的聚焦组合中破坏历史。因此存储不可变性始终启用。
|
||||
|
||||
### 仅在派生消息时克隆
|
||||
|
||||
@@ -55,6 +49,4 @@ TypeScript readonly 类型不是充分的运行时边界。它们在程序运行
|
||||
- 每个被接受的实时或种子会话事件在任何观察者接收之前,都已从调用方拥有的输入中分离并深度不可变。
|
||||
- 现有 `snapshotEvents()` 和 `eventAt()` 调用方在暂缓迁移期间仍获得不可变的读取结果;`seq` 无需复制数组即可读取日志长度。
|
||||
- 请求侧的修改无法通过派生消息触及已存储的历史。
|
||||
- 开发构建可以启用关系断言而不改变存储行为;dispose 或过滤一个配套插件不会削弱日志不可变性。
|
||||
- `dsh-invariants` 配置全局启用状态以及包名允许/阻止 regex 列表;每项检查仍由其产品包拥有并测试。
|
||||
- 运行时边界对每个被接受的事件产生一次递归快照与冻结的开销;后续读取者和缓存投影复用已拥有的不可变记录。
|
||||
@@ -27,8 +27,8 @@
|
||||
en: ee71f9c0f12b6a0e
|
||||
zh: 403b1723f808391d
|
||||
/agent-note-session-surface-an-ordered-projection-over-the-event-log/decision/invariants:
|
||||
en: 24b1342fb3b5be29
|
||||
zh: a62692b0250c072a
|
||||
en: 98f39e77083d67b7
|
||||
zh: 634a9227fc2a7ec9
|
||||
/agent-note-session-surface-an-ordered-projection-over-the-event-log/alternatives-considered:
|
||||
en: 193157129e65e487
|
||||
zh: a0a73b8a9cbd78e6
|
||||
|
||||
@@ -45,7 +45,7 @@ The `repair.ts` module synthesizes `tool/result` closers for orphaned tool calls
|
||||
|
||||
### Invariants
|
||||
|
||||
`Session` validates `sourceEventSeqs` and `surfaceOp` at the always-on seed/append boundary: source lists are non-empty, unique, earlier, and known; `assistant/message` carries no source list; replacement endpoints exist in surface order; and `sourceEventSeqs` covers every shadowed node. These are single-record acceptance and storage-projection rules, not optional invariant-service contributions.
|
||||
`Session` validates `sourceEventSeqs` and `surfaceOp` at the always-on seed/append boundary: source lists are non-empty, unique, earlier, and known; `assistant/message` carries no source list; replacement endpoints exist in surface order; and `sourceEventSeqs` covers every shadowed node. These are single-record acceptance and storage-projection rules.
|
||||
|
||||
Every surface-eligible event must carry `surfaceOp` or it would disappear from derived history. Typed `append` overloads enforce this for literal event types; runtime checks in `append` and the seed constructor cover widened unions and current loaded logs. Released validation and conversion belong to their versioned migration edges rather than generic Session code; see the [V2-to-V3 placement rules](../../../../packages/session/session-format-v2-to-v3/README.md#canonical-envelopes).
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ surface 元数据仅属于四种 surface 事件类型(`system/message`、`user
|
||||
|
||||
### 不变式
|
||||
|
||||
`Session` 在始终启用的 seed/append 边界校验 `sourceEventSeqs` 与 `surfaceOp`:source list 必须非空、唯一、更早且已知;`assistant/message` 不携带 source list;replacement endpoint 必须存在于 surface 顺序中;`sourceEventSeqs` 必须覆盖每个被遮蔽的节点。这些是单记录接纳与存储投影规则,不是由可选 invariant service 提供的规则。
|
||||
`Session` 在始终启用的 seed/append 边界校验 `sourceEventSeqs` 与 `surfaceOp`:source list 必须非空、唯一、更早且已知;`assistant/message` 不携带 source list;replacement endpoint 必须存在于 surface 顺序中;`sourceEventSeqs` 必须覆盖每个被遮蔽的节点。这些是单记录接纳与存储投影规则。
|
||||
|
||||
每个可进入 surface 的事件都必须携带 `surfaceOp`,否则它将从派生历史中消失。类型化的 `append` 重载对字面事件类型强制执行此规则;`append` 和种子构造函数中的运行时检查覆盖宽化联合类型和当前已加载日志。已发布格式的校验与转换属于各自版本化迁移边,而不属于通用 Session 代码;参见 [V2 到 V3 位置规则](../../../../packages/session/session-format-v2-to-v3/README.zh.md#canonical-envelopes)。
|
||||
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@
|
||||
en: 6da15cf986d59a91
|
||||
zh: 40282908b54295f9
|
||||
/agent-note-every-llm-request-is-reconstructable-from-the-session-log/decision/the-mechanism:
|
||||
en: 7d86e5058dbab2fb
|
||||
zh: 9a97481325c8eded
|
||||
en: ab22e8949f7adaf1
|
||||
zh: 55d4d85a711f8371
|
||||
/agent-note-every-llm-request-is-reconstructable-from-the-session-log/decision/the-minicode-shape-adopted-with-the-event-log-as-the-source:
|
||||
en: e5f2ffaa3d7de0cc
|
||||
zh: 5ba6b36787a0e189
|
||||
|
||||
@@ -28,7 +28,7 @@ Each proposed step first claims its inbox batch, assembles the system prompt and
|
||||
|
||||
**The open step is the reconstruction boundary.** Its entered `user/message` batch and any newly written `request/header` precede request dispatch. Injection after the atomic claim joins a later request, while a listener that must affect this request returns messages through `agent/pre-step`. Header reconstruction selects the step's `request/header`, or carries the prior snapshot when no new header is written.
|
||||
|
||||
**Enforcement.** The `dsh-agent-loop/invariant` companion registers with `ctx.invariants` and, when selected, independently rebuilds each loop request through a fresh `Session`, so the live cache cannot vouch for itself, then compares messages and folded header fields at `llm/stream`. The loop records the exact frozen request through `markAgentLoopRequest()` in `dsh-llm`; the process-local identity lets the companion and other request observers recognize conversation work, while direct one-shots remain excluded regardless of their frozen shape or session id. Correctness depends on sequence-bounded reconstruction rather than listener order. A with-key e2e requires positive cache-read tokens after the first request; per-step usage is the production signal, and a header change or compaction appears as a cache-read drop on the next step.
|
||||
**Enforcement.** The loop records the exact frozen request through `markAgentLoopRequest()` in `dsh-llm`; the process-local identity lets request observers recognize conversation work, while direct one-shots remain excluded regardless of their frozen shape or session id. A with-key e2e requires positive cache-read tokens after the first request; per-step usage is the production signal, and a header change or compaction appears as a cache-read drop on the next step.
|
||||
|
||||
### The MiniCode shape: adopted, with the event log as the source
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Status: implemented
|
||||
|
||||
**已打开步骤是重建边界。** 进入步骤的 `user/message` 批次与任何新写入的 `request/header` 都位于请求分派之前。原子领取后发生的注入加入后续请求;必须影响本次请求的监听器则通过 `agent/pre-step` 返回消息。header 重建选择该步骤的 `request/header`,或在无新 header 写入时沿用前一个快照。
|
||||
|
||||
**强制执行。** `dsh-agent-loop/invariant` 配套插件向 `ctx.invariants` 注册,并在被选用时通过一个全新的 `Session` 独立重建每个循环请求,使活跃缓存无法为自身背书,然后在 `llm/stream` 处比较消息和折叠后的 header 字段。循环通过 `dsh-llm` 的 `markAgentLoopRequest()` 记录精确的冻结请求;这一进程内标识让配套插件和其他请求观察者识别对话工作,而直接的一次性调用无论其冻结形状或会话 id 如何都保持排除。正确性依赖于序列有界的重建,而非监听器顺序。带密钥的 e2e 要求首次请求之后有正值的 cache-read token;逐步骤用量是生产信号,header 变更或压缩表现为下一步骤的 cache-read 下降。
|
||||
**强制执行。** 循环通过 `dsh-llm` 的 `markAgentLoopRequest()` 记录精确的冻结请求;这一进程内标识让请求观察者识别对话工作,而直接的一次性调用无论其冻结形状或会话 id 如何都保持排除。带密钥的 e2e 要求首次请求之后有正值的 cache-read token;逐步骤用量是生产信号,header 变更或压缩表现为下一步骤的 cache-read 下降。
|
||||
|
||||
### MiniCode 形态:采纳,以事件日志为真源
|
||||
|
||||
|
||||
+8
-11
@@ -33,8 +33,8 @@
|
||||
en: 28636ae6d55935d7
|
||||
zh: 33a989f049de2f3e
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/scope-routing-one-opaque-key-selects-one-layer/fused-dispatch-helpers-prevent-subject-drift:
|
||||
en: 638e3711206e3c41
|
||||
zh: 61d370123d30c1ed
|
||||
en: 79a8edc4bc5e52b4
|
||||
zh: 45d76f37d1f2fe70
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/agent-creation-one-transaction-owns-the-complete-operation:
|
||||
en: ef58215c8554180b
|
||||
zh: 44571074148d2467
|
||||
@@ -60,8 +60,8 @@
|
||||
en: d0a57b0ac227d84c
|
||||
zh: e9ae6ced0e65fe49
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/session-append-materialize-validate-commit-notify/pre-commit-listeners-can-veto-post-commit-observers-cannot:
|
||||
en: 15255d3d0b62fac0
|
||||
zh: c1995d5aa3c78f0a
|
||||
en: 35cb9a69d0c80a5a
|
||||
zh: d99d93c1d407dc81
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/trust-boundaries-copy-only-when-ownership-actually-changes:
|
||||
en: 22b381902fe903f3
|
||||
zh: 8a2efe9a8b458bba
|
||||
@@ -114,14 +114,11 @@
|
||||
en: 840564e766d2f05c
|
||||
zh: 059243afea7eb9c0
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/types-make-the-ordinary-path-hard-to-misuse:
|
||||
en: 9183eb29dddfd97f
|
||||
zh: e6ce13295e6c3e65
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/runtime-invariants-cover-cross-service-facts:
|
||||
en: fabafbebc0ab4f95
|
||||
zh: 06d94a98ed5aa40c
|
||||
en: fb4d5abf86a2f6a4
|
||||
zh: 190b710a27ae0d9c
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/correctness-enforcement/generated-artifacts-keep-public-contracts-aligned:
|
||||
en: d0ddc5f38289225d
|
||||
zh: 781d910228d5e334
|
||||
en: 77d470221115af97
|
||||
zh: 1924afe62df16430
|
||||
/agent-note-agent-scope-runtime-design-and-correctness/alternatives-considered:
|
||||
en: 5eef7873a795c8f0
|
||||
zh: 3e8ba109a6e29ba5
|
||||
|
||||
@@ -84,7 +84,7 @@ Each service retains its domain rule. Named command and prompt views use the sha
|
||||
|
||||
`agentEvents(context, agent)` constructs the agent's carrier and injects the same agent as the event subject. Session, tool, approval, prompt, and subagent services likewise derive routing from the object they already own instead of accepting an unrelated key.
|
||||
|
||||
The type marker rejects ordinary bare-receiver mistakes, and development invariants cover direct JavaScript or casted dispatch. The subject remains explicit because routing correctness and useful event data are different concerns.
|
||||
The type marker rejects ordinary bare-receiver mistakes; direct JavaScript or casted dispatch is not checked at runtime. The subject remains explicit because routing correctness and useful event data are different concerns.
|
||||
|
||||
## Agent creation: one transaction owns the complete operation
|
||||
|
||||
@@ -183,7 +183,7 @@ Append follows one sequence:
|
||||
5. Notify each observer independently, containing synchronous and asynchronous failures.
|
||||
6. Release append state and honor a detach requested during publication.
|
||||
|
||||
No observer error makes a committed event look uncommitted, and one bad listener cannot starve later listeners. Session invariants stage their transition before commit and apply it only when the same event reaches the contained post-commit observer.
|
||||
No observer error makes a committed event look uncommitted, and one bad listener cannot starve later listeners.
|
||||
|
||||
`flush()` starts every persistence listener and awaits every result before reporting failure. This deliberate all-settled behavior prevents a synchronous failure from starving another backend or final flush.
|
||||
|
||||
@@ -332,15 +332,11 @@ Readonly contracts describe borrowed same-process values. `Scoped<T>` marks even
|
||||
|
||||
TypeScript cannot govern JavaScript casts, direct Cordis dispatch, process messages, or durable files, so runtime enforcement remains at those escape points.
|
||||
|
||||
### Runtime invariants cover cross-service facts
|
||||
|
||||
The `dsh-scope/invariant` companion verifies, when selected, that every declared scoped event uses a marked carrier and that event families exposing a subject use the matching key. The separate `dsh-session/invariant` contribution stages trace validation before append commit and advances after the same event commits; both register through `ctx.invariants`.
|
||||
|
||||
The plugin does not police trusted setup by scanning registries or reject prompt assembly objects fabricated through casts. Those checks would turn composition contracts into speculative runtime machinery without protecting a real external boundary.
|
||||
No runtime check polices trusted setup by scanning registries or rejects prompt assembly objects fabricated through casts. Those checks would turn composition contracts into speculative runtime machinery without protecting a real external boundary.
|
||||
|
||||
### Generated artifacts keep public contracts aligned
|
||||
|
||||
The event catalog, service catalog, producer/consumer matrix, configuration catalog, module graph, tool catalog, type-equivalence blocks, and scoped-event resolver map are generated or freshness-gated from source. The [TypeScript semantic-gates Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) owns Program construction, semantic event discovery, and resolver-generation rules.
|
||||
The event catalog, service catalog, producer/consumer matrix, configuration catalog, module graph, tool catalog, and type-equivalence blocks are generated or freshness-gated from source. The [TypeScript semantic-gates Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) owns Program construction and semantic event discovery rules.
|
||||
|
||||
Behavioral tests pin scoped routing and disposal, final-entry collision cleanup, publication rollback, ordered quiescence, durable pre/post-commit behavior, live tool filtering across presentation and execution, cooperative prompt assembly, structured-output commit in native and PTC mode, async subagent startup and signal cancellation, workflow terminal arbitration, ACP settlement, and process teardown.
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ Receiver 是一个小型载体而非领域对象的透明代理。需要 agent
|
||||
|
||||
`agentEvents(context, agent)` 构造 agent 的载体并注入同一个 agent 作为事件主体。会话、工具、approval、提示词和 subagent 服务同样从它们已拥有的对象派生路由,而非接受一个无关的键。
|
||||
|
||||
类型标记拒绝普通的裸 receiver 误用,开发环境不变式覆盖直接 JavaScript 或强制转换的 dispatch。主体保持显式,因为路由正确性和有用的事件数据是不同的关注点。
|
||||
类型标记拒绝普通的裸 receiver 误用;直接 JavaScript 或强制转换的 dispatch 不做运行时检查。主体保持显式,因为路由正确性和有用的事件数据是不同的关注点。
|
||||
|
||||
## Agent 创建:一个事务拥有完整操作
|
||||
|
||||
@@ -183,7 +183,7 @@ Session 头部、种子和追加的事件是无损 JSON 数据。Session 构造
|
||||
5. 逐个通知每个观察者,隔离同步和异步失败。
|
||||
6. 释放追加状态并兑现发布期间请求的 detach。
|
||||
|
||||
没有观察者错误能让已提交的事件看起来未提交,一个坏的监听器也无法饿死后续监听器。Session 不变式在提交前暂存其转换,仅当同一事件到达被隔离的提交后观察者时才应用。
|
||||
没有观察者错误能让已提交的事件看起来未提交,一个坏的监听器也无法饿死后续监听器。
|
||||
|
||||
`flush()` 启动每个持久化监听器并等待所有结果后再报告失败。这种有意的 all-settled 行为防止同步失败饿死另一个后端或最终刷新。
|
||||
|
||||
@@ -332,15 +332,11 @@ Readonly 约定描述借用的同进程值。`Scoped<T>` 标记事件接收器
|
||||
|
||||
TypeScript 无法管控 JavaScript 强制转换、直接 Cordis dispatch、进程消息或持久化文件,因此运行时强制保留在这些逃逸点。
|
||||
|
||||
### 运行时不变式覆盖跨服务事实
|
||||
|
||||
`dsh-scope/invariant` 配套插件在被选用时验证每个声明的作用域事件使用带标记的载体,以及暴露主体的事件族使用匹配的键。独立的 `dsh-session/invariant` 贡献在追加提交前暂存 trace 验证,并在同一事件提交后推进;二者都通过 `ctx.invariants` 注册。
|
||||
|
||||
该插件不通过扫描注册表来管控可信 setup,也不拒绝通过强制转换构造的提示词 assembly 对象。这些检查会将组合约定变成推测性的运行时机制,却不保护真实的外部边界。
|
||||
没有运行时检查通过扫描注册表来管控可信 setup,也不拒绝通过强制转换构造的提示词 assembly 对象。这些检查会将组合约定变成推测性的运行时机制,却不保护真实的外部边界。
|
||||
|
||||
### 生成的产物使公开约定保持对齐
|
||||
|
||||
事件目录、服务目录、生产者/消费方矩阵、配置目录、模块图、工具目录、type-equiv 块和作用域事件解析器映射都是从源码生成或受新鲜度门禁约束的。[TypeScript 语义门禁 Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) 拥有 Program 构造、语义事件发现和解析器生成规则。
|
||||
事件目录、服务目录、生产者/消费方矩阵、配置目录、模块图、工具目录和 type-equiv 块都是从源码生成或受新鲜度门禁约束的。[TypeScript 语义门禁 Agent Note](../../archived/process/2026-07-14-typescript-program-backed-semantic-gates.md) 拥有 Program 构造和语义事件发现规则。
|
||||
|
||||
行为测试固定了作用域路由和 dispose、最终写入注册表时的碰撞清理、发布回滚、有序完全停稳、持久化前/后提交行为、跨展示和执行的活跃工具过滤、协作式提示词组装、原生和 PTC mode 中的结构化输出提交、异步 subagent 启动和信号取消、工作流终态仲裁、ACP 结算和进程拆除。
|
||||
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@
|
||||
en: 0971d6196b5358fd
|
||||
zh: 26175d648feccfd9
|
||||
/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/directory-shape:
|
||||
en: 74c45680c8aab51c
|
||||
zh: fd431ae5d3656038
|
||||
en: b08293ef60b790a9
|
||||
zh: a4f87449d44d48a2
|
||||
/agent-note-web-client-architecture-the-client-cordis-plugin-tree-the-slot-system-and-the-react-free-object-layer/how-to-develop:
|
||||
en: cbc6abcd9f291868
|
||||
zh: 0078f15f2d08a02e
|
||||
|
||||
@@ -85,7 +85,7 @@ The dynamic ui-renderer plugin owns the ctx↔React adapter, application-level i
|
||||
|
||||
## Directory shape
|
||||
|
||||
Client packages live under `packages/client/*`, with `apps/web` as the thin Vite application over the shell's boot export. Plugin packages keep their browser half under `src/client/`; **every build artifact lands in `lib/`** — the node half as `lib/index.js`/`lib/invariant.js`, the browser bundle as `lib/client.js` (the shared tsdown client preset emits both; there is no `dist/` directory, and `exports["./client"]` points at `./lib/client.js`). `ui-slots`, runtime, and ui-renderer form the infrastructure direction; feature plugins cooperate through services and slots rather than importing presentation implementations.
|
||||
Client packages live under `packages/client/*`, with `apps/web` as the thin Vite application over the shell's boot export. Plugin packages keep their browser half under `src/client/`; **every build artifact lands in `lib/`** — the node half as `lib/index.js`, the browser bundle as `lib/client.js` (the shared tsdown client preset emits both; there is no `dist/` directory, and `exports["./client"]` points at `./lib/client.js`). `ui-slots`, runtime, and ui-renderer form the infrastructure direction; feature plugins cooperate through services and slots rather than importing presentation implementations.
|
||||
|
||||
A multi-domain plugin package additionally splits its client half by future package boundaries — ui-conversation is the exemplar:
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ Notifier 微任务合批 ──► ConversationSnapshot 缓存 ──uSES──
|
||||
|
||||
## 目录形态
|
||||
|
||||
Client 包位于 `packages/client/*`,`apps/web` 是壳 boot 导出之上的薄 Vite 应用。插件包的浏览器半边在 `src/client/` 下;**一切构建产物落 `lib/`**——node 半边为 `lib/index.js`/`lib/invariant.js`,浏览器 bundle 为 `lib/client.js`(共享 tsdown client 预设两者皆出;无 `dist/` 目录,`exports["./client"]` 指向 `./lib/client.js`)。`ui-slots`、runtime 与 ui-renderer 构成基础设施方向;功能插件通过服务与 slot 协作,不导入展示实现。
|
||||
Client 包位于 `packages/client/*`,`apps/web` 是壳 boot 导出之上的薄 Vite 应用。插件包的浏览器半边在 `src/client/` 下;**一切构建产物落 `lib/`**——node 半边为 `lib/index.js`,浏览器 bundle 为 `lib/client.js`(共享 tsdown client 预设两者皆出;无 `dist/` 目录,`exports["./client"]` 指向 `./lib/client.js`)。`ui-slots`、runtime 与 ui-renderer 构成基础设施方向;功能插件通过服务与 slot 协作,不导入展示实现。
|
||||
|
||||
多域插件包的 client 半边还按未来包边界再拆——ui-conversation 即样板:
|
||||
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
# Bilingual-pair consistency record for 2026-07-19-package-invariant-runtime-contracts.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md
|
||||
/agent-note-meaningful-package-invariant-contracts:
|
||||
en: e5db3b3373f72761
|
||||
zh: 312b83d320d9aaab
|
||||
/agent-note-meaningful-package-invariant-contracts/problem:
|
||||
en: 220a4ceb3cd9b9c9
|
||||
zh: 9eac93e1762b2914
|
||||
/agent-note-meaningful-package-invariant-contracts/decision:
|
||||
en: a15c7c13f167e5ac
|
||||
zh: 63bcc2ef60c2f344
|
||||
/agent-note-meaningful-package-invariant-contracts/decision/published-assertions-must-be-meaningful:
|
||||
en: 11a1c080592def10
|
||||
zh: d1189de665f9e327
|
||||
/agent-note-meaningful-package-invariant-contracts/decision/representative-implemented-checks:
|
||||
en: 2f7ae4ed588d6de8
|
||||
zh: ed7ea9ccc6c54e87
|
||||
/agent-note-meaningful-package-invariant-contracts/decision/repository-gate-and-tests:
|
||||
en: 37e646754f13dd65
|
||||
zh: 355ebf3ce1403117
|
||||
/agent-note-meaningful-package-invariant-contracts/alternatives-considered:
|
||||
en: 74c6a5a0cfadbf88
|
||||
zh: e41356bb1a8c1fdf
|
||||
/agent-note-meaningful-package-invariant-contracts/consequences:
|
||||
en: 1f8d53063fb5903e
|
||||
zh: 5edd23739749b065
|
||||
+2
-2
@@ -21,8 +21,8 @@
|
||||
en: 0efad389c38dfe95
|
||||
zh: 13dd73de20226540
|
||||
/agent-note-separate-context-injection-from-turn-execution/verification:
|
||||
en: 3f3db450a3fc5848
|
||||
zh: b2027393d570339d
|
||||
en: 56fe8195ed669f6d
|
||||
zh: 503ca0ce723c69b1
|
||||
/agent-note-separate-context-injection-from-turn-execution/consequences:
|
||||
en: 71079e4dede5e12f
|
||||
zh: deb46b5a4f151678
|
||||
+1
-1
@@ -64,7 +64,7 @@ This decision preserves the caller-owned framing decision from [unwrapped inject
|
||||
- Idle `inject()` immediately appends one durable inbox insertion but no model-visible `user/message`; a later waking delivery may start pre-step processing.
|
||||
- Active-turn injection is claimed at the nearest later pre-step boundary, after complete tool-result batches and before the request that consumes it.
|
||||
- Rejected or failed pre-step drops its claimed batch; input inserted after the claim remains pending.
|
||||
- Unit, persistence/resume, invariant, and TUI coverage pin event order, claim ownership, and durable replay.
|
||||
- Unit, persistence/resume, and TUI coverage pin event order, claim ownership, and durable replay.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ enter 分支的 `PreStepDecision.messages` 是拟议步骤的完整批次。wate
|
||||
- idle 状态下的 `inject()` 会立即追加一条持久 inbox 插入记录,但不会追加模型可见的 `user/message`;后续可唤醒投递可能开始 pre-step 处理。
|
||||
- 活跃轮次中的注入会在最近的后续 pre-step 边界领取,并位于完整工具结果批次之后、消费它的请求之前。
|
||||
- pre-step reject 或失败会丢弃其已领取批次;领取后插入的输入继续保持待处理。
|
||||
- 单元测试、持久化与 resume 测试、不变量测试和 TUI 覆盖会固定事件顺序、领取归属和持久回放。
|
||||
- 单元测试、持久化与 resume 测试和 TUI 覆盖会固定事件顺序、领取归属和持久回放。
|
||||
|
||||
## 后果
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 41363fca57ae20e6
|
||||
zh: 1d05eeeecaafa7f3
|
||||
/agent-note-terminal-llm-stream-failures/decision:
|
||||
en: c3bfde5f736f8b27
|
||||
zh: f51ebb4089df6406
|
||||
en: dd1dd01dda70bfb7
|
||||
zh: 8715075a5be2f7b7
|
||||
/agent-note-terminal-llm-stream-failures/alternatives-considered:
|
||||
en: 46153a92b2135aae
|
||||
zh: 0b8fdaf1f7edeb77
|
||||
|
||||
@@ -16,7 +16,7 @@ Retry policy had the same indirect ownership. It was discovered through the stre
|
||||
|
||||
`LlmRuntime` is the normalization boundary for one adapter attempt. It catches only final-adapter selection, synchronous dispatch, iterator construction, and `next()` failures, converts the thrown value to immutable `LlmFailure`, and emits one terminal `finish`. Caller cancellation or an `ABORTED` failure selects the aborted reason; every other adapter failure selects error. An adapter may also emit either terminal reason directly.
|
||||
|
||||
The adapter-owned catch ends before each yielded chunk. Errors from `llm/stream` middleware, nested calls, adapter cleanup, chunk consumers, logging, signal checks, and assembly remain thrown as defects or lifecycle failures; they never enter model-request recovery. A transport failure after partial deltas may leave blocks open, so the stream invariant permits open blocks only for terminal error or aborted finishes. No assistant message or tool call is assembled from that incomplete output.
|
||||
The adapter-owned catch ends before each yielded chunk. Errors from `llm/stream` middleware, nested calls, adapter cleanup, chunk consumers, logging, signal checks, and assembly remain thrown as defects or lifecycle failures; they never enter model-request recovery. A transport failure after partial deltas may leave blocks open before a terminal error or aborted finish. No assistant message or tool call is assembled from that incomplete output.
|
||||
|
||||
`PreparedLlmCall` exposes the immutable retry policy captured with its config and registration. One-shot reuse and config mismatch remain synchronous `INVALID_PREPARED_CALL` misuse errors. A route served entirely by `llm/stream` middleware has no prepared registration and therefore no serving policy.
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ Status: implemented
|
||||
|
||||
`LlmRuntime` 是一次适配器尝试的规范化边界。它只捕获最终适配器选择、同步分发、iterator 构造与 `next()` 失败,将抛出值转换为不可变 `LlmFailure`,并发出一个终止 `finish`。调用方取消或 `ABORTED` 失败选择 aborted 结束原因;其他适配器失败选择 error 结束原因。适配器也可以直接发出任一终止原因。
|
||||
|
||||
适配器所属的 catch 会在每个分片被 yield 前结束。来自 `llm/stream` middleware、嵌套调用、适配器清理、分片消费方、日志记录、signal 检查与组装的错误仍作为缺陷或生命周期失败抛出;它们绝不进入模型请求恢复。部分 delta 之后的传输失败可能留下未关闭块,因此流 invariant 只允许在终止 finish 的结束原因为 error 或 aborted 时存在未关闭块。不会从这些不完整输出组装 assistant 消息或工具调用。
|
||||
适配器所属的 catch 会在每个分片被 yield 前结束。来自 `llm/stream` middleware、嵌套调用、适配器清理、分片消费方、日志记录、signal 检查与组装的错误仍作为缺陷或生命周期失败抛出;它们绝不进入模型请求恢复。部分 delta 之后的传输失败可能在结束原因为 error 或 aborted 的终止 finish 之前留下未关闭块。不会从这些不完整输出组装 assistant 消息或工具调用。
|
||||
|
||||
`PreparedLlmCall` 公开随其配置和注册捕获的不可变重试策略。一次性句柄复用与配置不匹配仍是同步的 `INVALID_PREPARED_CALL` 误用错误。完全由 `llm/stream` middleware 提供服务的路由没有准备完成的注册,因此也没有服务策略。
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 6228c81e4965b688
|
||||
zh: 04b702be0cfbf195
|
||||
/agent-note-credential-boundaries-whole-snapshot-requests-and-atomic-route-registration/decision:
|
||||
en: d2aa88cf1e9ec97b
|
||||
zh: da9422723fc9ac93
|
||||
en: 052787311a4e6edc
|
||||
zh: 963eed632ea198de
|
||||
/agent-note-credential-boundaries-whole-snapshot-requests-and-atomic-route-registration/alternatives-considered:
|
||||
en: 90c5883b247f7301
|
||||
zh: 6d2a660d8653f805
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ Two request-path defects sat beside them. DeepSeek resolved connection and crede
|
||||
|
||||
**Route replacement is a registry operation, not a caller sequence.** `registerAdapter` returns a handle carrying `replace(providers)`: the candidate set is validated in full first (conflicts, names, provider metadata), then swapped in one synchronous section. A refused replacement leaves the previous routes registered and serving, and the caller's facts cache only advances after the registry actually holds the new set, so reverting to a working configuration re-applies. pi-ai's registration facts are sorted by provider, so a settings document that merely reorders its keys is no longer a route change.
|
||||
|
||||
**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/reference-updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome, and `INVARIANT`-coded failures rethrow after every listener ran — the same shape the settings seam uses for `settings/updated`. `SettingsProvider.installSection()` cleanup distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown.
|
||||
**Contained publication for committed credential writes.** `CredentialProvider.notifyUpdated` fans `credentials/reference-updated` out one listener at a time; sync throws and async rejections are logged without changing the committed operation's outcome. `SettingsProvider.installSection()` cleanup distinguishes its two triggers: a provider detaching still falls back to the composition entry and re-derives, while the consumer's own unload returns immediately instead of re-registering routes during teardown.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ Status: implemented
|
||||
|
||||
**路由替换是注册表的操作,不是调用方的一串步骤。**`registerAdapter` 返回一个携带 `replace(providers)` 的句柄:候选集合先被完整校验(冲突、名称、提供方元数据),再在一个同步区段内完成替换。被拒绝的替换会让先前的路由保持注册并继续服务,而调用方的事实缓存只有在注册表确实持有新集合之后才会推进,因此改回可用配置时会重新生效。pi-ai 的注册事实按提供方排序,因此仅仅调换键顺序的设置文档不再算作路由变更。
|
||||
|
||||
**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/reference-updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果,而带 `INVARIANT` 代码的失败会在每个监听器都运行完之后重抛——与 settings seam 处理 `settings/updated` 的形状相同。`SettingsProvider.installSection()` 的清理会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不在拆卸过程中重新注册路由。
|
||||
**已提交的凭据写入采用收容式发布。**`CredentialProvider.notifyUpdated` 逐个监听器扇出 `credentials/reference-updated`;同步抛错与异步 rejection 都只记日志,不改变已提交操作的结果。`SettingsProvider.installSection()` 的清理会区分它的两个触发来源:提供方脱离时仍回退到组合的 entry 配置并重新推导,而消费方自身卸载时立即返回,不在拆卸过程中重新注册路由。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
+2
-2
@@ -21,5 +21,5 @@
|
||||
en: cd1ed7dfa77722ae
|
||||
zh: 4ad834f3cf3b5443
|
||||
/agent-note-the-end-seed-log-boundary/consequences:
|
||||
en: 69eb86480b191135
|
||||
zh: 51ef88fb78701b1f
|
||||
en: 75dd52b899053769
|
||||
zh: a5d887987c2360cd
|
||||
@@ -52,4 +52,4 @@ Cost: a seeded session's log is one event longer, including an empty resumed log
|
||||
|
||||
`session/end-seed` joins the on-disk vocabulary. The current format requires the validated marker semantics owned by Session; the frozen v0 codec and migration edge own which historical v0 seed layouts remain admissible. The exact inherited cut stays separate from the logical header and is available after a body read.
|
||||
|
||||
The [queued manual compaction decision](../feature/2026-07-30-queued-manual-compaction.md) now supplies the first consumer. Its tail scan independently finds the unmatched `compaction/start` and newest end-seed, treats only a start after that boundary as live, and clears the invariant trace on the same replay transition. The predicate remains in the compaction package rather than becoming a generic core helper.
|
||||
The [queued manual compaction decision](../feature/2026-07-30-queued-manual-compaction.md) now supplies the first consumer. Its tail scan independently finds the unmatched `compaction/start` and newest end-seed, and treats only a start after that boundary as live. The predicate remains in the compaction package rather than becoming a generic core helper.
|
||||
+1
-1
@@ -52,4 +52,4 @@ Status: implemented
|
||||
|
||||
`session/end-seed` 加入了落盘词汇表。当前格式要求由 Session 拥有的已校验 marker 语义;冻结的 v0 codec 与迁移边负责哪些历史 v0 seed 布局仍可接受。精确继承 cut 与逻辑 header 分离,并在读取正文后可用。
|
||||
|
||||
[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)如今提供了第一个消费方。其尾部扫描会分别查找未匹配的 `compaction/start` 与最新 end-seed,只把位于该边界之后的 start 视为存活,并在同一个回放转换上清除不变量追踪状态。该谓词仍位于压缩功能所在的包中,不会成为通用核心辅助函数。
|
||||
[排队手动压缩决策](../feature/2026-07-30-queued-manual-compaction.zh.md)如今提供了第一个消费方。其尾部扫描会分别查找未匹配的 `compaction/start` 与最新 end-seed,并只把位于该边界之后的 start 视为存活。该谓词仍位于压缩功能所在的包中,不会成为通用核心辅助函数。
|
||||
+2
-2
@@ -15,5 +15,5 @@
|
||||
en: 7d79dfa834d9168f
|
||||
zh: 0b3e82a7d0ef71b9
|
||||
/agent-note-goal-owned-durable-events/consequences:
|
||||
en: 0e4513ea5e3e1e2e
|
||||
zh: 69d81ba52ee2f4b6
|
||||
en: b2a2f902f126f318
|
||||
zh: 8a38fe8acb0a2241
|
||||
@@ -28,6 +28,6 @@ The domain does not automatically project each mutation into model input. Goal t
|
||||
|
||||
## Consequences
|
||||
|
||||
Goal state is independent of inbox placement and admission. Replay has one mutation path, projections advance directly on `goal/change`, and continuation messages carry only round attribution. The model does not receive a mutation-only `<goal_state>` message; model-visible state appears through goal tools and scheduled continuation prompts. Direct session writers remain trusted and can append malformed changes, which the strict fold and invariant companion reject.
|
||||
Goal state is independent of inbox placement and admission. Replay has one mutation path, projections advance directly on `goal/change`, and continuation messages carry only round attribution. The model does not receive a mutation-only `<goal_state>` message; model-visible state appears through goal tools and scheduled continuation prompts. Direct session writers remain trusted and can append malformed changes, which the strict fold rejects.
|
||||
|
||||
Focused goal, goal-round-driver, command, TUI, and Client RemoteMock tests pin durable replay, positive-round accounting, inbox independence, projection updates, and restored-session behavior. The keyless process test inspects the persisted `goal/change` event and verifies that creation alone starts no continuation round.
|
||||
@@ -28,6 +28,6 @@ Goal 领域需要持久状态,但不需要拥有待处理的模型输入。继
|
||||
|
||||
## 后果
|
||||
|
||||
Goal 状态不依赖 inbox 放置与准入。回放只有一条变更路径,投影直接由 `goal/change` 推进,继续执行消息只携带 Round 归属。模型不会收到仅用于变更的 `<goal_state>` 消息;模型可见状态来自 goal 工具与已调度的继续执行提示词。直接写入会话的写入方仍受信任,并且可以追加畸形变更;严格折叠与 invariant 配套模块会拒绝这些变更。
|
||||
Goal 状态不依赖 inbox 放置与准入。回放只有一条变更路径,投影直接由 `goal/change` 推进,继续执行消息只携带 Round 归属。模型不会收到仅用于变更的 `<goal_state>` 消息;模型可见状态来自 goal 工具与已调度的继续执行提示词。直接写入会话的写入方仍受信任,并且可以追加畸形变更;严格折叠会拒绝这些变更。
|
||||
|
||||
聚焦的 goal、goal-round-driver、command、TUI 与 Client RemoteMock 测试固定持久回放、正数 Round 计数、inbox 独立性、投影更新和恢复会话行为。无密钥进程测试检查持久的 `goal/change` 事件,并验证仅创建 goal 不会启动继续执行 Round。
|
||||
+4
-4
@@ -9,8 +9,8 @@
|
||||
en: 2c36946bbff637b7
|
||||
zh: f290de0866ba7caa
|
||||
/agent-note-the-ptc-runtime-python-fd-3-frame-protocol/decision:
|
||||
en: 1f93159850fc7f48
|
||||
zh: dcc2a4788e3254f7
|
||||
en: f0d0d5ba5a9066ad
|
||||
zh: fbdab3731c33eb26
|
||||
/agent-note-the-ptc-runtime-python-fd-3-frame-protocol/wire-contract:
|
||||
en: 913f9465320dc191
|
||||
zh: f8e0338645d6c204
|
||||
@@ -18,8 +18,8 @@
|
||||
en: 26128b65f2763920
|
||||
zh: df24359faebcbfed
|
||||
/agent-note-the-ptc-runtime-python-fd-3-frame-protocol/alternatives-considered:
|
||||
en: cc42636bd7793305
|
||||
zh: e196f980daec3037
|
||||
en: 2f52b91980d013ed
|
||||
zh: 12dc9a24c03c3c68
|
||||
/agent-note-the-ptc-runtime-python-fd-3-frame-protocol/consequences:
|
||||
en: 16df6efd46d71e85
|
||||
zh: 4062475bec04ab5f
|
||||
+2
-2
@@ -22,7 +22,7 @@ The experimental package contains both the protocol and runtime implementation:
|
||||
|
||||
`py/protocol.py` mirrors the message shapes as `TypedDict`s and re-declares the two surfaces both sides EXECUTE against — `PROTOCOL_FD = 3` and `log_truncation_marker` — with byte-identical text.
|
||||
|
||||
The package ships the runtime alongside the protocol; it remains independently buildable. `check-workspace-constraints` reads every `packages/<group>/<pkg>/package.json` unconditionally, while the coverage and invariant-topology checks exercise the package as soon as its directory exists.
|
||||
The package ships the runtime alongside the protocol; it remains independently buildable. `check-workspace-constraints` reads every `packages/<group>/<pkg>/package.json` unconditionally, while the coverage check exercises the package as soon as its directory exists.
|
||||
|
||||
## Wire contract
|
||||
|
||||
@@ -36,7 +36,7 @@ Frames are JSON-lines on fd 3, one object per line, leaving stdout/stderr free f
|
||||
|
||||
**Require a future Python JSON codec (`_encode_json_plain` / `_decode_json_plain`) to live in `py/protocol.py` for cross-side symmetry with `protocol.ts`.** Rejected. The repository's "prefer symmetry for parallel values" rule points at genuinely parallel values; these are not. The host-side codec in `protocol.ts` validates hostile input and is self-contained. A child-side codec would produce trusted output and belong with bootstrap-owned emission and cost accounting; forcing only its entry points into `protocol.py` would couple the vocabulary mirror to runtime internals or create an import cycle. `protocol.py` remains a pure wire-vocabulary mirror; the codec (`_encode_json_plain` / `_decode_json_plain`) lives in `bootstrap.py` with the runtime it serves.
|
||||
|
||||
**Keep the protocol files outside a buildable package until a runtime ships.** Rejected: the workspace-constraint, coverage, and invariant-topology checks require every directory under `packages/<group>/<pkg>` to be a buildable package, and the protocol has independent tests and a public wire vocabulary.
|
||||
**Keep the protocol files outside a buildable package until a runtime ships.** Rejected: the workspace-constraint and coverage checks require every directory under `packages/<group>/<pkg>` to be a buildable package, and the protocol has independent tests and a public wire vocabulary.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@d
|
||||
|
||||
`py/protocol.py` 用 `TypedDict` 镜像消息形状,并重新声明两侧都会 EXECUTE 的两个面——`PROTOCOL_FD = 3` 与 `log_truncation_marker`——文本逐字节一致。
|
||||
|
||||
该包随协议一起交付 runtime,同时保持独立可构建。`check-workspace-constraints` 会无条件读取每个 `packages/<group>/<pkg>/package.json`,coverage 与 invariant-topology 检查则会在包目录存在时立即覆盖该包。
|
||||
该包随协议一起交付 runtime,同时保持独立可构建。`check-workspace-constraints` 会无条件读取每个 `packages/<group>/<pkg>/package.json`,coverage 检查则会在包目录存在时立即覆盖该包。
|
||||
|
||||
## Wire contract
|
||||
|
||||
@@ -36,7 +36,7 @@ CPython PTC 运行时位于 `packages/experimental/ptc-runtime-python`,以 `@d
|
||||
|
||||
**要求未来的 Python JSON codec(`_encode_json_plain` / `_decode_json_plain`)放进 `py/protocol.py`,以便与 `protocol.ts` 跨侧对称。**拒绝。仓库的 “prefer symmetry for parallel values” 规则指向真正平行的值;这两者不是。`protocol.ts` 中的 host 侧 codec 校验敌意输入且自包含。Child 侧 codec 会产出受信任输出,应与 bootstrap 拥有的发出逻辑和成本核算放在一起;只把入口强塞进 `protocol.py` 会让 vocabulary 镜像耦合 runtime 内部实现,或制造 import 环。`protocol.py` 保持纯 wire-vocabulary 镜像;codec(`_encode_json_plain`/`_decode_json_plain`)与它所服务的 runtime 一起位于 `bootstrap.py`。
|
||||
|
||||
**在 runtime 交付前把协议文件放在不可构建的包外。**拒绝:workspace-constraint、coverage 与 invariant-topology 检查要求 `packages/<group>/<pkg>` 下的每个目录都是可构建包,而协议本身拥有独立测试与公开 wire vocabulary。
|
||||
**在 runtime 交付前把协议文件放在不可构建的包外。**拒绝:workspace-constraint 与 coverage 检查要求 `packages/<group>/<pkg>` 下的每个目录都是可构建包,而协议本身拥有独立测试与公开 wire vocabulary。
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+8
-8
@@ -9,14 +9,14 @@
|
||||
en: 5a5d4de7d40ed86b
|
||||
zh: abfae08f9c291c54
|
||||
/agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/decision:
|
||||
en: 60be2f515ff5f46d
|
||||
zh: ee8514deadb08260
|
||||
en: be767ee6f00ac6c3
|
||||
zh: 7742a77e3ea12a5e
|
||||
/agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/testing:
|
||||
en: a7b7ca354607dd21
|
||||
zh: f88356bfc7dc770e
|
||||
en: 960fc4b18dff9f38
|
||||
zh: d549e33100351124
|
||||
/agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/alternatives-considered:
|
||||
en: eac46307265e647d
|
||||
zh: acfad02f8ca1dc3f
|
||||
en: ca7c9088d0793e1e
|
||||
zh: ea648bac9e726c64
|
||||
/agent-note-what-stays-host-plane-once-presets-own-the-agent-plane/consequences:
|
||||
en: d8576d7db9dea547
|
||||
zh: 428bb5a7e609d170
|
||||
en: de26cde94adda770
|
||||
zh: 7f39760b77958966
|
||||
+3
-5
@@ -18,7 +18,7 @@ Nothing named an agent that joined no preset. The join is a scope-parent link; w
|
||||
|
||||
**The meter is host-plane.** `dsh-token-meter` returns to the host composition and leaves the presets' `isolate` map, so `compaction-basic` and `tool-result-pruner` resolve the one host instance from inside their realm. The presets keep the realm and the backend — what a preset chooses is whether its agent compacts, not whether its tokens are counted. This is the criterion `tasks` and `goals` are already read by, applied to a Service whose *projection* reach is what made preset ownership wrong: a unit whose empty value is indistinguishable from a real one cannot be per-composition while the table it registers into is per-process.
|
||||
|
||||
**An unjoined agent fails at model use.** The invariant companion checks `system-prompt/assemble`, because a bare Agent can legally be bound later through `recompose`. Host-only and cold-scope prompt reads have no Agent and remain outside this check.
|
||||
**Entry points join Agents explicitly.** No runtime check rejects an unjoined Agent, because a bare Agent can legally be bound later through `recompose`.
|
||||
|
||||
Projection key presence is not a per-session capability signal ([session projection](../../../../packages/session/session-projection/README.md)). Temporary plugins mounted through `cordis_mount` belong to the composition rather than the invoking session ([tool Cordis](../../../../packages/extensions/tool-cordis/README.md)). [Declarative presets](2026-09-18-declarative-agent-presets.md) own revision retention and reclaim retired trees after their final reference releases.
|
||||
|
||||
@@ -26,15 +26,13 @@ Projection key presence is not a per-session capability signal ([session project
|
||||
|
||||
`apps/cli/tests/web-agent-presets.e2e.ts` reads `ctx.get('tokenMeter')` on the booted Web composition before any Agent is created — a preset-side meter sits behind an `isolate` realm and is invisible to `ctx.get`, so the read is an ownership assertion rather than a mount-order coincidence — then asserts a `minimal` session's snapshot carries all three units.
|
||||
|
||||
`packages/preset/agent-preset-registry/tests/invariant.spec.ts` rejects an unjoined Agent's assembly and accepts a joined Agent, a Host read and a cold-scope read.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the meter in the preset and scope-layer the projection registry.** The precise fix, and much larger: `snapshot`, `checkpoint`, and the eager drive would each need a session→scope resolution that a cold read does not have without the api-proxy's `presenterScopeFor`. Rejected as disproportionate to one Service with no per-preset state at all; the general rule is documented on the registry instead.
|
||||
|
||||
**Veto publication for an unjoined agent.** Loud beats silent, and the registry supports it — a synchronous `agent/created` listener that throws rolls the creation back. Rejected because composing an agent outside the roster is legal: `recompose` documents the bare agent it then binds, and the ACP bridge, the SDK server, and the headless bundle all create one. A veto would convert a capability gap into an outage.
|
||||
|
||||
**Check the join at `agent/created` in the companion too.** Rejected: publication cannot distinguish a missed join from an agent that will be bound later, so the check would reject a documented path. Prompt assembly can distinguish them.
|
||||
**Check the join at `agent/created`.** Rejected: publication cannot distinguish a missed join from an agent that will be bound later, so the check would reject a documented path.
|
||||
|
||||
**Move `plan-mode` and `tool-todo` off the agent plane for the same projection reason.** Rejected: both are genuinely per-preset capabilities, and their units compute an empty value for a session that never uses them, which clients already read by value (`plan.active`, an empty list). Only a unit whose empty value is indistinguishable from a real one — the meter — forces host ownership.
|
||||
|
||||
@@ -42,4 +40,4 @@ Projection key presence is not a per-session capability signal ([session project
|
||||
|
||||
The context meter becomes a per-session fact instead of a function of mount history. A preset can no longer opt out of token accounting; no shipped preset did, and `minimal` now says it drops auto-compaction rather than the accounting.
|
||||
|
||||
The invariant reaches compositions loading `dsh-invariants`. Production entry points must compose Agents explicitly; installing the registry alone does not bind them.
|
||||
Production entry points must compose Agents explicitly; installing the registry alone does not bind them. An unjoined Agent receives no tools, prompt sections, or skills and reports no error.
|
||||
+3
-5
@@ -18,7 +18,7 @@ Status: implemented
|
||||
|
||||
**meter 属于宿主平面。** `dsh-token-meter` 回到宿主组装,并离开各 preset 的 `isolate` 映射,于是 `compaction-basic` 与 `tool-result-pruner` 在自己的 realm 内部解析到那一份宿主实例。preset 保留 realm 与压缩后端——preset 选择的是它的 agent 是否压缩,而不是它的 token 是否被计。这正是 `tasks` 与 `goals` 已经采用的判据,只是这次适用于一个因**投影**触达面而不该归 preset 所有的 Service:当一个单元的空值与真实值无法区分时,只要它注册进的那张表是进程级的,它就不能是逐组装的。
|
||||
|
||||
**未加入的 Agent 在使用模型时失败。** 不变量 companion 检查 `system-prompt/assemble`,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。纯 Host 和历史 scope 提示词读取没有 Agent,不属于此检查范围。
|
||||
**入口显式让 Agent 加入。** 没有运行时检查会拒绝未加入的 Agent,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。
|
||||
|
||||
投影 key 是否存在不能代表逐会话能力([会话投影](../../../../packages/session/session-projection/README.zh.md))。通过 `cordis_mount` 挂载的临时插件属于组装,而非调用会话([Cordis 工具](../../../../packages/extensions/tool-cordis/README.zh.md))。[声明式 preset](2026-09-18-declarative-agent-presets.zh.md) 负责代际保留,在最后一个引用释放后回收退役树。
|
||||
|
||||
@@ -26,15 +26,13 @@ Status: implemented
|
||||
|
||||
`apps/cli/tests/web-agent-presets.e2e.ts` 在创建任何 Agent **之前**,于已启动的 Web 组装上读取 `ctx.get('tokenMeter')`——preset 侧的 meter 会待在 `isolate` realm 里,对 `ctx.get` 不可见,因此这次读取是一次所有权断言而不是挂载顺序的巧合——随后断言一个 `minimal` 会话的快照带齐三个单元。
|
||||
|
||||
`packages/preset/agent-preset-registry/tests/invariant.spec.ts` 拒绝未加入的 Agent 组装,并接受已加入的 Agent、Host 读取和历史 scope 读取。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**把 meter 留在 preset,改为给投影注册表分层。** 这是更精确的修法,代价也大得多:`snapshot`、`checkpoint` 与主动驱动都需要一次「会话 → 作用域」的解析,而冷读在没有 api-proxy 的 `presenterScopeFor` 时并不具备。相对于一个完全没有 per-preset 状态的 Service,这不成比例,因此改为把通则写在注册表上。
|
||||
|
||||
**对未加入的 agent 否决发布。** 大声胜过静默,注册表也支持这么做——同步的 `agent/created` 监听器抛出会把创建整体回滚。否决的理由是:在名单之外组装 agent 是合法的——`recompose` 写明了它随后绑定的那个裸 agent,而 ACP 桥、SDK server 与 headless bundle 都会创建一个。否决会把能力缺口变成一次故障。
|
||||
|
||||
**让配套也在 `agent/created` 处检查加入情况。** 否决:发布时分不清漏掉的加入与之后才会被绑定的 agent,因此该检查会拒绝一条已写明的路径。提示词组装分得清。
|
||||
**在 `agent/created` 处检查加入情况。** 否决:发布时分不清漏掉的加入与之后才会被绑定的 agent,因此该检查会拒绝一条已写明的路径。
|
||||
|
||||
**基于同样的投影理由,把 `plan-mode` 与 `tool-todo` 也搬离 agent 平面。** 否决:两者确实是逐 preset 的能力,且对从不使用它们的会话,其单元算出的就是空值,而客户端本来就按值读取(`plan.active`、空列表)。只有空值与真实值无法区分的单元——meter——才被迫归宿主所有。
|
||||
|
||||
@@ -42,4 +40,4 @@ Status: implemented
|
||||
|
||||
context meter 成为逐会话的事实,而不再是挂载历史的函数。代价是 preset 不能再选择不做 token 记账;随附的 preset 没有一个这么做,`minimal` 现在也写明它放弃的是自动压缩而非记账。
|
||||
|
||||
不变量覆盖加载了 `dsh-invariants` 的组装。生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。
|
||||
生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。未加入的 Agent 不会获得工具、提示词段落或 skill,也不会报告错误。
|
||||
@@ -33,8 +33,8 @@
|
||||
en: 4ec0e1c76da8d3b6
|
||||
zh: 23de66f8fc659c15
|
||||
/agent-note-remote-event-delivery-ctx-remote-on/consequences:
|
||||
en: c50a0968e3f65df7
|
||||
zh: 786f33cf1e95dae4
|
||||
en: b5e5bdd536743b30
|
||||
zh: 78948abf22f16bdb
|
||||
/agent-note-remote-event-delivery-ctx-remote-on/related:
|
||||
en: b64e771dafa6c05b
|
||||
zh: 4c2f47884f5a2d19
|
||||
@@ -194,7 +194,7 @@ The few required Client symbols are mirrored on the test side: `scaffold.ts` exp
|
||||
- **Test-side mirrors can drift.** No mechanism compares mirrored Client constants under `apps/web/tests` with their source. Drift instead produces a selector mismatch. `apps/web/tests/README.md` records the review rule; a grep-level gate is deliberately omitted.
|
||||
- **Capabilities deliberately omitted.** Payload projection and redaction are unsupported, scopes other than Agent are unsupported, and ordinary notifications are not replayed. Recoverable state needs a query, cursor, or opening baseline; a waterfall is replayed only while its original Host invocation remains pending.
|
||||
- **Some Client packages remain in the Host graph.** Twelve projects, including `connection`, `runtime`, and `ui-slots`, remain reachable through unsplit `directory-picker-browse`/`-native` and `api/gateway → client/connection`. They compile and no longer pull in API Remotes' Client face, so this change does not split them. Direct `dsh-client-runtime/client` imports in two chat e2e files rely on Runtime's current presence in that graph rather than a general guarantee.
|
||||
- **The package intentionally publishes no invariant companion.** A prior revision asserted delivery form on the live event bus, coupling diagnostics to the allowlist and causing Rolldown to emit a third bundle chunk omitted by the mechanically derived publication list. The Host-face `TypertForwardableEventEntry` assertion already rejects those mismatches at compile time, and the package README records why no independent runtime relation remains.
|
||||
- **Delivery form is checked at compile time only.** The Host-face `TypertForwardableEventEntry` assertion rejects delivery-form mismatches; no runtime check inspects delivery form on the live event bus.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -196,7 +196,7 @@ Client 要求首项是带非空 `clientId` 与 `host.home` 的 `ready`;后续
|
||||
- **测试侧镜像值可能漂移**:没有任何机制核对 `apps/web/tests` 中镜像的 client 常量与其源;安全网只是漂移会让选择器失配。规则写在 `apps/web/tests/README.md`,由 review 守;grep 级门禁经评估后刻意不做。
|
||||
- **放弃的能力**:不支持投影或脱敏载荷,不支持 Agent 以外的 Scope,也不为普通通知提供重放。需要可靠恢复的状态必须拥有查询、cursor 或 opening baseline;waterfall 只重放仍处于同一次 Host 调用生命周期内的 pending request。
|
||||
- **仍有 client 包留在 host 图里**:12 个工程(`connection`、`runtime`、`ui-slots` 等)经未拆分的 `directory-picker-browse`/`-native` 与 `api/gateway → client/connection` 仍可达 host 图。它们都能编译且不再牵连 api/remotes 的 client face,因此没有阻塞本次改动;拆分那些包能减少几个,但经评估后不做。两个 chat e2e 直接引 `dsh-client-runtime/client` 依赖 `runtime` 本来就在图里——属偶然而非保证。
|
||||
- **本包不发布 invariant companion**:早先的修订曾在活事件总线上断言投递形状(`thisArg === null`、`mode === 'emit'`),这让诊断逻辑与名单值耦合,并使 rolldown 把它提成第三个 bundle chunk——而机械推导的发布文件清单并不携带它。Host 面的 `TypertForwardableEventEntry` 断言已在编译期拒绝这些偏离,包 README 也记录了不再存在独立运行时关系的原因。
|
||||
- **投递形式只在编译期检查**:Host 面的 `TypertForwardableEventEntry` 断言拒绝投递形式偏离;没有运行时检查会在活事件总线上检查投递形式。
|
||||
|
||||
## 相关
|
||||
|
||||
|
||||
+2
-2
@@ -21,5 +21,5 @@
|
||||
en: ef7c5f6fa346c1d8
|
||||
zh: 439b0d8394015f1e
|
||||
/agent-note-credential-records-and-authorization-flows/testing:
|
||||
en: 6c8c01f75e6f916b
|
||||
zh: 7c5c7491f64f2936
|
||||
en: 89f5714501a92b19
|
||||
zh: c92fac2c35532db1
|
||||
+1
-1
@@ -61,7 +61,7 @@ Two limits are recorded in the package READMEs rather than fixed. An attempt is
|
||||
|
||||
## Testing
|
||||
|
||||
The seam's suite pins the lifecycle it owns: single-flight refusal and release, withdrawal before the flow starts and during it, a flow that ignores its signal, the commit confirmation, and the settlement event including the `failed` case a caller sees as a thrown error. The invariant companion pins that a settled key is a free key, because a wedged one is otherwise invisible.
|
||||
The seam's suite pins the lifecycle it owns: single-flight refusal and release, withdrawal before the flow starts and during it, a flow that ignores its signal, the commit confirmation, and the settlement event including the `failed` case a caller sees as a thrown error.
|
||||
|
||||
`llm-pi-ai` covers the three translations against a real `$DSH_HOME` document — an api-key credential field by field, an OAuth credential verbatim including its refresh half, a foreign plugin's record skipped by scope, and the write refusal without a credentials service — plus every `AuthEvent` and `AuthPrompt` member restated, with `Models.login()` mocked at the collection boundary since a real one opens a browser. Two real-composition tests boot the plugin with and without the authorization seam.
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ seam 的边缘与写入路径同一纪律。prompt 被拒是结果而非故障
|
||||
|
||||
## Testing
|
||||
|
||||
seam 自己的套件钉住它拥有的生命周期:单飞的拒绝与释放、flow 启动前与进行中的撤销、一个忽略自身信号的 flow、提交核实,以及包含"调用方看到的是抛出错误"那种 `failed` 情形的结算事件。invariant companion 钉住"已结算的键就是空闲的键",因为被卡住的键否则不可见。
|
||||
seam 自己的套件钉住它拥有的生命周期:单飞的拒绝与释放、flow 启动前与进行中的撤销、一个忽略自身信号的 flow、提交核实,以及包含"调用方看到的是抛出错误"那种 `failed` 情形的结算事件。
|
||||
|
||||
`llm-pi-ai` 针对一份真实的 `$DSH_HOME` 文档覆盖三处翻译——逐字段的 api-key 凭据、连 refresh 半边一起原样保存的 OAuth 凭据、按 scope 跳过的他插件记录,以及没有凭据服务时的写入拒绝——外加每一个 `AuthEvent` 与 `AuthPrompt` 成员的重述;`Models.login()` 在集合边界处被 mock,因为真实登录会打开浏览器。两个真实组合测试分别在挂载与不挂载授权 seam 的情况下启动插件。
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: e45938d278a6c12d
|
||||
zh: 93c790d466f7bd12
|
||||
/agent-note-publish-agent-teams-under-experimental-package-names/decision:
|
||||
en: a2555b0c5a6f6f53
|
||||
zh: f7082ceb8542b0fb
|
||||
en: 2195965b69c27c77
|
||||
zh: 0ce002b6fb25aab4
|
||||
/agent-note-publish-agent-teams-under-experimental-package-names/alternatives-considered:
|
||||
en: 869decdfbc7b3466
|
||||
zh: b51f5ab731d1a84a
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ The [single-bundle decision](2026-09-18-agent-teams-single-bundle.md) supersedes
|
||||
|
||||
Profile startup resolves selected bundles before computing the [immutable runtime resolution](2026-09-09-profile-resolution-generations.md). The runtime resolution retains installation-first precedence, traverses each explicit bundle root completely in profile order, and keeps pnpm-managed profile packages authoritative. The runtime interception enforces the result in memory without materializing fallback links. A private profile layer can therefore carry experimental plugin rows without adding those plugins to a release app, requiring profile users to install transitive packages directly, weakening packaged-runtime module identity, or changing another profile's resolution.
|
||||
|
||||
Experimental status changes compatibility and support expectations, not publication for these four packages. They retain the repository's ordinary documentation, invariant, lifecycle, security, unit, real-composition, and snapshot requirements. Promotion still requires review of the public contracts, limitations, test evidence, runtime dependents, and a named owner accepting stable-package obligations.
|
||||
Experimental status changes compatibility and support expectations, not publication for these four packages. They retain the repository's ordinary documentation, lifecycle, security, unit, real-composition, and snapshot requirements. Promotion still requires review of the public contracts, limitations, test evidence, runtime dependents, and a named owner accepting stable-package obligations.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ dsh 打包与发布集合以及本地基线发布器包含这四个 Agent Teams
|
||||
|
||||
profile 启动会先解析所选 bundle,再计算[不可变 runtime resolution](2026-09-09-profile-resolution-generations.zh.md)。runtime resolution 保留安装优先顺序,按 profile 顺序完整遍历每个显式 bundle 根,并让 pnpm 管理的 profile 包保持优先。运行时拦截在内存中强制该结果,不物化 fallback 链接。因此,私有 profile 层可以携带实验性 plugin 配置行,而无需把这些 plugin 加入发布 app、要求 profile 用户直接安装传递依赖、破坏 packaged-runtime 的模块身份,或改变其他 profile 的解析结果。
|
||||
|
||||
对这四个包而言,实验性状态改变兼容性与支持预期,而不阻止发布。这些包仍须满足仓库的一般文档、不变式、生命周期、安全、单元测试、真实组合测试和快照要求。promotion 前仍须评审公开约定、限制、测试证据、运行时依赖方,并由一名具名 owner 接受稳定包义务。
|
||||
对这四个包而言,实验性状态改变兼容性与支持预期,而不阻止发布。这些包仍须满足仓库的一般文档、生命周期、安全、单元测试、真实组合测试和快照要求。promotion 前仍须评审公开约定、限制、测试证据、运行时依赖方,并由一名具名 owner 接受稳定包义务。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
|
||||
+2
-2
@@ -21,8 +21,8 @@
|
||||
en: eb69576e756f4aa1
|
||||
zh: 5c1923159da3f7a2
|
||||
/agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/verification:
|
||||
en: 0bda0074b5e11b44
|
||||
zh: 4989c1bb840c78ce
|
||||
en: 9935338a0970d09d
|
||||
zh: a9a36702bc72a9f7
|
||||
/agent-note-deepseek-llm-api-request-extensions-for-session-logs-and-plugin-packages/alternatives-considered:
|
||||
en: 5faf7699272f99b4
|
||||
zh: 7a402e24c80131cf
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ The process-lifetime manifest-identity cache remains separate because in-process
|
||||
|
||||
## Verification
|
||||
|
||||
Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, out-of-order acceptance, and late invariant loading. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests.
|
||||
Registry tests pin duplicate ownership, effect-scoped disposal, detached field values, concurrent and abortable preparation, receiver-preserving acceptance, one acceptance settlement, and failure aggregation. Session tests pin default-on and explicit-off policies, full-first/suffix-later delivery, direct complete event envelopes independent of base-body messages, incremental watermark folding, persisted restart recovery, fork identity fencing, and out-of-order acceptance. Package-inventory tests pin default-on and explicit-off policies, host and standing-preset discovery, conflicting Loader resolution bases, manifest resolution, lifecycle filtering, and exact name/version ordering. The direct adapter mock proves pre-HTTP preparation failure, cancellation, non-2xx non-acceptance, 2xx acceptance before a later stream failure, and field collision. Keyless replay pins post-2xx extension acceptance, and the TypeScript JSON-RPC plus Python packaged-runtime snapshots project the acceptance event through both SDKs. Real Loader composition pins default package metadata and Session upload plus explicit upload disablement, one real-API request mounts both shipped extensions and proves the official endpoint accepts them, and pi-ai tests retain their unchanged wire requests.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ Status: implemented
|
||||
|
||||
## 验证
|
||||
|
||||
注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。会话测试固定默认开启与显式关闭策略、首次完整/后续后缀交付、与基础正文消息无关的直接完整事件信封、增量水位 fold、持久化重启恢复、fork 身份围栏、乱序接受与 invariant 延迟加载。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放会固定 2xx 后扩展接受,TypeScript JSON-RPC 与 Python 打包运行时快照则通过两套 SDK 投影接受事件。真实 Loader 组合会固定默认包元数据、默认会话上传与显式关闭上传,一个真实 API 请求会挂载两个随附扩展并证明官方端点接受它们;pi-ai 测试保持其协议请求不变。
|
||||
注册表测试固定重复所有权、effect 作用域 dispose(资源释放)、分离字段值、并发且可取消的准备、保留接收者的接受操作、单次接受结算与失败聚合。会话测试固定默认开启与显式关闭策略、首次完整/后续后缀交付、与基础正文消息无关的直接完整事件信封、增量水位 fold、持久化重启恢复、fork 身份围栏与乱序接受。插件包清单测试固定默认开启与显式关闭策略、宿主与 standing preset 发现、冲突的 Loader 解析基址、manifest 解析、生命周期过滤及确切名称/版本排序。直接适配器 mock 测试证明 HTTP 前准备失败、取消、非 2xx 不接受、2xx 在后续流失败前接受,以及字段冲突。无密钥回放会固定 2xx 后扩展接受,TypeScript JSON-RPC 与 Python 打包运行时快照则通过两套 SDK 投影接受事件。真实 Loader 组合会固定默认包元数据、默认会话上传与显式关闭上传,一个真实 API 请求会挂载两个随附扩展并证明官方端点接受它们;pi-ai 测试保持其协议请求不变。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
|
||||
+4
-4
@@ -18,8 +18,8 @@
|
||||
en: a217a08932938062
|
||||
zh: 4924d99d55d9c973
|
||||
/agent-note-the-system-prompt-is-surface-node-0/decision/ownership-in-the-loop:
|
||||
en: 88945024c2efe271
|
||||
zh: 05dca84a4ce5e614
|
||||
en: a7d0d3a61d2fef04
|
||||
zh: 29a3d5dbed2f987a
|
||||
/agent-note-the-system-prompt-is-surface-node-0/decision/consumers:
|
||||
en: 39f9f4acffcccdc6
|
||||
zh: 36b23a57e7d28950
|
||||
@@ -33,5 +33,5 @@
|
||||
en: bccea8236b4c32b3
|
||||
zh: a1150784718a6639
|
||||
/agent-note-the-system-prompt-is-surface-node-0/testing:
|
||||
en: f5b21c4863cd92bd
|
||||
zh: 7d1efed827d55161
|
||||
en: 9a5a241ae3d0c676
|
||||
zh: 8f3e83cc54604bcc
|
||||
@@ -36,7 +36,7 @@ When the initial rendered prompt is empty, the loop reserves an empty system hea
|
||||
|
||||
`dsh-agent-loop` owns `SystemPromptProjection` beside `RuntimeContextProjection` in `packages/core/agent-loop/src/runtime-context.ts`. It reads the surviving `system/message` nodes from the current surface on every projection, so a compaction or replacement that ran earlier in the same step is already reflected. `project(rendered, { inHistory, startsSeries })` returns `{ message, intent }` — `intent` is `{ surfaceOp: 'append' }` when no system node survives or when the [in-history rule](../feature/2026-09-02-in-history-system-prompt-replacement.md) applies, otherwise a replacement of exactly the latest surviving system node — or `undefined` when the latest node already holds the rendered text.
|
||||
|
||||
In `packages/core/agent-loop/src/agent.ts`, `preStep` renders the prompt with `renderPrompt(assembly)` and projects it after the `agent/pre-step` waterfall, so a compaction provider's replacement inside that waterfall is visible to the decision; `turn()` commits the `system/message` immediately after `step/start` and before the step's `user/message` events, so log order is wire order. `buildRequest` sets no `system` on the request: the request is `header.config`, `session.deriveMessages()` (system message first), and `header.tools`. The loop step order is: claim inbox → `systemPrompt.assemble()` → project runtime context → `agent/pre-step` waterfall → project system prompt → `step/start` → commit `system/message` (when changed) → commit `user/message`s → `agent/request` waterfall → `request/header` → `request/context` → stream. The `dsh-agent-loop/invariant` companion (`packages/core/agent-loop/src/invariant.ts`) asserts that a loop-built request has `system === undefined` and `messages` equal to `deriveMessages()`.
|
||||
In `packages/core/agent-loop/src/agent.ts`, `preStep` renders the prompt with `renderPrompt(assembly)` and projects it after the `agent/pre-step` waterfall, so a compaction provider's replacement inside that waterfall is visible to the decision; `turn()` commits the `system/message` immediately after `step/start` and before the step's `user/message` events, so log order is wire order. `buildRequest` sets no `system` on the request: the request is `header.config`, `session.deriveMessages()` (system message first), and `header.tools`. The loop step order is: claim inbox → `systemPrompt.assemble()` → project runtime context → `agent/pre-step` waterfall → project system prompt → `step/start` → commit `system/message` (when changed) → commit `user/message`s → `agent/request` waterfall → `request/header` → `request/context` → stream.
|
||||
|
||||
`dsh-token-meter` anchors usage to the priced surface immediately before the successful `assistant/message`, not to `step/start`. The loop admits the system prompt and user messages after step start, and retry recovery can replace nodes before rebuilding the request. Capturing that current surface includes every admitted input once; the embedded provider output remains separately priced so durable assistant rewrites retain their signed delta. The open step stores only turn and step for lifecycle validation, not a second node snapshot.
|
||||
|
||||
@@ -90,6 +90,5 @@ The [canonical-envelope specification](../../../../packages/session/session-form
|
||||
- `packages/core/session/tests/surface.spec.ts` (`system/message surface node` block) pins the leading system-role projection, the empty-content `null` projection, `assertSystemHeadRewrite`'s acceptance and rejection paths, the unprotected later system nodes, and the rejection of a seeded `system/message` with a non-system role or non-plugin source.
|
||||
- `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` pins the append on first render (including empty), the later non-empty prompt at the derived head in replacement mode, the no-op on an unchanged prompt, the replacement of the latest surviving node on change, the tail append after a replacement shadowed a non-head system node, and the in-history append and re-baseline rules.
|
||||
- `packages/core/agent-loop/tests/request-reconstruction.spec.ts` (`a system-prompt change replaces surface node 0 and starts a new series under the same header`) pins the `series` header that follows a prompt replacement.
|
||||
- `packages/core/agent-loop/tests/invariant.spec.ts` pins the companion's rejection of a loop request carrying a `system` field and its `messages` equality check against the boundary derivation.
|
||||
- `packages/llm/llm-deepseek/tests/serialize.spec.ts` (`serializes a leading system message byte-for-byte like the same prompt passed as options.system`) pins wire identity. `packages/llm/llm-pi-ai/tests/context.spec.ts` compares both system sources on text and image paths. `packages/compaction/compaction-basic/tests/compaction-basic.spec.ts` pins the derived prefix, routed tools, absent separate `system` option, and protected non-empty or empty head through the region transaction and default summarizer.
|
||||
- The recorded snapshots under `snapshots/` pin the model-visible wire request of every shipped profile; a recorded session that renders a prompt carries the `system/message` event at surface node 0 in its `session.jsonl`, and a session with a mid-session prompt change carries the replacement of node 0 or, on an in-history route, the appended node.
|
||||
+1
-2
@@ -36,7 +36,7 @@ Status: implemented
|
||||
|
||||
`dsh-agent-loop` 在 `packages/core/agent-loop/src/runtime-context.ts` 中与 `RuntimeContextProjection` 并列拥有 `SystemPromptProjection`。它在每次投影时从当前 surface 读取存活的 `system/message` 节点,因此同一步骤中更早运行的压缩或替换已经反映在内。`project(rendered, { inHistory, startsSeries })` 返回 `{ message, intent }`——没有系统节点存活或[历史内规则](../feature/2026-09-02-in-history-system-prompt-replacement.zh.md)适用时 `intent` 为 `{ surfaceOp: 'append' }`,否则是对最新存活系统节点的精确替换——最新节点已持有渲染文本时返回 `undefined`。
|
||||
|
||||
在 `packages/core/agent-loop/src/agent.ts` 中,`preStep` 用 `renderPrompt(assembly)` 渲染提示词,并在 `agent/pre-step` waterfall 之后投影它,因此压缩提供者在该 waterfall 内做出的替换对决定可见;`turn()` 紧接在 `step/start` 之后、该步骤的 `user/message` 事件之前提交 `system/message`,因此日志顺序即协议顺序。`buildRequest` 不在请求上设置 `system`:请求由 `header.config`、`session.deriveMessages()`(系统消息在先)和 `header.tools` 构成。循环步骤顺序为:领取收件箱 → `systemPrompt.assemble()` → 投影运行时上下文 → `agent/pre-step` waterfall → 投影系统提示词 → `step/start` → 提交 `system/message`(有变化时) → 提交各条 `user/message` → `agent/request` waterfall → `request/header` → `request/context` → 流式请求。`dsh-agent-loop/invariant` 伴随组件(`packages/core/agent-loop/src/invariant.ts`)断言循环构建的请求满足 `system === undefined` 且 `messages` 等于 `deriveMessages()`。
|
||||
在 `packages/core/agent-loop/src/agent.ts` 中,`preStep` 用 `renderPrompt(assembly)` 渲染提示词,并在 `agent/pre-step` waterfall 之后投影它,因此压缩提供者在该 waterfall 内做出的替换对决定可见;`turn()` 紧接在 `step/start` 之后、该步骤的 `user/message` 事件之前提交 `system/message`,因此日志顺序即协议顺序。`buildRequest` 不在请求上设置 `system`:请求由 `header.config`、`session.deriveMessages()`(系统消息在先)和 `header.tools` 构成。循环步骤顺序为:领取收件箱 → `systemPrompt.assemble()` → 投影运行时上下文 → `agent/pre-step` waterfall → 投影系统提示词 → `step/start` → 提交 `system/message`(有变化时) → 提交各条 `user/message` → `agent/request` waterfall → `request/header` → `request/context` → 流式请求。
|
||||
|
||||
`dsh-token-meter` 把用量锚定到成功的 `assistant/message` 之前的已计价 surface,而不是 `step/start`。循环在步骤开始之后接纳系统提示词与用户消息,重试恢复还可能在重建请求之前替换节点。捕获当前 surface 会让每个已接纳输入恰好计入一次;内嵌的提供方输出仍单独计价,因此持久 assistant 改写保留其带符号增量。开放步骤只保存 turn 与 step 以验证生命周期,不保存第二份节点快照。
|
||||
|
||||
@@ -90,6 +90,5 @@ Status: implemented
|
||||
- `packages/core/session/tests/surface.spec.ts`(`system/message surface node` 块)钉住开头 system 角色的投影、空内容的 `null` 投影、`assertSystemHeadRewrite` 的接受与拒绝路径、更后位置系统节点不受保护,以及对 seed 中非 system 角色或非插件 source 的 `system/message` 的拒绝。
|
||||
- `packages/core/agent-loop/tests/system-prompt-projection.spec.ts` 钉住首次渲染时的追加(包括空提示词)、替换模式下后来非空提示词位于派生历史头部、提示词未变时的无操作、变更时对最新存活节点的替换、替换遮蔽了非头部系统节点之后的尾部追加,以及历史内追加与重新基线规则。
|
||||
- `packages/core/agent-loop/tests/request-reconstruction.spec.ts`(`a system-prompt change replaces surface node 0 and starts a new series under the same header`)钉住提示词替换之后跟随的 `series` header。
|
||||
- `packages/core/agent-loop/tests/invariant.spec.ts` 钉住伴随组件对携带 `system` 字段的循环请求的拒绝,以及其 `messages` 与边界派生结果的相等性检查。
|
||||
- `packages/llm/llm-deepseek/tests/serialize.spec.ts`(`serializes a leading system message byte-for-byte like the same prompt passed as options.system`)钉住协议一致性。 `packages/llm/llm-pi-ai/tests/context.spec.ts` 在文本与图片路径上比较两种系统提示词来源。`packages/compaction/compaction-basic/tests/compaction-basic.spec.ts` 通过区域事务与默认摘要器钉住派生前缀、已路由工具、不携带单独 `system` 选项,以及非空或空头节点的保护。
|
||||
- `snapshots/` 下的录制快照钉住每个随发 profile 的模型可见协议请求;渲染了提示词的录制会话在其 `session.jsonl` 中于 surface 第 0 号节点携带 `system/message` 事件,会话中途发生提示词变更的会话则携带对第 0 号节点的替换,或在历史内路由上携带追加的节点。
|
||||
@@ -15,5 +15,5 @@
|
||||
en: d5d9d6c2c05e563b
|
||||
zh: 66b2489d947365fe
|
||||
/agent-note-one-output-ring-one-projection-one-event-stream-for-background-jobs/consequences:
|
||||
en: 23b0112bba743396
|
||||
zh: 28f38ff246b4525f
|
||||
en: 464eb843a71eb04e
|
||||
zh: 783062f5b288b1b4
|
||||
@@ -32,4 +32,4 @@ The job seam had grown by accretion. Producers declared their output twice — a
|
||||
|
||||
The model-visible text is unchanged: status lines, tool descriptions and schemas, and notice wording are byte-identical, and the only new model-visible fact is the kill reason a `killed` status line now carries. A producer that needs to stream output picks pull sources or `append`; it never formats a consuming delta. The Web client renders every live job as expandable, so a job that never writes shows an empty running panel instead of a static row. `JobKindMap` merges are host-side, so a browser program types `JobView.kind` as the kinds it knows and treats the rest as opaque strings, which is all the roster does with it.
|
||||
|
||||
Review follow-ups after the master merge tightened four of these edges. A job's first event is always `registered`: the announcement precedes the pump, whose first drain runs synchronously and may announce output. A settled ring keeps every byte the model cursor has not consumed until the first terminal model read, so a job that finishes before its first `job_output` loses nothing the live cap retained. `dsh-tool-jobs` holds one claim per wait call with a `live` flag that the caller's abort clears synchronously from the abort event, so a settlement announced before the registry's rejection reaches the tool still delivers its notice, and a timed-out wait drops only its own claim while a concurrent wait keeps the settlement covered. The browser records an observation failure that precedes the anchor as an errored view, so the panel shows the notice instead of staying blank. The `@deepseek-ai/dsh-jobs/invariant` companion checks the announced event protocol per job (registered first, one settlement, removal last) and each announced projection against the registry's own read, which is the cross-observation the [companion rule](../simplification/2026-08-28-omit-unneeded-invariant-companions.md) requires; the earlier single-view field checks are gone. The spill file a pull source keeps is job metadata (`JobView.output.spillPaths`, reported by every pump read and withdrawn once a read stops naming it), not per-chunk metadata, and the model's read renders the shell tools' dropped-output notice for a producer-side gap as well as for ring eviction, naming those files even after the gap chunk itself was evicted — the `before this read` variant this change had introduced is gone. An observation generation that sees its job removed mid-stream (the owner's teardown) closes with the removed job's terminal projection instead of a failed read. A rejected background spawn produces no process output, so the shell providers now serve the `spawn failed: …` note as the whole observed stderr stream (the consuming read still folds it in once), and `ShellProcess.observed` with both readers is required: the model's first `job_output` after such a failure reads exactly what master's consuming read rendered.
|
||||
Review follow-ups after the master merge tightened four of these edges. A job's first event is always `registered`: the announcement precedes the pump, whose first drain runs synchronously and may announce output. A settled ring keeps every byte the model cursor has not consumed until the first terminal model read, so a job that finishes before its first `job_output` loses nothing the live cap retained. `dsh-tool-jobs` holds one claim per wait call with a `live` flag that the caller's abort clears synchronously from the abort event, so a settlement announced before the registry's rejection reaches the tool still delivers its notice, and a timed-out wait drops only its own claim while a concurrent wait keeps the settlement covered. The browser records an observation failure that precedes the anchor as an errored view, so the panel shows the notice instead of staying blank. The spill file a pull source keeps is job metadata (`JobView.output.spillPaths`, reported by every pump read and withdrawn once a read stops naming it), not per-chunk metadata, and the model's read renders the shell tools' dropped-output notice for a producer-side gap as well as for ring eviction, naming those files even after the gap chunk itself was evicted — the `before this read` variant this change had introduced is gone. An observation generation that sees its job removed mid-stream (the owner's teardown) closes with the removed job's terminal projection instead of a failed read. A rejected background spawn produces no process output, so the shell providers now serve the `spawn failed: …` note as the whole observed stderr stream (the consuming read still folds it in once), and `ShellProcess.observed` with both readers is required: the model's first `job_output` after such a failure reads exactly what master's consuming read rendered.
|
||||
@@ -32,4 +32,4 @@ job seam 是逐步堆出来的。生产者要把输出声明两次——模型
|
||||
|
||||
模型可见文本保持不变:状态行、工具描述与 schema、播报措辞逐字节相同,唯一新增的模型可见事实是 `killed` 状态行现在携带的 kill 原因。需要流式输出的生产者选择拉取源或 `append`,从不格式化消耗式增量。Web 客户端把每个进行中的 job 渲染为可展开,因此从不写输出的 job 显示为空的运行中面板而非静态行。`JobKindMap` 的合并发生在宿主侧,浏览器程序把 `JobView.kind` 视为它认识的 kind 集合,其余当作不透明字符串——名册对它也只做这些。
|
||||
|
||||
合并 master 之后的评审跟进收紧了其中四条边。job 的第一个事件永远是 `registered`:通告先于泵,而泵的首轮 drain 同步执行且可能通告输出。已结算的环在模型的首次终态读取之前保留模型游标尚未消费的全部字节,因此在首次 `job_output` 之前结束的 job 不会丢失运行期上限保留的任何内容。`dsh-tool-jobs` 为每次等待调用保存一条带 `live` 标记的认领,调用方 abort 时在 abort 事件里同步清掉该标记,因此注册表的拒绝尚未传回工具之前宣布的结算仍会投递通知;超时的等待只丢弃自己的认领,并发的另一个等待仍覆盖该结算。浏览器把先于锚点发生的观察失败记录为带错误的视图,面板显示提示而不是留白。`@deepseek-ai/dsh-jobs/invariant` 伴生插件校验每个 job 的事件协议(先 `registered`、恰一次结算、最后 `removed`)以及每个通告的投影与注册表自身读取的一致性,这正是[伴生插件规则](../simplification/2026-08-28-omit-unneeded-invariant-companions.zh.md)要求的交叉观察;先前只针对单个视图的字段校验已删除。拉取源保留的 spill 文件是 job 元数据(`JobView.output.spillPaths`,每次泵读取都会上报,读取不再点名时即撤回),不是逐块元数据;模型读取对生产者侧缺口与环淘汰都渲染 shell 工具的丢失输出提示并列出这些文件,即使缺口块本身已被淘汰——本次变更曾引入的 `before this read` 变体已删除。观察生成过程若在流中看到其 job 被移除(拥有者 teardown),以被移除 job 的终态投影收尾,而不是一次失败的读取。被拒绝的后台 spawn 不产生任何进程输出,因此 shell provider 现在把 `spawn failed: …` 提示作为 observed stderr 的整条流提供(消耗式读取仍只折入一次),且 `ShellProcess.observed` 及其两个读取器改为必填:这类失败之后模型的首次 `job_output` 读到的正是 master 的消耗式读取所渲染的内容。
|
||||
合并 master 之后的评审跟进收紧了其中四条边。job 的第一个事件永远是 `registered`:通告先于泵,而泵的首轮 drain 同步执行且可能通告输出。已结算的环在模型的首次终态读取之前保留模型游标尚未消费的全部字节,因此在首次 `job_output` 之前结束的 job 不会丢失运行期上限保留的任何内容。`dsh-tool-jobs` 为每次等待调用保存一条带 `live` 标记的认领,调用方 abort 时在 abort 事件里同步清掉该标记,因此注册表的拒绝尚未传回工具之前宣布的结算仍会投递通知;超时的等待只丢弃自己的认领,并发的另一个等待仍覆盖该结算。浏览器把先于锚点发生的观察失败记录为带错误的视图,面板显示提示而不是留白。拉取源保留的 spill 文件是 job 元数据(`JobView.output.spillPaths`,每次泵读取都会上报,读取不再点名时即撤回),不是逐块元数据;模型读取对生产者侧缺口与环淘汰都渲染 shell 工具的丢失输出提示并列出这些文件,即使缺口块本身已被淘汰——本次变更曾引入的 `before this read` 变体已删除。观察生成过程若在流中看到其 job 被移除(拥有者 teardown),以被移除 job 的终态投影收尾,而不是一次失败的读取。被拒绝的后台 spawn 不产生任何进程输出,因此 shell provider 现在把 `spawn failed: …` 提示作为 observed stderr 的整条流提供(消耗式读取仍只折入一次),且 `ShellProcess.observed` 及其两个读取器改为必填:这类失败之后模型的首次 `job_output` 读到的正是 master 的消耗式读取所渲染的内容。
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: bd3ad5505204ec0b
|
||||
zh: 770b6089c760d99f
|
||||
/agent-note-plugin-owned-message-projections/decision:
|
||||
en: 2b33d4a1cfba0f53
|
||||
zh: 55347f3ecf234862
|
||||
en: 50c02960b1f087c6
|
||||
zh: fa3f5c18b897eab9
|
||||
/agent-note-plugin-owned-message-projections/alternatives-considered:
|
||||
en: c6e4c770739dca68
|
||||
zh: 7bfcb3dda06a22a1
|
||||
en: b0da1fbf24bc506a
|
||||
zh: c4e35896599f824c
|
||||
/agent-note-plugin-owned-message-projections/consequences:
|
||||
en: c2f3ff0b9639bec5
|
||||
zh: 357f90d9a3423751
|
||||
+2
-2
@@ -16,7 +16,7 @@ The `@messageProjection` tag on the owning `SessionEventMap` member generates th
|
||||
|
||||
The compaction-image-offload plugin registers its definition through a fiber-owned `ctx.sessions.registerMessageProjection()` effect. Each event has one registered owner. Removing a definition invalidates pending committed decisions and cached reads that used it; a replacement definition requires restoring the session. Rejected, uncommitted candidates retain no interpreter dependency.
|
||||
|
||||
Detached readers pass definitions explicitly. The current Session format catalog assembles the same browser-safe plugin exports for persistence validation and offline queries. This static assembly does not mount recovery listeners. Live sessions use the registered composition, while compaction invariants borrow that composition's definitions. No process-global registry or import-time registration is involved.
|
||||
Detached readers pass definitions explicitly. The current Session format catalog assembles the same browser-safe plugin exports for persistence validation and offline queries. This static assembly does not mount recovery listeners. Live sessions use the registered composition. No process-global registry or import-time registration is involved.
|
||||
|
||||
The image event payload and required-on-read semantics remain owned by the image-offload note. Its indexes, selection policy, retry behavior, and SDK recordings are unchanged; this decision partially supersedes that note's core-owned interpretation.
|
||||
|
||||
@@ -26,7 +26,7 @@ The image event payload and required-on-read semantics remain owned by the image
|
||||
|
||||
**Use the ordinary session-projection registry.** Its folds expose domain state after commit. They cannot veto an invalid append or supply canonical `deriveMessages()` content without another integration point.
|
||||
|
||||
**Transform only the outgoing request.** Compaction, fork, offline replay, and request invariants also derive messages. A send-time transformation does not cover these consumers.
|
||||
**Transform only the outgoing request.** Compaction, fork, and offline replay also derive messages. A send-time transformation does not cover these consumers.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ Status: implemented
|
||||
|
||||
compaction-image-offload 插件通过 fiber 拥有的 `ctx.sessions.registerMessageProjection()` effect 注册处理器。每种事件只有一个注册者。移除处理器后,尚未应用的已提交决策和使用过它的缓存读取都会失效。替换处理器需要重新恢复会话。被拒绝且未提交的候选事件不会保留对处理器的依赖。
|
||||
|
||||
独立读取器显式传入处理器。当前 Session 格式目录装配相同的浏览器安全插件导出,供持久数据校验和离线查询使用。这项静态装配不挂载恢复监听器。实时会话使用已注册的组合,压缩 invariant 借用该组合的处理器。不使用进程全局注册表,也不在导入模块时注册。
|
||||
独立读取器显式传入处理器。当前 Session 格式目录装配相同的浏览器安全插件导出,供持久数据校验和离线查询使用。这项静态装配不挂载恢复监听器。实时会话使用已注册的组合。不使用进程全局注册表,也不在导入模块时注册。
|
||||
|
||||
图片事件载荷和读取时必须识别的语义仍由图片省略记录负责。索引、选图策略、重试行为和 SDK 录制保持不变。本决策部分取代该记录中由核心负责解释事件的职责划分。
|
||||
|
||||
@@ -26,7 +26,7 @@ compaction-image-offload 插件通过 fiber 拥有的 `ctx.sessions.registerMess
|
||||
|
||||
**使用普通的 session-projection 注册表。** 它的折叠在提交后公开领域状态。没有额外集成点,它无法否决无效追加,也无法提供标准 `deriveMessages()` 内容。
|
||||
|
||||
**只转换发出的请求。** 压缩、分叉、离线回放和请求 invariant 也会派生消息。发送时转换无法覆盖这些消费方。
|
||||
**只转换发出的请求。** 压缩、分叉和离线回放也会派生消息。发送时转换无法覆盖这些消费方。
|
||||
|
||||
## 影响
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 2c774d05ae75784f
|
||||
zh: 619ba7f281b13233
|
||||
/agent-note-host-owned-scheduled-messages/decision:
|
||||
en: de81925284f13c12
|
||||
zh: d0ef35c8b58813f2
|
||||
en: 89dde12a97dce767
|
||||
zh: b5bdab5f8008ad45
|
||||
/agent-note-host-owned-scheduled-messages/alternatives-considered:
|
||||
en: 21d7ec9a9ff3cbf2
|
||||
zh: 93d03b8b3012aa7e
|
||||
|
||||
@@ -14,7 +14,7 @@ The existing Schedule package owns one Host storage domain containing each task'
|
||||
|
||||
The task domain declares the whole-unit layout. Tasks are authoritative, so the JSON backend rejects an unreadable file, malformed document, unsupported version, or invalid task as a complete load failure instead of interpreting it as deletion. Failed recovery leaves `schedule.json` unchanged, while an initially absent file opens as an empty domain; repairing the file preserves task identity. `Service.init` waits for both storage validation and runtime setup, management methods wait for the same initialization result, and an unloading owner releases late-acquired storage without starting dispatch. Dispatch admission is separate from initialization: an ancestor can close Agent admission after a task mutation commits but before runtime cleanup starts. That refusal is logged without automatic retry and does not turn the persisted mutation into a failed management result. Invalid storage and failed cleanup registration still reject startup.
|
||||
|
||||
This supersedes the Session-log authority, live-only activation, and read-only catalog decisions in the [Session-local reminders note](../feature/2026-08-05-durable-web-schedule.md). Its explicit absolute-time validation, fixed-rate arithmetic, and distinction between queue admission and model completion remain applicable. Historical `schedule/change` creation, fold, and invariant types remain restricted to After, At, and Every through `LegacyScheduleRecord`; a separate Host decoder accepts the current `ScheduleRecord`, including Daily, Weekly, and Cron. The Host record decoder requires a stored `title` that is non-empty after trimming, already trimmed, and at most 120 characters, and the strict task schema rejects the whole domain open instead of dropping such a task. The historical change decoder tolerates an absent `title` so an already-written Session log stays readable, and it applies the same three checks when the member is present. The storage domain remains version 1. Historical events do not populate that domain, and existing `at` tasks are not converted into daily or weekly rules. Loading a Session with active legacy reminders warns that they must be recreated.
|
||||
This supersedes the Session-log authority, live-only activation, and read-only catalog decisions in the [Session-local reminders note](../feature/2026-08-05-durable-web-schedule.md). Its explicit absolute-time validation, fixed-rate arithmetic, and distinction between queue admission and model completion remain applicable. Historical `schedule/change` creation and fold types remain restricted to After, At, and Every through `LegacyScheduleRecord`; a separate Host decoder accepts the current `ScheduleRecord`, including Daily, Weekly, and Cron. The Host record decoder requires a stored `title` that is non-empty after trimming, already trimmed, and at most 120 characters, and the strict task schema rejects the whole domain open instead of dropping such a task. The historical change decoder tolerates an absent `title` so an already-written Session log stays readable, and it applies the same three checks when the member is present. The storage domain remains version 1. Historical events do not populate that domain, and existing `at` tasks are not converted into daily or weekly rules. Loading a Session with active legacy reminders warns that they must be recreated.
|
||||
|
||||
A plugin-sourced `followup()` synchronously appends the message to the Session inbox. Successful Session flush acknowledges durable delivery. One task-record put stores `lastDelivery`, appends a delivery record with the occurrence target, acknowledgment time, actual message id, and instruction snapshot, and updates the one-shot's `inactive` status or the recurring task's next target and status. Task state and its saved history therefore share one commit. Inactive tasks remain stored and never schedule another delivery; explicit deletion stops future delivery and removes the row together with its saved records. These records acknowledge inbox delivery, not user receipt or model execution completion.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Status: implemented
|
||||
|
||||
任务 domain 声明整 unit 布局。任务是权威数据,因此 JSON 后端会把文件不可读、文档损坏、版本不受支持或任务非法作为整批加载失败,而不是解释为删除。恢复失败不会改写 `schedule.json`,初始不存在的文件则作为空 domain 打开;修复该文件后会保留任务身份。`Service.init` 等待存储校验和运行时初始化,管理方法等待同一初始化结果;拥有方卸载时释放迟到的存储资源,而不开始投递。投递调度准入与初始化分开:任务变更提交后、运行时清理开始前,祖先可能关闭 Agent 准入。此类拒绝会记录日志而不自动重试,不会将已持久化的变更变成失败的管理结果。存储非法和清理注册失败仍拒绝启动。
|
||||
|
||||
此决策替代[会话内提醒记录](../feature/2026-08-05-durable-web-schedule.zh.md)中的 Session 日志权威来源、仅 live 时激活和只读目录决策。其中显式绝对时间校验、固定速率计算,以及区分排队接纳与模型完成的决策仍然适用。历史 `schedule/change` 的创建、fold 和 invariant 类型通过 `LegacyScheduleRecord` 保持为 After、At 和 Every;独立的宿主解码器接受包含 Daily、Weekly 与 Cron 的当前 `ScheduleRecord`。宿主记录解码器要求已存储的 `title` 去除首尾空白后非空、本身已去除首尾空白且不超过 120 个字符,严格的 task schema 会拒绝整个 domain 的打开,而不是丢弃这样的任务。历史变更解码器容忍缺失的 `title`,以便已写入的 Session 日志仍可读取;该成员存在时,同样按这三项检查校验。storage domain 保持版本 1。历史事件不会填充该 domain,已有 `at` 任务也不会转换为每日或每周规则。加载含有活动旧提醒的 Session 时,会警告需要重新创建提醒。
|
||||
此决策替代[会话内提醒记录](../feature/2026-08-05-durable-web-schedule.zh.md)中的 Session 日志权威来源、仅 live 时激活和只读目录决策。其中显式绝对时间校验、固定速率计算,以及区分排队接纳与模型完成的决策仍然适用。历史 `schedule/change` 的创建与 fold 类型通过 `LegacyScheduleRecord` 保持为 After、At 和 Every;独立的宿主解码器接受包含 Daily、Weekly 与 Cron 的当前 `ScheduleRecord`。宿主记录解码器要求已存储的 `title` 去除首尾空白后非空、本身已去除首尾空白且不超过 120 个字符,严格的 task schema 会拒绝整个 domain 的打开,而不是丢弃这样的任务。历史变更解码器容忍缺失的 `title`,以便已写入的 Session 日志仍可读取;该成员存在时,同样按这三项检查校验。storage domain 保持版本 1。历史事件不会填充该 domain,已有 `at` 任务也不会转换为每日或每周规则。加载含有活动旧提醒的 Session 时,会警告需要重新创建提醒。
|
||||
|
||||
插件来源的 `followup()` 同步将消息追加到 Session 收件箱。Session flush 成功后确认持久投递。一次任务记录 put 存储 `lastDelivery`,追加包含发生目标、确认时间、真实消息 id 和指令快照的发送记录,并更新一次性任务的 `inactive` 状态或重复任务的下一目标和状态。因此,任务状态与其保存的历史共用一次提交。未运行任务仍保留在存储中,且不会再次安排投递;显式删除会阻止后续投递,并把该行连同其已保存记录一并移除。这些记录确认收件箱投递,不表示用户收到消息或模型执行完成。
|
||||
|
||||
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: cfb527c1c2ca661a
|
||||
zh: b61a906330cdaf97
|
||||
/agent-note-mandatory-native-v4-relationship-validation/decision:
|
||||
en: 14feaf74a58d5609
|
||||
zh: 8aea80c89110e638
|
||||
en: 534efaae5eb9beb0
|
||||
zh: ccb3734458248383
|
||||
/agent-note-mandatory-native-v4-relationship-validation/alternatives-considered:
|
||||
en: d6296e00a6b30d48
|
||||
zh: 9f7074c69fb42e5f
|
||||
en: 96a37d9ba0252630
|
||||
zh: fdf2d085805f0010
|
||||
/agent-note-mandatory-native-v4-relationship-validation/consequences:
|
||||
en: b76aaca1c35dcc6c
|
||||
zh: b933c2518c64c60c
|
||||
@@ -16,7 +16,7 @@ The V4 format restorer and current JSONL scanner share one mandatory cross-event
|
||||
|
||||
An unfinished tail may retain an open turn, step, tool call, or compaction. A closing event must settle the relationships it closes. An inherited unfinished compaction ends at its `session/end-seed` marker; it does not constrain the child's lifecycle. Repair message identities retain their historical numeric suffix after earlier migrations change sequence coordinates. Fork repairs retain their separately validated native identities.
|
||||
|
||||
Installed Session adoption continues to own event envelopes, message roles and metadata, canonical request headers, and surface replacement reference coverage. Optional runtime invariants provide diagnostics; their installation is not required for durable admission. Native system-message admission also validates coordinates, identity, content blocks, and known image/tool-call fields before physical recovery can discard rows. Additional JSON fields and unknown nonempty content tags remain intact; retired tool-result wrappers are refused. Retired `request/header.header.system` and required `tool/code-dispatch*` tags remain hard refusals before recovery; obsolete ignorable PTC records remain opaque. Physical framing alone does not establish semantic validity.
|
||||
Installed Session adoption continues to own event envelopes, message roles and metadata, canonical request headers, and surface replacement reference coverage. Native system-message admission also validates coordinates, identity, content blocks, and known image/tool-call fields before physical recovery can discard rows. Additional JSON fields and unknown nonempty content tags remain intact; retired tool-result wrappers are refused. Retired `request/header.header.system` and required `tool/code-dispatch*` tags remain hard refusals before recovery; obsolete ignorable PTC records remain opaque. Physical framing alone does not establish semantic validity.
|
||||
|
||||
A retired tool-result wrapper outside its owning `tool/result` row would require changing the event, role, or source to represent it in V4. The adjacent migration lifts only the canonical owned wrapper; target restoration refuses wrappers remaining in interpreted content. Native row admission applies the same refusal before recoverable tail suppression. The check follows declared message, content, and Assistant-stream slots instead of recursively inspecting JSON, so tool arguments, replay state, schema parameters, nested extensions, and unknown ignorable payloads retain their recorded values.
|
||||
|
||||
@@ -26,8 +26,6 @@ A retired tool-result wrapper outside its owning `tool/result` row would require
|
||||
|
||||
**Project V4 back into a frozen V3 validation view.** This couples current acceptance to retired representations and invites new fields to disappear in the projection. Frozen generations remain independently readable, while the current restorer interprets its own fields.
|
||||
|
||||
**Rely on optional runtime invariants.** Detached readers and restoration paths may not install diagnostic companions. A required relationship must be checked before publication or consumption of the restored Session.
|
||||
|
||||
## Consequences
|
||||
|
||||
The native restorer maintains one bounded state machine per read and rejects contradictions before successor publication. Frozen V0–V3 validators and committed Session generations remain unchanged. Public catalog regressions cover invalid relationships alongside partial tails, inherited cuts, immutable unknown attribution, and complete tool exchanges; these checks enforce the declared V4 representation without changing its schemas. Breaking changes require a successor; compatible additions use new acknowledgements, and implementation fixes preserve the declared meaning.
|
||||
@@ -16,7 +16,7 @@ V4 格式恢复器与当前 JSONL 扫描器共用同一强制跨事件关系校
|
||||
|
||||
未完成的尾部可以保留打开的 turn、step、工具调用或压缩事务。关闭事件必须结清它关闭的关系。继承的未完成压缩事务在其 `session/end-seed` 标记处结束,不约束子 Session 的生命周期。较早迁移改变序号坐标后,修复消息身份仍保留历史数字后缀。fork 修复保留经过独立校验的原生身份。
|
||||
|
||||
已安装的 Session 接纳器继续负责事件封装、消息角色与元数据、规范请求头,以及 surface 替换的引用覆盖。可选运行时不变式提供诊断;持久化准入不要求安装它们。原生 system 消息准入还会在物理恢复可能丢弃行之前,校验坐标、身份、内容块及已知 image/tool-call 字段。额外 JSON 字段和非空的未知内容标签保持不变,退役的 tool-result 包装会被拒绝。退役的 `request/header.header.system` 和必需的 `tool/code-dispatch*` 标签仍在恢复前强制拒绝;标记为可忽略的过时 PTC 记录保持不透明。仅通过物理分帧不能证明语义有效。
|
||||
已安装的 Session 接纳器继续负责事件封装、消息角色与元数据、规范请求头,以及 surface 替换的引用覆盖。原生 system 消息准入还会在物理恢复可能丢弃行之前,校验坐标、身份、内容块及已知 image/tool-call 字段。额外 JSON 字段和非空的未知内容标签保持不变,退役的 tool-result 包装会被拒绝。退役的 `request/header.header.system` 和必需的 `tool/code-dispatch*` 标签仍在恢复前强制拒绝;标记为可忽略的过时 PTC 记录保持不透明。仅通过物理分帧不能证明语义有效。
|
||||
|
||||
退役的 tool-result 包装若不属于对应的 `tool/result` 行,就需要改变事件、角色或来源才能在 V4 中表示。相邻迁移只提升规范的自有包装;目标恢复器拒绝解释内容中剩余的包装。原生行准入在可恢复尾部被忽略之前执行同样的拒绝。检查沿声明的消息、内容和 Assistant stream 字段进行,不递归扫描 JSON,因此工具参数、replay state、schema 参数、嵌套扩展和未知可忽略载荷保留其记录值。
|
||||
|
||||
@@ -26,8 +26,6 @@ V4 格式恢复器与当前 JSONL 扫描器共用同一强制跨事件关系校
|
||||
|
||||
**将 V4 投影回冻结的 V3 校验视图。** 这会将当前准入与退役表示绑定,并容易让新增字段在投影中消失。冻结代际仍可独立读取,当前恢复器解释自己的字段。
|
||||
|
||||
**依赖可选运行时不变式。** 分离读取器和恢复路径可能不安装诊断组件。必需关系必须在发布或消费恢复后的 Session 之前完成校验。
|
||||
|
||||
## Consequences
|
||||
|
||||
原生恢复器为每次读取维护独立状态机,在发布后继文件之前拒绝矛盾。冻结的 V0–V3 校验器及已提交的 Session 代际保持不变。公开目录回归覆盖无效关系、部分尾部、继承切分点、未知归属的不可变保存,以及完整工具交互;这些检查落实已声明的 V4 表示,不改变其 schema。破坏性变更要求后继版本;兼容新增使用新的确认记录,实现修复须保留声明的含义。
|
||||
+2
-2
@@ -15,5 +15,5 @@
|
||||
en: 8e5f4d8fa8c2e78d
|
||||
zh: 3353c7042c6a1d6b
|
||||
/agent-note-schedule-as-an-opt-in-optional-bundle/consequences:
|
||||
en: 36cd0eb71abeedbe
|
||||
zh: 7c134095cc19fcfd
|
||||
en: 095751360f0b7585
|
||||
zh: 69f59f6d1d2b5361
|
||||
+1
-1
@@ -32,4 +32,4 @@ Enabling the bundle inserts `time-context` (a per-step clock reading with the sa
|
||||
- An enabled installation adds four tool schemas to every live root Agent and one durable clock message per eligible step; the reading is model-visible and durable, so it replays, compacts, and appears in exported Session logs like any other user message.
|
||||
- The Plugins page lists the three rows under the bundle with the titles their packages' `locale/*.json` declare, their state, and, while the bundle is on, a switch per row; switching one of them off leaves Schedule without that part.
|
||||
- A profile patch or `--patch` overlay that targets one of the three ids matches no row while the bundle is not selected, and the loader warns `patch: entry <id> not found` for it; selecting the bundle replaces switching the rows on by id.
|
||||
- The switch is configuration-only: `src/index.ts` is an empty module, the patch carries the runtime content, and the package owns no mutable runtime state, so it publishes no invariant companion.
|
||||
- The switch is configuration-only: `src/index.ts` is an empty module, the patch carries the runtime content, and the package owns no mutable runtime state.
|
||||
+1
-1
@@ -32,4 +32,4 @@ Schedule 会给每个活跃根 Agent 的请求增加四个工具 schema,并在
|
||||
- 启用后的安装会给每个活跃根 Agent 增加四个工具 schema,并在每个符合条件的步骤追加一条持久时钟消息;该读数对模型可见且持久,因此它与其他 user 消息一样参与回放、压缩,并出现在导出的 Session 日志中。
|
||||
- 插件管理页在该 bundle 下列出这三行,显示各自包的 `locale/*.json` 声明的标题、它们的状态,并在 bundle 打开时为每行提供开关;关掉其中一行会让 Schedule 缺少这一部分。
|
||||
- 未选中该 bundle 时,按 id 定位这三行之一的 profile 补丁或 `--patch` overlay 匹配不到任何条目,加载器会为它报告 `patch: entry <id> not found` 警告;选中该 bundle 即可取代按 id 打开这些行。
|
||||
- 该开关只涉及配置:`src/index.ts` 是空模块,运行时内容由 patch 承载,包本身不拥有可变的运行时状态,因此不发布不变量伴随模块。
|
||||
- 该开关只涉及配置:`src/index.ts` 是空模块,运行时内容由 patch 承载,包本身不拥有可变的运行时状态。
|
||||
@@ -39,14 +39,14 @@
|
||||
en: 1c71cf67450e78d5
|
||||
zh: fb44b3cf5cee070a
|
||||
/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/decision/blocking-via-a-log-recorded-lock-plus-a-crash-recoverable-failure-taxonomy:
|
||||
en: 5aed57a49e031b1e
|
||||
zh: 1424f332197ef541
|
||||
en: 6c3878b3ec1aebb8
|
||||
zh: 10175d34ddf1c543
|
||||
/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/alternatives-considered:
|
||||
en: 24a7bb1949445136
|
||||
zh: 7914ea99f498ed67
|
||||
/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/consequences:
|
||||
en: 400bf1cadb974d04
|
||||
zh: 7612a4634ae98b32
|
||||
en: a14d5d63b35bf945
|
||||
zh: 87678ddf2ce5e7d7
|
||||
/agent-note-compaction-as-a-capability-seam-abstract-contract-basic-backend/testing:
|
||||
en: 843476bd607fe832
|
||||
zh: 50be0b06553a54d0
|
||||
en: 5919e8aee57c9f06
|
||||
zh: e17f19bd1856a356
|
||||
@@ -106,7 +106,7 @@ The lifecycle boundary makes crash state unambiguous:
|
||||
|
||||
`compaction/end` keeps its `error?` field (mirroring `tool/result`'s self-contained error — one event tells success from failure without correlating a sibling). There is no separate `compaction/error` event.
|
||||
|
||||
**Core session repair stays compaction-agnostic — deliberately.** `interruptedTurnClosers` is never taught about `compaction/*`. The general `session/end-seed` lifecycle boundary supplies the evidence the compaction owner needs; the compaction invariant and backend interpret it without adding plugin-specific repair to core.
|
||||
**Core session repair stays compaction-agnostic — deliberately.** `interruptedTurnClosers` is never taught about `compaction/*`. The general `session/end-seed` lifecycle boundary supplies the evidence the compaction owner needs; the compaction backend interprets it without adding plugin-specific repair to core.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -122,12 +122,12 @@ The lifecycle boundary makes crash state unambiguous:
|
||||
- **Automatic extension points**: `agent/pre-step` (`@mode waterfall`) handles pressure before request derivation and `agent/request-error` (`@mode waterfall`) handles final request failures after the failed step closes. The pre-step payload carries the claimed batch, turn, step, and signal (see the [payload-object events decision](../../archived/architecture/2026-08-06-agent-event-payload-objects.md)), with no compaction-only prompt/prefix payload.
|
||||
- **`SessionEventMap`** gains `compaction/start` / `compaction/summary` / `compaction/end` by declaration merging (merge-extensible); `SurfaceEventType` is **not** touched. These are session events, not cordis `Events`, so the event-taxonomy gate needs no entry.
|
||||
- **`dsh-compaction`** owns `COMPACT_CHECKPOINT_SOURCE`, `isCompactCheckpointSource(source)`, `toolPairingBalancedBefore(session, seq)`, and `toolPairingBalancedAfter(session, seq)`. The marker identifies replacement summaries across backend implementations. The cached surface-edge checks prevent `compactRegion` and `compactIfNeeded` from splitting a tool-call/result pair, validate current membership by seq, answer both edges from one per-cut balance sequence, and reject stale or missing seqs and orphan results.
|
||||
- **`dsh-session`** validates positional replacement, complete cited source-event coverage, and content-only single-node `tool/result` rewrites through its one surface manager. Its invariant companion treats fresh appended tool results as executions that require an open step and pending call, while the compaction companion owns numeric-turn versus standalone-null bracket relations.
|
||||
- **`dsh-session`** validates positional replacement, complete cited source-event coverage, and content-only single-node `tool/result` rewrites through its one surface manager.
|
||||
- **Wiring**: `examples/tui-agent/cordis.yml` loads zero-config `dsh-token-meter`, `dsh-compaction-tool-result-pruner`, `dsh-compaction-basic`, then `dsh-command-compact`; service-wide defaults make the composition usable without repeated numeric policy.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Unit:** Real Loader and invariant plugins cover whole-unit retention, pruning configuration and replay, rich-block ordering, metadata preservation, convergence, both `compaction/end` outcomes, open-tail refusal, pruning-only and summarized overflow recovery, generation proof, caps, and original-error preservation.
|
||||
- **Unit:** Real Loader compositions cover whole-unit retention, pruning configuration and replay, rich-block ordering, metadata preservation, convergence, both `compaction/end` outcomes, open-tail refusal, pruning-only and summarized overflow recovery, generation proof, caps, and original-error preservation.
|
||||
- **Loop:** Tests pin pre-step after the preceding `step/end` and before the next `step/start`, actual `agent/request` routing, closed failed steps, fresh retry numbering, and complete thrown/in-band overflow → compaction → reconstructed retry composition.
|
||||
- **Manual:** Maintenance serialization, marker ordering, injection retention, live/stale orphan classification, cancellation, close/flush failures, command mapping, and the queued TUI journey are pinned without a model key.
|
||||
- **With-key e2e:** A real model and bash session with lowered limits triggers compaction, records a complete `compaction/start…end` pair, shrinks the surface, and finishes the task.
|
||||
|
||||
@@ -106,7 +106,7 @@ compaction/end → log-only. Releases the lock (carries `error` on a recove
|
||||
|
||||
`compaction/end` 保留其 `error?` 字段(与 `tool/result` 的自包含错误一致——一个事件即可区分成功与失败,无需关联兄弟事件)。没有单独的 `compaction/error` 事件。
|
||||
|
||||
**核心会话修复保持对压缩无感知——这是有意为之。** `interruptedTurnClosers` 从不被教导 `compaction/*`。通用 `session/end-seed` 生命周期边界提供压缩所有者所需的证据;压缩不变量与后端负责解释它,无需向核心添加插件专属修复。
|
||||
**核心会话修复保持对压缩无感知——这是有意为之。** `interruptedTurnClosers` 从不被教导 `compaction/*`。通用 `session/end-seed` 生命周期边界提供压缩所有者所需的证据;压缩后端负责解释它,无需向核心添加插件专属修复。
|
||||
|
||||
## 曾考虑的替代方案
|
||||
|
||||
@@ -122,12 +122,12 @@ compaction/end → log-only. Releases the lock (carries `error` on a recove
|
||||
- **自动扩展点**:`agent/pre-step`(`@mode waterfall`)在请求派生前处理压力,`agent/request-error`(`@mode waterfall`)处理失败步骤关闭后的最终请求失败。pre-step 的 payload 携带已领取批次、轮次、步骤与 signal(参见 [payload-object 事件决策](../../archived/architecture/2026-08-06-agent-event-payload-objects.md)),不携带压缩专属的提示词/前缀 payload。
|
||||
- **`SessionEventMap`** 通过可合并扩展的声明合并获得 `compaction/start` / `compaction/summary` / `compaction/end`;`SurfaceEventType` **未被**触及。这些是会话事件,不是 cordis `Events`,因此事件分类门禁无需新增条目。
|
||||
- **`dsh-compaction`** 拥有 `COMPACT_CHECKPOINT_SOURCE`、`isCompactCheckpointSource(source)`、`toolPairingBalancedBefore(session, seq)` 与 `toolPairingBalancedAfter(session, seq)`。该标记用于跨后端实现识别替换摘要。带缓存的 surface 边缘检查会防止 `compactRegion` 和 `compactIfNeeded` 拆分工具调用/结果对,按 seq 校验当前成员关系,从每个切割点的一条平衡序列回答两侧边缘,并拒绝陈旧或缺失的 seq 与孤立结果。
|
||||
- **`dsh-session`** 通过唯一的 surface 管理器校验位置替换、引用的来源事件是否覆盖完整,以及仅内容的单节点 `tool/result` 重写。其不变式配套插件将新追加的工具结果视为执行,要求存在已打开的步骤与待处理调用,而压缩配套组件负责维护数字轮次 owner 与独立 `null` owner 事件对之间的关系。
|
||||
- **`dsh-session`** 通过唯一的 surface 管理器校验位置替换、引用的来源事件是否覆盖完整,以及仅内容的单节点 `tool/result` 重写。
|
||||
- **接线**:`examples/tui-agent/cordis.yml` 依次加载零配置的 `dsh-token-meter`、`dsh-compaction-tool-result-pruner`、`dsh-compaction-basic`,然后加载 `dsh-command-compact`;服务级默认值使组合无需重复数值策略即可使用。
|
||||
|
||||
## 测试
|
||||
|
||||
- **单元测试:** 使用真实 Loader 和 invariant 插件覆盖完整单元保留、修剪配置与回放、富块顺序、元数据保留、收敛、`compaction/end` 的两种结果、开放尾部拒绝、仅修剪与带摘要的溢出恢复、generation 证明、上限和原始错误保留。
|
||||
- **单元测试:** 使用真实 Loader 组合覆盖完整单元保留、修剪配置与回放、富块顺序、元数据保留、收敛、`compaction/end` 的两种结果、开放尾部拒绝、仅修剪与带摘要的溢出恢复、generation 证明、上限和原始错误保留。
|
||||
- **循环测试:** 测试固定 pre-step 发生在前一个 `step/end` 之后、下一个 `step/start` 之前,使用实际 `agent/request` 路由,关闭失败步骤,分配新的重试编号,并覆盖完整的抛出/带内溢出 → 压缩 → 重建重试组合。
|
||||
- **手动测试:** 无需模型密钥即可固定 maintenance 串行化、标记顺序、注入保留、活动/陈旧未匹配标记分类、取消、闭合/flush 失败、命令映射以及排队 TUI 流程。
|
||||
- **带密钥 e2e:** 真实模型和 bash 会话在降低的限制下触发压缩,记录完整的 `compaction/start…end` 对,缩小 surface,并完成任务。
|
||||
|
||||
+4
-4
@@ -12,8 +12,8 @@
|
||||
en: 70182cd2979bdfd6
|
||||
zh: a6580a23cfbe6c07
|
||||
/agent-note-workspace-registration-deletion/durable-commit-and-publication:
|
||||
en: f97d526274c3608b
|
||||
zh: b0ae161f4e9febb8
|
||||
en: 930257532b3892c8
|
||||
zh: c510faf75f06a3f9
|
||||
/agent-note-workspace-registration-deletion/client-convergence:
|
||||
en: 84e56775e22ecf23
|
||||
zh: ab146e3179868efb
|
||||
@@ -24,8 +24,8 @@
|
||||
en: d7b866f763db5364
|
||||
zh: 5e0865d42522d189
|
||||
/agent-note-workspace-registration-deletion/verification:
|
||||
en: a7e98d39e3ffdba7
|
||||
zh: 9c4f8168e3fff83e
|
||||
en: d4d131da609fff89
|
||||
zh: c026d6cf53cbd42d
|
||||
/agent-note-workspace-registration-deletion/consequences:
|
||||
en: 3a50bfb5d93f6ef7
|
||||
zh: 80b8dd19b2d50b16
|
||||
@@ -18,7 +18,7 @@ Unknown ids return `false` at the domain contract. `workspace.delete({ workspace
|
||||
|
||||
## Durable commit and publication
|
||||
|
||||
Registry operations serialize create and delete. Deletion first writes the Workspace order without the id, then removes the entity from the cache, then deletes the table row. The table deletion is the notification commit point: the package invariant accepts it only after the cache stopped publishing the entity, and the Host emits `host/workspace-removed` only from that committed deletion. A table-write failure restores the cache and prior durable order; no removal frame is published.
|
||||
Registry operations serialize create and delete. Deletion first writes the Workspace order without the id, then removes the entity from the cache, then deletes the table row. The table deletion is the notification commit point: the cache has already stopped publishing the entity, and the Host emits `host/workspace-removed` only from that committed deletion. A table-write failure restores the cache and prior durable order; no removal frame is published.
|
||||
|
||||
The Host stream keeps its committed-id set through the preceding global-order write and removes the id only on the table deletion. Create rollback therefore emits no false removal, while every connected tab receives exactly the id needed to delete its projection.
|
||||
|
||||
@@ -50,7 +50,7 @@ The menu, Modal, and buttons retain their existing structure and design tokens.
|
||||
|
||||
## Verification
|
||||
|
||||
Workspace package tests pin successful metadata-only deletion, same-path re-registration, unknown-id idempotence, table-failure rollback, explicit-marker restart recovery, unexplained-corruption rejection, and cache/table invariant behavior. Apiproxy and carrier tests pin the schema, handler, `workspace-not-found`, retained Session/folder, fresh-id re-registration, and committed `host/workspace-removed` frame. Client tests pin unary direct echo, duplicate removal, late changed frames, and deletion racing an in-flight baseline. Component tests pin confirmation, projection-settled closing, success-frame-before-unary ordering, failure, Cancel, Escape, and Close. The browser scenario observes every transient alert, slot error, console error, and page error while reusing a deleted title for a different directory.
|
||||
Workspace package tests pin successful metadata-only deletion, same-path re-registration, unknown-id idempotence, table-failure rollback, explicit-marker restart recovery, and unexplained-corruption rejection. Apiproxy and carrier tests pin the schema, handler, `workspace-not-found`, retained Session/folder, fresh-id re-registration, and committed `host/workspace-removed` frame. Client tests pin unary direct echo, duplicate removal, late changed frames, and deletion racing an in-flight baseline. Component tests pin confirmation, projection-settled closing, success-frame-before-unary ordering, failure, Cancel, Escape, and Close. The browser scenario observes every transient alert, slot error, console error, and page error while reusing a deleted title for a different directory.
|
||||
|
||||
The assembled keyless Web scenario registers an existing temporary project directory, accounts a persisted Session, makes that Session current, confirms deletion in Chromium, and verifies the Workspace group disappears while Ungrouped retains the current Session. It checks the user file and JSONL log before and after deletion and repeats the UI, directory, and log assertions after reload. The scenario holds the seeded attachment frame until the browser selects the adoption-created New Session, proves that the seed is absent from the group, then delivers the frame and selects the only non-blank Session. Host attachment completion and a two-row count cannot prove browser membership: the group header plus New Session already satisfy that count, and the arriving seed can replace the positional locator between click and assertion.
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Workspace 注册已有代码目录,使 GUI 能够为目录命名,并对其
|
||||
|
||||
## 持久化提交与发布
|
||||
|
||||
注册表操作会串行执行创建与删除。删除时先写入移除该 id 后的 Workspace 顺序,再从缓存中移除实体,最后删除表行。表删除是通知提交点:只有缓存停止发布该实体后,包不变量才接受该删除;Host 也只根据这次已提交的删除发出 `host/workspace-removed`。表写入失败时,系统会恢复缓存和此前的持久化顺序,且不会发布移除帧。
|
||||
注册表操作会串行执行创建与删除。删除时先写入移除该 id 后的 Workspace 顺序,再从缓存中移除实体,最后删除表行。表删除是通知提交点:此时缓存已停止发布该实体;Host 只根据这次已提交的删除发出 `host/workspace-removed`。表写入失败时,系统会恢复缓存和此前的持久化顺序,且不会发布移除帧。
|
||||
|
||||
Host 流在前一笔全局顺序写入期间继续保留其已提交 id 集合,只在删除表行时移除该 id。因此,创建回滚不会发出错误的移除帧,而每个已连接标签页都能收到从自身投影中删除该记录所需的准确 id。
|
||||
|
||||
@@ -50,7 +50,7 @@ Host 流在前一笔全局顺序写入期间继续保留其已提交 id 集合
|
||||
|
||||
## 验证
|
||||
|
||||
Workspace 包测试固定了仅删除元数据的成功路径、同路径重新注册、未知 id 的幂等行为、表操作失败回滚、明确标记的重启恢复、来源不明损坏的拒绝,以及缓存/表不变量行为。Apiproxy 与载体测试固定了 schema、处理器、`workspace-not-found`、保留会话/文件夹、使用新 id 重新注册,以及已提交的 `host/workspace-removed` 帧。客户端测试固定了一元直接回显、重复移除、延迟到达的 changed 帧,以及删除与进行中基线并发的行为。组件测试固定了确认交互、投影稳定后关闭、成功帧先于一元响应、失败、Cancel、Escape 与 Close。浏览器场景会在为不同目录复用已删除名称时,观测每一次瞬时 alert、slot error、console error 与 page error。
|
||||
Workspace 包测试固定了仅删除元数据的成功路径、同路径重新注册、未知 id 的幂等行为、表操作失败回滚、明确标记的重启恢复,以及来源不明损坏的拒绝。Apiproxy 与载体测试固定了 schema、处理器、`workspace-not-found`、保留会话/文件夹、使用新 id 重新注册,以及已提交的 `host/workspace-removed` 帧。客户端测试固定了一元直接回显、重复移除、延迟到达的 changed 帧,以及删除与进行中基线并发的行为。组件测试固定了确认交互、投影稳定后关闭、成功帧先于一元响应、失败、Cancel、Escape 与 Close。浏览器场景会在为不同目录复用已删除名称时,观测每一次瞬时 alert、slot error、console error 与 page error。
|
||||
|
||||
组装后的无密钥 Web 场景会注册一个已有临时项目目录,将持久化会话计入账本,把该会话设为当前会话,在 Chromium 中确认删除,并验证 Workspace 分组消失,而 Ungrouped 保留当前会话。该场景在删除前后检查用户文件和 JSONL 日志,并在刷新后重复验证 UI、目录与日志。场景会扣住种子会话的归属帧,直到浏览器选中接纳目录时创建的 New Session,确认分组中尚无种子会话,再交付该帧并选中唯一的非空白会话。Host 完成归属写入和行数达到二都不能证明浏览器已收到成员关系:分组标题加 New Session 已满足该行数,而到达的种子会话会使位置定位器在点击与断言之间指向另一行。
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
en: e9d8043178dc946f
|
||||
zh: 56808150ba2ab275
|
||||
/agent-note-queued-manual-compaction-with-one-durable-lock/decision/end-seed-distinguishes-live-and-stale-orphans:
|
||||
en: cc767e216225e117
|
||||
zh: 4ec7dbc179f96c37
|
||||
en: b4c949af9f56c0a3
|
||||
zh: 8481c8baf95a6ab5
|
||||
/agent-note-queued-manual-compaction-with-one-durable-lock/decision/reference-implementation-boundaries:
|
||||
en: 97b121c53b8f3b68
|
||||
zh: 13d86db92c30bef1
|
||||
@@ -36,8 +36,8 @@
|
||||
en: f1c5bae090425598
|
||||
zh: 60a30f851d85dfb0
|
||||
/agent-note-queued-manual-compaction-with-one-durable-lock/verification:
|
||||
en: 37587419460fcf36
|
||||
zh: e45c752f93d5a87e
|
||||
en: acd99ed77d2c36d4
|
||||
zh: f7f93c4ac2b7dc2c
|
||||
/agent-note-queued-manual-compaction-with-one-durable-lock/consequences:
|
||||
en: b8c71ea1d1db7b8e
|
||||
zh: 0d276bb8317ada91
|
||||
@@ -67,8 +67,6 @@ Failed `changed` or `summary` attempts leave the conversation surface unchanged,
|
||||
|
||||
Tail scanning finds the current turn, unmatched compaction start, and newest `session/end-seed` independently. An unmatched start after the newest end-seed is live and blocks every compaction entry point. An unmatched start before a later end-seed belongs to an earlier session lifecycle and is stale, so it does not wedge the resumed or forked session.
|
||||
|
||||
The compaction invariant uses the same transition logic during seed replay: `session/end-seed` clears an open historical trace. The boundary need not publish live from the constructor for this case; replay is the load-bearing path.
|
||||
|
||||
The client request projection closes an unmatched compaction request as interrupted at the `session/end-seed` time and clears its active index. A later `compaction/start` therefore creates an independent request instead of leaving or overwriting a permanently running orphan.
|
||||
|
||||
Once a transaction has appended its start, every later failure makes one closing attempt. A failed close leaves the unmatched start deliberately visible and blocking, and no flush is attempted. A closed manual attempt is flushed even when it reports an expected failure. Cancellation retains exact-reason precedence after required close and flush cleanup.
|
||||
@@ -97,7 +95,7 @@ That reference also carried client-side replacement-anchor machinery to preserve
|
||||
|
||||
## Verification
|
||||
|
||||
Agent-loop tests cover same-tick right of way, preserved IDs and FIFO lifecycle, waking and quiet queued work, idempotent release, `whenIdle()`, cancellation, and teardown. Compact tests cover standalone and numbered invariant ownership, end-seed replay, live versus stale orphans, re-entrant listeners, selected-span drift, commit and close failures, flush ordering, exact cancellation causes, raw output and usage preservation, and automatic/manual mutual exclusion.
|
||||
Agent-loop tests cover same-tick right of way, preserved IDs and FIFO lifecycle, waking and quiet queued work, idempotent release, `whenIdle()`, cancellation, and teardown. Compact tests cover end-seed replay, live versus stale orphans, re-entrant listeners, selected-span drift, commit and close failures, flush ordering, exact cancellation causes, raw output and usage preservation, and automatic/manual mutual exclusion.
|
||||
|
||||
The command package pins registration, Loader composition, argument rejection, exact success/failure text, cancellation, absence from model history, and disposal waiting across separate close and flush boundaries after an abort stops the executor from awaiting the handler. The client runtime projection test pins end-seed interruption followed by an independent completed attempt. The `queued-manual-compact` terminal snapshot drives real keystrokes through the assembled TUI: `/help` discovers the command, a held summary admits a queued prompt and immediate injection, `turn: null` markers and the flush precede the queued prompt turn, command lifecycle stays log-only, and the derived order is checkpoint → injection → queued prompt.
|
||||
|
||||
|
||||
@@ -67,8 +67,6 @@ DSH 有意在调用摘要器前记录 `compaction/start`。缓慢或崩溃的尝
|
||||
|
||||
尾部扫描会分别查找当前轮次、未匹配的 compaction start 与最新 `session/end-seed`。位于最新 end-seed 之后的未匹配 start 是活动锁,会阻塞每个压缩入口点。位于较新 end-seed 之前的未匹配 start 属于更早的会话生命周期,已经陈旧,因此不会卡住恢复或 fork 后的会话。
|
||||
|
||||
压缩不变量在 seed 回放期间使用同一项转换逻辑:`session/end-seed` 会清除未闭合的历史追踪状态。此场景不要求构造函数实时发布该边界;回放才是承重路径。
|
||||
|
||||
客户端请求投影会在 `session/end-seed` 时刻将未匹配的压缩请求以中断状态结束,并清除其活动索引。因此,后续 `compaction/start` 会创建一个独立请求,而不是让该遗留的未匹配请求永久保持运行状态或将其覆盖。
|
||||
|
||||
事务追加 start 后,每次后续失败都会进行一次闭合尝试。闭合失败会有意留下可见且具有阻塞作用的未匹配 start,并且不尝试 flush。已闭合的手动尝试即使报告预期失败也会 flush。完成必需的闭合与 flush 清理后,取消仍保留原始原因优先级。
|
||||
@@ -97,7 +95,7 @@ DSH 有意在调用摘要器前记录 `compaction/start`。缓慢或崩溃的尝
|
||||
|
||||
## 验证
|
||||
|
||||
agent loop(智能体循环)测试覆盖同一 tick 内的优先权、保留 ID 与 FIFO 生命周期、会唤醒和静默的排队工作、幂等释放、`whenIdle()`、取消与 teardown。压缩测试覆盖独立与数字形式的不变量 owner、end-seed 回放、活动与陈旧未匹配标记、listener 重入、所选区段漂移、commit 与闭合失败、flush 顺序、原始取消原因、raw output 与 usage 保留,以及自动/手动互斥。
|
||||
agent loop(智能体循环)测试覆盖同一 tick 内的优先权、保留 ID 与 FIFO 生命周期、会唤醒和静默的排队工作、幂等释放、`whenIdle()`、取消与 teardown。压缩测试覆盖 end-seed 回放、活动与陈旧未匹配标记、listener 重入、所选区段漂移、commit 与闭合失败、flush 顺序、原始取消原因、raw output 与 usage 保留,以及自动/手动互斥。
|
||||
|
||||
命令包固定注册行为、Loader 组合、参数拒绝、精确的成功/失败文本、取消、不进入模型历史的保证,以及 dispose 在中止使执行器停止等待处理器后,仍会跨越相互独立的闭合与 flush 边界等待该处理器结算。客户端运行时投影测试固定 end-seed 中断,以及随后一次独立尝试的完成。`queued-manual-compact` 终端快照通过已组装 TUI 驱动真实按键:`/help` 可发现该命令;被暂停的摘要会接纳一个排队提示词和即时注入;`turn: null` 标记与 flush 先于排队提示词轮次;命令生命周期保持纯日志;派生顺序固定为检查点 → 注入 → 排队提示词。
|
||||
|
||||
|
||||
+4
-4
@@ -18,14 +18,14 @@
|
||||
en: fa9f572f763bc6b0
|
||||
zh: f3924fa49d2be22d
|
||||
/agent-note-background-job-completion-wakes-an-idle-owner/decision/teardown-claims-the-report:
|
||||
en: f79257596c755a32
|
||||
zh: 856bf42492425f9c
|
||||
en: 89efaa83188b28d7
|
||||
zh: 7e78f3b616602402
|
||||
/agent-note-background-job-completion-wakes-an-idle-owner/decision/completion-is-announced-last:
|
||||
en: 9cab013b3513f1e0
|
||||
zh: 78ea1b05df94d8a8
|
||||
/agent-note-background-job-completion-wakes-an-idle-owner/alternatives-considered:
|
||||
en: debb2c9b924474c3
|
||||
zh: 90e2cefc230e3ec8
|
||||
en: 0cebce77d37e74b2
|
||||
zh: 1339714c0885f8af
|
||||
/agent-note-background-job-completion-wakes-an-idle-owner/consequences:
|
||||
en: a2a6f76c8cc92d57
|
||||
zh: 6eed3e6a5c75dfb9
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ The cap exists because this chain is self-exciting in a way subagent settlement
|
||||
|
||||
`cancelForTeardown` now marks the record `reported`, exactly as `kill()` does after cancelling. The asymmetry was invisible while the notice was a harmless inject; a waking reporter turns it into one model request per teardown layer, on agents the host is destroying.
|
||||
|
||||
`reported` was already the right bit — "a kill, read, or wait has reported or committed to report the terminal state" — and teardown is a kill without a caller. Using it keeps every observer of the settlement intact: `onJobDone` still fires, so runtime invariants and the force-fail path stay covered, and only notice reporters go quiet.
|
||||
`reported` was already the right bit — "a kill, read, or wait has reported or committed to report the terminal state" — and teardown is a kill without a caller. Using it keeps every observer of the settlement intact: `onJobDone` still fires, so the force-fail path stays covered, and only notice reporters go quiet.
|
||||
|
||||
### Completion is announced last
|
||||
|
||||
@@ -56,7 +56,7 @@ The cap exists because this chain is self-exciting in a way subagent settlement
|
||||
|
||||
**A wall-clock window** on top of the counter. For an interactive agent the slow case is the wanted one — an hour-long build finishing and the agent resuming is the feature — and `dsh run` exits once its task turn goes idle regardless. Worth revisiting only if an unattended long-lived deployment appears.
|
||||
|
||||
**Suppressing `onJobDone` entirely during owner drain,** symmetric with the service-wide `listenersClosed`. It reads cleaner and removes a signal that is not only for notices: the force-fail record and the runtime invariant both observe teardown settlements. The `reported` bit denies exactly the reporters and nothing else.
|
||||
**Suppressing `onJobDone` entirely during owner drain,** symmetric with the service-wide `listenersClosed`. It reads cleaner and removes a signal that is not only for notices: the force-fail record observes teardown settlements. The `reported` bit denies exactly the reporters and nothing else.
|
||||
|
||||
## Consequences
|
||||
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ Update:本文依赖的 `reported` 位与 `onJobDone` 已随 [jobs seam 收敛]
|
||||
|
||||
`cancelForTeardown` 现在会把记录标记为 `reported`,与 `kill()` 在取消之后所做的完全一致。当通知只是一次无害的注入时,这处不对称看不出来;而会唤醒的报告方会把它变成每个 teardown 层级一次模型请求,作用在宿主正要销毁的 agent 上。
|
||||
|
||||
`reported` 本来就是正确的那个 bit——「kill、read 或 wait 已报告或承诺报告终止状态」——而 teardown 是一次没有调用方的 kill。用它可以让该结算的每一个观察者都保持完整:`onJobDone` 仍会触发,因此运行时不变量与强制失败路径依旧被覆盖,只有通知报告方会安静下来。
|
||||
`reported` 本来就是正确的那个 bit——「kill、read 或 wait 已报告或承诺报告终止状态」——而 teardown 是一次没有调用方的 kill。用它可以让该结算的每一个观察者都保持完整:`onJobDone` 仍会触发,因此强制失败路径依旧被覆盖,只有通知报告方会安静下来。
|
||||
|
||||
### 完成是最后才宣布的
|
||||
|
||||
@@ -56,7 +56,7 @@ Update:本文依赖的 `reported` 位与 `onJobDone` 已随 [jobs seam 收敛]
|
||||
|
||||
**在计数之上再加墙钟窗口**。对交互式 agent 而言,慢的那种情形恰恰是想要的——一小时的构建结束、agent 接着干下去,这就是特性本身——而 `dsh run` 无论如何都在任务轮次空闲后退出。只有当出现无人值守的长生命周期部署时才值得重新考虑。
|
||||
|
||||
**在 owner 排空期间整体压制 `onJobDone`**,与服务级的 `listenersClosed` 对称。它读起来更干净,但会移走一个不只服务于通知的信号:强制失败记录与运行时不变量都会观察 teardown 结算。`reported` 位恰好只否决报告方,别的什么也不否决。
|
||||
**在 owner 排空期间整体压制 `onJobDone`**,与服务级的 `listenersClosed` 对称。它读起来更干净,但会移走一个不只服务于通知的信号:强制失败记录会观察 teardown 结算。`reported` 位恰好只否决报告方,别的什么也不否决。
|
||||
|
||||
## 影响
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
en: 26a46d3b6f21288a
|
||||
zh: 94c32f789b3e28ac
|
||||
/agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/alternatives-considered:
|
||||
en: cc7c2ed172e4de3f
|
||||
zh: b8fed28d9ee835c5
|
||||
en: 7e2043244ea608cf
|
||||
zh: 2b2f63d82803138a
|
||||
/agent-note-promote-open-anywhere-from-community-plugin-to-first-party-package/consequences:
|
||||
en: 5be6b2891e3429c9
|
||||
zh: 68ffcff52676824f
|
||||
@@ -32,7 +32,7 @@ The pair lives in `packages/host/` and `packages/client/` because that is what t
|
||||
|
||||
**Offer VS Code's remote CLI during SSH sessions.** Its installed executable does not prove a usable editor connection: the inherited IPC socket belongs to a live VS Code connection and can disappear while Harness keeps running. Browser-side SSH-target configuration and local editor handoff remain outside this host-application feature.
|
||||
|
||||
**Vendor the plugin's `lib/` as-is under `packages/`.** Fastest, but the hand-authored JavaScript fails typecheck, coverage, i18n, JSDoc, and invariant gates wholesale; keeping it exempt would create a package class the repository deliberately does not have.
|
||||
**Vendor the plugin's `lib/` as-is under `packages/`.** Fastest, but the hand-authored JavaScript fails typecheck, coverage, i18n, and JSDoc gates wholesale; keeping it exempt would create a package class the repository deliberately does not have.
|
||||
|
||||
**A Typert Remote instead of raw webServer routes.** The apps/open calls fit the Remote RPC shape, but the icon route serves binary PNGs, which the JSON RPC vocabulary does not carry; splitting icons onto a raw route while apps/open ride Remote gives two transports for one feature. Raw routes also match the original's client, and `webhook-github` establishes the validated-raw-route pattern.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ Status: implemented
|
||||
|
||||
**在 SSH 会话中提供 VS Code 的远端 CLI。** 已安装的可执行文件不能证明编辑器连接可用:继承的 IPC socket 属于一个仍在运行的 VS Code 连接,Harness 继续运行时它也可能消失。浏览器侧的 SSH 目标配置与本地编辑器唤起不属于这个主机应用功能。
|
||||
|
||||
**将插件的 `lib/` 原样 vendor 进 `packages/`。** 最快,但手写 JavaScript 会整体不过 typecheck、覆盖率、i18n、JSDoc 和 invariant 门禁;为其保留豁免会造出仓库刻意不设的包类别。
|
||||
**将插件的 `lib/` 原样 vendor 进 `packages/`。** 最快,但手写 JavaScript 会整体不过 typecheck、覆盖率、i18n 和 JSDoc 门禁;为其保留豁免会造出仓库刻意不设的包类别。
|
||||
|
||||
**用 Typert Remote 而非裸 webServer 路由。** apps/open 调用符合 Remote RPC 形态,但 icon 路由提供二进制 PNG,JSON RPC 词汇承载不了;把 icon 拆去裸路由而 apps/open 走 Remote 会让一个功能有两种传输。裸路由也匹配原插件的客户端,且 `webhook-github` 已确立带校验裸路由的先例。
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 896ffdae7d51cc19
|
||||
zh: eed0d7e80456bd8e
|
||||
/agent-note-parallel-pre-push-gates/decision:
|
||||
en: c95d213689164ad6
|
||||
zh: da85165d13b26d58
|
||||
en: 381e7d9b5281ace7
|
||||
zh: 08542da75620602c
|
||||
/agent-note-parallel-pre-push-gates/verification:
|
||||
en: c7124c68afb12a8e
|
||||
zh: 0a0269476ae353f3
|
||||
|
||||
@@ -16,7 +16,7 @@ Aggregate jobs such as documentation synchronization hide long sequential chains
|
||||
|
||||
Long coordinator gates whose own subprocesses preserve useful attribution may opt into `streamOutput`. Their stdout and stderr reach the parent immediately without being buffered or printed again at completion. Partitioned coverage and parallel Web snapshots use this mode so a mid-run failure is visible without waiting for sibling work.
|
||||
|
||||
The Node 24 consumer job is one ten-gate mode rather than a shell-owned process pool. Its default worker count equals its gate count, while pull-request CI caps active gates at eight and dependencies control readiness. Build and source compatibility start immediately; after build, `publint` and built-package invariant validation run in parallel. Lint, both snapshot suites, documentation typechecking, NodeNext type checks, and built-bin smokes wait for the invariant validator to remove its temporary package views.
|
||||
The Node 24 consumer job is one ten-gate mode rather than a shell-owned process pool. Its default worker count equals its gate count, while pull-request CI caps active gates at eight and dependencies control readiness. Build and source compatibility start immediately; after build, `publint`, lint, both snapshot suites, documentation typechecking, NodeNext type checks, and built-bin smokes become ready together.
|
||||
|
||||
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) discovers packages from `packages/<group>/<pkg>` and runs `publint` with a worker pool sized from `availableParallelism()`. `DSH_PUBLINT_CONCURRENCY` can cap or raise the worker count for local machines and CI runners with different resource profiles. Results are buffered per package and printed in deterministic package order, so parallel execution does not scramble each package's log block.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Status: implemented
|
||||
|
||||
自身子进程能够保留有效归因的长时间协调门禁可以选择 `streamOutput`。其 stdout 与 stderr 会立即到达父进程,不会被缓冲,也不会在结束时重复打印。分区覆盖率与并行 Web 快照使用该模式,使运行中途的失败无需等待兄弟工作结束就能显示。
|
||||
|
||||
Node 24 消费方任务采用单个包含 10 道门禁的模式,而非由 shell 管理的进程池。其默认 worker 数等于门禁数,拉取请求 CI 则把活动门禁限制为 8 道,并由依赖关系控制就绪状态。构建与源码兼容性立即启动;构建完成后,`publint` 与已构建包不变式验证并行运行。lint、两套快照、文档类型检查、NodeNext 类型检查和 built-bin 冒烟测试等待不变式验证器清除临时包视图。
|
||||
Node 24 消费方任务采用单个包含 10 道门禁的模式,而非由 shell 管理的进程池。其默认 worker 数等于门禁数,拉取请求 CI 则把活动门禁限制为 8 道,并由依赖关系控制就绪状态。构建与源码兼容性立即启动;构建完成后,`publint`、lint、两套快照、文档类型检查、NodeNext 类型检查和 built-bin 冒烟测试同时就绪。
|
||||
|
||||
[scripts/publint-all.ts](../../../../scripts/publint-all.ts) 从 `packages/<group>/<pkg>` 发现包,并以根据 `availableParallelism()` 确定大小的 worker 池运行 `publint`。`DSH_PUBLINT_CONCURRENCY` 可以针对资源配置不同的本地机器和 CI runner 限制或提高 worker 数量。结果按包缓冲,并按确定性的包顺序打印,因此并行执行不会打乱各包的日志块。
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 4f399390f58d7a34
|
||||
zh: 9df67248acfc61c8
|
||||
/agent-note-private-npm-publication-as-three-independent-sequences/decision/workspace-internal-references-use-the-workspace-protocol:
|
||||
en: 6cff087e7e2d0ac7
|
||||
zh: 5d45a3abdd960d9c
|
||||
en: 79c003c8838bb88c
|
||||
zh: d23efb6ecccda3cc
|
||||
/agent-note-private-npm-publication-as-three-independent-sequences/decision/published-dependency-faces-use-an-explicit-policy:
|
||||
en: 30a64e0e919a27c0
|
||||
zh: 19729d7ebee35439
|
||||
|
||||
@@ -80,7 +80,7 @@ Two registry behaviours shape how a publish is attempted. Writes are spaced by a
|
||||
|
||||
Every reference to a workspace member uses the `workspace:` protocol. The [release-range policy](2026-09-22-workspace-release-ranges.md) requires exact `workspace:*` DSH references and `workspace:~` vendor/native references in every dependency section and consumer, including the native entry's optional platform packages. Local workspace linking is unchanged.
|
||||
|
||||
`scripts/check-workspace-constraints.ts` reads every member declared in `pnpm-workspace.yaml` plus the root manifest and enforces ranges by dependency target, not consumer directory. The invariant-companion rule requires `workspace:*` for `@deepseek-ai/dsh-invariants`; the dependency repairer preserves vendor/native tilde ranges. Published DSH peers therefore require the matching release instead of admitting later compatible versions.
|
||||
`scripts/check-workspace-constraints.ts` reads every member declared in `pnpm-workspace.yaml` plus the root manifest and enforces ranges by dependency target, not consumer directory. The dependency repairer preserves vendor/native tilde ranges. Published DSH peers therefore require the matching release instead of admitting later compatible versions.
|
||||
|
||||
### Published dependency faces use an explicit policy
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ registry 的两个行为决定了「怎么尝试一次发布」。写入之间
|
||||
|
||||
所有指向 workspace 成员的引用都用 `workspace:` 协议。[发布范围策略](2026-09-22-workspace-release-ranges.zh.md)要求每个消费者的所有依赖区段对 DSH 使用精确的 `workspace:*` 引用,对 vendor/native 使用 `workspace:~` 引用,包括原生入口的可选平台包。本地 workspace 链接方式不变。
|
||||
|
||||
`scripts/check-workspace-constraints.ts` 读取 `pnpm-workspace.yaml` 声明的全部成员及根清单,按依赖目标而非消费者目录校验范围。Invariant companion 规则要求 `@deepseek-ai/dsh-invariants` 使用 `workspace:*`;依赖修复器保留 vendor/native tilde 范围。因此,发布的 DSH peer 要求匹配的发布版本,而不接纳后续兼容版本。
|
||||
`scripts/check-workspace-constraints.ts` 读取 `pnpm-workspace.yaml` 声明的全部成员及根清单,按依赖目标而非消费者目录校验范围。依赖修复器保留 vendor/native tilde 范围。因此,发布的 DSH peer 要求匹配的发布版本,而不接纳后续兼容版本。
|
||||
|
||||
### 发布依赖门面使用显式策略
|
||||
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
en: 528a0c462d456493
|
||||
zh: a2af4901a8f498a7
|
||||
/agent-note-evidence-led-simplification-surveys/decision:
|
||||
en: 865cfb210f13b188
|
||||
zh: 777d8155216d98c6
|
||||
en: c292a9b7fa087874
|
||||
zh: 4a3e09282b085889
|
||||
/agent-note-evidence-led-simplification-surveys/historical-evidence:
|
||||
en: 53eea2f1d7560461
|
||||
zh: 3c55432d9cd3ebe3
|
||||
|
||||
@@ -12,7 +12,7 @@ Unused-symbol searches can miss working behavior whose implementation costs more
|
||||
|
||||
The [simplification skill](../../../skills/dsh-find-simplifications/SKILL.md) traces a complete producer-to-outcome path, asks which distinctions change consumer actions, considers explicit reductions in supported behavior, and searches for maintained copies that an authoritative read can replace. It separates composition availability from deployment policy and counts removed configuration, lifecycle, tests, and documentation together with source code.
|
||||
|
||||
Production use changes the required trade-off evidence; it does not automatically veto a proposal or authorize implementation. Dynamic plugin discovery, installed consumers, generated runtime assets, profiles, applications, and Python participate in consumer tracing. Protected adapters and persistence designs, released data, trust boundaries, independent invariant observations, synchronous publication, cancellation, and quiescent disposal remain constraints.
|
||||
Production use changes the required trade-off evidence; it does not automatically veto a proposal or authorize implementation. Dynamic plugin discovery, installed consumers, generated runtime assets, profiles, applications, and Python participate in consumer tracing. Protected adapters and persistence designs, released data, trust boundaries, synchronous publication, cancellation, and quiescent disposal remain constraints.
|
||||
|
||||
The entry point separates decision criteria from an [optional historical reference](../../../skills/dsh-find-simplifications/references/historical-patterns.md) pairing mechanisms with counterexamples and residual obligations. Archive mechanics remain at their existing owner, and the proposal skeleton uses the canonical headings.
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ Status: implemented
|
||||
|
||||
[简化 skill](../../../skills/dsh-find-simplifications/SKILL.md) 追踪从生产方到结果的完整路径,判断哪些区分会改变消费方动作,考虑明确收缩受支持行为,并寻找可以用权威读取替代的持续维护副本。它区分组合可用性与部署策略,并在源码之外合计删除的配置、生命周期、测试和文档成本。
|
||||
|
||||
生产使用会改变所需的取舍证据,但不会自动否决提案,也不构成实施授权。动态插件发现、已安装消费方、生成的运行时产物、profile、应用和 Python 都纳入消费方追踪。受保护的适配器与持久化设计、已发布数据、信任边界、独立 invariant 观察、同步发布、取消及达到静止状态的释放仍是约束。
|
||||
生产使用会改变所需的取舍证据,但不会自动否决提案,也不构成实施授权。动态插件发现、已安装消费方、生成的运行时产物、profile、应用和 Python 都纳入消费方追踪。受保护的适配器与持久化设计、已发布数据、信任边界、同步发布、取消及达到静止状态的释放仍是约束。
|
||||
|
||||
入口将判断标准与[可选历史参考页](../../../skills/dsh-find-simplifications/references/historical-patterns.md) 分开,后者将机制与反例、残留义务配对。归档操作仍由原有归属处负责,提案结构使用规范标题。
|
||||
|
||||
|
||||
+2
-2
@@ -24,8 +24,8 @@
|
||||
en: 4098e049cd6d28d8
|
||||
zh: 4ab332ad6af53ccf
|
||||
/agent-note-plan-specific-collaboration-state/verification:
|
||||
en: 41179b6ab02b3f90
|
||||
zh: 47f5a602496281cd
|
||||
en: 25717419b3123d8e
|
||||
zh: c45f2abbc7cb902f
|
||||
/agent-note-plan-specific-collaboration-state/consequences:
|
||||
en: a71537da7d3921e2
|
||||
zh: a2ab69676bfe985d
|
||||
+1
-1
@@ -59,7 +59,7 @@ The tool renders the submitted plan as a generic card titled by its first headin
|
||||
|
||||
## Verification
|
||||
|
||||
- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and PTC mode schemas, review outcomes, and invariant coverage through the boolean service.
|
||||
- Package tests retain boundary ordering, retry, append-failure, HMR disposal, prompt assembly, stable native and PTC mode schemas, and review outcomes through the boolean service.
|
||||
- Command tests cover bare `/plan`, `/plan <message>`, active `/plan off`, pending-entry cancellation, inactive idempotence, absence of `/mode` and `/review`, and effect-scoped removal.
|
||||
- The keyless TUI scenarios enter through `/plan <message>`, leave through `/plan off`, and prove that each committed `plan/mode` precedes the request header it changes, the entry message is logged under plan guidance, and the post-exit request omits that guidance.
|
||||
- The complete `exit_plan_mode` review arc is package-tested but has no assembled-application snapshot after the interactive ACP scenarios were retired; current keyless TUI scenarios cover command entry and direct exit only.
|
||||
|
||||
Loaded 100 of 1355 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user