diff --git a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml index 2ee32110ba..93a831b4f9 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.i18n.yaml @@ -2,5 +2,5 @@ # 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/implemented/architecture/2026-08-10-session-log-version-mechanism.md -2026-08-10-session-log-version-mechanism.md: 952c11d3383af961922a65909c4024ca53089716 -2026-08-10-session-log-version-mechanism.zh.md: 2e5a3544e7a03bb8c7c598f80f398f1557bc73c9 +2026-08-10-session-log-version-mechanism.md: 82748212b10edf5b201f2be7395cbdb54108fbf7 +2026-08-10-session-log-version-mechanism.zh.md: 3950f41398d032d02a7d6c4487220c6da78388f4 diff --git a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md index 952c11d338..82748212b1 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md +++ b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md @@ -27,4 +27,4 @@ What shipped in v0 (release 0812): direction-aware refusal with the raw-log path - **Major/minor versioning** — the "is it convertible" bit lives on each step's upgrader, and pre-committing it into a number shape invites wrong promises. - **Default-ignorable unknown events** — inverts the failure mode of a forgotten marker from visible over-refusal into silent corruption. - **Auto-migrating on view** — rewriting the artifact on open turns a read into a destructive write: a converter bug corrupts logs at browse time, and a same-directory older runtime loses access because a newer one merely looked. -- **Per-plugin runtime registration of known event types** — would make the known set composition-dependent, so a leaner same-version composition would refuse logs a fuller one wrote. The generated repo-wide list keeps same-version reads uniform; out-of-repo plugin events are outside it by construction, and a registration surface for them is deferred until such a consumer exists. +- **Per-plugin runtime registration of known event types** — rejected because it would make the known set composition-dependent and register event names without classifying whether omission is safe. The persisted `ignorable` marker keeps that classification with each record; the [external-plugin retention decision](2026-08-30-retain-ignorable-external-session-events.md) owns the current consumer constraint. diff --git a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md index 2e5a3544e7..3950f41398 100644 --- a/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.zh.md @@ -27,4 +27,4 @@ v0(0812 发布)交付的内容:分方向的拒绝并带原始日志路径 - **大小两级版本号**:能否转换这一位信息属于每一步的升级器,把它预先固化进编号形状会做出错误承诺。 - **未知事件默认可忽略**:把忘写标记的后果从可见的过度拒绝反转成静默损坏。 - **查看时自动迁移落盘**:打开即改写把读操作变成破坏性写操作,转换器的 bug 会在浏览时损坏日志,同目录的旧版本运行时也会因为新版本只是看了一眼就失去访问能力。 -- **插件运行时注册已知事件类型**:会让已知集依赖插件组合,同版本的精简组合会拒绝完整组合写出的日志。生成的全仓库清单保证同版本读取行为一致;仓库外插件的事件按构造就在清单之外,为它们提供注册表面推迟到真有这样的消费者时再做。 +- **插件运行时注册已知事件类型**:不予采用,因为该方案会让已知集依赖插件组合,而且只注册事件名称,无法判定省略事件是否安全。持久化的 `ignorable` 标记把该分类保留在每条记录中;[外部插件保留决策](2026-08-30-retain-ignorable-external-session-events.zh.md)定义当前消费方约束。 diff --git a/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.i18n.yaml index 3ac4444d7a..fc26b05e5c 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.i18n.yaml @@ -2,5 +2,5 @@ # 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/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.md -2026-08-18-sqlite-physical-chunk-row-compression.md: fea313eb6de38d432d42d0fd44858509ef948262 -2026-08-18-sqlite-physical-chunk-row-compression.zh.md: 007811dee5d4d69a19d84cd6ad1159dd3572c309 +2026-08-18-sqlite-physical-chunk-row-compression.md: 3324d15abbc87b69a222c74f784fc565e287a38a +2026-08-18-sqlite-physical-chunk-row-compression.zh.md: 57b252e2f4561f4659e0ed0ad34e0b4b5db68227 diff --git a/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.md b/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.md index fea313eb6d..3324d15abb 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.md +++ b/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.md @@ -58,7 +58,7 @@ The repository regression guard writes 1,000 streamed deltas in 40-event durable **Compress every payload.** Rejected because small independent Zstandard frames add headers and synchronous CPU work while losing the cross-record dictionary opportunity of a whole-file stream. On the 105-session comparison corpus, a threshold sweep produced 75.01 MB at 4 KiB, versus 93.87 MB at 16 KiB and 60.92 MB at 1 KiB. The writer fixes level 3 rather than inheriting a library default, matching the moderate level used by [Codex cold-rollout compression](https://github.com/openai/codex/blob/main/codex-rs/rollout/src/compression.rs) while retaining independent row access. -The final frozen comparison used 105 sessions, 2,507,860 logical events, 512-event durable batches, three independent builds per backend, and three read passes per build. SQLite used 75.01 MB, wrote in 8.58 s, read complete sessions at 3.95/21.58 ms p50/p95, read 50-event tails at 0.253/0.378 ms, and forked every session in 13.10 s. Zstandard JSONL used 30.65 MB and measured 28.21 s, 4.49/23.36 ms, 10.58/80.90 ms, and 14.48 s. The predecessor scalar SQLite layout used 709.57 MB and measured 10.64 s, 9.02/69.16 ms, 0.189/0.293 ms, and 19.30 s. The packed layout is 89.4% smaller than the predecessor, writes 19.4% faster, improves complete-read p50/p95 by 56.2%/68.8%, and reduces 2,507,860 physical event rows to 65,810. Scalar tail-50 and list micro-latency are lower, but the packed provider remains materially faster than JSONL on those paths and wins the dominant size, write, full-read, and fork costs. The 4 KiB threshold is the accepted balance rather than a strict dominance claim. +The final frozen comparison used 105 sessions, 2,507,860 logical events, 512-event durable batches, three independent builds per backend, and three read passes per build. SQLite used 75.01 MB, wrote in 8.58 s, read complete sessions at 3.95/21.58 ms p50/p95, read 50-event tails at 0.253/0.378 ms, and forked every session in 13.10 s. Zstandard JSONL used 30.65 MB and measured 28.21 s, 4.49/23.36 ms, 10.58/80.90 ms, and 14.48 s. The predecessor scalar SQLite layout used 709.57 MB and measured 10.64 s, 9.02/69.16 ms, 0.189/0.293 ms, and 19.30 s. The packed layout is 89.4% smaller than the predecessor, writes 19.4% faster, improves complete-read p50/p95 by 56.2%/68.8%, and reduces 2,507,860 physical event rows to 65,810. Scalar tail-50 and list micro-latency are lower, but the packed provider remains materially faster than JSONL on those paths and wins the dominant size, write, full-read, and fork costs. The 4 KiB threshold is the accepted balance rather than a strict dominance claim. This comparison measured schema 17; its exact values are evidence for the original packed-row decision, not schema-20 measurements. The [persistence latency and page-size decision](2026-08-25-persistence-latency-and-page-size.md) owns the schema-19 benchmark and current encoding refinements. **Store packed payloads under the logical `assistant/chunk` type.** Rejected because payload heuristics make malformed rows ambiguous and couple physical decoding to future logical payload fields. Explicit tags fail loudly. diff --git a/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.zh.md b/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.zh.md index 007811dee5..57b252e2f4 100644 --- a/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-18-sqlite-physical-chunk-row-compression.zh.md @@ -58,7 +58,7 @@ SQLite 在 schema 20 包内拥有分片编码和验证。字段完全匹配的 **压缩每个 payload。** 不予采用,因为小型独立 Zstandard frame 会增加 header 和同步 CPU 工作,也无法利用整文件流的跨记录字典。在 105 个会话的对比语料上,阈值扫描结果为:4 KiB 生成 75.01 MB,16 KiB 为 93.87 MB,1 KiB 为 60.92 MB。写入方固定使用 level 3,而不是继承库默认值;这与 [Codex 冷 rollout 压缩](https://github.com/openai/codex/blob/main/codex-rs/rollout/src/compression.rs)所用的适中级别一致,同时保留独立行访问。 -最终冻结对比包含 105 个会话、2,507,860 个逻辑事件,以 512 个事件为持久批次;每个后端独立构建三次,每次构建执行三轮读取。SQLite 使用 75.01 MB,写入耗时 8.58 秒,完整读取 p50/p95 为 3.95/21.58 毫秒,读取最后 50 个事件为 0.253/0.378 毫秒,对所有会话执行 fork 为 13.10 秒。Zstandard JSONL 使用 30.65 MB,对应指标为 28.21 秒、4.49/23.36 毫秒、10.58/80.90 毫秒和 14.48 秒。此前的标量 SQLite 布局使用 709.57 MB,对应指标为 10.64 秒、9.02/69.16 毫秒、0.189/0.293 毫秒和 19.30 秒。打包布局比此前布局小 89.4%,写入快 19.4%,完整读取 p50/p95 改善 56.2%/68.8%,并把 2,507,860 个物理事件行减少到 65,810 行。标量布局的最后 50 个事件读取与 list 微延迟更低,但打包提供方在这些路径上仍明显快于 JSONL,并改善主要的空间、写入、完整读取和 fork 成本。4 KiB 阈值是接受的平衡点,而不是严格支配所有指标的结论。 +最终冻结对比包含 105 个会话、2,507,860 个逻辑事件,以 512 个事件为持久批次;每个后端独立构建三次,每次构建执行三轮读取。SQLite 使用 75.01 MB,写入耗时 8.58 秒,完整读取 p50/p95 为 3.95/21.58 毫秒,读取最后 50 个事件为 0.253/0.378 毫秒,对所有会话执行 fork 为 13.10 秒。Zstandard JSONL 使用 30.65 MB,对应指标为 28.21 秒、4.49/23.36 毫秒、10.58/80.90 毫秒和 14.48 秒。此前的标量 SQLite 布局使用 709.57 MB,对应指标为 10.64 秒、9.02/69.16 毫秒、0.189/0.293 毫秒和 19.30 秒。打包布局比此前布局小 89.4%,写入快 19.4%,完整读取 p50/p95 改善 56.2%/68.8%,并把 2,507,860 个物理事件行减少到 65,810 行。标量布局的最后 50 个事件读取与 list 微延迟更低,但打包提供方在这些路径上仍明显快于 JSONL,并改善主要的空间、写入、完整读取和 fork 成本。4 KiB 阈值是接受的平衡点,而不是严格支配所有指标的结论。该对比测量的是 schema 17;其精确数值是原始打包行决策的证据,并非 schema 20 实测。[持久化延迟与 page size 决策](2026-08-25-persistence-latency-and-page-size.zh.md)记录 schema 19 基准与当前编码细节。 **把打包 payload 存在逻辑 `assistant/chunk` 类型下。** 不予采用,因为 payload 启发式判断会使畸形行产生歧义,并把物理解码耦合到未来逻辑 payload 字段。显式标签会明确失败。 diff --git a/packages/core/session/src/known-event-types.ts b/packages/core/session/src/known-event-types.ts index 4c0c30d706..774e1a55cb 100644 --- a/packages/core/session/src/known-event-types.ts +++ b/packages/core/session/src/known-event-types.ts @@ -13,8 +13,11 @@ * in `./types.ts`): such a log was likely written by a newer harness, and * silently skipping a required event would reconstruct a wrong session. * Downstream (out-of-repo) plugin events are outside this list by - * construction; a registration surface for them is deferred until such a - * consumer exists. + * construction. The persisted `SessionEvent.ignorable` marker is the + * compatibility mechanism; event-name registration was rejected because + * it does not classify omission safety and would make reads + * composition-dependent. The rationale is in + * `.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md`. */ export const KNOWN_SESSION_EVENT_TYPES: ReadonlySet = new Set([ 'agent-preset/selected', diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index 75520db95a..c0ed4a5c04 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -414,8 +414,11 @@ export function renderKnownEventTypes(events: AnnotatedLogEventEntry[]): string ' * in `./types.ts`): such a log was likely written by a newer harness, and', ' * silently skipping a required event would reconstruct a wrong session.', ' * Downstream (out-of-repo) plugin events are outside this list by', - ' * construction; a registration surface for them is deferred until such a', - ' * consumer exists.', + ' * construction. The persisted `SessionEvent.ignorable` marker is the', + ' * compatibility mechanism; event-name registration was rejected because', + ' * it does not classify omission safety and would make reads', + ' * composition-dependent. The rationale is in', + ' * `.agents/notes/implemented/architecture/2026-08-30-retain-ignorable-external-session-events.md`.', ' */', 'export const KNOWN_SESSION_EVENT_TYPES: ReadonlySet = new Set([', ...names.map(name => ` '${name}',`),