Skip to content

feat(runtime-host): add session continuity - #1358

Merged
Astro-Han merged 3 commits into
apache:mainfrom
M4n5ter:feat/runtime-host-session-continuity
Jul 26, 2026
Merged

feat(runtime-host): add session continuity#1358
Astro-Han merged 3 commits into
apache:mainfrom
M4n5ter:feat/runtime-host-session-continuity

Conversation

@M4n5ter

@M4n5ter M4n5ter commented Jul 22, 2026

Copy link
Copy Markdown
Member
English

Context

This PR establishes Session continuity on top of the Host-owned Message authority in #1357. It is part of the Runtime Host Session-core migration tracked in #1167.

The Runtime Host remains non-serving in production, and this PR does not switch any Desktop, TUI, CLI, or Headless entrypoint.

What changes

  • Add a canonical Session projection over durable Session/Run state and the Host-owned Message queue.
  • Add connection-scoped subscription.open and subscription.close operations with an atomic snapshot/live barrier, per-subscription sequence numbers, bounded delivery, and slow-consumer isolation.
  • Add a Client async-iterator API whose lifecycle is tied to its connection and Host Epoch.
  • Feed canonical invalidation, transient assistant/tool events, permission waiting, hosted linked-child execution, root-Turn lifecycle changes, and terminal publication through one continuity boundary.

Design

  • The snapshot is canonical; live frames provide continuity, not durable replay. Opening a subscription reads one closed snapshot from durable Stores and Message authority, then publishes only events admitted after that barrier.
  • Canonical invalidation is coalesced without dropping an in-flight change. A refresh that becomes dirty performs one follow-up read; another change during that read schedules the next bounded pass.
  • Tool output preserves its existing event identity. Core admits at most 8,192 UTF-16 code units per output event. Continuity accepts that complete event in one frame, under a derived 24 KiB UTF-8 bound, instead of truncating the tail or inventing duplicate id/seq values through wire-only fragmentation.
  • Permission waiting is canonical state. Persisting a permission request schedules a projection refresh, so subscribers observe waiting_for_user without relying on an unrelated queue change.
  • Terminal publication has one canonical cut. Runtime terminal facts and the final Message queue fold are fenced so Clients cannot observe a terminal Turn paired with a stale queue projection.
  • Subscription failure remains local. A slow subscriber preserves its already-dispatched frame, discards queued backlog, and receives a contiguous terminal frame. The shared connection, sibling subscriptions, and ordinary requests remain available.
  • Reload is explicit. Sequence gaps, Host Epoch changes, malformed frames, and local queue overflow fail the subscription closed. The Client reloads a fresh canonical snapshot rather than pretending to resume an incomplete stream.

Evidence

Coverage exercises the snapshot/live barrier, in-flight refresh invalidation, permission waiting, exact terminal fencing, complete tool-output transfer through the real Pi producer and protocol decoder, hosted linked-child projection, pre-start interrupt, connection handoff, sequence/Epoch rejection, local slow-consumer isolation, and EOF cleanup.

Relevant Runtime Host and Runtime tests, workspace typechecks and builds, Biome checks, and git diff --check pass. Focused correctness review found no remaining P0-P2 issue in this slice.

Scope

This slice intentionally does not add a generic event bus, durable replay log, resume token, independent Session query API, Interaction ownership, archive/remove control, Automation/Goal continuity, production surface wiring, or the M4/M5 production cutover.

Part of #1167. Related to #853.

简体中文

背景

本 PR 在 #1357 的 Host-owned Message authority 之上建立 Session continuity,属于 #1167 跟踪的 Runtime Host Session core 迁移。

Runtime Host 仍未进入 production serving;本 PR 不切换 Desktop、TUI、CLI 或 Headless 的任何入口。

改动内容

  • 在持久化 Session/Run 状态与 Host-owned Message queue 之上建立 canonical Session projection。
  • 增加 connection-scoped subscription.opensubscription.close operation,提供原子 snapshot/live barrier、每订阅序号、有界投递和慢消费者隔离。
  • 增加生命周期绑定到 connection 与 Host Epoch 的 Client async-iterator API。
  • 让 canonical invalidation、瞬态 assistant/tool event、permission waiting、hosted linked child execution、root-Turn lifecycle 与 terminal publication 经过同一个 continuity boundary。

设计

  • snapshot 是 canonical fact;live frame 只提供连续观测,不承担持久化重放。 打开订阅时从 durable Store 与 Message authority 读取一个封闭快照,随后只发布 barrier 之后接纳的事件。
  • canonical invalidation 在合并请求时不会丢失 in-flight 期间的变化。 refresh 变脏后会追加一次读取;若第二次读取期间再次变化,则安排下一轮有界 pass。
  • tool output 保留既有事件身份。 Core 每个 output event 最多接纳 8,192 个 UTF-16 code unit;continuity 在推导出的 24 KiB UTF-8 上限内用一个 frame 传输完整事件,不截断尾部,也不通过 wire-only 分片制造重复 id/seq
  • permission waiting 属于 canonical state。 permission request 持久化后会触发 projection refresh,订阅者无需等待无关 queue 变化即可看到 waiting_for_user
  • terminal publication 只有一个 canonical cut。 Runtime terminal fact 与最终 Message queue fold 通过 fence 对齐,避免 Client 看到 terminal Turn 与过期 queue projection 的组合。
  • 订阅失败保持局部。 慢订阅会保留已经 dispatch 的 frame、丢弃尚未发送的 backlog,并收到连续序号的 terminal frame;共享 connection、兄弟订阅和普通 request 不受影响。
  • reload 是显式语义。 sequence gap、Host Epoch 变化、非法 frame 与本地 queue overflow 都会使订阅 fail closed;Client 会重新读取 canonical snapshot,而不是假装从不完整 stream 继续。

验证证据

覆盖 snapshot/live barrier、in-flight refresh invalidation、permission waiting、精确 terminal fence、经过真实 Pi producer 与协议 decoder 的完整 tool-output 传输、hosted linked-child projection、pre-start interrupt、connection handoff、sequence/Epoch 拒绝、局部慢消费者隔离及 EOF 清理。

相关 Runtime Host 与 Runtime 测试、workspace typecheck 与 build、Biome 检查及 git diff --check 均通过;定向正确性审查未发现本 slice 仍有 P0-P2 问题。

范围

本 slice 明确不加入 generic event bus、durable replay log、resume token、独立 Session query API、Interaction ownership、archive/remove control、Automation/Goal continuity、production surface wiring,或 M4/M5 production cutover。

属于 #1167;关联 #853

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The projection boundary, terminal fence, and sequence validation have useful focused coverage.

[P2] A transition to waiting_for_user is not published. Runtime records that status through run_status_changed, but RootTurnCoordinator only forwards text, thinking, and tool events to SessionContinuityCoordinator. The subscribed snapshot can therefore remain running while the runtime is waiting for permission, until a terminal transition or unrelated queue update forces another projection read.

[P2] When an activated subscription fills its queue while a send is in flight, #evictSlowSubscriber calls sink.close() at packages/runtime-host/src/server/session-continuity-coordinator.ts:464-466. ConnectionSession wires that method to #teardown() at packages/runtime-host/src/server/connection-session.ts:165-174, so one slow subscription closes every other subscription and in-flight request on the same connection. The current slow-subscriber test uses two separate connections and does not cover this case.

I also found two blocking issues and will leave them inline. I would hold this until those are fixed.

Comment thread packages/runtime-host/src/server/session-continuity-coordinator.ts Outdated
Comment thread packages/runtime-host/src/server/session-continuity-coordinator.ts Outdated
@M4n5ter

M4n5ter commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

@Astro-Han The two review-summary findings are also fixed in bced3670.

  • A persisted permission_request now schedules a canonical refresh, so subscribers observe waiting_for_user without waiting for an unrelated queue mutation.
  • Slow-consumer eviction is subscription-local. It preserves the frame already being sent, discards queued backlog, then sends a contiguous subscription.closed(slow_consumer) frame. It no longer tears down the shared connection, sibling subscriptions, or ordinary request/reply traffic.

The tests use the canonical Store projection for permission state and one real connection containing the blocked subscription, a sibling subscription, and normal status traffic. I also updated the PR body to record these decisions and the complete-event tool-output bound.

简体中文

Review summary 中的两项 finding 也已在 bced3670 中修复。

  • permission_request 持久化后会安排 canonical refresh,订阅者无需等待无关 queue mutation 即可观察到 waiting_for_user
  • 慢消费者驱逐现在只影响对应 subscription:保留已经发送中的 frame、丢弃排队 backlog,再发送连续序号的 subscription.closed(slow_consumer)。共享 connection、兄弟订阅与普通 request/reply 不再被关闭。

测试通过 canonical Store projection 验证 permission state,并在同一真实 connection 中同时覆盖受阻订阅、兄弟订阅和普通 status traffic。PR body 也已同步记录这些决策及完整事件的 tool-output bound。

@M4n5ter
M4n5ter force-pushed the feat/runtime-host-session-continuity branch from bced367 to c5a50f9 Compare July 26, 2026 02:08
@M4n5ter
M4n5ter force-pushed the feat/runtime-host-session-continuity branch from c5a50f9 to b0c2e84 Compare July 26, 2026 04:10
@M4n5ter
M4n5ter marked this pull request as ready for review July 26, 2026 04:10
@M4n5ter

M4n5ter commented Jul 26, 2026

Copy link
Copy Markdown
Member Author

@Astro-Han #1357 has landed, so I have rebased this branch onto the latest main and marked it ready for review. The three slice commits remain patch-equivalent under range-diff; the rebase did not change the continuity design or undo the earlier review fixes. The refreshed CI is also green.

简体中文

#1357 已合并,因此我已将本分支 rebase 到最新 main 并转为 Ready for review。三个 slice commit 经 range-diff 确认仍然 patch-equivalent;本次同步没有改变 continuity 设计,也没有覆盖此前的 review 修复。最新 CI 也已全部通过。

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. The previous review blockers are fixed: dirty refreshes are not dropped, legal tool output fits one frame, permission entry invalidates the projection, and slow-subscriber eviction stays local to that subscription.

Non-blocking follow-ups:

  • [P2] Permission-state projections do not converge on every transition. permission_request can refresh before the asynchronous Run status write completes, and permission_decision_ack does not refresh at all. A subscriber can retain a mixed or stale waiting_for_user snapshot until terminal.
  • [P2] Continuity accepts Pi messageId values that its wire decoder rejects. A provider ID such as provider:msg:1 can be emitted by the server but fail requireEntityId on the client, which currently closes the shared connection.
  • [P3] Canonical Turn snapshot logic is duplicated between canonical-turn-snapshot.ts and RootTurnCoordinator.
  • [P3] The execution handoff relies on production start() paths yielding through durable I/O before onRunStarted() re-enters the Session gate. Please make that contract explicit or cover it with a regression test.
  • [P3] session_removed exists in the protocol but has no server send path; a canonical read returning null leaves the old subscription alive.

None of these block this non-serving slice. CI is green, and I did not find a P0 or P1.

@Astro-Han
Astro-Han merged commit 7997a97 into apache:main Jul 26, 2026
3 checks passed
@M4n5ter
M4n5ter deleted the feat/runtime-host-session-continuity branch July 26, 2026 05:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants