refactor(orchestration): resolve every caller to one orchestration actor - #22555
brennanb2025 wants to merge 11 commits into
Conversation
There was a problem hiding this comment.
ℹ️ No critical issues — minor suggestions inline.
Reviewed changes
The host-side half of the structured-chat orchestration stack (stacked on #22522), reviewed at bbf398d6 against 9d8e5734. Nothing sends the new field yet.
- One resolver at both dispatch entries.
orchestration-session-caller.tsresolvesorchestrationCompatibilityEvidence.agentSessionIdonce, after the migration fence and before params parse / the unary-streaming split, inRpcDispatcher.dispatchandRpcStreamingDispatcher.dispatch. It refuses the paired route and SSH/WSL stamps, validates the Orca id (naming it when handed a provider id), requires a live lease, maps a structured worker's session to its minted handle and pane, then rebinds the declared caller and reduces the evidence to{ agentSessionId }. - Every caller becomes one
OrchestrationCallerIdentity(address/terminalHandle/paneKey/actor) rather than a bare pane key. Run lookup moves fromgetCurrentRunForPanetogetCurrentRunForCoordinatorin run-create/use/current, check, send, group send, worker-start and run-scope. - Run binding and mail routing become actor-aware.
coordinator_actor/assignee_actor/creator_actorare written on bind/create/assign/claim/authority attach; the stale-actor "handle wins" rule governs matching;activeDispatchOwnsAddressSqlwidens the three ownership matchers. - Receipts fold the resolved actor into the payload hash, leaving terminal hashes byte-identical.
- Tests: three new suites (74 tests), a shared fixture, targeted updates to the #22522 suites, and 34 documented ablations.
I traced the actor binding, the stale-actor rule, the two new mail-routing branches and the receipt hash myself, and confirmed the two partial indexes behind INDEXED BY idx_dispatch_assignee_actor / idx_messages_undelivered_direct_run are eligible and hit (reproduced against SQLite). I could not construct a sequence that misroutes mail or takes over a Run without a fence.
ℹ️ The session id is a broader credential than the handle and pane key it replaces
The resolver's only checks on agentSessionId are format, local non-WSL host, a live lease, and existence in the local store — no token, signature, fence or per-call nonce. For a structured worker it then injects that worker's random handle and paneKey. Those two were made random on purpose because the session id is not secret, so resolving the id into them is strictly more permissive than the terminal path this PR preserves. This looks like the intended same-user threat model, but it is worth an explicit confirmation before the next PR makes the field reachable.
Technical details
# Session id as a bearer credential
## Where the id is accepted
- `src/main/runtime/rpc/orchestration-session-caller.ts:82-119` — format (`sessionOrchestrationActor`), local host, live lease (`agentSessionLeaseAdmitsWriter`), store hit. For a structured worker the resolved caller then carries the worker's random `handle`/`paneKey` (`:110-118`).
## Where a same-host non-owner can read the id
- `orchestration.dispatchShow` returns the raw dispatch row (`src/main/runtime/rpc/methods/orchestration/runs/dispatch-methods.ts:195,218` — `db.getDispatchContext`, a `SELECT *`), now including `assignee_actor = session:<id>`; `orchestration.workerShow` / `worker-list` publish `processIncarnation = structured:<id>` (`worker-observation.ts:196`, `worker-list-projection.ts:39`), and `sessionIdFromStructuredWorkerIncarnation` strips the prefix. Neither handler consults caller identity.
- Tab ids embed the id verbatim (`structuredAgentSessionTabId`, `src/shared/structured-agent-session-projection.ts:220`); the tree already notes this exposure (`src/main/runtime/structured-worker-identity.ts:13,61-69`).
- `<userData>/orchestration.db` and `agent-sessions.json` are `0600`, which does not separate same-UID processes.
## Required outcome
- A conscious answer, not a code change: either "any same-host process may act as any live session" (a design-doc note suffices), or the resolver should carry the same per-call attestation terminal callers still obtain.
## Open questions for the human
- Is the same-user, session-id-as-identity model the intended end state, or an artifact of porting from #19949 with the credential rule dropped?ℹ️ Nitpicks
bindDeclaredCallerbuildsnamesincludingcaller.terminalHandle, which isnullfor a handle-less chat, so{ from: null }passes the fence and is then overwritten by the address (orchestration-session-caller.ts:216-224). Harmless, but filtering nulls would make the fence literally mean "the declared caller must name this session".
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
| export function claimsOrchestrationSession(request: RpcRequest): boolean { | ||
| return ( | ||
| request.method.startsWith('orchestration.') && | ||
| request.orchestrationCompatibilityEvidence?.agentSessionId !== undefined |
There was a problem hiding this comment.
agentSessionId is client-controlled wire data and is not schema-validated (only params go through parseRpcRequestParams), so !== undefined makes a null/''/0/false field count as a session claim: every orchestration.* method is then refused with session_caller_unknown before the handler runs. A client that serializes an unset optional as null (env.ORCA_AGENT_SESSION_ID ?? null) would break all orchestration verbs once the next PR wires injection. Gate on a non-empty string, matching the resolver's own typeof claimed === 'string' check.
| request.orchestrationCompatibilityEvidence?.agentSessionId !== undefined | |
| typeof request.orchestrationCompatibilityEvidence?.agentSessionId === 'string' && | |
| request.orchestrationCompatibilityEvidence.agentSessionId.length > 0 |
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Re-reviewed the delta since the prior pullfrog review at bbf398d6 — one commit, 22f54f28, on the second PR of the structured-chat orchestration stack.
- Every-open repair of the unbind residue.
backfillStructuredWorkerActorsnow clearscoordinator_actoron handle-less, pane-less Runs whose actor this host recorded as a structured worker, leaving a chat coordinator's binding intact. - Refuse a worker session whose identity is gone. The resolver throws
session_caller_not_livewhen a session has a recorded worker Dispatch but no resolvable worker identity, so it can never bind handle-less and split one worker into two identities. - New coverage. The residue is shown matching as the worker session's binding before repair and cleared after (never the chat's); a
runCreatefor the abandoned worker session is refused with no Run written.
Both changes are scoped to the only current-binary shape that produces this residue (unbindOtherRunsForCoordinator clears all three columns; adoptLegacyRunIfNeeded clears handle/pane without an actor), and the new tests fail without their fixes. structured-worker-actor-backfill.test.ts passes 5/5 locally.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
22f54f2 to
3ace023
Compare
f5aee23 to
f06e716
Compare
f771efd to
6746bb5
Compare
There was a problem hiding this comment.
✅ No new issues found. All substantive feedback from the prior reviews is addressed; the one open inline suggestion is unchanged and left for the author.
Reviewed changes
Re-reviewed the delta since the prior pullfrog review at 22f54f28 — the three architecture-review commits on the second PR of the structured-chat orchestration stack (f771efd6..6746bb52a4).
- A Run's coordinator actor now counts only at its write generation.
currentRunCoordinatorActor/currentRunCoordinatorActorSqlgate the actor oncoordinator_actor_generation === consumer_generation;createRun,bindRun(rebind and same-coordinator correction) andunbindOtherRunsForCoordinatorwrite the matching generation, andrunBoundToCoordinatormatches through it. D1's "actor beside its own handle" rule, the open-time unbind-repair and their tests are deleted in favour of the one rule; the resolver's worker-identity refusal moves next to the worker lookup. - One owner for a party's addresses.
addressSpellingsOfandrunCoordinatorKeyreplace the hand-listed handle+actor pairs increateRun,bindRun,unbindOtherRunsForCoordinatorand the declared-caller fence;isRecordedStructuredWorkerSessionreplaces the backfill-local worker probe. - Wording and comment fixes. A released lease now reads "is not running right now" (wakeable) instead of "has ended"; the pane-key comment scopes its credential claim to requests that name no session.
- Tests. New generation/stale-actor cases in
run-coordinator-actor-binding.test.ts, a same-coordinator correction case, the both-addresses reroute cases, and the declared-self cases inorchestration-session-caller.test.ts; the migration fixture restores main's handle-only routing trigger.
I traced the generation rule end to end: every writer on runs.consumer_generation either sets coordinator_actor_generation in the same statement or leaves the actor counting only when the generations agree; the one raw-actor read (RUNS_BOUND_TO_COORDINATOR_SQL) is a candidate prefilter behind the generation-aware JS predicate; no reader re-persists a stale actor; and a same-session rebind is not wrongly fenced or over-bumped. run-coordinator-actor-binding, run-coordinator-actor-address, structured-worker-actor-backfill and orchestration-actor-column-migration pass 32/32 locally.
ℹ️ The historical mailbox-owner cache is generation-blind, so a multi-Run handle-less coordinator can be unaddressable
run_coordinator_handles records every address a party ever coordinated under, and nothing prunes it. The generation rule stops a stale actor from matching as the coordinator, but the mailbox-owner path (getRunMailboxOwnerIdsForHandle, the routing trigger, and recipient-routing.ts) never consults consumer_generation. Actors newly populate that cache via createRun/bindRun, so a live handle-less coordinator with more than one past Run now resolves to multiple owners; selectHistoricalRun returns undefined and a sender with no Run of its own gets recipient_ambiguous at that coordinator's session:<id> address. The PR documents the "former coordinator's address stays remembered" half as intended, and this mirrors existing terminal-handle semantics, but the multi-owner side effect is untested.
Technical details
# Generation-blind historical address cache
## Affected sites
- `src/main/runtime/orchestration/db/runs/run-lookup.ts:55-79` — `getRunMailboxOwnerIdsForHandle` joins `run_coordinator_handles -> runs` with `runs.legacy = 0`, no generation predicate.
- `src/main/runtime/orchestration/db/runs/run-coordinator-mail-routing.ts:41-67` — the INSERT trigger matches the cached address by string equality only.
- `src/main/runtime/rpc/methods/orchestration/messaging/recipient-routing.ts:79-86` and `selectHistoricalRun:128-139` — multiple owners with no explicit/sender Run becomes `recipient_ambiguous`.
- `src/main/runtime/orchestration/db/runs/run-create.ts:35-37` and `run-binding.ts:124-131` — the delta is what puts actor addresses into the cache.
- `src/main/runtime/orchestration/db/runs/run-coordinator-actor-address.test.ts:155-157` — pins the intended "stays remembered" half.
## Required outcome
- A conscious call, not necessarily a code change: either accept the ambiguity (a design note plus a test pinning the multi-Run outcome), or prune/replace the prior owner row for a party when a new Run supersedes it.
## Open questions for the human
- Should `getRunMailboxOwnerIdsForHandle` return only the Run a party currently coordinates once it has one, so a live coordinator is not ambiguous at its own address?DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
…ry and bind it by actor WIP: entry resolver on both dispatchers, caller identity through run scope, actor-keyed Run binding and unbind, actor writes on bind/create/assign, and actor-aware mail ownership exclusions.
…nce and actor mail ownership Keeps the non-session dispatch path synchronous so terminal and session-tab streams reach their handler without an extra async hop.
…pin every verb on both routes A session caller need not name itself in a param that requires a caller: the entry binds the declared caller to the session before the schema runs. Session refusal codes pass through the RPC error map, and DB row reads added here carry their SAFETY rationale.
…t binding without a caller param Drops the actor clause from self-dispatch detection: a creator and assignee can only share an actor when they already share a handle or pane.
…s and the assignee-only mail sweep
…ves, and refuse a worker without its identity An older binary unbinds a structured worker's Run by clearing handle and pane, which leaves the actor looking like a handle-less chat binding. The every-open repair clears that shape for actors recorded as structured workers only, and the resolver refuses a worker session whose worker identity is gone, so this binary never writes the shape itself.
…eneration, and give a party's addresses one owner The coordinator actor now counts only at the consumer generation it was written at, so a Run binding matches a session by that rule alone. It replaces two mechanisms for the same fact: the rule that an actor beside a handle it did not bind with never matches, and the open-time repair that cleared a structured worker's actor an older binary's unbind left. Every write of an older binary that rebinds or unbinds bumps the generation, so both shapes stop counting by themselves, including a chat's actor after a rebind then an unbind, which neither old mechanism caught. createRun and the same-coordinator actor correction write the generation in the statement that writes the actor. The resolver still refuses a structured worker whose worker identity is gone; its predicate moves next to the worker identity lookup. addressSpellingsOf is the one owner of the addresses a party is reachable at (a structured worker's handle and session actor). createRun, bindRun, the coordinator unbind and the declared-caller check use it instead of hand-built sets, and each has a test at both of a worker's addresses.
…the pane-key credential claim to requests without a session A released lease is evicted, not ended: a user turn resumes the session, so the refusal now says it is not running right now instead of that it has ended. The worker pane-key comment claimed the random leaf is what stops anyone who learns a session id from acting as the worker. On the same-host socket route the session id now names the worker with no token by design; the pane key still matters where a request names no session (a PTY agent's, or the paired-client route, which refuses session ids).
…ration write on a row an older binary wrote
b183a5c to
19218ab
Compare
6746bb5 to
8dac986
Compare

ELI5
Orchestration only knows how to recognize a terminal. A native chat has no terminal, so it cannot create or use a Run. This PR teaches the Orca host to recognize a chat by the session id Orca gave it. The host checks that id once, where every request comes in, turns it into the chat's orchestration identity (
session:<id>), and every orchestration command then works with that identity. Nothing sends the id yet: #22568, the next PR in this stack, makes the CLI send it. Terminal agents behave exactly as before.Merge order
Second PR of the structured-chat orchestration stack: #22522 (actor columns) → this PR → #22568 (the CLI sends the id) → result delivery to a session coordinator (not open yet) → telling agents their own address (not open yet). #22522 must merge before this PR.
What Changed
Second PR of the structured-chat orchestration stack, stacked on #22522. Host side only: no CLI or UI change.
Wire contract (what #22568 sends)
orchestrationCompatibilityEvidence.agentSessionId: string, the Orca-minted id from the caller's injected environment. Every orchestration call already carries this envelope on both transports.from,terminalorcallerTerminalHandlethat is present must name this same session:session:<id>, the bare<id>, or a structured worker's ownstructworker_handle. Any other value is refused.inbox --terminal,workerTerminalUserInput,federationAttachStart,runShow,dispatchShow, the retiredrun), the field is not fenced. A coordinator must be able to reach its worker's terminal.src/shared/orchestration-session-caller-codes.ts, passed through the RPC error map:session_caller_host_boundarysession_caller_unknownterm_handle included)<id>exists on this host."session_caller_provider_id/clear<id>is the provider's own session id… This session's Orca id is<orca id>; use that instead."data.orcaSessionIdcarries the Orca id.session_caller_not_live<id>is not running right now… / is switching between chat and terminal view… / has no live owner… / is a structured worker whose worker identity this host no longer has…"consumer_fenced(existing)check --terminal-pane-keynames someone else<id>and cannot act as<declared>."All of them carry
{ effectsApplied: false }.session_caller_not_liveis one code for ended, mid-handoff, ownerless and lost-worker-identity; the message says which, and whether to retry. No client branches on the distinction today; if one ever must, add adatafield, not a new code.One resolver, at the entry of both dispatchers (
src/main/runtime/rpc/orchestration-session-caller.ts)RpcDispatcher.dispatch(the Unix socket and Electron IPC) andRpcStreamingDispatcher.dispatch(the paired WebSocket, and IPC subscriptions) both call it:claimsOrchestrationSessionis a synchronous check, so terminal callers reach their method exactly as before, with no extra async hop.agentSessionLeaseAdmitsWriter) under either owner,nativeortui, on this host outside WSL;ctx.orchestrationCaller, anOrchestrationSessionCaller={ actor: 'session:<id>', sessionId, address, terminalHandle, paneKey, workspaceId }.{ agentSessionId }, so terminal evidence a terminal view inherits never attests anyone.The caller-param map is the verb contract.
ORCHESTRATION_CALLER_PARAMlists the 15 RPC methods that consult caller identity, and the param each uses:from: runCreate, runUse, runCurrent, send, reply, ask, dispatch, gateCreate, gateResolve, gateList, workerStartterminal: checkcallerTerminalHandle: taskCreate, taskList, taskUpdatePopulation, pinned by a test:
run(retired),runShow,dispatchShow,inbox,federationAttachStart,workerTerminalUserInput.reset,workerRelease,workerRetain,workerRead,workerShow,workerList,workerStopandworkerAbandon.Methods never inspect the actor's kind. They pass the caller identity (
OrchestrationCallerIdentity: address, terminal handle, pane key, actor) to the lookups. A plain terminal caller's identity has no actor, so every lookup takes the same pane path, and the same statement, as before. A structured worker calling by itsstructworker_handle without a session claim now carries its actor too. Its lookups run the combined pane-or-actor statement, which returns the same rows, and it keeps its Run if its pane lookup fails.resolveOrchestrationCallerandresolveRunScopereturn and use the identity. They take the resolved session from the context.getCurrentRunForCoordinator(identity): run-create/use/current, check, send, group send, and worker-start.--worktree currentplaces the worker in the session record's workspace.Binding a Run by actor (
db/runs/)runsBoundToCoordinator/getCurrentRunForCoordinator/unbindOtherRunsForCoordinatorgeneralize the pane-keyed lookups. A caller without an actor runs the originalrunsBoundToPanequery unchanged.consumer_generationit was written at (currentRunCoordinatorActor, from refactor(orchestration): give structured sessions an orchestration actor column #22522).createRun,bindRunand the same-coordinator correction writecoordinator_actor_generationin the statement that writes the actor.consumer_generationwhen it rebinds or unbinds a Run. So an actor such a write leaves behind stops counting, beside another handle or beside none, with nothing needing to clear it.consumer_generationas the pane path does. Session X'srun-createnever unbinds session Y's Run.check --runand any waiting check fail asconsumer_fenced;#22522's four obligations
Actors on every bind, create and assign, structured workers included.
createRunandbindRunwritecoordinator_actor.assignee_actorderived from astructured:<id>process incarnation: the claim insert, worker-start authority attach, and the failed-start identity copy. Anything else gets NULL, so a reassignment never leaves the previous actor. This subsumes refactor(orchestration): give structured sessions an orchestration actor column #22522's defensive clear.creator_actor. A handle-less session creates as a newDispatchCreatorkindactor; a structured worker creates as a terminal with its actor.isSelfCreatedDispatchcompares handle and pane only, by design: an assignee actor is always recorded beside a worker handle, and an actor-kind creator has neither, so actors alone can never make creator == assignee. The delivery PR (not open yet) extends it before any dispatch is assigned to a handle-less session.Actor-aware mail matchers. All three readers that match "this address is an active assignee in this Run" now also match
assignee_actor, through one SQL fragmentactiveDispatchOwnsAddressSql:routeAllUnreadDirectMessagesToRunMailboxand the paged direct-mail router;routeForeignDirectMessagesToOwnedMailboxes, which gains an actor branch, with its owner lookup.For a terminal handle the SQL is unchanged. The trigger is rebuilt on every open, so no migration is needed. Unbind and rebind reroute unread mail for both the handle and the actor.
A structured worker that coordinates is reachable at its
session:address too.createRunandbindRunremember both its handle and its actor in the coordinator-address cache.addressSpellingsOfis the one owner of a party's addresses;createRun,bindRun, the coordinator unbind and the declared-caller check all take it. Everyone is toldsession:<id>is their public address, so mail sent there must reach the Run.Stale actors from an older binary: the generation rule above.
Receipts.
replayStableCallerParamsfolds the resolved actor into the payload hash, so a retry from the same session replays and the same request id from another session isrequest_mismatch. This holds even for methods that carry no caller param. Terminal callers' hashes are unchanged.A structured worker carries both a handle and (after #22568) a session id. The session id wins for every verb, and the host maps it to the worker's minted handle and pane. The worker's address, dispatch mailbox and Run bindings are therefore exactly what they are today through the handle.
Why
This supersedes #19949, which is closed in its favor, and absorbs the purpose of #16859 (coordinator takeover).
Ported from #19949:
agentSessionLeaseAdmitsWriter: live, reconciled, no handoff in progress, a proven owner process);Dropped from #19949:
agentSessionId/runtimeFenceparams;structuredWorkerRecordIsCurrent), which would have refused a chat in terminal view.#16859's purpose: main already solved coordinator takeover with caller-evidence attestation, and its authority columns never landed. This PR makes that mechanism hold for session actors:
Design decisions a reviewer should check:
runCreate/runUse/runCurrent/workerStartrequirefromin their schemas. Binding the declared caller before the schema runs keeps every schema unchanged.claimStatusis the verdict.Architecture review
An architecture review asked for three changes. #22522 also changed, and this PR now builds on that change. All four are at head
6746bb52a4, rebased onto #22522'sb183a5c294.One mechanism for stale actors. This PR used to guard stale actors three ways: the handle-wins rule when reading, an open-time repair for a structured worker's unbind leftover, and the resolver refusal. #22522 now records the generation each coordinator actor was written at, and an actor counts only at that generation. This PR reads the actor only through that rule, and both the handle-wins rule and the repair are deleted. The generation also covers the one case neither old rule caught: an older binary rebinds a chat's Run to a terminal and then unbinds it, which leaves a row that looks exactly like a live chat binding (tested). The resolver refusal stays, because it covers something else: it stops this binary from giving one worker a second identity. Its check moved from the backfill module to
structured-worker-authority.ts, next to the worker identity lookup.One owner for a worker's addresses. A structured worker is reachable at two addresses: its
structworker_handle andsession:<id>. Each place that needed both used to list them by hand.addressSpellingsOfis now the only owner, andrunCoordinatorKeygives a Run's current coordinator, leaving out an actor that no longer counts.createRun,bindRun, the coordinator unbind and the declared-caller check use them. Each of those has a test at both of a worker's addresses, and each test turns red when that one use goes back to handles only. Rule for the rest of the stack: any new code that compares an agent address needs a test at both of a worker's addresses. Difference from the usual pattern, in Orca terms: one agent still has two stored addresses rather than one. That is kept on purpose so PTY workers keep working; the shared owner is what keeps it safe.Released is not ended. A released lease is refused with "is not running right now. A new message or user turn revives it; retry then." (it used to say "has ended"). The text matches #22631, which uses the same words. The "new message" wake-up arrives with #22631; in this PR alone, a user turn resumes the session.
The pane-key credential comment.
mintStructuredWorkerPaneKey's comment said the random pane key is what stops anyone who learns a session id from acting as the worker. That is still true for a request that names no session: a PTY agent's, or any request from a paired client, which refuses session ids. On the same-host socket route, the session id already names the worker with no token, and that is intended. The comment now says so and adds that the pane key must not be made into a credential there.Ablations at this head, each red: reading the raw actor (4 tests, all older-binary cases); no generation from
createRun(14); none frombindRun(2); none from the same-coordinator correction (1); handles only inbindRun(2), the unbind (2),createRun(2) and the declared-caller check (1); the old "has ended" text (1); the resolver refusal removed (1). The tests: 81 in the three files above, and 2,234 across the orchestration, RPC, SSH, CLI and cross-version suites (259 files; 10 skipped).tc:node,tc:cli,tc:web, the fulloxlint, the changed-code gate andaudit:anti-slopall pass. The ablation and suite tables above are from the earlier head.Linked Issue
None — part of the structured chat status/orchestration program. Supersedes #19949; absorbs #16859's purpose. Stacked on #22522.
Visual Proof
N/A — host-side orchestration only. Nothing sends a session id until #22568, and no UI changed.
Testing
New tests (3 test files and a shared fixture, 75 tests, plus 2 in the actor-repair test):
orchestration-session-caller.test.ts(47 tests) covers:session_caller_host_boundaryand no Run is written. On the local route it resolves as the session: no session refusal, and inherited terminal evidence never reaches attestation.OrcaRuntimeRpcServer.handleWebSocketMessagewith a paired device refuses the claim;handleMessageover the Unix-socket path admits it.term_handle, provider id (the message names the Orca id), released, mid-handoff, unreconciled, a record on another host, SSH and WSL stamps, an unverifiable host, and a structured worker whose identity is gone (no Run written). Each is asserted through a mailbox-consumingcheckand a destructivereset, with the pending mail still unread afterwards.--fromnaming someone else and acheck --terminal-pane-keyare refused.session:<id>and the bare id are accepted.orchestration-session-coordinator.test.ts(15 tests) covers:session:<id>landing in the Run mailbox, a consuming check, send, reply, gates, ask;check --runand its in-flight waiting check;session:address or its bare id, all binding by its handle;request_mismatchfrom another, includingreset, which names no caller;run-coordinator-actor-binding.test.ts(13 tests) covers the DB layer:The existing tests were updated for decided changes:
The two open-time repair tests were removed with the repair (Architecture review).
Ablations: 37 mechanisms, each red. Each deletes or neutralizes one mechanism in source that is identical to the final head (only tests were added after the runs). Each runs the three new files, 71–74 tests, then restores from HEAD.
checkpane key not checkedresetjoined across sessions)6746bb52a4checkkeeps the pane-only guard6746bb52a46746bb52a4The first two ablations of the foreign sweep and of send's sender Run were green. Tests isolating each were added, and both then went red.
Suites at this head (
env -u ORCA_STRUCTURED_SESSION npx vitest run --config config/vitest.config.ts …):src/main/runtime/orchestration,src/main/runtime/rpc, the 66 other test files that import the orchestration DB or the dispatcher, the CLI runtime and orchestration identity tests, andorca-runtime.test.ts.tests/e2e/cross-version-wire/orchestration-delivery-downgrade.unit.test.tstimes out materializing a release checkout. It fails identically at refactor(orchestration): give structured sessions an orchestration actor column #22522's head in this environment.pnpm tc:node,tc:cliandtc:webare clean.oxlintandoxfmt --checkon the 51 changed files are clean.check-changed-code-quality: 0 findings. The new DB row reads carry theSAFETYrationale the other row casts indb/use.audit:anti-slopis clean.pnpm-lock.yamlis absent from the range; no documentation files are added.AI Disclosure
Review
consumer_generationon a Run except a rebind or unbind (true of every writer today). If something else did, a valid actor would stop counting; a stale one would never count.Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
REMOTE_CONTEXT_ENV_VARS).agentSessionIdis a new optional envelope field. The four new codes reach only a caller that sends a claim. Dispatch rows now carry non-NULL actor values; refactor(orchestration): give structured sessions an orchestration actor column #22522 already published the keys. Run receipts still stripcoordinator_actor, so readers must take it from the DB.ORon an indexed column.Out of scope, for the later PRs in this stack:
session:<id>reaches only a session that coordinates a Run; a send to any other session address is refused at recipient routing.coordinator_actorin the pointer lane) belongs to the delivery PR (not open yet). Until then a chat coordinator polls withcheck.session:<id>". The address works; the wording is for the PR that tells agents their own address (not open yet).Not verified:
tar/gitextraction timeouts, which also fail at refactor(orchestration): give structured sessions an orchestration actor column #22522's head. The terminal-journey stub was therefore not exercised locally. The resolver calls no runtime member unless a request claims a session, and those journeys never do. CI will run them.