feat(runtime-host): establish message authority foundation - #1357
Conversation
c581695 to
9da9f4b
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
The durable admission checks and recovery tests cover the main identity and crash-recovery paths.
[P2] turn.start reuses any existing admission whose content matches, but it never checks that existing.execution.kind is external_message in packages/runtime-host/src/server/root-turn-coordinator.ts:553-567. A client that submits the ID and content of a linked-child turn joins that hosted child and gets a successful start result, even though turn.start did not create or own that execution. Keep idempotency within the same execution kind and return a conflict for child admissions.
I also found one blocking issue and will leave it inline. I would hold this until a start that has not reached onRunStarted can be cancelled.
9da9f4b to
50bfe5f
Compare
Astro-Han
left a comment
There was a problem hiding this comment.
The overall direction looks sound, and I’m approving because the Runtime Host remains non-serving and I did not find a P0 or P1. I do think the following P2/P3 items should be addressed before production cutover.
Review findings:
-
[P2]
admitRootTurn()persists the admission before reserving its source-message proofs. If a second Turn reuses a source message ID, the call rejects but leaves the conflicting admission on disk, and subsequent recovery remains poisoned. The conflict test currently checks only the rejection; it should also verify that the failed admission is absent and recovery still succeeds. (packages/storage/src/agent-run-store.ts:226) -
[P2] This branch changes
ROOT_TURN_ADMISSION_SCHEMA_VERSIONfrom the currentmainvalue of 2 back to 1 while introducing another incompatible record shape. A v2 admission written by currentmaincannot be read after this change, and the old and new v1 formats become indistinguishable. Please use a new version or provide an explicit compatibility/migration path with a fixed-version upgrade fixture. (packages/storage/src/agent-run-store.ts:48) -
[P2] A stop committed before
onRunStartedstill cannot cancel setup that is blocked in the backend factory,run.begin(), or the first iterator step.deliverHostedRootStop()waits for the pending start to reach registration. The regression test manually releases the backend factory, so it proves that stop was invoked early, not that stop itself releases the pending setup. (packages/runtime-host/src/server/root-turn-coordinator.ts:973) -
[P2] If interrupt commits its queue fence and
deliverStop()then rejects, the in-memory receipt is removed without fail-stopping the Host. A retry seesstopRequestedand does not redeliver the stop; it only waits for a terminal result that may never arrive. (packages/runtime-host/src/server/message-coordinator.ts:707) -
[P2] Receipt-publication fail-stop is checked only when an operation enters. An operation that already passed that check and is waiting for the Session gate can still mutate the queue after another operation has failed receipt publication. Please recheck or make the fail-stop cut atomic with mutation, and add a test with an operation already waiting on the gate. (
packages/runtime-host/src/server/message-coordinator.ts:406) -
[P2] An already-aborted embedded child resume or provider retry still creates and advances its execution. The initial stop runs before the Runtime has registered an active or pending Run, so it is a no-op; the generator can then start the provider and tools. This changes the embedded behavior that this PR intends to preserve. (
packages/runtime/src/session-manager.ts:1952,:2254) -
[P2] An external follow-up racing an internal resume can turn a normal Session-busy conflict into a Host-Epoch drain. The resume passes the successor check,
executeRoot()finds the external follow-up active, andrunCommand()drains the Host on the resulting error. This should remain a scoped typed conflict. (packages/runtime-host/src/server/root-turn-coordinator.ts:371,:1073) -
[P2] The canonical Host
MessageContentomits the structuredQuoteRef[]now used by the Desktop and Runtime onmain. The Host protocol currently rejectsquotes, so production wiring would have to drop them or flatten them into text, losing model/UI provenance and recovery semantics. (packages/core/src/events.ts:81) -
[P2] Old-Epoch durable proofs establish payload and disposition, but not the original
queueRevision. Returning the current in-memory revision or 0 invents part of the result; if the exact result cannot be proven, the stated contract calls foroutcome_unknown, or the revision needs to be part of the durable proof. (packages/runtime-host/src/server/message-coordinator.ts:752) -
[P3]
MessageReceiptStore.commit()can successfully write a receipt that its own reader rejects. For example,payload: undefinedis omitted byJSON.stringify, whileread()requires the property. Canonicalizing and validating a detached JSON snapshot before the durable write would keep the returned value equal to the stored bytes. (packages/storage/src/message-receipt-store.ts:110) -
[P3]
decodeFollowupMessages()checks onlystate === "queued"and accepts a follow-up entry withplacement: "current_turn". The decoder should requireplacement: "next_turn"and include the inverse protocol test. (packages/runtime-host/src/protocol/message.ts:261)
The actual merge result against current main passes build, lint, format, Storage tests (475 passed, 1 skipped), Runtime Host tests (110 passed), and Runtime tests (2454 passed, 7 skipped). The test infrastructure is generally strong; the gaps above are mostly cases where a test asserts that a step occurred or an error was thrown without asserting the final durable behavior.
One non-blocking process suggestion: this PR now spans several independently reviewable authority boundaries—canonical message content, durable admission and source-message proofs, queue/receipt semantics, stop/interrupt delivery, and hosted child lifecycle. It would have been easier to validate as a small stack of vertical slices, with each PR owning one durable cut and its failure behavior. I would not require restructuring the current PR at this stage, but I recommend using those boundaries for the remaining Runtime Host work.
50bfe5f to
e64b899
Compare
e64b899 to
0f2c8b3
Compare
|
@Astro-Han I went through the review findings again against the real Storage, Runtime, and Host paths. The branch now handles them as follows:
The final audit also closed two adjacent failures inside this authority boundary:
Missing Sessions now map consistently from both File and SQLite stores to typed Host After rebasing onto the latest graph-execution changes, claimed graph execution now fails closed before claim lookup or durable mutation when a Hosted graph composition is absent. Full graph admission and exact stop routing remain a dedicated pre-M4 slice rather than being partially implied here. 简体中文我重新沿真实 Storage、Runtime 与 Host 路径核对了全部 review finding,当前处理如下:
最终审计还关闭了两个属于同一 authority boundary 的相邻问题:
File 与 SQLite Store 的 Session 缺失现在都会稳定映射为 Host typed Rebase 到最新 graph execution 改动后,如果 Hosted graph composition 尚未建立,claimed graph execution 会在读取 claim 或持久化 mutation 前 fail closed。完整 graph admission 与 exact stop routing 仍属于 M4 前的独立 slice,本 PR 不提供容易造成误解的部分支持。 |
Astro-Han
left a comment
There was a problem hiding this comment.
Approved. These are non-blocking follow-ups; the author can decide whether to address them here or separately.
- [P2] Commit the queue fence before waiting for start
turn.interrupt reaches claimStop while holding the shared SessionAdmissionGate. If the backend factory or run.begin stalls, the fence is not committed and later submit, retract, and interrupt calls for the session wait behind it. deliverRuntimeStop already waits for started, so the queue cut can happen before this await. A barrier test around a reserved but unbound Run would cover the case.
- [P2] Stop new leases after fail-stop
The queue mutation happens before receipt publication. If persisting the receipt fails, failStop returns an error, but pull can still lease the entry before shutdown installs a stop fence. The backend may execute a message whose submit call failed. Closing the consumer side when fail-stop is set, plus a receipt-failure-to-pull test, would close the window.
- [P2] Keep the recovered child prompt in Runtime history
Recovery writes the admission input to the Session message cache, then creates a failed Run with only a terminal RuntimeEvent. Child resume rebuilds history from RuntimeEvents and requires a user anchor. A pending initial child can become unresumable; a pending resume can skip the prompt accepted before the crash. The recovery path should preserve that user event, and the restart test should call the real resume path.
- [P2] Recheck the interrupt receipt inside the gate
A retry can read a stale miss while the first interrupt is publishing its success receipt. After the first call deletes the in-memory entry, the retry enters the gate, recomputes a different result, and can collide with the durable receipt. Rechecking under the gate, or retaining the in-memory owner through publication, keeps the same interruptId idempotent.
English
Context
This PR establishes the Host-owned Message authority slice for the Runtime Host Session-core migration tracked in #1167.
The Runtime Host remains non-serving in production. This PR does not switch any Desktop, TUI, CLI, or Headless entrypoint.
What changes
Canonical content and durable admission
MessageContentacross Core, Runtime, Storage, and the Host wire: model-facingtext, optional human-facingdisplayText, ordered attachment references, and ordered structured quotes. Attachment bytes remain outside this contract.v1). No migration path is added because no production Runtime Host state exists.Host-owned queue and control authority
turn.message.submit,queue.retract, andturn.interrupt, with exact inputs, outputs, typed failures, and bounded projections.HostMessageCoordinatorown Host-Epoch queue revision, steering/follow-up entries, Runtime leases, retry state, and residency.operation_conflict.outcome_unknown.Root and linked-child lifecycle
app_restarted; embedded execution remains unchanged.turn.startidempotency withinexternal_message; a matching linked-child admission remains owned by hosted execution and returnsoperation_conflict.run.begin(), or the first iterator step remains an immediate root-execution follow-up and is not claimed by this PR.Receipts, failure boundaries, and terminal handoff
not_foundresult at the real Host boundary.The in-memory queue is deliberately a current-Epoch authority structure, not a crash-durable replay journal. After process termination, immutable proofs resolve outcomes that can be proven; unproven work returns
outcome_unknownrather than being replayed speculatively.Evidence
The tests exercise real Storage and Host boundaries for conflicting source proofs, steering identity linearization, old-Epoch proof exactness, cross-connection retry and retract, SQLite missing-Session projection, linked-child ownership, queue/interrupt cuts, receipt publication failure, startup and successor-admission fail-stop, restart closure, and terminal late-input handoff.
Scope
This slice intentionally does not add complete pending-start cancellation, Hosted graph composition, continuity/query/subscription, Interaction ownership, automation/goal ownership, production surface wiring, or the M4/M5 production cutover. Those remain in their owning follow-up slices.
Part of #1167. Related to #853.
简体中文
背景
本 PR 建立 Runtime Host Session core 迁移所需的 Host-owned Message authority slice,整体由 #1167 跟踪。
Runtime Host 仍未进入 production serving;本 PR 不切换 Desktop、TUI、CLI 或 Headless 的任何入口。
改动内容
Canonical content 与 durable admission
MessageContent:面向模型的text、可选的面向用户的displayText、有序 attachment reference,以及有序的结构化 quote。附件字节本身不经过该契约。v1)。Runtime Host 尚无 production state,因此不增加没有实际对象的 migration。Host-owned queue 与控制 authority
turn.message.submit、queue.retract与turn.interrupt,具有精确 input/output、typed failure 与有界 projection。HostMessageCoordinator统一持有 Host Epoch 内的 queue revision、steering/follow-up entry、Runtime lease、retry state 与 residency。operation_conflict。outcome_unknown。Root 与 linked-child lifecycle
app_restarted;embedded execution 保持不变。turn.start的幂等范围限制在external_message;命中 linked-child admission 时,其 ownership 仍归 hosted execution,并返回operation_conflict。run.begin()或首个 iterator step 属于紧随其后的 root-execution follow-up,本 PR 不宣称已经交付。Receipt、失败边界与 terminal handoff
not_found。内存 queue 有意只作为当前 Epoch 的 authority structure,而不是 crash-durable replay journal。进程终止后,可由 immutable proof 证明的结果会被解析;无法证明的工作返回
outcome_unknown,不会被推测性重放。验证证据
测试通过真实 Storage 与 Host 边界覆盖 source-proof conflict、steering identity 线性化、old-Epoch proof exactness、跨 connection retry/retract、SQLite missing-Session projection、linked-child ownership、queue/interrupt cut、receipt publication failure、startup 与 successor-admission fail-stop、restart closure,以及 terminal late-input handoff。
范围
本 slice 明确不加入完整 pending-start cancellation、Hosted graph composition、continuity/query/subscription、Interaction ownership、automation/goal ownership、production surface wiring,或 M4/M5 production cutover。这些内容保留给各自所属的 follow-up slice。
属于 #1167;关联 #853。