refactor(runtime): establish model history boundaries - #4779
Conversation
5117613 to
c7ee0d1
Compare
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
Generated-by: Codex
c7ee0d1 to
949a71a
Compare
jackwener
left a comment
There was a problem hiding this comment.
Reviewed at exact head 949a71ac029e339c5fb6b3896ded7b057f57c4c3. Four independent reviews, each sealing before reading the others. Three cleared their slice; one found a [P1], so I am not approving this head. The rest of the change is in unusually good shape, and the fix looks contained.
[P1] Retiring the old checkpoint strands long Sessions that change route
0.1.x's real writer produces a version: 2 portable checkpoint with no summaryFormat. The old loader accepted it and replay substituted its short summary for the raw history it covered. This head requires summaryFormat === sections_v1 (history-compact-checkpoint.ts:481-486), so the ledger loader ignores it and conversation-copy.ts:896-907 drops it — both paths expand back to the full RuntimeEvents the checkpoint existed to replace.
The reachable path is ordinary: upgrade a long, already-compacted Session, then continue on a different route — the original Connection unavailable, or a deliberate switch to a smaller or newer model — or open a Side Conversation.
Measured end to end, building the data with the base branch's own builder and loader, then running the current SQLite store, loader, budget, planner, Codex fallback and copy:
| Step | Result |
|---|---|
| base loader | accepts the old checkpoint; a 12,460-byte raw span replays as 1 compact event |
| current loader | ignores it; the same Session restores as 2 raw events |
| new route, native compact then text fallback | each rejects once |
planHistoryCompaction() |
{ decision: 'fail_open', reason: 'summarizer_failed', diagnosticReason: 'input_too_large' } |
| same route, control | falls back from coverage 2 to an already-accepted coverage 1 and compacts successfully |
| copy | keeps the raw content, drops the checkpoint event — the copied Session carries the same failure surface |
The control row is what makes this specific rather than general: on the original route there is an accepted input to retreat to, so compaction still succeeds. On a new route there is no history of a reply, so fail_open returns with no replacement request and the turn ends in context overflow. Both the native compactor and the text fallback receive the same raw span through the same selected model, so the fallback seam is genuinely invoked — it simply cannot fit what the first request could not fit.
The old portable checkpoint was what prevented that overflow, and once the original route is unavailable the user has no recoverable way to continue.
Smallest fix: keep an unmarked V2 as its own legacy persisted variant, admitted and replayed under the old truncation-only rule, never by presenting free text as sections_v1; have copy preserve or rebuild the legacy boundary when it matches a prefix, and let a successful compaction replace it with sectioned V2/V3 afterwards. A regression needs a real checkpoint from the old encoder, covering reopen, copy, and a capacity rejection on a new or smaller route.
Worth noting the shape of what was removed: findCheckpointSummaryTruncationDefect — with the comment "Legacy checkpoints predate the section contract, so load recovery may only quarantine writer-agnostic truncation" — was that truncation-only rule. Its deletion is where the loader lost the ability to accept an unmarked V2, and restoring that capability is what the fix above amounts to.
What four reviews confirmed
The unified timeline is byte-equivalent where it matters. Base against head, 1,000 deterministic stamped histories through the AI SDK request projection and 1,000 through the Codex compactor: zero serialized mismatches on both. The text summarizer's differences are the intended convergence on the primary client-tool chronology — no text, call or result was lost or cross-paired, and it still skips thinking exactly as the merge-base did.
The invocation-scoped IDs cut where intended and nowhere else. Removing invocation from the identity fails the cross-invocation regression immediately; the restored head passes 6/6; and sequential same-invocation ID reuse stays correctly paired by occurrence. So the new rule rejects the old cross-invocation pairing without breaking legitimate same-invocation reuse.
Decode-level degradation is graceful, which is why the P1 above is about capacity rather than loading: an unmarked checkpoint does not break Session or ledger decode, copy does not fail, short Sessions replay their raw history intact, and the Codex fallback seam is reached.
Existing continuations do survive the upgrade. The digest strips the new invocationId before hashing, and the reason it is exactly stable is that the base type never carried that field — head adds it to all four variants — so both sides serialize identical JSON.
[P2] Nothing pins the three consumers to one timeline
All three now call buildRuntimeEventReplayTimeline (model-history.ts:410), and model-history-timeline.test.ts covers only that shared function. No fixture feeds one set of events through the AI SDK projection, the text summarizer and the Codex compactor and compares the chronology, so a consumer that quietly rebuilt a private grouping loop would keep every unit test green. The equivalence measured above is the current state, not a guarded one.
[P3] The digest test does not pin the compatibility claim
keeps internal invocation identity outside the projection v2 digest asserts digest('invocation-a') === digest('invocation-b') — that the value does not matter. The claim is that the digest matches what the previous version produced. Those differ: change the strip to also remove toolCallId and the test still passes while every existing continuation stops matching. A golden digest computed on the base branch would pin the actual promise.
Description
ai-sdk-backend.ts is 5137 lines on the merge base, not 4884. The net −67 is the whole PR rather than the split; the split itself is a move plus a smaller cut, and the identifiable unique deletions are AgentRun/RuntimeKernel no longer sending StoredMessage context, and Codex and the summarizer dropping their private timeline builders.
Everything except the legacy checkpoint path is well supported, and the P1 has a contained fix that the codebase already had the shape for.
简体中文
在 949a71ac029e339c5fb6b3896ded7b057f57c4c3 上评审。四次独立评审,各自封存后才互看。三席确认了自己那一片;一席发现 [P1],因此这个 head 我不批准。 其余部分状态相当好,而且这个修复是收敛的。
[P1] 退役旧 checkpoint,会让切换路由的长 Session 无法继续
0.1.x 的真实 writer 产生的是 version: 2、没有 summaryFormat 的 portable checkpoint。旧 loader 接受它,replay 会用它的短摘要替换它所覆盖的原始历史。本 head 强制要求 summaryFormat === sections_v1(history-compact-checkpoint.ts:481-486),于是 ledger loader 忽略它,conversation-copy.ts:896-907 也把它丢掉——两条路径都展开回了「这个 checkpoint 当初正是为替换它而存在」的完整 RuntimeEvents。
可达路径很普通:升级一个已压缩的长 Session,然后换一条路由继续——原 Connection 不可用,或主动切到更小/更新的模型——或者开一个 Side Conversation。
端到端实测,用 base 分支自己的 builder 和 loader 造数据,再跑当前的 SQLite store、loader、budget、planner、Codex fallback 与 copy:
| 步骤 | 结果 |
|---|---|
| base loader | 接受旧 checkpoint;12,460 字节的 raw span 重放为 1 条 compact event |
| 当前 loader | 忽略它;同一个 Session 恢复成 2 条 raw event |
| 新路由下,native compact 然后 text fallback | 各拒绝一次 |
planHistoryCompaction() |
{ decision: 'fail_open', reason: 'summarizer_failed', diagnosticReason: 'input_too_large' } |
| 同路由对照 | 从 coverage 2 退回到一个已被接受的 coverage 1,压缩成功 |
| copy | 保住原始内容,丢掉 checkpoint event——复制出的 Session 带着同一个失败面 |
对照那一行是让这条结论「具体」而非「泛泛」的关键:在原路由上存在一个「已被接受过的输入」可以退回,所以压缩仍会成功。而在新路由上没有任何回复历史,于是 fail_open 直接返回、没有替换请求,这个 turn 以 context overflow 结束。 native compactor 与 text fallback 通过同一个被选中的模型收到同一份 raw span,所以 fallback 接缝确实被调用了——它只是装不下第一个请求就已经装不下的东西。
旧的 portable checkpoint 正是当初避免这次 overflow 的东西;而一旦原路由不可用,用户就没有可恢复的继续路径了。
最小修法: 把无 marker 的 V2 保留为独立的 legacy 持久化变体,按旧的 truncation-only 规则来 admission 与 replay,绝不能把自由文本冒充成 sections_v1;copy 在匹配前缀时也应保留或重建 legacy boundary;等一次压缩成功之后,再用 sectioned V2/V3 替换它。回归测试需要由旧 encoder 造出真实 checkpoint,覆盖重开、复制,以及新/小路由上的容量拒绝。
值得指出被移除的那个东西的形状:findCheckpointSummaryTruncationDefect——注释写着*「Legacy checkpoints 早于 section 契约,所以加载恢复只能隔离与写入者无关的截断」*——它就是那条 truncation-only 规则。 loader 失去「接受一个无 marker V2」的能力正是在它被删除的地方,而上面那个修法,本质上就是把这个能力恢复回来。
四次评审确认无误的部分
统一时间线在要紧处逐字节等价。 base 对 head,1,000 组确定性 stamped 历史走 AI SDK 请求投影、另 1,000 组走 Codex compactor:两边都是零序列化不匹配。 文本 summarizer 的差异是有意收敛到主路径的 client-tool 时序——没有任何 text、call 或 result 被丢失或错配,而且它仍然跳过 thinking,与 merge-base 完全一致。
invocation 作用域化的 ID 只切在该切的地方。 把 invocation 从身份中移除,跨 invocation 回归立即失败;恢复后的 head 6/6 通过;而同一 invocation 内顺序重用的 ID 仍按出现次序正确配对。 所以新规则拒绝了旧的跨 invocation 配对,没有误伤合法的同 invocation 重用。
解码层面的降级是优雅的,这也正是上面那条 P1 关于容量而非加载的原因:无 marker 的 checkpoint 不会破坏 Session 或 ledger 解码,copy 不会失败,短 Session 会完整重放原始历史,Codex fallback 接缝也确实被走到。
既有的 continuation 确实跨升级存活。 digest 在哈希前剥掉了新增的 invocationId,而它之所以恰好稳定,是因为 base 上那个类型从来没有这个字段——head 才给四个变体都加上——所以两边序列化出完全相同的 JSON。
[P2] 没有任何东西把三个消费者钉在同一条时间线上
三者现在都调用 buildRuntimeEventReplayTimeline(model-history.ts:410),而 model-history-timeline.test.ts 只覆盖那个共享函数。没有任何 fixture 把同一组事件同时喂给 AI SDK 投影、文本 summarizer 和 Codex compactor 并比较时序,所以一个消费者如果悄悄重建了私有的分组循环,所有单元测试都会保持绿色。 上面测到的等价性是当前状态,而不是被守住的状态。
[P3] digest 测试钉的不是那条兼容性承诺
keeps internal invocation identity outside the projection v2 digest 断言的是 digest('invocation-a') === digest('invocation-b')——即这个值不影响结果。而承诺是digest 与上一个版本产出的相同。两者不同:把剥离改成同时移除 toolCallId,这条测试照样通过,而所有既有 continuation 都不再匹配。 一个在 base 分支上算出的黄金 digest 值才能钉住真正的承诺。
关于描述
ai-sdk-backend.ts 在 merge base 上是 5137 行,不是 4884。净 −67 是整个 PR 而非拆分本身;拆分是一次搬移加一次较小的削减,可指认的独立删除是 AgentRun/RuntimeKernel 不再发送 StoredMessage context,以及 Codex 与 summarizer 各自删掉了私有的时间线构建器。
除 legacy checkpoint 这条路径外,其余都有充分支撑,而这条 P1 的修法是收敛的,而且代码库本来就有它的形状。
Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.
Generated-by: Codex
|
Reviewed the feedback against exact head
Validation on the updated code: format, lint, Runtime build, and Runtime |
|
Follow-up on the legacy-checkpoint finding: the compatibility boundary is now explicit in the PR description. The unmarked portable V2 writer is confined to the pre-Apache v0.1.x line; The deliberate contract is:
This is a compatibility-policy decision, not an assertion that the measured capacity failure is unreal. The PR now states that consequence directly. The existing regressions already cover all three relevant mechanics: unmarked V2 shape rejection, superseded checkpoints failing open while retaining canonical history, and conversation copy dropping an unusable checkpoint rather than failing. No additional production compatibility path or duplicate test oracle is added. The accepted digest golden remains in |
|
Re-adjudicated against exact head One factual correction is accepted: RC1 did not generate new free-form summaries, but its conversation-copy seam could re-emit an unmarked checkpoint while preserving a summary originally generated by a v0.1.x writer. I updated the PR description to state that explicitly, including the consequence for an RC1 copy whose compacted prefix originated in v0.1.x. That correction does not change the compatibility decision. Support from RC1 onward applies to canonical Session data: RuntimeEvents remain readable and copyable and are not deleted when a derived checkpoint is rejected. It does not promise transparent continuation for every derived cache contract inherited from pre-Apache data. The measured new-route overflow is therefore a real consequence of the declared breaking boundary, not a violation of it. Restoring I am also not adding the cross-consumer mirror fixture. All three consumers depend directly on The digest P3 remains resolved by the exact projection-v2 golden. Please re-evaluate the NO-GO against the clarified contract rather than an implicit promise of v0.1.x derived-cache continuation. |
jackwener
left a comment
There was a problem hiding this comment.
Re-evaluated against 89fefaa82873c8c5e34ac604e2438bfb5bc8c4e0 and the clarified contract. I withdraw the [P1] and the cross-consumer [P2]. The [P3] is closed. No blocking findings remain.
Taking the three in turn, because two of them I was wrong about and one I was right about for a reason that no longer applies.
The [P1] — withdrawn
The measurement stands and the grading does not. A Session whose compacted prefix came from v0.1.x still overflows on a new route, and both the native compactor and the text fallback still receive the same raw span through the same model. What changed is that this is now a stated consequence of a declared boundary rather than an unstated one.
The distinction the response draws is the right one, and I had collapsed it: support is for canonical Session data, not for every derived cache format inherited from pre-Apache writers. RuntimeEvents remain the durable authority, superseding a derived checkpoint never deletes the original events, and Sessions stay readable and copyable. A derived cache that a later format no longer admits is a different kind of loss from history that cannot be read — and my grading treated them as the same kind.
The new Compatibility policy section is what makes this reviewable. It names v0.1.11 as the last writer of unmarked summaries, states that RC1's copy path could preserve that identity when cloning, and says outright that such a Session "may no longer fit a later model route and therefore may not be continuable, but its canonical history is retained." That is the sentence the description was missing when I raised the finding; with it present, whether the trade is worth making is a product decision and not something a review should hold.
One observation I would still put on the record, not as a finding: "the canonical history is readable" and "the Session can be continued" are different guarantees, and the gap between them is widest exactly when the original route is gone — an expired key, a retired model, a withdrawn provider. For those users the Session is effectively finished even though nothing was deleted. The updated description does acknowledge this. I mention it only so the decision is made with that case in view rather than the case where the original route is still available.
The cross-consumer [P2] — withdrawn
The objection is correct and I should have seen it. Comparing three deliberately different projections would establish a second chronology oracle, and it would still not enforce that a future consumer keeps calling the owner — a consumer that rebuilt a private grouping loop could satisfy a mirror fixture and would simply diverge somewhere the fixture does not look.
So the fixture I suggested does not do the job I asked it to do. The property worth enforcing is ownership, not agreement, and a test comparing outputs is the wrong instrument for it. Something structural — a lint rule, or an architecture check that fails on a private grouping loop in a consumer — would enforce what I was actually after. I am not asking for that in this PR; I am withdrawing the request I made.
The [P3] — closed
The golden digest is exactly the right fix, and the renamed test now describes what it pins. Worth noting why it genuinely closes the compatibility question rather than merely restating the old assertion: the test passing on this head establishes digest(items after the strip) === the golden, and the base type never carried invocationId at all — head adds it to all four variants — so the stripped items serialize to the same JSON the base produced. The golden therefore anchors the digest to the pre-upgrade value, and a strip that also removed toolCallId would now fail.
Everything else verified
The unified timeline is byte-equivalent where it matters: base against head, 1,000 stamped histories through the AI SDK request projection and 1,000 through the Codex compactor, zero serialized mismatches on both; the text summarizer's differences are the intended convergence on the primary client-tool chronology, with nothing lost or cross-paired and thinking still skipped as before.
The invocation-scoped IDs cut where intended: removing invocation from the identity fails the cross-invocation regression immediately, the restored head passes 6/6, and sequential same-invocation ID reuse stays correctly paired by occurrence.
Decode-level degradation is graceful — an unmarked checkpoint breaks neither Session nor ledger decode, copy does not fail, short Sessions replay intact, and the Codex fallback seam is genuinely reached.
ai-sdk-backend.ts measures 5137 lines on the merge base rather than 4884, and the net −67 is the whole PR rather than the split; the identifiable unique deletions are AgentRun/RuntimeKernel no longer sending StoredMessage context, and Codex and the summarizer dropping their private timeline builders. Minor, and only worth correcting if the description is edited again for another reason.
Approving. This is a refactor with a declared breaking change, so the merge decision is a human's.
简体中文
针对 89fefaa82873c8c5e34ac604e2438bfb5bc8c4e0 与澄清后的契约重新评估。我撤回 [P1] 与那条跨消费者 [P2];[P3] 已关闭。不再有阻塞性发现。
三条分别说,因为其中两条是我错了,另一条我当时是对的、但依据已不再适用。
[P1]——撤回
测量结果成立,定级不成立。 一个 compacted prefix 来自 v0.1.x 的 Session,在新路由上仍会 overflow,native compactor 与 text fallback 仍然通过同一个模型收到同一份 raw span。变化的是:它现在是一个「已声明边界的既述后果」,而不是一个未被说明的后果。
回复中所作的区分是对的,而我把它压扁了:支持的对象是 canonical Session data,而不是每一个继承自 pre-Apache writer 的 derived cache 格式。 RuntimeEvents 仍是持久权威,取代一个 derived checkpoint 从不删除原始事件,Session 仍可读、可复制。一个「较新格式不再接纳的派生缓存」,与「读不出来的历史」是两种不同性质的损失——而我的定级把它们当成了同一种。
新增的 Compatibility policy 一节正是让这件事变得可评审的东西。它点名 v0.1.11 是最后一个写出无标记摘要的版本,说明 RC1 的复制路径在克隆时可能保留该身份,并且直言这样的 Session 「可能不再适配后来的模型路由,因而可能无法继续,但其 canonical 历史被保留」。那正是我提出这条发现时描述里缺的那句话;有了它,这笔取舍值不值得,就是产品决定,而不是评审该拦的东西。
有一条我仍想记录在案,但不作为发现:「canonical 历史可读」与「这个 Session 能继续」是两种不同的保证,而两者之间的差距,恰恰在原路由已经消失时最大——过期的密钥、退役的模型、撤出的供应商。对那些用户来说,尽管什么都没被删除,这个 Session 事实上已经结束了。 更新后的描述确实承认了这一点。我提它,只是为了让这个决定是在看着那种情形做出的,而不是看着「原路由仍然可用」的情形。
跨消费者 [P2]——撤回
这个反驳是对的,而我本该自己看出来。 比较三个有意不同的投影会确立第二个时序 oracle,而且它仍然不能强制未来的消费者继续调用那个 owner——一个重建了私有分组循环的消费者,完全可以满足一个镜像 fixture,然后在 fixture 看不到的地方发散。
所以我建议的那个 fixture,做不到我要求它做的事。 真正值得强制的性质是「所有权」,而不是「一致」,而一个比较输出的测试对此是错的工具。结构性的东西——一条 lint 规则,或一项在消费者中出现私有分组循环时失败的架构检查——才能强制我真正想要的东西。我不在这个 PR 里要求它;我撤回我提出的那个要求。
[P3]——已关闭
黄金 digest 正是该做的修复,重命名后的测试现在描述的就是它所钉住的东西。值得说明它为什么真的关闭了兼容性问题、而不只是换个说法重述旧断言:测试在这个 head 上通过,确立了 digest(剥离后的 items) === 黄金值;而 base 上那个类型从来就没有 invocationId——是 head 给四个变体都加上的——所以剥离后的 items 序列化出的 JSON,与 base 产出的完全相同。 因此这个黄金值把 digest 锚定在了升级前的值上,而一个同时移除 toolCallId 的剥离,现在会失败。
其余全部验证通过
统一时间线在要紧处逐字节等价:base 对 head,1,000 组 stamped 历史走 AI SDK 请求投影、另 1,000 组走 Codex compactor,两边都是零序列化不匹配;文本 summarizer 的差异是有意收敛到主路径的 client-tool 时序,没有丢失或错配,thinking 仍如既往被跳过。
invocation 作用域化的 ID 只切在该切的地方:把 invocation 从身份中移除,跨 invocation 回归立即失败;恢复后的 head 6/6 通过;同一 invocation 内顺序重用的 ID 仍按出现次序正确配对。
解码层面的降级是优雅的——无标记 checkpoint 既不破坏 Session 也不破坏 ledger 解码,copy 不失败,短 Session 完整重放,Codex fallback 接缝确实被走到。
ai-sdk-backend.ts 在 merge base 上实测为 5137 行而非 4884,而净 −67 是整个 PR 而非拆分本身;可指认的独立删除是 AgentRun/RuntimeKernel 不再发送 StoredMessage context,以及 Codex 与 summarizer 各自删掉私有时间线构建器。次要问题,只在描述因别的原因再次编辑时顺手改即可。
批准。这是一次带已声明破坏性变更的重构,合并与否由人决定。
Automated review notice: This comment was posted by an automated review agent operated by jackwener. It is not an independent human review and does not replace one.
Generated-by: Codex
What this PR changes
This PR does two connected things:
The result is not a second replay system or a cosmetic file split. It is one history pipeline with smaller, named consumers.
In one picture
flowchart LR subgraph Before["Before: several history interpretations"] direction TB B1["RuntimeEvent"] B2["StoredMessage fallback"] B3["legacy_freeform checkpoint"] B4["ai-sdk-backend.ts<br/>history + turn + projection + telemetry"] B1 --> B4 B2 --> B4 B3 --> B4 end subgraph After["After: one history, explicit projections"] direction TB A1["RuntimeEvent<br/>single durable authority"] A2["model-history.ts<br/>chronology + causal pairing"] A3["AI SDK request"] A4["Text summarizer"] A5["Codex compactor"] A1 --> A2 A2 --> A3 A2 --> A4 A2 --> A5 endRead it from left to right:
RuntimeEventanswers what happened.model-history.tsowns in what order, including invocation-scoped tool call/result pairing and provider-step adjacency.StoredMessageremains a UI/import projection, but it is no longer a provider-history fallback.legacy_freeformcheckpoints are no longer accepted.Smaller ownership boundaries
The former 5,137-line
ai-sdk-backend.tsmixed session composition, one-turn execution, provider message construction, and request accounting. After this PR, the backend is 581 lines and each extracted module has one readable job:ai-sdk-backend.tsai-sdk-turn.tsai-sdk-message-projection.tsprovider-request-telemetry.tsmodel-history.tssession-recap.tsThis is an ownership reduction, not a claim that 4,556 lines of behavior disappeared. The PR is net -85 lines; the benefit is that the coordination surface is smaller and each semantic decision has one owner.
Behavioral boundaries
Compatibility policy
Canonical Session data is supported from the first Apache release candidate,
v0.2.0-incubating-rc1, onward. RuntimeEvents remain the durable authority: superseding a derived checkpoint never deletes the original messages or events, and Sessions remain readable and copyable from that canonical history.Unmarked free-form V2 summaries were generated by pre-Apache v0.1.x writers;
v0.1.11was the last such release. RC1 already stamped newly summarized text checkpoints withsummaryFormat: sections_v1, but its copy path could preserve the unmarked legacy identity when cloning a v0.1.x checkpoint. This PR intentionally stops admitting or copying that derived cache format. Consequently, a Session whose compacted prefix originated in v0.1.x — including a copy made by RC1 — may no longer fit a later model route and therefore may not be continuable, but its canonical history is retained. Keeping transparent continuation would require preservinglegacy_freeformas a second summary contract across types, load/repair, replay, and copy; this PR deliberately chooses one sectioned summary authority instead.Text checkpoints admitted by the current format must carry a valid
sections_v1summary. Derived checkpoint source-policy versions may still be superseded during prerelease development; the Runtime falls back to canonical RuntimeEvents and rebuilds from them.Verification
npm run formatnpm run lintnpm --workspace @maka/core run buildnpm --workspace @maka/storage run buildnpm --workspace @maka/runtime run buildnpm --workspace @maka/runtime run test:dist— 3,226 tests, 3,213 passed, 13 skipped, 0 failednpm --workspace @maka/runtime-host run buildnpm --workspace @maka/runtime-host run test:dist— 1,694 tests, 1,682 passed, 12 skipped, 0 failednpx tsc -p packages/runtime/tsconfig.json --noEmit --noUnusedLocals --noUnusedParameters— reports the existing repo-wide no-unused baseline on bothorigin/mainand this branch; no diagnostic points to the new split production modules,model-history.ts, orcontinuation-replay.tsAI use
Select exactly one:
Tool(s) and scope: Codex analyzed the history authorities, implemented the Runtime and test changes, split the AI SDK modules, and ran the listed verification.
Checklist
Does this PR entail a change in behavior?