Private worker IO
An internal worker thread has its own stable identity and event log. A worker's channel handle can record replies and progress without a Slack handle or thread-open operation. This primitive is separate from the Ship unit's authority and from the main conversation's public replies.
- Code:
src/channels/legacySlackAudience.ts,src/channels/originalUnitRecipient.ts,src/channels/privateWorker.ts,src/core/privateWorkerLog.ts,src/core/privateWorkerLogWorker.ts,src/core/coordinator/handOff.ts,src/core/coordinator/mainWorkerRelay.ts,src/core/dispatch/mainWorkerCapability.ts,src/tools/mainWorker.ts,src/core/threadAdmission.ts,src/channels/adminCoordinator.ts,src/channels/liveView.ts,src/channels/liveView/viewer.ts,src/channels/residentsView.ts,src/core/coordinator/driver.ts,src/core/dispatch/threadAssets.ts,src/core/dispatcher.ts,src/core/types.ts,src/core/planeService.ts,src/core/resumeLaunch.ts,src/core/runsService.ts,src/core/unitRuns.ts,src/index.ts,deploy/cloudflare-memory/worker.ts - Tests:
src/channels/legacySlackAudience.test.ts,src/channels/originalUnitRecipient.test.ts,src/channels/privateWorker.test.ts,src/core/coordinator/handOff.test.ts,src/core/coordinator/mainWorkerRelay.test.ts,src/core/dispatch/mainWorkerCapability.test.ts,src/core/threadAdmission.test.ts,src/tools/mainWorker.test.ts,src/channels/adminCoordinator.test.ts,src/channels/liveView.test.ts,src/channels/residentsView.test.ts,src/core/coordinator/driver.test.ts,src/core/dispatcher.test.ts,src/core/planeService.test.ts,src/core/resumeLaunch.test.ts,src/core/runsService.test.ts,src/core/unitRuns.test.ts,deploy/cloudflare-memory/runLedger.test.ts - Docs: Thread admission, Ship
Behavior
| Criterion | Proof |
|---|---|
| The instance and unit derive one internal namespaced thread key after rehost; malformed identities are refused before an event is written. | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::derives the same internal thread key after rehost and rejects ambiguous identities |
| Before a main task claims its unit or starts a Workflow, including a retry of a saved unit, the exact private thread's durable log answers a read; an unavailable log refuses without starting a Workflow. | [unit] src/core/coordinator/handOff.test.ts::main-agent work hand-off::proves the durable private log before claiming a main task or starting a Workflow, ::rechecks the private log before retrying a saved main task |
| An authorized input id is idempotent. Replies and attributed prior inputs rebuild as history after rehost; progress and the triggering input stay out of that history. The handle offers no Slack thread creation. | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::persists attributed input and worker replies, then rebuilds history without status noise or the current request |
| A main-linked worker receives the bounded durable work brief, even when the main run input is long; its private log records the actual child request. | [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::starts from the bounded work brief instead of a long main-run request |
| A private input longer than the log limit keeps a shortened history copy with a visible marker and digest of the full text; retries keep one valid row, changed text after the cutoff is refused under the same id, and no code point is split. | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::bounds escaped long input without splitting a code point or duplicating its retry; [worker] deploy/cloudflare-memory/runLedger.test.ts::private worker log on the state Worker::stores ordered private history, replays an input id once, and refuses a changed replay |
| A valid full unit report fits the durable reply limit even with JSON escapes. An oversized channel reply keeps a marked, valid private copy. | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::bounds an oversized private reply while preserving a full valid unit report |
| A status update is appended in order, and closing it waits for every queued frame. | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::writes ordered status frames and waits for updates before closing the status |
| A missing durable log fails by name, so the worker cannot silently claim a delivered reply or progress update. | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::fails closed when the private log is unavailable |
| The state Worker persists ordered private input, reply and status rows. A repeated input or settlement id returns its original row; a changed replay is refused. | [worker] deploy/cloudflare-memory/runLedger.test.ts::private worker log on the state Worker::stores ordered private history, replays an input id once, and refuses a changed replay |
| A main-agent unit binds to its private thread before spawn. Its coding child receives the brief and replies through private IO without opening or posting to Slack; missing durable IO refuses admission. | [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::a main-agent worker starts and replies only through its durable private thread; [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::a main-agent worker refuses admission when its durable log is missing |
| A main-linked private worker starts only from its stored act, requester, original proved private conversation and exact internal unit/spawn identity. The coordinator and dispatcher recheck that audience before the private seed reaches a model; a lost or changed audience refuses admission, including after rehost or an attach restart. Its channel logs replies and progress internally and never posts as the child in Slack. | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::checks the original act and private requester before exposing worker history; ::rehosts a saved private child from its original unit and requester DM; src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::rechecks the original requester DM before dispatching a private child; src/core/dispatcher.test.ts::dispatch::admits a bound internal worker only after fresh requester DM verification; ::refuses a changed DM or foreign coordinator before a private worker model starts; src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::restarts an attaching private child from its durable coordinator row |
| A worker's full final report and wake reply use stable delivery ids. A 20,000-character report survives the private log even with JSON escapes; a failed private write retries under the same id, even when the requester-facing report is quiet. An exhausted final-report delivery never replaces a committed ending with failure, and an exhausted wake refuses to advance to another wait id. | [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::retries a long private unit report after a transient log failure without losing or duplicating it; [worker] deploy/cloudflare-memory/runLedger.test.ts::private worker log on the state Worker::stores ordered private history, replays an input id once, and refuses a changed replay; [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::replays a saved private wake answer until its reply is durably logged once; [unit] src/core/coordinator/driver.test.ts::the plan runner's driver — the Workflow body over the step runner (item 9)::does not advance to a new wait when private wake delivery stays unavailable; [unit] src/core/coordinator/driver.test.ts::the plan runner's driver — a step that throws inside the walk becomes the unit's ending (issue 2100)::a private report delivery outage never replaces the original unit ending |
| A recovered worker whose row settled before its private report was logged retries that report before acknowledging replay, without duplicating it. | [unit] src/channels/adminCoordinator.test.ts::POST /admin/coordinator/recover-unit — unchanged-head original-unit recovery::replays a recovered private unit report after its settlement committed but the log failed |
| Held and idle private worker reports stay in the private log. Hosted parent events project only unit state, and pull request comments never copy the private report. | [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::keeps a private worker's held report out of hosted events and pull request comments; [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::keeps a private worker's idle report out of hosted events and pull request comments |
| Ordinary unit and parent-run views show a private worker's state and time without its report, title, or internal thread key. | [unit] src/core/unitRuns.test.ts::unitFactsOf — private worker projection::keeps the private ending, title, and thread out of ordinary unit facts |
| A private worker's round-gate findings remain in the coordinator row for its next decision; the hosted parent event, card, and ordinary unit facts omit them. | [unit] src/channels/adminCoordinator.test.ts::the plan runner's steps — plan, unit-start, branch, round, unit-end, finish (item 9)::keeps a private worker's round-gate findings in its unit row, not the hosted run or card; src/core/unitRuns.test.ts::unitFactsOf — private worker projection::keeps a private round gate in the coordinator row, not ordinary unit facts |
| Ordinary run lists, points, event pages, friction and live streams hide private worker prompts and replies across this process, another generation's ledger and persisted history. The coordinator can still read its original child by a non-serializable internal grant. | [unit] src/core/runsService.test.ts::RunsService.getRun::hides a private worker's live and persisted prompt from ordinary run reads; src/core/runsService.test.ts::RunsService with the run ledger — one registry across generations (run-history item 41)::hides a private worker live on another generation's ledger while preserving an internal read; src/channels/liveView.test.ts::live view on RunsService: history pages + index toggle::the viewer's actor binds the index and the tokenless history routes (authorization.md items 5–7)::keeps private worker rows and live tokens out of every ordinary runs page and feed; src/channels/residentsView.test.ts::createResidentsViewHandler — the runs on residents and the live feed (resident-repos item 42)::hides private worker runs from the residents seed and feed even for an admin |
| Ordinary list paging passes private rows internally without exposing their ids in page cursors, then marks history incomplete at a fixed scan bound. A plan stop can still find and hard-stop its linked private child. | [unit] src/core/runsService.test.ts::RunsService.getRun::pages past private worker records without exposing their ids as cursors; src/core/runsService.test.ts::RunsService.getRun::bounds a private-only history scan and marks the ordinary page incomplete; src/core/planeService.test.ts::plane stop — the runner_stop move (record 0064; issue 1924)::hard-stops a linked private worker while keeping ordinary plane reads private |
| A resumed private worker carries its recorded input id into its channel handle, so history omits the triggering turn after rehost. | [unit] src/core/resumeLaunch.test.ts::launchResumes::passes the stored private input id to rehosted IO so history excludes the triggering turn |
| The requester-bound relay service projects bounded worker progress and the unit's final report, using durable event sequences as replay cursors. It rechecks the exact linked unit and omits raw worker input, freeform replies, status details and activity. | [unit] src/core/coordinator/mainWorkerRelay.test.ts::main worker relay::returns only bounded progress and the durable final report to the linked requester; [unit] src/core/coordinator/mainWorkerRelay.test.ts::main worker relay::uses the durable sequence as a replay cursor and pages without skipping private rows; [unit] src/core/coordinator/mainWorkerRelay.test.ts::main worker relay::refuses another actor, thread, channel, or mismatched durable binding before reading private history; [unit] src/core/coordinator/mainWorkerRelay.test.ts::main worker relay::bounds a long final report; [unit] src/core/coordinator/mainWorkerRelay.test.ts::main worker relay::fails by name on an unavailable log or invalid cursor; [worker] deploy/cloudflare-memory/runLedger.test.ts::private worker log on the state Worker::pages private events after a durable cursor with a fixed bound |
On a later main-agent turn in a verified unshared requester Slack DM or organization-private browser conversation, the orchestrator may read linked work. The run binds the resolved actor and current thread; model input supplies only the act id and progress cursor. Unverified, shared, external, pending, and unverified ordinary web turns have no private read capability, and their model-visible and resident-seeded tool lists omit work_progress, because tool calls and results persist in run events. The run requires a channel-supplied audience attestation and a fresh verifier check before exposure, before each read, and before returning its result; an absent verifier removes the tool. The model receives fenced, bounded progress and final report data, without child Slack delivery or private transcript rows. | [unit] src/tools/mainWorker.test.ts::work_progress — the main agent reads its linked private worker::*; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::binds a later main turn to the resolved requester and current thread; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::keeps private worker reports out of shared and web conversation run events; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::hides private progress for an unverified, shared, external, or pending Slack D conversation; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::rechecks the direct audience on every private read and denies a changed audience; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::drops a private result when the audience changes during a read; [unit] src/core/dispatch/run.test.ts::claimRun — the ledger claim once the prompt exists::does not seed a shared-channel orchestrator run with the private progress tool; [unit] src/core/dispatch/run.test.ts::claimRun — the ledger claim once the prompt exists::does not seed a Slack D run with private progress without fresh unshared audience proof; [unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::does not show the private progress tool to a model in a shared or unknown channel; [unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::does not show private progress to a model in an unverified or newly shared Slack D conversation; [unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::a later orchestrator turn reads only its linked private worker projection through the tool context; [unit] src/tools/toolsets.test.ts::filterUnavailableTools — run-bound private tools::omits an unavailable tool from the model-visible list without changing other tools |
| The original verified private audience stamp survives reserve, claim, restart and rehost only when its requester and thread match. A recovered private DM run refuses without exposing private progress, even when its saved stamp matches and the live audience verifies; the requester must send a new DM message. A fresh verified turn can read its linked worker's durable progress. Slack U and W requesters both qualify after verification. | [unit] src/core/runLedger/inboxMessage.test.ts::durable inbox — staged references (record 0033)::keeps a matching private audience stamp for a restarted request and drops a forged one; [unit] src/core/dispatch/provision.test.ts::reserveRun — the ledger reservation before the attach::stores only a matching requester DM provenance on the reserved row; [unit] src/core/dispatch/run.test.ts::claimRun — the ledger claim once the prompt exists::does not seed a Slack D run with private progress without fresh unshared audience proof; [unit] src/core/resumeLaunch.test.ts::the pure pieces::resumeMessage carries only a matching private audience stamp for a fresh verifier on rehost; [unit] src/core/dispatch/privateAudience.test.ts::private audience publication gate::fails closed on a recovered private run even when no indirect follow-up remains pending; [unit] src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::a resumed main run refuses replayed private source evidence before the model or Slack reply sees it; [unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::a later orchestrator turn reads only its linked private worker projection through the tool context; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::accepts a verified Slack W requester on its own linked work |
| A main run permanently loses private worker reads when a foreign user, app, or run follow-up joins it, including after the inbox drains. Durable input events must prove one source before a resumed capability can read private progress; the dispatcher still refuses a recovered private DM run. A denied read persists no private projection. | [unit] src/core/threadAdmission.test.ts::FollowUpInbox — one durable follow-up folds in once (thread-admission item 5)::permanently records a foreign, app, or run source after its follow-up drains; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::permanently revokes private reads when an app follow-up joins the main run; [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::keeps a resumed main run private only when its durable inputs prove one source; [unit] src/core/dispatch/privateAudience.test.ts::private audience publication gate::fails closed on a recovered private run even when no indirect follow-up remains pending; [unit] src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::revokes private progress when an app follow-up folds into the live main run |
| A fresh turn formed from multiple unconsumed follow-ups drops the first sender's private audience proof, including when the later input came from another person or an app. A single direct follow-up may retain validated proof. | [unit] src/core/dispatch/settle.test.ts::prepareFreshTurn — the one request the unconsumed follow-ups run as::does not carry private audience proof into a merged fresh turn with another sender or app |
A later main turn does not replay a saved work_progress result into its model prompt or session tools. It reads linked progress again under the current requester and thread. | [unit] src/core/dispatcher.test.ts::dispatch::continues a main DM after a saved work_progress read without replaying its answer |
| A worker ending wakes the same main conversation without posting as the child, and the main agent decides what to tell the user while remaining responsive to new requests. | [gap] |
| Browser private worker recovery resolves only the exact captured source run and original claim; lost source proof blocks reads and late output | [unit] src/channels/privateWorker.test.ts::private worker IO — a task thread with no Slack delivery::rehosts browser worker history only through the original claim and fresh private source proof |
| Browser progress uses the original requester and current private source proof | [unit] src/core/dispatch/mainWorkerCapability.test.ts::main worker capability::reads browser work progress only for the proved original private requester and current source |
| Authoritative private turns use only their verified retained seed; they do not inspect, select or reuse an older session tail, while ordinary isolated-session recovery keeps its original validation | [unit] src/core/dispatcher.test.ts::dispatch::keeps authoritative session tools and saved provider recovery behind the current private projection |
| A rehosted private worker's origin callback receives the exact canonical unit key alongside its protected source-run pointer; it cannot restore source history or another worker. | [unit] src/channels/legacySlackAudience.test.ts::legacy private Slack recipient restoration::rehosts only the original claimed worker and recipient without restoring source history | | Owned-unit metadata after linking or unlinking requires the exact canonical claim and fresh native recipient permission; it provides no source input, joined reply sink, transcript or execution authority. | [unit] src/channels/originalUnitRecipient.test.ts::original owned unit recipient::reads the exact accepted unit after initial link and unlink without restoring source input or a joined reply sink, ::refuses missing canonical ownership, expired attribution and lost current source permission, ::withholds owned metadata when source permission changes during the canonical ownership recheck |
| Criterion | Proof |
|---|---|
| The browser legacy-recipient callback returns its promised recipient or undefined; source reads and verification finish before a recipient is used. Missing, rejected or revoked native evidence never restores source history or permits delivery. | [unit] src/channels/legacySlackAudience.test.ts::legacy private Slack recipient restoration::rehosts only the original claimed worker and recipient without restoring source history, src/channels/legacySlackAudience.test.ts::legacy private Slack recipient restoration::refuses revoked native facts, a shared destination, mismatched address or claim and relayed source, src/channels/legacySlackAudience.test.ts::legacy private Slack recipient restoration::never falls back when modern ingress exists is fenced unavailable or unreadable |