Skip to content

feat(runtime-host): establish message authority foundation - #1357

Merged
Astro-Han merged 1 commit into
apache:mainfrom
M4n5ter:feat/runtime-host-message-authority
Jul 26, 2026
Merged

feat(runtime-host): establish message authority foundation#1357
Astro-Han merged 1 commit into
apache:mainfrom
M4n5ter:feat/runtime-host-message-authority

Conversation

@M4n5ter

@M4n5ter M4n5ter commented Jul 22, 2026

Copy link
Copy Markdown
Member
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

  • Define one canonical MessageContent across Core, Runtime, Storage, and the Host wire: model-facing text, optional human-facing displayText, ordered attachment references, and ordered structured quotes. Attachment bytes remain outside this contract.
  • Apply exact decoding, normalization, copying, and equality at protocol and durable boundaries, preserving model input, UI provenance, and retry identity.
  • Persist each root-Turn admission with its ordered source messages, placement, disposition, normalized aggregate, and execution identity.
  • Preflight every source-message proof before publishing the admission, so a conflicting identity cannot leave a poisoned admission behind.
  • Keep the admission record at the sole current schema version (v1). No migration path is added because no production Runtime Host state exists.

Host-owned queue and control authority

  • Add the closed operations turn.message.submit, queue.retract, and turn.interrupt, with exact inputs, outputs, typed failures, and bounded projections.
  • Make one HostMessageCoordinator own Host-Epoch queue revision, steering/follow-up entries, Runtime leases, retry state, and residency.
  • Same-Epoch retries are idempotent only for the same canonical payload. Reusing an identity with different content fails with operation_conflict.
  • Old-Epoch retries resolve only from immutable keyed durable proofs. If the exact result, including its revision, cannot be proven, they return outcome_unknown.
  • An idle submission starts exactly one root Turn. Active submissions become steering or follow-up entries; retract removes only entries that have not crossed the pull cut; interrupt commits one stop fence and returns the canonical terminal Turn result.
  • Preflight immutable steering identity before appending the Runtime ledger. Recovery alone repairs the narrow crash window between durable append and proof publication; the normal path does not scan Session history.

Root and linked-child lifecycle

  • Route hosted linked-child initial runs, resumes, provider retries, and stops through the same root admission and Message authority used by external roots.
  • Hold a per-child execution gate from preparation through terminal projection. Resume and retry share one successor rule, while an exact duplicate initial spawn joins the existing in-flight operation.
  • Recover a pending foreground child admission after restart by closing it canonically as app_restarted; embedded execution remains unchanged.
  • Keep external turn.start idempotency within external_message; a matching linked-child admission remains owned by hosted execution and returns operation_conflict.
  • Allow an exact stop to establish its queue fence before Run binding, so later Message effects cannot cross that cut. Active cancellation of a backend factory, run.begin(), or the first iterator step remains an immediate root-execution follow-up and is not claimed by this PR.
  • Reject claimed graph execution before claim lookup or durable side effects when a Hosted graph composition is absent. A dedicated pre-M4 slice will route graph activations through exact root admission and stop authority.

Receipts, failure boundaries, and terminal handoff

  • Store keyed root and steering proofs under the owning Session, and scope retry receipts to the Host Epoch.
  • Publish a queue mutation before its receipt so the observable cut matches the admitted effect. Concurrent equivalent retries share one in-flight operation.
  • If an accepted effect cannot be given its required proof or receipt, fail-stop the Host Epoch and retain owner residency until bounded Host termination rather than continuing with an ambiguous writer.
  • Preserve a confirmed queue entry or terminal transition when startup or successor admission fails; do not reclaim its root owner as though the operation were cleanly abandoned.
  • At terminal handoff, fold unpulled steering ahead of follow-up input, preflight the exact durable successor admission, and replace the previous root owner without an observable idle window.
  • Map missing Sessions from both File and SQLite stores to the same typed not_found result 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_unknown rather 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

  • 在 Core、Runtime、Storage 与 Host wire 之间定义唯一的 canonical MessageContent:面向模型的 text、可选的面向用户的 displayText、有序 attachment reference,以及有序的结构化 quote。附件字节本身不经过该契约。
  • 在协议与持久化边界使用严格解码、规范化、复制和相等性判断,保留模型输入、UI provenance 与 retry identity。
  • 每条 root-Turn admission 持久化其有序 source message、placement、disposition、规范化后的聚合输入与 execution identity。
  • 在发布 admission 前 preflight 全部 source-message proof,使冲突 identity 无法留下污染后续 recovery 的坏 admission。
  • Admission record 只保留当前唯一的 schema version(v1)。Runtime Host 尚无 production state,因此不增加没有实际对象的 migration。

Host-owned queue 与控制 authority

  • 增加封闭 operation:turn.message.submitqueue.retractturn.interrupt,具有精确 input/output、typed failure 与有界 projection。
  • 由一个 HostMessageCoordinator 统一持有 Host Epoch 内的 queue revision、steering/follow-up entry、Runtime lease、retry state 与 residency。
  • 同一 Epoch 的 retry 只有在 canonical payload 相同时才幂等;相同 identity 配合不同内容会返回 operation_conflict
  • 旧 Epoch retry 只能由不可变的 keyed durable proof 解析;无法证明包括 revision 在内的精确结果时返回 outcome_unknown
  • Idle submission 只启动一个 root Turn。Active submission 成为 steering 或 follow-up;retract 只移除尚未越过 pull cut 的 entry;interrupt 提交唯一 stop fence,并返回 canonical terminal Turn 结果。
  • 在追加 Runtime ledger 前 preflight immutable steering identity。只有 recovery 才修复 durable append 与 proof publication 之间的窄 crash window;正常路径不会扫描 Session 历史。

Root 与 linked-child lifecycle

  • 让 hosted linked child 的 initial run、resume、provider retry 与 stop 经由外部 root 使用的同一套 root admission 与 Message authority。
  • 从 preparation 到 terminal projection 持有 per-child execution gate。Resume 与 retry 共用一个 successor rule;完全相同的 initial spawn retry 会加入既有 in-flight operation。
  • Host 重启后,将 pending foreground child admission 规范地关闭为 app_restarted;embedded execution 保持不变。
  • 将外部 turn.start 的幂等范围限制在 external_message;命中 linked-child admission 时,其 ownership 仍归 hosted execution,并返回 operation_conflict
  • Exact stop 可以在 Run bind 前建立 queue fence,使之后的 Message effect 无法越过该 cut。主动取消 backend factory、run.begin() 或首个 iterator step 属于紧随其后的 root-execution follow-up,本 PR 不宣称已经交付。
  • Hosted graph composition 尚未建立时,在读取 claim 或产生持久化副作用前拒绝 claimed graph execution。M4 前的独立 slice 会让 graph activation 经由精确的 root admission 与 stop authority。

Receipt、失败边界与 terminal handoff

  • 在所属 Session 下保存 keyed root/steering proof,并让 retry receipt 受 Host Epoch 约束。
  • 先发布 queue mutation,再发布对应 receipt,使可观测 cut 与已接纳 effect 一致;并发且等价的 retry 共用一个 in-flight operation。
  • 如果已接纳 effect 无法取得所需 proof 或 receipt,则 fail-stop 当前 Host Epoch,并保留 owner residency 直到有界 Host termination;不让存在歧义的 writer 继续运行。
  • Startup 或 successor admission 失败时,保留已经确认的 queue entry 或 terminal transition,不把对应 root owner 当作正常 abandon 回收。
  • Terminal handoff 时,将尚未 pull 的 steering 折入 follow-up 前部,preflight 精确的 durable successor admission,并在不存在可观测 idle window 的情况下替换 previous root owner。
  • File 与 SQLite Store 的 Session 缺失都会在真实 Host 边界映射为同一个 typed 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

@M4n5ter
M4n5ter force-pushed the feat/runtime-host-message-authority branch 2 times, most recently from c581695 to 9da9f4b Compare July 23, 2026 16:41
@M4n5ter
M4n5ter marked this pull request as ready for review July 23, 2026 16:42

@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 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.

Comment thread packages/runtime-host/src/server/root-turn-coordinator.ts
@M4n5ter
M4n5ter force-pushed the feat/runtime-host-message-authority branch from 9da9f4b to 50bfe5f Compare July 24, 2026 07:39

@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 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_VERSION from the current main value of 2 back to 1 while introducing another incompatible record shape. A v2 admission written by current main cannot 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 onRunStarted still 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 sees stopRequested and 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, and runCommand() 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 MessageContent omits the structured QuoteRef[] now used by the Desktop and Runtime on main. The Host protocol currently rejects quotes, 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 for outcome_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: undefined is omitted by JSON.stringify, while read() 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 only state === "queued" and accepts a follow-up entry with placement: "current_turn". The decoder should require placement: "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.

@M4n5ter
M4n5ter force-pushed the feat/runtime-host-message-authority branch from 50bfe5f to e64b899 Compare July 26, 2026 01:12
@M4n5ter
M4n5ter force-pushed the feat/runtime-host-message-authority branch from e64b899 to 0f2c8b3 Compare July 26, 2026 01:52
@M4n5ter

M4n5ter commented Jul 26, 2026

Copy link
Copy Markdown
Member Author

@Astro-Han I went through the review findings again against the real Storage, Runtime, and Host paths. The branch now handles them as follows:

  • Source-message identity is preflighted before admission publication; a conflict leaves no bad admission behind.
  • The admission format is the sole current v1. No migration is added because there is no production Runtime Host state to preserve.
  • Complete pending-start cancellation is not claimed here. The misleading test and PR text were removed; it remains an immediate root-execution follow-up.
  • Stop-delivery failure and operations already waiting at the Session gate now cross the same fail-stop boundary.
  • Pre-aborted linked children do not start execution, and ordinary external/internal successor conflicts remain scoped rather than draining the Host.
  • Canonical content preserves ordered quotes, old-Epoch results require exact durable revision evidence, receipt writes use a canonical validated snapshot, and follow-up decoding requires next_turn.

The final audit also closed two adjacent failures inside this authority boundary:

  • Immutable steering identity is checked before Runtime-ledger mutation and linearized per Session; only explicit recovery scans history to repair the append/proof crash window.
  • Startup or successor-admission failure cannot abandon a root that already owns a confirmed Message effect. The Host fail-stops while retaining owner residency until bounded termination.

Missing Sessions now map consistently from both File and SQLite stores to typed Host not_found, and the dependency remains behind the existing @maka/storage/execution-stores narrow entrypoint.

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,当前处理如下:

  • Source-message identity 会在 admission 发布前完成 preflight;冲突不会留下坏 admission。
  • Admission format 只保留当前唯一的 v1。Runtime Host 尚无 production state,因此不增加没有实际兼容对象的 migration。
  • 本 PR 不再宣称完整 pending-start cancellation。误导性的测试与 PR 文案已移除;该能力保留为紧随其后的 root-execution follow-up。
  • Stop delivery failure 与已经在 Session gate 内等待的 operation 现在进入同一 fail-stop 边界。
  • 已 abort 的 linked child 不会启动 execution;普通 external/internal successor conflict 保持局部错误,不再触发 Host drain。
  • Canonical content 保留有序 quote;old-Epoch result 必须有包含 revision 的精确 durable evidence;receipt 写入使用规范化并验证过的 snapshot;follow-up decoder 强制要求 next_turn

最终审计还关闭了两个属于同一 authority boundary 的相邻问题:

  • Immutable steering identity 会在 Runtime ledger mutation 前检查,并按 Session 线性化;只有显式 recovery 才扫描历史,修复 append/proof 之间的 crash window。
  • Startup 或 successor-admission 失败时,已经持有 confirmed Message effect 的 root 不会被 abandon。Host 会 fail-stop,并保留 owner residency 直到有界 termination。

File 与 SQLite Store 的 Session 缺失现在都会稳定映射为 Host typed not_found,依赖也保持在既有的 @maka/storage/execution-stores 窄入口之后。

Rebase 到最新 graph execution 改动后,如果 Hosted graph composition 尚未建立,claimed graph execution 会在读取 claim 或持久化 mutation 前 fail closed。完整 graph admission 与 exact stop routing 仍属于 M4 前的独立 slice,本 PR 不提供容易造成误解的部分支持。

@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. These are non-blocking follow-ups; the author can decide whether to address them here or separately.

  1. [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.

  1. [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.

  1. [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.

  1. [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.

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