Skip to content

Orchestration plane ​

Switchboard can say what is happening: every live and recently ended run, every ship unit and every tracked pull request, each with its owner and its health, as one table read from the stores that already exist. The table is the first surface of the orchestration plane (record 0064, docs/decisions/0064-the-plane-owns-every-runs-state-a-refusal-becomes-a-queue-position-an-ending-is-judged-by-the-ledger-that-saw-it-and-a-release-is-a-quiet-window-a-person-closes.md, which merges beside this unit): the read-only projection the plane's later units (the queue, the windows, the watches and their moves) grow beside. Nothing in this feature decides, writes or steers.

Pure table, thin reads (locked design). One node-free module says what the rows mean, src/core/plane/table.ts, so the command's text, the page and the tests agree on every word; the service reads and hands the facts over. A health flag is derived from a fact the row already carries and never from a clock the plane owns: the stall facts are the runs index's (live-view.md item 32), the pull request facts are the merge door's (agent-ship.md item 9), a unit's ending is the coordinator's row (run-history.md item 50).

Behavior ​

  1. The private Slack golden flow crosses the real admission boundaries. A deterministic fixture sends ordinary Slack events through the adapter, dispatcher and scripted provider, reads a requester-bound fake production source, and passes its query, result, time window, source and uncertainty to one private worker through the existing typed starter. Replayed delivery or repeated start proposals must not create a second worker. Restart checks recreate bot-side objects from disk checkpoints of the reference stores; they do not establish production Durable Object recovery or live Slack acceptance. The separately gated deployed ordinary-language smoke and fresh Slack receipt remain required before broader rollout.

  2. A main conversation reads its durable work link. The orchestrator can read the current requester's runs and Ship units in its exact thread from the run and coordinator stores, without supplying a thread or requester id in model input. A missing, live-only or incomplete store read is unknown, never "no work"; another requester's runs are absent even in a shared channel. The result names original run, instance, unit, PR and status facts, and does not treat the agent's own recall log or recent fleet rows as a cross-agent work record. A saved result is not replayed across turns or resumption, and every result used in a reply must still match a fresh requester-authorized durable read before publication.

  3. Runs and their health. The table lists every run live in this process or on the ledger under another generation, then every run finished within the recent window (one hour by default), each under the viewer's runs:read predicate, exactly the rows runs list would show. A row's owner is its requester, with the name the adapter resolved when it did, or the generation driving it when the row is live under another container. Its health flags come from the pace facts the live summary carries: bound-exceeded when the in-flight call ran past the bound it declared, stalled when no tool call landed for a whole pace window, no-signal for a row live under another generation, which carries no pace fact and is never read as stalled; a finished row reads provisional, interrupted or failed from its record, or nothing. Live rows sort first, stalled ahead of healthy, then rows with a flag, then the recently ended, newest first within each band.

  4. Pull requests and their health. Every pull request a unit row or a run's record names is read once through the merge door's own GitHub reads (the facts, the checks at the head, the reviews), up to a bound (20 by default, the units' first), and judged in the merge door's order: approved when a review approves the head, dirty when GitHub reports a conflict with the base, red when a check at the head failed else pending when one is still running, mistitled when the title fails the repository's title rule, merged or closed for a closed one, and unknown alone when GitHub could not be read or the process holds no GitHub credential. A pull request is owned by the unit whose row names it, else by the run that opened it, else by a person.

  5. Units and the owner gap. Every instance a listed run belongs to contributes its unit rows, under the same predicate the unit page applies, with the instance's facts beside them. A unit with no ending is live while a run of it is live and waiting otherwise; an ending record 0051 idles on reads idle; merged and merge-ready read as they are, and a merge-ready unit whose pull request is still open is also an owner-gap, the state record 0064 names; a pull request the plane could not read, on GitHub or past the read cap, leans open, so a read failure never hides the gap. A coordinator child is joined to its unit through the spawn's idempotency key, else through the unit whose thread it runs in.

  6. plane show. One typed registry command, action runs:read, effect read, on every surface by derivation (plane show in chat and on the CLI, GET /api/plane.show, the plane_show MCP tool), answering the table as JSON and rendering it as three sections, aligned columns for a terminal and bullets for chat, each section saying (none) when empty; a row's health prints in the user's words — the owner-gap flag reads unmerged beside merge-ready (record 0066, "The collapse"), and the flags join as a phrase, never the raw flag join. The router is offered it with its three fixtures.

  7. The panel. GET /plane serves the shared web shell with the table as its seed and GET /plane.json answers the same table, one view in two encodings (record 0056), Access-gated beside /runs and /costs; a viewer the gate resolved to nobody sees nothing, a table that cannot be built is a 502 with the reason, never a 500. The page paints the three sections with the same words and links each run as the runs index does: a live run of this process through its capability token, every other run tokenless. Plane is a nav section beside Runs, always present because its rows are run facts under the same predicate.

  8. The decider. One pure, node-free function says what the plane does: decide(state, event) → { state, effects, writes } (src/core/plane/decide.ts), over a closed event union (ask, sealed, withdraw, window, level, observation, reask) and a closed effect union (admit, probe, the durable-sequence steer, and item 10's moves), never throwing — an event about a run or thread the state does not know is a no-op. An ask on a free thread proceeds: state unchanged, no writes, no effects (planeAskWordOf reads proceed). An ask on a live thread queues with a thread_free condition and a position counted among the thread's waiting rows. A seal flips the condition and the queue walk admits oldest first, re-evaluating after each admission — the admitted run's thread is live again, so a second row on the same thread keeps waiting. A withdraw marks the waiting row withdrawn, and a withdrawn row is never admitted. Effects carry stable ids derived from the run (admit:<runId>), so a duplicate offer after a roll is the same effect. The ledger object applies it inside ONE transactionSync (planeApply): the state read from its own tables, the decider's writes and the offered effects committed together or not at all.

  9. The tables and the answers' effects. The ledger object holds the plane's six tables from this unit on — plane_queue(run_id, requester, thread_key, stage, request_json, conditions_json, position_at, queued_at, state), plane_reservations(kind, key, run_id, at), plane_windows(kind, key, phase, opened_at, reason_json), plane_findings(id, watch, subject, timeline_json, filed_at), plane_effects(id, body_json, offered_at, acked_at) and plane_levels(resident, name, side, reported_at, generation, cause) — so an earlier bot and a later Worker agree on the schema; only the queue and the effects are written yet. The endings unit adds the seventh, plane_endings(run_id, kind, cause, at) (run-history.md item 33). Every answer the object gives the bot's heartbeat and reclaim-sweep calls carries effects: PlaneEffect[] — the unacknowledged offers, oldest first, at most 32 — always present, empty until a unit writes one; the client reads a field-less answer from an older Worker as none offered. The bot acknowledges each effect by id on POST /plane/ack: done and skipped close the offer, deferred leaves it riding the next answer; an unknown id is a no-op, never an error. The write-through executes an admit through the restart pass, a probe through the resident status seam and a provider-recovery steer only into the generation's matching live registry/admission run; draining or non-owning generations defer, and the inbox sequence turns a duplicate push, heartbeat or reclaim delivery into skipped. Move effects without an executor remain deferred.

  10. Shadow. plane.admission: off | shadow | on is the plane's gate (routing-and-config.md item 31; default off — planeAdmissionOf). Under shadow the dispatch's admission stage posts its own outcome per dispatch to POST /plane/outcome, fire and forget through the write-through (planeOutcome — a failed or missing route is one warning, never a wait or a throw): proceeded for a claim, refused:thread-live for every thread-in-flight refusal, refused:allowlist for the agent gate's; a steer, a supersede and a redispatch are not asks for a new run and post nothing, and the executor's fell_cold:<token> arrives with the resident unit. The object judges the same ask with the decider inside one transaction and logs the decider's word beside the bot's; nothing runs — and nothing queues — from the decider under shadow, and a disagreement (a proceeded the decider would queue, a thread-live refusal it would admit) bumps a per-condition counter in meta (plane_disagreements) for the later plane disagreements line; a pair the decider does not model yet is logged uncounted. off posts nothing.

  11. The resident conditions (record 0064, "The queue"). Each resident computes two levels — seat (canonical active or unknown thread and operation owners plus durable native-operation holds: above when the separate active-work limit is reached or ownership cannot be verified; historical UID counts do not consume seats) and memory (the gate's soft side, resident-repos.md item 70) — in a pure module (deploy/cloudflare-resident/levels.ts), stamped with its generation (the container's boot id where memoized, else the DO incarnation). Every /attach, /exec and /status answer carries the levels document; a crossing lands in a per-name outbox re-offered on every answer until a newer crossing supersedes it, and a boot or a replaced runtime re-states both names. The registry posts the fleet drain the same way (name: "drain" on /drain, /undrain and its answers): the set posts above and arms ONE alarm at the record's until, a clear posts below, and the alarm posts below for a drain nobody lifted. The bot forwards a post to POST /plane/level; on the object, seat and memory land as level events — a below side walks the queue — and drain as the resident-drain window's open and lift. A resident-stage ask (stage: "resident", the resident named) queues on a level reported above, holds no reservation (its thread is already this run's), and proceeds — for the bot to fall cold — when the resident is unknown (residentSideOf: no report, or one from another generation); a restartOf ask passes the windows and the memory line. A refusal-by-name the bot meets at attach or exec (POST /plane/observe: user-pool-exhausted → seat, the gate's memory-pressure → memory, draining → the drain window, runtime-unreachable/runtime-replaced → seat) re-enters an admitted run's queue row waiting at its old position and writes the level above — the refusal is evidence. While a waiting row waits on a resident that has said nothing within the re-ask cadence (plane.reaskMinutes, default 2, stored from the admit post's reaskMs; PLANE.reaskMinutes in src/core/budgets.ts), the object's alarm — pulled forward to the cadence, never pushed back — emits one probe(resident) effect per resident (probe:<resident>: at most one open per resident, re-offered after an ack), which the heartbeat loop answers by probing /status and forwarding the levels; a probe is never deferred by a drain and a process without the seam skips it. plane.coldFallback (default false — planeColdFallbackOf) is the rollback lever: write presets fall cold instead of queueing. The bot's stage-three ask and its forwarding are the executor's half, still to land: [gap] a coding ask on a resident above a line queues at stage three, a read preset falls cold, and the bot forwards levels and refusals.

  12. The moves and the findings (record 0064, "Endings and the watches"). A watch is a named predicate over the plane's state, evaluated only at the events that can change it; a move is its mechanical recovery, one pure step of the decider; a finding is filed when no move applies. The decider's event union grows pr_tracked (a tracked pull request's title, judged against the title rule by the caller — the decider is node-free and holds no vocabulary), child_sealed (a child's seal with its pushed branch, pull request and runner-liveness facts), approval (the merge door's read: the head and its mergeableState) and runner_status (the engine's status with the unfinished units and the attempt); the effect union grows the moves — retitle (unit_title: a failing title, id retitle:<repo>#<number>), pr_open (orphaned_child: a pushed branch with no pull request and no live runner, id pr_open:<repo>#<branch>), rebase_round (dirty_at_approval: briefed REBASE_ROUND_BRIEF, "rebase onto the base and push", keyed by the approved head — the reviewed-head gate voids the approval and re-review follows), and reissue (runner_gone: errored or terminated with units unfinished, or the hosting deadline passed on an instance not waiting, keyed by the attempt number). Every id is derived from the event, so a fact read twice offers the same effect once; a move whose precondition no longer holds when the bot executes it is acknowledged skipped, and the write-through defers every move effect until its executor lands. The runner's own open takes the same title rule at open (the recover pr-check titles by the head commit's subject when it passes, agent-ship.md item 10), and plane stop <instance> is the runner_stop move as one destructive command (agent-ship.md item 16). A finding (src/core/plane/findings.ts) carries the watch and the timeline of events that fired it, deduplicated by watch and subject (mergePlaneFindings — two findings on one subject are ONE, timelines merged and bounded); the bot dispatches a research-tier run over the timeline inside the untrusted fence (findingResearchRequest) and files its answer through the path friction propose uses (filePlaneFinding, the issue-tracker seam, an open issue with the finding's title a duplicate). [gap] the bot-side observation posts (pr_tracked, child_sealed, approval, runner_status onto the object) and the executors for retitle, pr_open, rebase_round and reissue land with the transport's later units.

  13. The chat column (record 0070, docs/decisions/0070-the-control-plane-is-where-the-maintainer-works-the-plane-page-pins-a-chat-column-beside-the-panels-and-an-orchestrator-thread-answers-about-the-fleet-and-acts-on-it.md). From the wide breakpoint up the page is two columns: the panels beside a pinned chat — the existing web chat's composer, turns and cards (web-chat.md), never a second chat implementation — bound to the viewer's orchestrator thread: one conversation per person, keyed on the session (web:<sub>:orchestrator), created on first open and continued ever after, served and sent through the web chat's own /threads routes. The seed carries both halves at one at (the table's), so a row the chat cites is the row the panel paints; the chat half never rides the JSON twin, and a chat that cannot be read costs the column, never the panels. A viewer without a session gets the panels full-width as before the column existed. The column defaults to 400 px, the divider drags within 320–640 px, and the chosen width is remembered per person in the browser's localStorage — a per-viewer convenience, never shared state; empty or blocked storage renders the default. Below the wide breakpoint the chat folds behind a floating button that opens it as a sheet; the panels' own rules are unchanged.

  14. The orchestrator preset (record 0070, criteria 3 and 5). orchestrator remains one preset and one thread session: no workspace, shell or machine credential (machine: none, identity: none). Its built-in toolset is plane_show, thread_work, the GitHub read tools and the thread's recall/notes; a freshly verified requester DM also admits slack_context, work_start for an affirmative change request in a verified repository, and work_status/work_progress/work_steer/work_stop for this conversation's linked work, with bounded private progress. work_start hands one bounded brief to a private worker through the existing Ship coordinator; the main agent keeps answering in this thread. It has no public progress-posting, generic run, shell, issue-write, submission or merge tool; registry commands use the same fenced executor and policy inside this conversation when offered. A non-fleet question is answered in this same conversation using authorized GitHub reads or explicitly scoped MCP read sources, not refused merely because it is outside the plane. GitHub uses the existing injected capability: orchestrator reads are limited to public repositories on the requester's current repo list and allowed repo scopes (github-tools.md item 3). The pilot MCP path uses only the requester's user-scoped configuration and credential in a one-person Slack DM, never an org, channel or substituted service actor. MCP sources must explicitly list orchestrator; omitted agent lists remain general/research. The run receives ordinary tools advertised read-only and not destructive, plus versioned bound source operations with separate resource and incidental effects; unannotated generic tools and writes are absent, including on cache hits (mcp-tools.md item 7). Configure these sources with read-only upstream permissions: annotations select tools, not enforce the remote server's behavior. The declared Slack source tool selects one intake path: requester URLs remain in the prompt, while linked text and prior-run file catalogues are read only through the requester-bound tool and its durable source receipts (slack-context-tools.md). Enabling automatic references for specialists does not preload the main agent or add a second audience policy. Missing capability or failed receipt persistence never falls back to that preload.

  15. The writes through the door (record 0070, criteria 4 and 6). A write typed in the orchestrator thread is the Slack door's write: the operator binds the sentence to one registry command (routing-and-config.md item 29), the policy table authorizes the PERSON the session names — a grant-less person is refused by the table, so the thread can do what its person can do and nothing a service actor could — and the blast-radius class the confirmation renders from is the same on every surface (record 0044). The two acts that had no command are registry commands born fenced (src/core/commands/merge.ts): pulls merge squash-merges one approved pull request at exactly its reviewed head and pulls enqueue puts exactly that approved head in the base branch's merge queue (GitHub atomically refuses a moved head) — both action merge:write (a grant, never a chat or browser baseline), destructive with a risk line naming the pull request, hidden without the github capability. Both refuse by reason: a head no review approves, a reviewer's changes-requested standing at it (their last word at that head, even beside another's approval), a red check at it (the merge also refuses one still running, and a head where no check has reported yet — the fresh-head window before CI registers never merges unverified, and a repository with no CI stays a person's click on GitHub; the queue runs pending checks itself), and a person nobody bound to a GitHub login (record 0062) — the App credential never merges as nobody; the merge that lands carries the person's bound login as the squash commit's Merged-by: trailer and the answer names them, and the enqueue's answer records who asked. The release pull request — release-please's branch or its chore(main): release title — is answered with the handoff card (record 0064's Handed phase: the pull request by name, "this deploys production", no merge called) on both commands: that click stays a person's. The thread holds no admission rule in prose: a move the decider owns is asked of the decider, and the preset's writes are exactly the registry commands its person's grants admit — the boundary is the tool list and the policy table, never preset prose. [gap] the merge attribution proven against a real pull request before the orchestrator preset holds the command (human-gated, plan 004 unit 3's validation).

Validation criteria ​

CriterionEvidence
15: ordinary Slack question and fix cross real adapter, dispatcher, source bridge and typed worker admission; answer and contract follow immutable source-action receipts and a fresh reread; the accepted unit stores the known source capsule; its read-only research child retains a canonical brief on the inspected revision, and coding receives that exact source ID and capsule under canonical unit admission; the coordinator verifies the original requester DM while the child's assigned IO keeps its output off Slack; delivery and model retries create one worker[unit] src/channels/slackGoldenFlow.test.ts::Slack golden flow::carries a sourced answer and ordinary fix into one private durable worker
15: missing query, result, time window or source URL in either source response withholds the scripted answer or proposal; these are transport checks, not live-model evidence interpretation[unit] src/channels/slackGoldenFlow.test.ts::Slack golden flow::withholds missing…
15: new bot-side objects restore conversation and unit data from disk; replay after completed admission or a lost workflow reply retains the original instance, branch, brief and exact context capsule without a second workflow create[unit] src/channels/slackGoldenFlow.test.ts::Slack golden flow::reopens persisted conversation and task state before replaying the same fix, ::reconciles a lost workflow reply after restart without a second creation
15: an unanswered workflow creation preserves effect uncertainty in the model-visible start result[gap] Initial admission currently groups an unanswered creation with confirmed failure; the replay fixture proves same-task reconciliation, not truthful initial effect reporting.
15: a foreign requester with equivalent grants, shared destination and ungrounded model target proposals cannot admit work; source revocation before publication withholds the answer[unit] src/channels/slackGoldenFlow.test.ts::Slack golden flow::refuses…, ::withholds the answer when source authorization changes before publication
15: after exact deployed-build and credential/configuration gates, a new ordinary-language Slack request starts and completes a real agent run and posts its answer; before broader rollout, record the build, Slack thread, run and independently read terminal facts[gap] A fixture pass, candidate routing check and HTTP ingress smoke do not prove deployed Slack acceptance.
Exact terminal Workflow discovery offers a bounded durable reconciliation request through the existing outbox and alarm. Bot outage, uncertain native status, lost acknowledgement and unconfirmed admission retain the original owner. Native absence additionally requires created admission and a saved begun exact execution; absence alone is never execution proof. An offered request prevents reentry from clearing its ending or report admission; acknowledgement requires the original frozen report, status receipt and required private reply.[unit] deploy/cloudflare-memory/runLedger.test.ts::durable coordinator Workflow reconciliation::*, src/core/coordinator/workflowReconciliation.test.ts::coordinator Workflow reconciliation identity::*
A fresh tool call is no flag; a whole window without one is stalled; a call past its bound is bound-exceeded ahead of stalled[unit] src/core/plane/table.test.ts::runHealthOf — a live run's flags from the pace facts and its status::a live run of this process with a fresh tool call has no flag, ::a live run with no tool call for a whole window is stalled, ::a call past its own declared bound is bound-exceeded, and outranks stalled
A row live under another generation is no-signal, never stalled; finished rows read provisional, interrupted or failed[unit] ::a row live under another generation carries no pace fact and reads no-signal, never stalled, ::a provisional record and an interrupted or failed ending are named
Pull request flags in the merge door's order; unknown alone when unread[unit] src/core/plane/table.test.ts::prHealthOf — a tracked pull request's flags from the merge door's facts::an open pull request with green checks and an approval at its head is merge-ready, ::a red check at an approved head is red, a conflict is dirty, a failing title is mistitled, ::pending checks are named; a merged or closed pull request carries its state alone, ::a pull request GitHub could not be read is unknown and nothing else
Unit flags from the ending and the live run; the owner gap[unit] src/core/plane/table.test.ts::unitHealthOf — a unit's flags from its ending and its runs::a unit with no ending and a live run is live; with no ending and no run it is waiting, ::an idling ending kind (record 0051) reads idle with its kind; merged and merge-ready read as they are, ::a merge-ready unit whose pull request is still open is an owner gap , src/core/plane/table.test.ts::buildPlaneTable — the owner gap when the pull request could not be read::an unread pull request and one past the read cap both lean open: the gap is flagged either way
A child is joined to its unit by key, else by thread[unit] src/core/plane/table.test.ts::unitIdOfRun — which unit a coordinator child belongs to::reads the unit off the spawn's idempotency key, else off the unit whose thread the run is in
Row order and owners; units joined to instances and pull requests to their owners; empty windows and findings[unit] src/core/plane/table.test.ts::buildPlaneTable — the rows, their owners and their order::lists live runs first, stalled before healthy, then recently ended; names each owner, ::joins units to their instance and their runs, and pull requests to the unit that owns them, ::a pull request a plain run opened is owned by that run; one nobody names is a person's, ::carries empty windows and findings until the plane has them
The service reads live and recent runs under the predicate, joins units, reads each pull request once, bounds the reads, and never throws for an unreadable GitHub[unit] src/core/planeService.test.ts::createPlaneService — the table over the stores that exist::lists the live runs and the runs finished within the recent window, under the viewer's predicate, ::joins a coordinator child to its instance's units and reads the units' pull requests once each, ::an instance the viewer may see nothing of contributes no units and no pull requests, ::a pull request GitHub cannot read is unknown; without a GitHub reader every pull request is, ::reads at most the configured number of pull requests, the units' first
plane show is one read under runs:read, refused before the service is asked, rendered as three sections[unit] src/core/commands/plane.test.ts::plane.show::registers the read under runs:read and the stop under runs:write on every surface, ::answers the table under the caller's own predicate: an all-channels reader gets every row, ::a caller without runs:read is refused before the service is asked, ::renders three sections for a terminal, the header counting live, recent, units and pull requests, ::renders bullets for chat, with the sections in bold, ::an empty table says so in every section
The health words on the text surfaces: a unit whose health is owner-gap prints merge-ready, unmerged on chat, the CLI and MCP output[unit] src/core/commands/plane.test.ts::plane.show::a unit whose health is owner-gap prints \merge-ready, unmerged` on chat, the CLI and MCP output — never the raw flag join (record 0066)`
The panel's routes, the seed with tokens for this process's live rows, the twin, the nobody viewer, the 502[unit] src/channels/planeView.test.ts::createPlaneViewHandler::falls through for other paths and refuses non-GET, ::serves the shell with the table as the seed, read under the viewer's predicate, with tokens for this process's live rows only, ::answers the twin as the same table in JSON, ::a viewer the gate resolved to nobody sees nothing, ::a table that cannot be built is a 502 with the reason, never a 500
The chat half of the seed: the viewer's own orchestrator thread — one per person, in their lane, empty on first open — with the composer's send URL and palette; the page's seed carries it for the session's viewer at the table's one at, a session-less viewer gets none, a failing chat read costs the column never the panels, and the JSON twin stays the table alone[unit] src/channels/web.test.ts::orchestratorChatSeed — the plane's chat half (record 0070; orchestration-plane item 11)::reads the viewer's own orchestrator thread — one per person, in their lane — with the composer's send URL and palette, ::a viewer with no orchestrator runs yet gets an empty thread — created on first open, continued ever after, src/channels/planeView.test.ts::createPlaneViewHandler::the page's seed carries the chat half for the session's viewer, at the table's one at (record 0070), ::a session-less viewer gets the panels alone — no chat half is built, ::a chat half that cannot be read costs the column, never the panels, ::the JSON twin stays the table alone — the chat half never rides it
12: the orchestrator has plane, requester-scoped GitHub, bounded DM Slack context and session reads, plus work_start and linked-work controls, including bounded private progress, only in a verified requester DM; it has no public status, generic run, shell or merge tool. Fleet facts cite the current plane row.[unit] src/agents/registry.test.ts::orchestrator agent (record 0070 — the plane's chat preset)::*, src/tools/toolsets.test.ts::the toolset table::orchestrator: source reads and private linked-work controls
12: asked what it can help with, the orchestrator describes Switchboard's available agent paths from the About block and its own current conversation tools separately; it does not turn its no-shell limit into a claim that Switchboard cannot edit code or review pull requests. It qualifies actions by the available tools, channel and requester permissions and does not promise a specialist launch from a plain follow-up here.[unit] src/agents/registry.test.ts::orchestrator agent (record 0070 — the plane's chat preset)::answers capability questions for Switchboard as a gateway, without confusing this preset's limits with other agent paths; [agent] After deployment, ask "what can you help me with?" in a direct Slack DM and check that the answer names the coding, review and Ship paths plus the actions this conversation can take, with access limits.
12: GitHub and explicitly scoped MCP reads carry citable evidence; a later turn revalidates typed original source dependencies before reusing saved GitHub or MCP results, and omits unproved legacy results before a fresh read; foreign recall is denied; private work_start has a separate verified-request gate, with no generic run, issue-write, submission or merge tool and no fallback on unavailable sources[unit] src/tools/orchestrator.test.ts::orchestrator conversational reads::reads GitHub and MCP evidence then recalls it in the same session without gaining writes or another requester's access; src/core/dispatcher.test.ts::dispatch::continues a main DM after a saved…
12 pilot: the main conversation may use private MCP evidence only from the requester's user-scoped source in a one-person Slack DM. A fresh later turn may reuse saved MCP results, notes and recall only with a known complete source envelope and fresh checks of original source action, owner, query, connection and destination. Unproved legacy content is omitted while requester-authored Slack words remain; the agent may read the current source again. Unproved legacy prompt seeding refuses mixed author, missing author, truncated log or compaction; publication rechecks the same source audience and incarnation, withholding the answer when a source is replaced under the same name. A fresh main run stamps its passed audience check on the durable row. A resumed run requires that stamp and checks the original tool catalog and replayed messages before the model or a reply receives them; MCP or Slack-context exposure, compaction or an unproved finish refuses resumption and asks for a fresh source check in the same thread. Typed source receipts do not prove the absence of a lost private revocation or compaction; they do not reopen this recovery gate (mcp-tools.md). Public Slack links and their files in the verified requester DM use the bound source-receipt path (slack-context-tools.md). Shared-channel production reads and cross-channel private sharing remain [gap] until a destination audience grant is represented and checked.[unit] src/mcp/service.test.ts::McpService — the run-time view (item 17)::orchestrator opens only its requester's user-scoped source in a one-person Slack DM; src/core/dispatch/mainAudience.test.ts::main conversation source audience::*, src/core/dispatcher.test.ts::dispatch::routes plain Slack DM conversation to the configured main agent while operator routing is on, ::withholds a main answer when its private source disappears before the reply, ::refuses an unverified source audience before the main model sees it, ::run ledger write-through (docs/reference/specs/run-history.md item 35)::a fresh main run stamps its checked audience on the row for later resumption, ::a resumed main run refuses replayed private source evidence before the model or Slack reply sees it; [gap] shared-channel source and cross-channel private audience proof
12 pilot: a plain follow-up such as “fix it” remains with the main agent in its one-person Slack DM, including after an explicit specialist turn or when the orchestrator transcript established the conversation. A first main turn excludes an earlier specialist answer with no proven source label and keeps only requester-authored text. An explicit agent directive still selects its named agent.[unit] src/core/dispatcher.test.ts::dispatch::returns a plain follow-up to the configured main agent after an explicit specialist turn, ::keeps an established main DM with the orchestrator when the operator is on, ::keeps an established main DM after an explicit specialist turn when the operator is on, ::finds an established main DM behind more specialist runs than the thread page holds, ::uses an established main DM run when channel history is empty, ::uses the requester's scoped main agent and keeps an explicit specialist directive, src/core/dispatch/thread.test.ts::establishedMainDmOf — retained main conversation::*
12 pilot: a specialist review verdict or post in the same DM does not block the main follow-up. The main agent keeps its own verified session but does not inherit other agents' unlabeled artifacts in its prompt; it must check their source afresh.[unit] src/core/dispatcher.test.ts::dispatch::keeps a main DM usable after a specialist review recorded a verdict and review post
12 pilot: the main conversation's GitHub reads recheck requester access immediately before the Slack reply, after the card closes. Covered public GitHub tool results retain exact response and call hashes with their original repository dependency before exposure. A fresh later turn rechecks the current requester-authorized public catalog before reuse; results without that proof and their derived answers or notes are omitted, then the agent may read the source again. Same-run resume still refuses unproved replay. A DM that becomes shared after the read cannot receive the answer.[unit] src/core/dispatch/mainAudience.test.ts::main conversation source audience::refuses saved GitHub reads after a requester grant may have changed, ::refuses a resumed GitHub result before the model or answer sees a revoked repo, ::withholds a current GitHub answer after a repo grant is revoked or its repository is unknown; src/core/dispatcher.test.ts::dispatch::continues a main DM after a saved…, ::rechecks a GitHub read at publication and withholds its answer after requester repo access is revoked, ::withholds a GitHub answer when a listed repository has no usable identity, ::withholds a github_repos answer when its listed repo is revoked before publication, ::withholds a GitHub answer when its verified DM becomes shared after the source read; src/core/dispatch/reply.test.ts::deliverAnswer — the answer reaches the thread::rechecks source access after the card closes and before the channel reply
12 pilot: plane_show records every exposed full run, unit and pull request identity. Before the Slack answer, a new requester-filtered table must still contain all of them; missing access or a malformed identity withholds the answer. A saved plane result is omitted from a fresh later turn so the main agent can read the current table; same-run resume refuses unproved replay.[unit] src/core/dispatch/mainAudience.test.ts::main conversation source audience::refuses a saved plane result after its requester-visible rows may have changed, ::withholds a current plane answer when any exposed run, unit or PR disappears, ::records full plane run, unit and PR identities for requester visibility rechecks; src/core/dispatcher.test.ts::dispatch::withholds a plane answer when the requester loses the exposed private run before publication, ::continues a main DM after a saved…
12 pilot: a spawned main agent refuses a parent's unproved text seed; the main agent's status card and run event stream expose only generic progress, including when a GitHub grant is revoked after a tool result. The private answer reaches only the Slack DM after fresh requester scope verification. The run page deliberately omits the private answer and source detail until source-aware run access exists. Replay preserves event sequence through a restart.[unit] src/core/dispatch/mainAudience.test.ts::main conversation source audience::refuses a spawned main agent's parent seed before its first model turn, src/core/dispatcher.test.ts::dispatch::refuses a spawned main agent's parent text before its first model turn, ::rechecks a GitHub read at publication and withholds its answer after requester repo access is revoked; src/core/runRegistry.test.ts::RunRegistry.create::a private main run replays redacted events at their original sequence and accepts its next live state
12: ordinary answers name their source, time window or unknown freshness; fleet standing still requires plane_show[unit] src/agents/registry.test.ts::orchestrator agent (record 0070 — the plane's chat preset)::answers non-fleet questions in this same thread from authorized GitHub and explicitly scoped MCP reads, citing the source and time window
12 live: ask a repository question, then an opted-in MCP source question, then fleet status in the same orchestrator thread; answers cite the source and time window (or explicitly unknown) and status cites fresh plane rows[agent] After deployment with a read-only MCP source, inspect the answers and tool timeline; the session key and requester must stay unchanged. Unit tests prove tool admission and prompt instructions, not a model's adherence.
12: a fleet question is answered from the plane's tables with the row cited, never from memory[unit] src/tools/plane.test.ts::plane_show — the orchestrator preset answers a fleet question from the tables (record 0070)::*
14: the main conversation reads requester-owned runs and Ship units from its exact thread after another agent worked there, with PR and original ids; a foreign requester is excluded, missing durable pages answer unavailable, saved reads are not replayed, and a fresh requester-authorized read must match before publication[unit] src/tools/threadWork.test.ts::thread_work — the main conversation's durable work link (orchestration-plane item 14)::*, src/core/runsService.test.ts::RunsService.listRuns — read merge::marks a complete persisted-history read only when a caller requests its receipt, src/core/dispatch/mainAudience.test.ts::main conversation source audience::refuses a saved thread work result on a later turn or resumed run, ::withholds a current thread work answer when the fresh durable view loses a linked unit, src/core/dispatcher.test.ts::dispatch::withholds a thread work answer when a linked run disappears after its tool read, src/core/dispatcher.test.ts::dispatch::publishes a thread work answer when the earlier durable run remains visible, src/core/dispatcher.test.ts::dispatch::continues a main DM after a saved thread_work read without replaying its answer, src/agents/registry.test.ts::orchestrator agent (record 0070 — the plane's chat preset)::uses durable thread work facts across agents instead of mistaking its own recall log for the whole thread
12: agent:orchestrator resolves through the config layers like any preset — the request directive picks it anywhere, a user scope picks it with no directive, and a directive still beats the scope[unit] src/core/dispatch/resolve.test.ts::resolveRun — the (agent, model, effort) triple::\agent:orchestrator` resolves through the layers: the request directive picks it anywhere, a user scope picks it with no directive, and a directive still beats the scope`
The router is offered plane show with its three fixtures[unit] src/core/commandConformance.test.ts::command conformance — catalogue fences::every offered command has its three routing fixtures — happy path, paraphrase, decoy (load:route's command half)
The decider: a free thread is admitted with a reservation written in the same decision; a live or reserved thread queues with thread_free and a position ranked per shared condition; an open window queues on window_open and a pending deploy on deploy_settled, each admitted on its lift; the seal deletes the reservation and admits oldest first, re-evaluating after each admission; a no-transition event returns the state unchanged; a withdraw closes the row; effects carry stable ids[unit] src/core/plane/decide.test.ts::decide — the plane's pure decider (orchestration-plane, record 0064)::an ask on a free thread is admitted: no queue row, a reservation on the thread written in the same decision, no effects, ::two asks a second apart on one thread: the first is admitted with a reservation, the second queues with position 1 behind it, ::an ask that meets an open window queues on window_open and is admitted by the window's lift, ::an ask that meets a pending deploy queues on deploy_settled and deploy.landed flips it, ::a reserved thread queues a second ask until the seal deletes the reservation and admits it, ::position counts waiting rows sharing the unmet condition, per condition, ::an ask on a live thread queues with a thread_free condition and position 1, ::a second ask on the same live thread queues behind the first, position 2, ::a thread_free condition flips on a seal and the oldest waiting row is admitted, with an effect carrying its id, ::a queue walk admits oldest first and re-evaluates after each admission: the admitted run's thread is live again, so its follower keeps waiting, ::two waiting rows on different threads are both admitted on their seals, oldest first, ::an event with no transition returns the state unchanged: a seal nothing waits on, ::a withdraw marks the waiting row withdrawn and a withdraw of an unknown run changes nothing, ::a withdrawn row is never admitted by a later seal, ::effects carry stable ids derived from the run they admit
Inside workerd: the plane's tables exist, and planeApply commits the state flip and the offered effect in one transactionSync[unit] deploy/cloudflare-memory/runs.test.ts::orchestration plane — the tables, the decider, shadow and the effects::a fresh object holds the plane's seven tables, ::planeApply commits the queue row, then the seal's admitted state and its effect, together in transactionSync, ::two child_sealed findings on one subject merge into ONE plane_findings row, the first filed_at kept
Shadow on the object: the decider's word is logged beside the bot's outcome, nothing queues, and a disagreement bumps the per-condition count; a malformed post is 400 before any object call[unit] deploy/cloudflare-memory/runs.test.ts::orchestration plane — the tables, the decider, shadow and the effects::shadow logs refused:thread-live beside queued for a second ask on a live thread, and persists nothing, ::an agreeing proceeded post counts nothing; a proceeded post on a live thread bumps the per-condition disagreement count, ::a bad stage, outcome word or ack word is 400 before any object call
The effects field: present and empty on every heartbeat and reclaim answer, an offer rides until acked done or skipped, deferred leaves it, an unknown ack id is a no-op[unit] deploy/cloudflare-memory/runs.test.ts::orchestration plane — the tables, the decider, shadow and the effects::a heartbeat answer and a reclaim answer carry effects: [], present even with nothing offered, deploy/cloudflare-memory/runs.test.ts::orchestration plane — the tables, the decider, shadow and the effects::an offered effect rides the heartbeat answer; deferred leaves it offered, done closes it, an unknown id is a no-op
The client half: the write-through acks received effects deferred when nothing is wired, runs an admit through the wired executor — a draining generation defers it — and the outcome post fires and forgets: a failure is one warning[unit] src/core/runLedger/writeThrough.test.ts::events, state, heartbeat::plane effects on a heartbeat answer are each acked deferred — this generation executes none yet (orchestration-plane item 7), ::an admit effect runs through the wired executor and its word is the ack; a draining generation defers it instead (record 0064), ::planeOutcome fires the post and swallows a failure with one warning — the dispatch never waits on the plane (orchestration-plane item 8)
The admission stage under plane.admission: on: two asks a second apart answer admitted then queued at position 1; the claim promotes the reservation; the seal admits the queued run with its admit effect and its attaching row under the plane's id; a withdrawn row is never admitted; deploy.landed flips deploy_settled; a sealed run's open effects are dropped[unit] deploy/cloudflare-memory/runLedger.test.ts::the plane's admission stage — /plane/admit, reservations, the seal's walk (orchestration-plane; record 0064)::two asks a second apart on one thread: the first is admitted with a reservation, the second queued at position 1; the first's claim promotes the reservation; the seal admits the queued run with its attaching row, ::a duplicate admit decision after a roll keeps the effect's first offer and the attaching row (INSERT OR IGNORE), ::runs stop on a queued id withdraws it: the row goes withdrawn, a second withdraw answers false, and the seal admits nothing, ::a pending deploy queues an ask on deploy_settled and deploy.landed flips it, admitting the queued run, ::effects for a sealed run are dropped at the seal: an admitted-then-finished run's open admit goes with its finish, ::validates: a missing threadKey, requester or request is 400; a malformed withdraw run id is 400
The bot's door under plane.admission: on: a queued ask holds nothing — the slot released, no row reserved or adopted, no heartbeat — and every verbosity preserves the actionable position and withdraw lever because no run card exists; an admitted ask proceeds; off, a resume and a restart never ask[unit] src/core/dispatch/admission.test.ts::admit — the plane's admission ask under plane.admission: on (record 0064, "The queue")::an ask the plane queues holds nothing and $level preserves its actionable position and withdrawal command, ::an admitted ask proceeds — asked once, the reservation is the object's to promote, ::off — the default — never asks; a resume and a restart re-enter decided work and never ask either
The push transport: the state Worker POSTs committed effects to the bot's /plane/effects, which runs each through the SAME executor the heartbeat answers use and acks its word; draining or unwired defers; a pushed steer atomically verifies the open effect, fences authoritative ownership and renews its lease before registry delivery, so reclaim cannot cross the local wake; a wrong bearer or a malformed body runs nothing; a push that fails leaves the effect riding the next heartbeat answer[unit] src/channels/planeEffects.test.ts::POST /plane/effects — the push transport (record 0064)::runs each pushed effect through the wired executor and acks its word on the object, ::delivers a pushed steer through the wired executor and acks only its outcome; probe effects remain for the heartbeat executor, ::the stale generation defers a pushed steer, then the durable owner heartbeat delivers and acks it, ::renews the durable owner fence before registry delivery, so reclaim cannot cross the local wake, ::rechecks drain after the durable fence returns, so handoff cannot wake stale registry state, ::a draining generation defers, and no wired executor defers too — the offer stays for a bot that can run it, ::an effect whose execution or ack throws is one warning and stays offered — it rides the next heartbeat answer; the rest of the push still acks, ::refuses a wrong or missing bearer, and everything when the token is unset (fail closed); refuses non-POST, ::a non-JSON body, a body without an effects array and a malformed effect are 400 — nothing runs, deploy/cloudflare-memory/runLedger.test.ts::the plane's admission stage — /plane/admit, reservations, the seal's walk (orchestration-plane; record 0064)::a push that fails leaves the effect on the next heartbeat answer: the bot answered 404, nothing was acked, and the offer rides the heartbeat
Provider down persists its closed cause on the level row; provider recovery live delivery: provider-up atomically writes the durable row and sequence-bearing offer; the Worker pushes it, a failed transaction or push loses nothing, an atomic effect/owner fence renews the lease before registry delivery and only an owner-fenced ack closes it, duplicate paths reissue once, and a sealed run emits none[unit] src/core/plane/decide.test.ts::decide — the checkpoint steers and the provider condition (record 0064)::the provider's next up report — from any run — writes each held turn's durable row and typed live steer together, then deletes the park; a repeat up steers nothing, ::a provider up after the parked run is no longer live deletes the stale park and offers no steer, deploy/cloudflare-memory/runLedger.test.ts::the plane's checkpoint steers and the provider condition — the heartbeat body, /plane/park, /plane/level provider (record 0064)::a provider down and parked live run recover atomically as one durable row and one pushed offered steer; repeat up writes nothing, ::fences a pushed steer and renews its owner's lease atomically before registry delivery, ::a stale generation cannot close a steer offer after reclaim; the durable owner heartbeat receives and closes it, ::a failed provider-up transaction writes neither the durable row nor its steer effect, ::a failed live push leaves both the durable row and steer offer for an owner heartbeat; sealing first removes the park and produces no steer, src/channels/planeEffects.test.ts::POST /plane/effects — the push transport (record 0064)::the stale generation defers a pushed steer, then the durable owner heartbeat delivers and acks it, src/core/runLedger/writeThrough.test.ts::events, state, heartbeat::an owner heartbeat delivers a live steer once; duplicate heartbeat and reclaim copies of its durable sequence are skipped, src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::a provider-up effect pushed into the owning live inbox reissues the parked turn immediately and acks done before any heartbeat or reclaim
A queued id has a page and a lever: getRun answers the stored ask's view (the requester, the position, the waiting words — the queued reply's own) and authorizes it against the request's durably stamped channel, never one re-derived from an ambiguous thread key; only a legacy row without that stamp falls back to the thread key's channel. The run page heads with those facts, and runs stop on the id withdraws the waiting row for either mode; a row the queue holds not-waiting is a conflict[unit] src/core/runsService.test.ts::RunsService — a queued ask on the plane (record 0064)::authorizes a queued run with its stamped request channel, not a channel re-derived from the thread key, ::getRun answers a queued id's view: the requester, the thread's channel, the position and the waiting words — so the id a person was told has a page (legacy row without request.channelId), ::stopRun withdraws a queued id — the reply's runs stop lever — for either mode; the queue knows the id but not as waiting is a conflict, web/src/pages/runPage.test.ts::RunPage — history mode::a queued ask's page heads with the position and the waiting words, amber
The deploy runner posts the window: deploy all with a bot step posts pending before the first Worker uploads and landed after the run — landed or failed alike — so deploy_settled flips end-to-end; no state Worker, a missing bearer, an older Worker or an unreachable one is one line each and never fails the deploy[unit] src/deploy/planeDeploy.test.ts::postPlaneDeploy — the /plane/deploy post from the deploy runner::posts pending and landed to the state Worker's /plane/deploy with the bearer, the store key and the commit; landed logs how many asks were admitted, ::no state Worker posts nothing; a missing bearer is one line and nothing is sent, ::an older state Worker (404) and an unreachable one (throw) are one line each — the deploy is never failed by the plane
The resident stage's decider: a write ask above the soft memory line or an exhausted seat queues at stage three holding nothing; a below report admits it and another resident's admits nothing; below both lines it proceeds with no writes; a restartOf ask passes windows and the memory line; an unknown resident queues nothing; an observed refusal re-enters an admitted run at its old position, maps every named refusal, and a no-op otherwise; the drain window opens and lifts on the registry's posts; a reask probes each silent resident once and none that reported within the cadence[unit] src/core/plane/decide.test.ts::decide — the resident stage's conditions (record 0064)::*
The resident's levels: the seat and memory sides, the crossing posts, the boot's re-statement, the outbox's supersession, and the Worker wiring — levels on every /status, /attach and /exec answer, the registry's drain posts and its one alarm at until[unit] deploy/cloudflare-resident/levels.test.ts::the sides::*, deploy/cloudflare-resident/levels.test.ts::the crossing posts and the boot's re-statement (record 0064)::*, deploy/cloudflare-resident/levels.test.ts::the Worker wiring (static): levels on every answer, the registry's drain posts and its alarm::*
The resident stage on the object: /plane/level lands in plane_levels and a below side admits the queued resident ask (which held no reservation) with its attaching row; a drain post opens and lifts the resident-drain window; /plane/observe re-enters an admitted run and the next below report re-offers the SAME admit despite its acked row; the re-ask alarm is pulled forward and never pushed back, probes a silent resident and re-offers the probe after its ack; a malformed level or observe post is 400[unit] deploy/cloudflare-memory/runLedger.test.ts::the plane's resident stage — /plane/level, /plane/observe, the re-ask alarm (orchestration-plane item 9; record 0064)::*
plane.coldFallback parses as a boolean, defaults false, and a non-boolean is refused by name[unit] src/config.test.ts::plane block (routing-and-config item 31, record 0064)::coldFallback parses as a boolean, defaults false, and a non-boolean is refused by name (record 0064)
The shadow outcome post from the admission stage: proceeded on a claim, refused:thread-live on a thread-in-flight refusal, nothing for a steer, nothing under off[unit] src/core/dispatch/admission.test.ts::admit — the plane's shadow outcome post (orchestration-plane item 8)::under shadow a claim posts proceeded with the requester, the thread and the admission stage, ::under shadow a second ask on a live thread that asks for another agent posts refused:thread-live, ::a steered follow-up is not an ask for a new run: nothing is posted for it, ::off — the default — posts nothing, for a claim and for a refusal alike
The moves (item 10): a tracked pull request whose title fails the rule gets one retitle effect and a passing title none; a child sealed with a pushed branch, no pull request and no live runner gets pr_open from the branch, a seal missing the watch's precondition nothing, and a pushed branch with no known repository a finding, never a guessed move; mergeableState: dirty at an approved head gets rebase_round briefed "rebase onto the base and push" keyed by the head, any other state nothing; an errored or terminated instance with units unfinished — or the hosting deadline passed on one not waiting — gets one reissue keyed by the attempt, and a plan with nothing unfinished none[unit] src/core/plane/decide.test.ts::the moves (record 0064, 'Endings and the watches')::*
The findings (item 10): two findings on one watch and subject are one, timelines merged in time order and bounded to the newest; the research request names the watch and subject and fences the timeline as untrusted data; the filing path files once through the tracker seam under the plane's label and answers an open issue with the finding's title as a duplicate[unit] src/core/plane/findings.test.ts::mergePlaneFindings — two findings on one subject are one (record 0064)::*, src/core/plane/findings.test.ts::the research dispatch and the filing path (record 0064)::*
13: a move from the orchestrator thread is fenced like the Slack door — a grant-less person refused by the policy table before the service is asked, the same policy row admitting the person that holds the grant, and the move's blast radius destructive at every input so the confirm card renders the class the Slack door confirms[unit] src/core/commands/plane.test.ts::a move from the orchestrator thread is fenced like the Slack door::*
13: pulls merge and pulls enqueue register under merge:write, destructive at every input; a grant-less person is refused by the table before the door is asked; an unbound person is refused by name; the merge lands at exactly the approved head with the person's login on the commit and in the answer[unit] src/core/commands/merge.test.ts::pulls merge and pulls enqueue — the merge door as commands (record 0070)::*, src/execution/githubPulls.test.ts::githubPulls::the plan runner's merge — mergePullRequest and fetchCommitChecks::a merge naming the person it acts for carries a \Merged-by:` trailer as the commit message — the App holds the token, the commit names them (record 0070)`
13: the merge refuses an unapproved head, a standing changes-requested at it, a red check, one still running and a head with no check reported, each by reason with nothing merged; GitHub's own refusal is answered in GitHub's words; the release pull request is answered with the handoff card on both commands and no merge is called[unit] src/core/commands/merge.test.ts::the merge refuses unapproved heads and hands the release to a person::*
13: the enqueue is atomically pinned to the reviewed head and acknowledged only by a matching native entry, then recorded under the person's name — the answer carries their id and bound login — a still-running check passes it (the queue's job), and it keeps the approval and red-check fences[unit] src/core/commands/merge.test.ts::the enqueue recorded under the person's name::*, src/execution/githubPulls.test.ts::githubPulls::the merge queue — the rule, the enqueue and the queue's outcome (issue 2011)::enqueuePullRequest pins the mutation and validates the native entry; a failed lookup or write cannot acknowledge it
13: a grant-less person is refused pulls merge by the policy table — the merge:write row admits the grant alone, never a chat or browser baseline[unit] src/core/authz/policy.test.ts::merge:write command [has-grant(merge:write)]::*

The criterion marker requires the declaration below, which binds the existing acknowledgement criterion to its source and exact proof. Removing or disguising its fence fails the gate. It validates references and the closed outcome set; it does not prove every effect adapter or retry path.

json
{
  "id": "plane.ack",
  "criterion": "plane-effect-ack",
  "kind": "effect-result",
  "input": {
    "file": "src/core/plane/decide.ts",
    "symbol": "PlaneAckOutcome",
    "kind": "trusted-adapter-result"
  },
  "validator": {
    "file": "deploy/cloudflare-memory/worker.ts",
    "symbol": "handlePlane"
  },
  "consumer": {
    "file": "deploy/cloudflare-memory/worker.ts",
    "symbol": "RunHistoryDO.planeAck"
  },
  "terminal": [
    "done",
    "skipped"
  ],
  "retry": {
    "kind": "same-effect-until-ack",
    "owner": {
      "file": "src/core/runLedger/writeThrough.ts",
      "symbol": "createLedgerWriteThrough"
    },
    "identity": "effect-id",
    "pending": [
      "deferred"
    ],
    "outstandingCap": {
      "file": "src/core/plane/decide.ts",
      "symbol": "PLANE_EFFECTS_TOTAL_CAP"
    }
  },
  "failure": {
    "invalid": "refuse",
    "transport": "ack-unconfirmed"
  },
  "proofs": [
    "deploy/cloudflare-memory/runs.test.ts::orchestration plane \u2014 the tables, the decider, shadow and the effects::an offered effect rides the heartbeat answer; deferred leaves it offered, done closes it, an unknown id is a no-op"
  ]
}

Roadmap ​

The plane's later units (record 0064) grow the decider into the plane: the runner conditions, the executor's resident-stage ask (item 9's gap), the quiet and pressure windows, the bot-side observation posts and effect executors for item 10's moves, and the panels beside the table. Each lands with its own items here.

| Publication preserves the actual failing producer as a closed diagnostic: GitHub identity/access, plane identity/visibility and thread-work snapshot checks remain separate predicates. | [unit] src/core/dispatch/mainAudience.test.ts::main conversation source audience::distinguishes missing identities, lost access and changed snapshots without source text |

CriterionProof
Push and heartbeat run the same closed reconciliation effect, defer missing/draining/invalid receipts, and pass exact report receipts to the existing ACK.[unit] src/channels/planeEffects.test.ts::*::*; src/core/runLedger/writeThrough.test.ts::*::*
A restarted original execution retains its exact terminal report rendering and private delivery identity; native completion never invents success, and active effects retain execution ownership.[unit] src/core/coordinator/reconcileExecution.test.ts::original execution reconciliation performer::*; src/core/coordinator/reportFinalization.test.ts::original coordinator report finalization::*; src/core/coordinator/contract.test.ts::retained terminal report rendering::*
Public terminal delivery with a nonempty thread copy records its original owner, proposal and frozen thread hash after a positive channel ACK. Producer and discovery retries suppress delivery when that immutable ACK exists; absent delivery remains retryable, and malformed/conflicting ACK bytes remain protected. Native outbox acknowledgement independently reads this proof and forbids public receipts on private reports.[unit] src/core/coordinator/reportFinalization.test.ts::original coordinator report finalization::*; src/core/coordinator/workflowReconciliation.test.ts::coordinator Workflow reconciliation identity::decodes only typed public delivery references and refuses extra receipt data; deploy/cloudflare-memory/runLedger.test.ts::durable coordinator Workflow reconciliation::finishes only the original frozen report and status before acknowledging, then permits reentry; src/channels/adminCoordinator.test.ts::*::a final ending without a channel retains pending report delivery and unconsumed leftovers
Existing terminal report discovery requires the original raw delivery identity and retained rendering or strictly decoded canonical original bytes before acquiring an outbox obligation. Missing/corrupt identity, admission or bytes leave historical ending/private state unchanged and create no permanently blocking offer; both store implementations share this predicate.[unit] src/core/coordinator/reportReconcileEligibility.test.ts::original report discovery eligibility::*; src/core/coordinator/instanceStore.test.ts::*::never creates a blocking report offer for legacy endings without their original delivery identity and rendering; deploy/cloudflare-memory/runLedger.test.ts::durable original report eligibility::*
Only an explicit HTTP/MCP job channel state-delivery capability may select an empty thread copy at the first terminal report admission. The complete original report and status stay durable; its empty-copy receipt does not claim a message was delivered. A missing or merely undeliverable handle cannot select this contract, and existing endings, admissions or frozen nonempty copies remain owed without rewriting their bytes.[unit] src/core/nullChannelIo.test.ts::nullChannelIO::declares state report delivery only for known machine jobs and their children; src/channels/adminCoordinator.test.ts::original Workflow report reconciliation through the admin settlement::finishes a first machine job report as durable state without claiming a reply; ::preserves an existing nonempty report obligation after its channel becomes state-only; ::never changes an original frozen proposal when selecting initial machine report delivery
Maintenance commands and authenticated watches reserve each actual PR under canonical ownership. Only an accepted original native push advances the exact binding; changed grants, rival owners and moved native heads refuse before another write. Restart reuses the same frozen plan.[unit] src/core/coordinator/maintenanceSweep.test.ts::durable maintenance sweep integration::*
A producer retry after its first empty machine report committed but freeze failed adopts only the exact original owner/proposal admission. It needs no current adapter capability, preserves the original full report and raw delivery identity, and finishes the empty-copy receipt without a reply. Missing admission, changed identity and retained nonempty copies cannot be silently emptied.[unit] src/channels/adminCoordinator.test.ts::original Workflow report reconciliation through the admin settlement::retries only its own committed empty machine admission after the first freeze fails; ::does not adopt an empty legacy rendering without its exact original admission
Explicit machine state delivery applies only before the first report admission. A conservative terminal settlement after an earlier segment retains its nonempty thread copy.[unit] src/core/coordinator/reconcileExecution.test.ts::original execution reconciliation performer::preserves a thread copy when a prior segment already admitted its report

Maintenance keeps the existing owner ​

An authorized command or authenticated watch supplies its actual source intent and exact target. Admission reserves the unit's existing effect cell before local preparation. An eligible ended unit keeps its original requester, report, private brief and history. An unowned PR receives a logical maintenance instance and unit in the same store; that identity has no native Workflow. Its immutable lease, turn bound and reserved paid-round allowance survive retries. The frozen plan names each native call or funded model child. Unstarted no-op work can release only its exact reservation; pending or uncertain native calls retain it.

Native-only maintenance spends no model charge. A coding resolver needs positive original child admission and explicit funding; an unanswered dispatch neither records a charge nor replays. The allowance is budget accounting, while the dispatcher enforces the original lease and turn bound. Child metadata carries the maintenance action instead of inventing a Workflow recipient. Exact accepted native push evidence advances a publication binding; a display head, mutable listing or prose does not.

CriterionProof
Actual command/watch intent reserves the existing cell before preparation, without a native Workflow. Replays retain original immutable bounds and owner; active producers, foreign watch sources and incomplete scans refuse. Logical unowned maintenance admission is the same store transaction and refuses a rival.[unit] src/core/coordinator/maintenanceAdmission.test.ts::durable maintenance admission::atomically reserves one existing cell before any local preparation, without requiring a Workflow; ::creates a real rowless maintenance owner and replays only its exact original source intent; ::preserves an ended original owner's requester and terminal report while refusing active producers; ::refuses incomplete owner scans, changed exact target and a watch source naming a different original owner; deploy/cloudflare-memory/runLedger.test.ts::durable maintenance admission::atomically reserves actual command intent and refuses a rival without a Workflow
A model child is charged only after positive original admission. Lost admission retains the pending cell, and withdrawn authority refuses before native dispatch.[unit] src/core/coordinator/maintenanceSweep.test.ts::durable maintenance sweep integration::charges only a durably admitted coding child and retains its live ownership across restart; ::an unanswered model admission retains the pending cell without charging or replaying; ::authority withdrawn after preparation never begins native dispatch
Native terminal child records preserve exact maintenance transport identity; a conflicting or missing identity cannot overwrite it, and completion never calls a Workflow for this logical owner.[unit] deploy/cloudflare-memory/runLedger.test.ts::run ledger — the coordinator's event and the key (items 47–48)::preserves exact maintenance transport in the native ledger and never calls a Workflow on completion
Cancellation releases only an exact never-begun reservation, including lost cancellation acknowledgement. A prepared plan that began a call or has accepted results retains them.[unit] src/core/coordinator/maintenanceAdmission.test.ts::durable maintenance admission::recovers exact no-op cancellation ACK loss but never cancels a begun or foreign action; ::releases a prepared multi-call plan only before any call begins and retains accepted results
A maintenance child keeps exact original action, owner, conversation, absolute lease, turn bound and reserved paid-round allowance. Coding uses the unit thread and review uses its review thread. Private input commits before fresh audience verification; missing/lost input or a dispatch without a positive native run-start callback cannot credit admission.[unit] src/core/coordinator/maintenanceChild.test.ts::original maintenance child admission::native callback retains exact %s owner, conversation, lease and cost cap; ::private input is saved once before its verifier reads it, with stable original effect identity; ::private %s never starts a child or deletes saved input; ::dispatch %s without a positive native callback stays uncertain and releases once; ::actual callback resolves admission before background completion and preserves bound adapter methods; ::DM stamps require exact requester provenance and fresh verifier success; a lease spent during verification refuses

| A maintenance child freezes the original requester and coding/review conversation before preparation, rereads its canonical owner and stop after audience awaits, and refuses drift before paid dispatch. The actual dispatcher receives exact remaining milliseconds; rounded minute prose cannot extend the original lease, and a subminimum remainder starts no child. | [unit] src/core/coordinator/maintenanceChild.test.ts::original maintenance child admission::a fractional lease bounds the actual dispatcher with exact remaining milliseconds and integer prose; ::remaining lease below the dispatch boundary minimum never starts paid work; ::a different frozen %s never dispatches the original child; ::fresh %s after audience awaits refuses before paid dispatch; src/core/coordinator/maintenanceSweep.test.ts::durable maintenance sweep integration::freezes the original %s conversation instead of the wiring's synthetic identity; ::a canonical stop after pending spawn admission prevents the service from dispatching a snapshot owner; src/core/dispatcher.test.ts::trusted original action remaining clock::clips the actual dispatcher profile without fabricating a parent run |

| Execution identity ignores JSON object key order and retains every authority field; exact whole-unit CAS remains unchanged. | [unit] src/core/coordinator/recoveryHistory.test.ts::recovery history store::active recovery effect preserves execution key order | | Maintenance native admission verifies exact durable child identity without comparing bot and Worker timestamps. The Worker refuses a new call at the original server deadline without changing its prepared cell; ordinary Workflow evidence retains its original timestamp floor. | [unit] deploy/cloudflare-memory/runLedger.test.ts::maintenance server clock::refuses a new maintenance begin at the original server deadline without changing the prepared cell; ::credits an exact durably admitted maintenance child whose bot start precedes the Worker timestamp; src/core/coordinator/unitEffect.test.ts::spawn effect durable receipt::ordinary Workflow child evidence cannot predate its original instance | | Settled maintenance receipts describe acknowledged historical writes. Later native head moves do not keep known calls active or manufacture a current publication binding; unknown calls retain ownership. | [unit] src/core/coordinator/maintenanceSweep.test.ts::durable maintenance sweep integration::settles already published accepted history even when the native head moves before settlement, ::settles known accepted history without inventing a current binding when the head moves before publish |

CriterionProof
Retained maintenance ownership never dispatches another requester, channel, credential or relay as its original owner. A new same-owner intent may bind its natively read moved head/base only after prior settlement and a complete atomic ownership scan; active/uncertain effects, live children and rivals retain ownership. Original Ship requester, private report and historical base remain unchanged.[unit] src/core/coordinator/maintenanceAdmission.test.ts::durable maintenance admission::refuses foreign requester context for a retained standalone or ended Ship owner; ::rebinds a new same-owner intent to the current native head and base without changing original private custody; ::holds active effects, accepted live children and rivals before rebinding a new intent; deploy/cloudflare-memory/runLedger.test.ts::durable maintenance admission::atomically rebinds only a settled same-requester intent and retains foreign or live owners
A retargeted maintenance plan and its native child use the frozen effect/publication base, preserving historical requester, private brief, conversation and report. A later base drift refuses new calls; ordinary Workflow/recovery execution retains the historical instance base.[unit] src/core/coordinator/sweepEffectJournal.test.ts::durable sweep journal::executes retargeted %s maintenance against its frozen base while preserving original custody; ::holds a frozen maintenance plan when its base drifts again and keeps ordinary Workflow base checks; src/core/coordinator/maintenanceChild.test.ts::original maintenance child admission::retargeted maintenance sends the frozen base to its %s child without changing original custody; ::ordinary Workflow children still refuse a base that differs from the historical instance; ::fresh %s after audience awaits refuses before paid dispatch; ::private input is saved once before its verifier reads it, with stable original effect identity

The embedded chat shows the same saved-action confirmation control as Threads. Confirm and Cancel post only the saved id through the existing send route.

CriterionProof

| Merge and queue entry use the verified linked person's GitHub binding after MCP approval. The credential remains the audit actor, and approval is consumed once. | [unit] src/channels/mcpApprovals.test.ts::MCP approval parity::a browser-approved MCP… |

CriterionProof
The shared state alarm saves its next wake before native Workflow discovery or other external work starts. A process loss before finally cannot remove that saved wake. An earlier deadline for other work is preserved. This also applies with no live agent runs and with physical history maintenance paused.[unit] deploy/cloudflare-memory/runLedger.test.ts::durable coordinator Workflow reconciliation::saves the next wake before native discovery…; ::blocks native discovery until the next wake is acknowledged and resumes on a later alarm
A merge command with a recorded head refuses a later approved head; existing callers without the option keep the current merge door[unit] src/core/commands/merge.test.ts::pulls merge and pulls enqueue — the merge door as commands (record 0070)::merges the recorded head and refuses a later approved head for the same saved action

Browser cutover ​

The old Plane table and pinned chat are replaced by Home under record 0102. Native Plane commands and immutable owner records remain unchanged. Home's one composer uses the server-selected broad session and explicit thread focus.

CriterionEvidence
Home keeps completed original pipeline work beside moving work and renders one explicit-destination composer[unit] web/green/frontend.test.ts::Green browser frontend::keeps completed pipeline units visible beside proven moving preview work, ::mounts one composer and changes its announced destination only after explicit thread focus
Saved choices use the original exact ID without pasting or retargeting[unit] web/green/components/confirmationPrompt.test.ts::Scoped saved action confirmation::replays an uncertain confirmation with its original key and exact choice

Registry commands in the orchestrator ​

With operator mode on, a fresh root orchestrator run in a public, private-channel or machine conversation can use command_schema and command_run. It uses the same prepared projection, typed bindings, recorded decision and registry policy as the verified-DM command path. The command's native result goes to its original conversation; the model receives only the outcome. Delegated readers and coordinator children do not acquire command-write authority from the orchestrator preset's name. This adds no private source or linked-work capability outside the verified requester DM.

The latest delivered requester turn supplies the action source. Its sender, bound credential and relay identity must stay the same. MCP follow-ups retain the exact connection, request hash and consent mode used for approval. Combined settlement text remains context; it has no single command source. A queued foreign, indirect or unknown source closes command authority immediately. A queued valid requester turn does not become the selected source until the runtime confirms delivery. A stop, lost source, changed mode, failed decision save or unproved recovery holds the command. An acknowledged selection is neither permission nor execution proof.

CriterionProof
Explicit orchestrator requests expose registry commands on Slack, CLI, HTTP and MCP without a preceding routing-model call. Private worker and Slack-context tools stay absent there.[unit] src/core/dispatcher.test.ts::dispatch::an explicit orchestrator request on…; ::records a shared orchestrator command before its authorized effect on…
A bound credential retains its grants. The command decision is saved before the effect or confirmation offer. A foreign requester cannot consume the approval.[unit] src/core/dispatcher.test.ts::dispatch::shared commands retain the bound credential's…; ::shared command confirmation keeps the original requester and conversation
Missing decision storage holds a command; a later tracked request can execute. Operator off and shadow keep this command path disabled.[unit] src/core/dispatcher.test.ts::dispatch::holds a shared command without decision storage and permits a later tracked request; ::keeps shared command effects off when operator mode is…
A delegated read-only orchestrator does not acquire the root conversation's command authority.[unit] src/core/dispatch/runLoop.test.ts::*::shared command authority belongs to a root conversation rather than a delegated reader
Public follow-ups keep their exact credential and acquire no private-audience stamp. Queue observation cannot select an undelivered request. An untrusted queued source also closes an in-flight call before its effect.[unit] src/core/dispatch/mainSource.test.ts::main action source::binds a public requester follow-up without inventing a private audience; src/core/dispatch/runLoop.test.ts::*::shared commands use confirmed requester turns and stop on an untrusted queued source
MCP follow-up approval retains its connection, request hash and consent mode through admission, durable replay and source selection. Bare confirmation cannot replace recorded consent. Malformed approval metadata is held.[unit] src/core/dispatcher.test.ts::dispatch::MCP follow-up approval keeps its connection and refuses bare confirmation; src/core/dispatch/mainSource.test.ts::main action source::preserves the delivered MCP approval binding through durable follow-up selection; src/core/runLedger/inboxMessage.test.ts::durable inbox continuity::retains merged source context and refuses malformed approval metadata
Every recovery path holds shared command authority: transcript resume, setup restart and request-only restart with the original or a replacement run ID.[unit] src/core/dispatcher.test.ts::dispatch::holds shared commands through dispatcher recovery…; src/core/dispatch/runLoop.test.ts::*::holds shared commands on recovered root conversations and permits a fresh requester
A fresh turn made from more than one follow-up cannot expose shared command tools or use one sender's authority for another sender's text. This hold survives durable replay and later follow-ups. A single fresh requester can still run authorized commands.[unit] src/core/dispatcher.test.ts::dispatch::merged public input cannot borrow requester authority…; src/core/dispatch/mainSource.test.ts::main action source::does not restore command authority from merged context or a later ordinary follow-up; src/core/runLedger/inboxMessage.test.ts::durable inbox continuity::retains merged source context and refuses malformed approval metadata
Generic requests enter the orchestrator loop with all authorized action tools, without a separate operator model. External live/unit folds and unlinked legacy tasks have the same saved decision owner.[gap] Remaining shared front-door migration. This registry-command cut does not enable GitHub evidence delivery.

Orchestrator read delegation ​

A fresh public or machine orchestrator with operator mode on can use the existing spawn_run, await_runs, list_runs and get_run_status tools. The spawn call must quote the latest delivered requester turn. The existing action binder checks the selected preset and requester-authored PR target. The existing dispatcher applies the child's permissions, identity policy and profile limits.

Before a child thread opens, the parent saves the selection and its frozen context in the same bounded decision history as commands and private work. The original requester source must remain current through binding, context capture and thread creation. Time spent in those steps reduces the original launch budget. A repeated tool call returns its original in-process result; changed arguments under the same call ID are held. Recovered, delegated and merged-source conversations receive no delegation tools. Recovery does not replay a saved selection as a new launch.

Write starts keep their existing paths. The child-steer extension below uses the shared requester decision boundary. Generic messages, live folds and unit folds still need front-door migration. This cut does not enable a GitHub event producer.

CriterionProof
Fresh orchestrator conversations record the original requester and source before child thread creation across Slack, CLI, HTTP and MCP; no second routing model runs[unit] src/core/dispatcher.test.ts::dispatch::records requester delegation before opening a child thread on %s
The existing typed binder preserves a current requester-authored PR target and refuses missing, foreign or relabeled identifiers; a child's narrower budget stays separate from parent directives[unit] src/core/dispatch/conversationRead.test.ts::conversation read binding::*
Recovery, delegation and merged input withhold read delegation; saved decisions precede child launch; failed storage or changed sources hold; one call ID cannot create another child or select different work[unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::offers child controls only to a fresh root with one requester source, ::delegation keeps its saved source through the spawn path: %s
A source revoked during context or thread creation cannot dispatch a child; a new current call can still launch[unit] src/core/dispatch/spawn.test.ts::child launch after asynchronous waits::holds a revoked requester source after the %s wait
The requester tool reuses spawn validation, requires a source quote and forwards the original tool-call identity; saved spawn decisions survive the existing journal round trip[unit] src/tools/runs.test.ts::requester delegation tool::*; src/core/dispatch/orchestratorDecisions.test.ts::orchestrator decision records::uses one history and identity space for commands and worker decisions

Recorded child steers ​

A fresh public or machine orchestrator can use send_to_run only for its own live children. It selects the delivered requester source before the existing target lookup. The current source, original parent and remaining budget are checked again before context capture, decision storage and delivery. The existing child ownership, requester permission and durable inbox checks remain in force.

The existing bounded history saves the selected target and input/context hashes before a message is sent. An internal message ID links the input to that exact decision; the parent identity stays the actual sending run. An identical live call returns its original result. Changed input under the same call ID is held. An unconfirmed delivery closes further changes in that run. Recovery does not replay a saved selection. This does not add durable delivery receipts or migrate external source evidence.

CriterionProof
Source selection precedes target lookup and reuses the existing child gate[unit] src/tools/runs.test.ts::requester child steer tool::*
Canonical context avoids an earlier unneeded conversation read before source selection[unit] src/tools/runs.test.ts::requester delegation tool::uses canonical context without reading a second conversation before source selection
Child controls stay absent for recovery, delegated or merged sources; saved decisions precede delivery; storage/source failures hold; unknown delivery prevents another send[unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::offers child controls only to a fresh root with one requester source; ::records child steers before the existing delivery path: %s
The decision message ID and actual parent/requester survive the existing durable steer construction, and the new decision kind survives history replay[unit] src/core/dispatch/spawn.test.ts::child launch after asynchronous waits::keeps an internal decision message ID and parent provenance in durable steering input; src/core/dispatch/orchestratorDecisions.test.ts::orchestrator decision records::uses one history and identity space for commands and worker decisions