# Self-Hosted Task Backend

Detailed decomposition for hosting the multi-agent development task backend on
capOS itself. Today that backend is loopyard, a companion host-side project
(PostgreSQL-backed; not part of this repository): it computes runnable task
sets from typed dependencies, validates status transitions, checks conflict
domains, and issues lock leases with generation fencing, all enforced inside
the database so no client can corrupt task state, with CLI/MCP/REST as thin
transports over one API surface. The long-term intent — recorded in
[Stateful Task and Job Graphs](../proposals/stateful-task-job-graphs-proposal.md)
and [capOS-Hosted Agent Swarms](../proposals/hosted-agent-swarm-proposal.md) —
is for capOS to serve that coordination role in-system through typed
capability services instead of an external SQL server.

## Visible Outcome

A capOS instance serves the task backend for the multi-agent development
workflow that builds capOS: host-side workers resolve runnable tasks, acquire
fenced lock leases, report status transitions, and receive conflict reports
from a capOS-hosted coordinator service, speaking the same task-source and
lock wire contracts that loopyard serves today. loopyard's schema and
invariants are the reference semantics the capOS services must reproduce, and
task state must be exportable/importable in both directions for migration.

## Gap Inventory

What exists and what is missing, largest gap first:

1. **Transactional structured record store.** The coordinator needs
   record-level atomic multi-record commits, secondary lookups, and
   constraint-style invariant enforcement. Current durable storage is below
   that level: the userspace `Store`/`Namespace` service persists a
   whole-state snapshot (`CAPOSUS1`), the kernel `persistent_store` fixture is
   a disk-backed `CAPOSST1` `Store`, and `writable_fs` is a single-writer
   `CAPOSWF1` filesystem. Crash-safe A/B superblock commits and torn-grow
   mount recovery are landed building blocks one level down. The write-ahead-log
   record store itself is now landed: the `capos-lib` `CAPOSRS1` transactional
   WAL (`capos_lib::recordstore` — checksum-chained torn-tail discard, epoch
   fence, atomic multi-record commit frames, secondary index, host-tested and
   fuzzed) is wired to the real `BlockDevice` cap by the
   `demos/record-store-blockdevice` service and proven in QEMU for reboot
   durability and bounded forced-poweroff crash recovery (`make
   test-record-store`). The coordinator's persistence is now switched onto this
   store (2026-07-15 17:00 UTC): the `demos/task-coordinator-persist-proof`
   process drives the real `task-coordinator-logic` coordinator write-through
   over a granted `BlockDevice` cap — every accepted mutation commits the
   affected task's full snapshot (record + status + fenced lease) as one atomic
   WAL frame keyed by `task:<key>` and secondary-indexed by `status:<label>`,
   and on boot rebuilds the live coordinator from the replayed log. `make
   test-task-coordinator-persist` proves, across three boots of one disk image,
   reboot reload of the runnable set / statuses / lease generations plus a
   bounded forced-poweroff torn-write recovery in which the interrupted task is
   atomically absent. The snapshot codec (`encode_record`/`decode_record` with a
   fail-closed value-level decode) and boot-rebuild seam (`restore_task`) are
   host-tested in `task-coordinator-logic`. Log compaction is now landed
   (2026-07-15 17:52 UTC): the compaction-capable CAPOSRS1 format
   (`RecordStore::format_compacting` / `compact`) folds the append-only log
   down to the live record set into a fresh generation over an A/B superblock
   plus two log segments — writing the folded set into the inactive segment
   under `epoch + 1` and flipping the generation-fenced superblock last, so an
   interrupted compaction recovers to either the pre- or post-compaction
   consistent state — host-tested and fuzzed in `capos_lib::recordstore` and
   proven in QEMU (`demos/record-store-compaction-proof`, `make
   test-record-store-compaction`) for reboot log-space reclamation plus a
   bounded forced-poweroff in the compaction flip window. Still missing at this
   level: wiring the same durable seam into the Endpoint-serving
   `task-coordinator-service` (an optional-`BlockDevice`-cap durable path over
   its serve loop).

   A bounded QEMU dogfooding path is now landed without claiming to close that
   production-shaped gap. The Endpoint-serving service can optionally consume
   a single-writer, spawn-granted writable 9p `Directory`; it persists one
   canonical current/temp/backup record per task and recovers before serving.
   `make test-task-coordinator-9p` proves two-boot reload, host inspection, and a
   post-restart mutation. The path accepts at most 20 tasks and 60 directory
   entries, fails closed on malformed, stray, duplicate, oversized, or
   ambiguous state, preserves fencing generations, and invalidates active
   leases at restart because the guest monotonic clock is boot-relative. Its
   v2 records carry the lease actor; the decoder remains compatible with v1
   records and drops any v1 lease because that layout cannot attribute it,
   while preserving the task generation. The identity-free persistence-work
   readback attributes logical read-requested/read-returned and write-submitted
   bytes, bounded chunk high-waters, and transaction byte high-waters without
   changing persistence behavior; [Resource Governance](../architecture/resource-governance.md#landed-task-coordinator-core-table-descriptor-inventory)
   owns that descriptor. Its host-filesystem plus QEMU `Tfsync` proof remains
   bounded dogfooding rather than production storage authority, and supplies no
   per-actor charge, admission budget, protected recovery reserve, physical-I/O
   attribution, or allocator accounting.

   That remaining step is **blocked on a spawn-grant authority gap**, not on the
   durable seam. Under the `qemu` feature a `BlockDevice` is grantable only to
   the *init* process, through the bootstrap CapSet (`cap::BootCapFactory`'s
   `build`, the `#[cfg(feature = "qemu")]` `KernelCapSource::BlockDevice` arm
   over the boot virtio-blk device). The
   spawn path `cap::process_spawner::build_child_caps` carries a
   `KernelCapSource::BlockDevice` arm only under `#[cfg(not(feature = "qemu"))]`
   (the brokered-NVMe arm), and no `KernelCapSource::BlockDeviceTarget` arm at
   all, so a *spawned service* that requests either source falls through to
   `Err("spawn grant unsupported kernel source")`. `task-coordinator-service` is
   a spawned service in `system-task-coordinator.cue`, and `make
   run-task-coordinator` is a `qemu` build, so an optional `BlockDevice` grant
   cannot reach it on that proof path. This is why every disk-holding QEMU proof
   (`task-coordinator-persist-proof`, `record-store-blockdevice`,
   `storage-persist-service`, `multi-virtio-blk-smoke`) makes the disk holder
   the init process with `services: []`. The manifest offers no way around it:
   `CapSource` is `Kernel` or `Service` only, so init cannot delegate a cap it
   already holds to a service in the graph.

   Three ways forward, in preference order:

   - **Wire `KernelCapSource::BlockDeviceTarget` into `build_child_caps` under
     `qemu`.** The bootstrap arm already scopes this source to a
     manifest-declared *non-boot* disk by PCI identity and fails closed on
     boot-device identity, absent selector, and no match
     (`virtio::block_device_target_for_pci_selector`), so the authority being
     extended to spawned services is a PCI-scoped non-boot disk rather than the
     boot medium. This keeps `system-task-coordinator.cue` in its current shape
     (init spawns coordinator + client, `exitWhenServiceExits`) and needs a
     second QEMU disk plus `kernelParams.blockDeviceTarget.pci`. It is a kernel
     authority change and needs a decision before implementation.
   - **Make the coordinator the init process on a durable proof manifest.**
     Mirrors `storage-persist-service`: the coordinator holds the disk, spawns
     the client itself, and the client signals shutdown out-of-band because
     `exitWhenServiceExits` is unavailable with `services: []`. No kernel
     change, but it reshapes the existing acceptance proof and pulls
     `demos/task-coordinator-client-smoke/` into the slice.
   - **Prove the durable path on the non-`qemu` brokered-NVMe arm**, where
     `build_child_caps` already grants a spawned service a `BlockDevice`. This
     is the production-shaped path but requires a verified NVMe controller and
     an ordered `device_mmio` grant, i.e. a `run-cloud-provider-*`-class
     vehicle rather than `run-task-coordinator`.
2. **The coordinator service itself.** A userspace service owning task
   records, statuses-as-data with validated transitions, dependency edges
   with cycle rejection, runnable-set computation, conflict-domain checks,
   and lock leases with expiry and generation fencing — the
   [Stateful Task and Job Graphs](../proposals/stateful-task-job-graphs-proposal.md)
   Stage E operator task surface and Stage G agent workflows, with invariants
   enforced in the service the way loopyard enforces them in PL/pgSQL.
   The phase-1 in-memory local proof is landed: the
   `demos/task-coordinator-service` process owns the records and enforces
   those invariants server-side over a demo-local Cap'n Proto `Endpoint`
   protocol (`demos/task-coordinator-proto`), with the pure rules host-tested
   in `demos/task-coordinator-logic` (`make run-task-coordinator`,
   `make task-coordinator-logic-test`). The phase-2 HTTP/JSON API surface is
   also landed (2026-07-14 03:32 UTC): `demos/task-coordinator-api-service` serves the
   coordinator's task-source and lock contracts (show/list/runnable-set,
   create/transition, lease acquire/renew/release with generation fencing)
   as JSON over the Phase C userspace network-stack listener, with the
   bounded HTTP/JSON rules host-tested in `demos/task-coordinator-api-logic`
   and a host-side harness driving the contracts through a hostfwd TCP port
   (`make test-task-coordinator-api`). Lock-response holder-metadata parity
   with the loopyard lock adapter is landed (2026-07-14 08:16 UTC): contention
   refusals echo the live blocking lease as a `metadata` blob in loopyard's
   contention shape — see the mapping below. Still missing at this level:
   durable persistence, authentication, and any production backend authority
   (the demos are bounded QEMU proofs).

   Core-table whole-scan attribution is landed as a bounded read-side proof.
   The coordinator brackets conflict-domain acquisition, dependency/cycle
   validation, runnable-set construction, and state-transfer size projection
   and validation (mutation admission plus export/import), `listTasks`
   projection, and `lockList` projection as six exclusive dispatch classes, and
   the occupancy sample's own table traversal as a seventh. Its
   identity-free occupancy record publishes saturating per-class visits and
   their sum, per-class scanned-entry high-waters, the largest observed
   scan class over all seven, and the largest over the six dispatch classes
   alone. The traversal outscans every dispatch class once any task exists, so
   it holds the overall position by construction and the dispatch position is
   the one that still names driven serve-path work. The traversal class is
   counted on every sample, including the
   samples whose record the due-check withholds, and is excluded from the
   due-check so the observer cannot trigger its own emission. It also publishes Rust-owned stored length and allocation
   capacity for both map keys, dependency and conflict-domain vector backing
   and string buffers, and lease worker strings, plus the capacity high-water
   and a capacity-below-length mismatch counter. The fixed-width lease actor is
   inline rather than a heap string. Ordered-map nodes, allocator-internal
   overhead, and physical memory remain outside the byte totals. Heap occupancy,
   heap high-water, and scan-entry high-water changes emit immediately;
   scan-visit-only and mismatch-only changes retain exact-power-of-two damping.
   The record carries no task,
   actor, worker, lease-holder, generation, expiry, or capability material.
   The publisher takes no new clock sample and changes no task, lease,
   admission, persistence, or ingress authority.
   The authoritative coordinator descriptor inventory now also covers the
   bounded 9p persistence-work readback and its remaining production gaps; see
   [Resource Governance](../architecture/resource-governance.md#landed-task-coordinator-core-table-descriptor-inventory).

   Lock wire-contract mapping, verified field-by-field against the installed
   loopyard lock adapter (`loopyard vibe lock`, `src/loopyard/vibe.py`
   `_op_acquire`/`_op_update`/`_op_release`), operation by operation:

   - **acquire, success** — loopyard: `{"acquired": true, "metadata":
     {<client-stored blob> + "task_id", "run_id", "path"}}`; adapter:
     `{"acquired": true, "metadata": {"task_id", "run_id", "generation",
     "expires_at_ms"}}`. loopyard round-trips a client-supplied blob
     (`lease_seconds`, `heartbeat_at`, `fencing_token`, `workspace`, ...);
     the adapter synthesizes its blob from coordinator lease state —
     `generation` is the fencing token and `expires_at_ms` the staleness
     authority. `path` (a lock-root file path loopyard defaults in) has no
     capOS equivalent and is absent.
   - **acquire, contention** — loopyard: `{"acquired": false, "metadata":
     {<holder's stored blob>, "run_id": <holder run>, "task_id", "path"}}`;
     adapter: `{"acquired": false, "error": "lease-held"|"conflict-domain",
     "metadata": {"task_id", "run_id": <holding worker>, "generation":
     <holder generation>, "expires_at_ms": <holder expiry>[, "held_task"]}}`.
     The `error` label is an additive diagnostic (loopyard carries none);
     `held_task` is a capOS extension naming the blocking task on
     conflict-domain refusals (loopyard's lock layer has no cross-task
     conflicts).
   - **renew/update, success** — loopyard: `{"updated": true, "metadata":
     {...}}`; adapter: same shape with the synthesized blob.
   - **renew/update, refusal** — loopyard: bare `{"updated": false}` (its
     client raises `LockBackendError` on it, no holder echo); adapter:
     `{"updated": false, "error": "stale-generation"}` — additive label, no
     metadata, matching loopyard's metadata-free refusal.
   - **release** — loopyard: `{"released": true|false}`; adapter:
     `{"released": true}` / `{"released": false, "error":
     "stale-generation"}`.
   - **Host worker wiring** (landed in `05324347`):
     `tools/capos-task-backend-adapter.py` exposes the installed vibe-loop
     task-source and fenced-lock command contracts over the bounded HTTP/JSON
     API. The coordinator remains authoritative for task state and fencing
     generations; an owner-only local ledger maps opaque vibe-loop fencing
     tokens and recognized workspace metadata onto those generations. The
     adapter rejects non-loopback service URLs, bounds all input and response
     data, maps HTTP failures to stable command exits, and hashes oversized
     run identifiers into coordinator-sized worker names without losing their
     local identity. `make task-backend-vibe-adapter-test` covers the command
     contracts, including an installed `vibe-loop` process. `make
     test-task-backend-vibe-adapter-9p` drives the same adapter through two
     QEMU boots and proves stale-token rejection, generation advancement, and
     workspace-metadata continuity. Arbitrary extension metadata, remote
     authentication, public ingress, and production backend authority remain
     outside this bounded path.

     The coordinator-side half of the `status`/`list` gap is now landed
     (2026-07-16 17:02 UTC): the `Endpoint` protocol carries read-only
     `lockStatus` (9) and `lockList` (10) returning a `LockInfo`
     (`key`/`worker`/`generation`/`expiresAtMs`) for leases live at the served
     instant, over `Coordinator::lock_view`/`lock_views`. These were the
     missing primitive: the HTTP adapter is a stateless transport holding no
     lease state, and `TaskInfo.generation` is the task's fencing token, which
     survives lease expiry — so `showTask`/`listTasks` could not answer "is a
     lock held, by whom, until when". Query and refusal liveness share one
     `Coordinator::live_lease` predicate so they cannot drift. Proven by `make
     run-task-coordinator` (live holder, conflict-blocked-but-unlocked,
     unknown key, expiry exclusion before reclaim, post-reclaim holder) and
     `make task-coordinator-logic-test`. The HTTP adapter routes on top are
     now landed
     ([`loopyard-lock-status-list-adapter-routes`](https://tasks.cap-os.dev/p/capos/t/loopyard-lock-status-list-adapter-routes)):
     `GET /v1/locks/list` returns `{"locks":[...]}` (one lock-metadata blob per
     live lease) and `GET /v1/locks/status/<key>` returns `{"locked":true,
     "metadata":{...}}` for a live holder or a bare `{"locked":false}` for a
     released or never-locked key (not a 404), matching loopyard's
     `GET /locks` / `GET /locks/{name}` read contract. Both share
     `lock_metadata_json`, extracted from the existing lock response builder,
     so no second response shape was added. Proven end-to-end by `make
     test-task-coordinator-api` and the `task-coordinator-api-logic` host tests.
     The same Endpoint service now has the optional bounded 9p persistence path
     described above; it remains in-memory when no `state` `Directory` cap is
     granted. Still missing at this level: authentication and production
     backend authority.
3. **Public ingress and TLS.** Host-side workers need to reach the backend.
   Private GCE self-hosted Web UI serving is proven; public exposure and TLS
   remain gated by the explicit on-hold
   [`cloud-gce-public-self-hosted-webui-ingress-tls`](https://tasks.cap-os.dev/p/capos/t/cloud-gce-public-self-hosted-webui-ingress-tls)
   task. TLS building blocks (`capos-tls`, the ACME http-01 solver) exist.
4. **A reusable multi-client API service layer.** The remote-session Web UI
   already runs a persistent accept/recv/send/close loop with per-connection
   deadlines and slow-client bounds over the Phase C userspace stack, but as
   a demo-grade single flow. The phase-2 API adapter
   (`demos/task-coordinator-api-service`) reuses that serving shape and is
   the first in-tree `capos-service` lifecycle consumer. Its bounded
   multi-client extension landed 2026-07-14 13:00 UTC: each accepted Phase C
   connection receives a distinct socket service-object capability and
   receiver badge over the shared socket-service endpoint, while the
   network-stack loop multiplexes per-slot `recv`/`send`/`close` calls from
   smoltcp readiness. The adapter maps those readiness observations through
   the `capos-rt` poll/select bridge into a two-entry connection table with
   per-request deadlines and fail-closed HTTP 503 refusal on exhaustion. Each
   submitted ring call retains its `CallId` through completion; if its deadline
   expires while the target is still pending, the adapter terminates instead
   of resuming with an orphaned call. Service-object helper faults use bounded,
   ordered object release, helper termination, and process reaping. The network
   stack's accepted-holder lease is 60 seconds, covering the adapter's
   five-second coordinator budget while retaining the two-second per-socket-call
   slow client bound. The QEMU harness observes client A's new admission after a
   log-position snapshot before it opens client B, then proves B completes
   while A holds a partial request; it also proves that a third client is
   refused while both slots are occupied and that repeated connection churn
   does not exhaust the process capability table. The reusable HTTP/JSON
   service layer landed 2026-07-27 11:43 UTC
   ([task record](https://tasks.cap-os.dev/p/capos/t/task-api-service-reusable-http-json-service-layer)):
   `capos-http-json-service` now owns configurable bounded request framing,
   JSON/octet response construction, route/handler composition, connection
   admission, absolute read/send deadlines, readiness-driven multiplexing, and
   retained submitted-call ownership. The coordinator adapter supplies the
   `/v1/tasks/*`, `/v1/locks/*`, `/v1/state/export`, and `/v1/state/import`
   routes plus its coordinator-call budget; its JSON result/error shapes and
   all existing wire limits remain coordinator-owned. The layer receives no
   coordinator, storage, or session capability. The other remote-session Web
   UI modes retain their serial serving path. Authentication and production
   backend authority remain absent.
5. **Remote actor authentication.** Endpoint-level lease attribution landed
   2026-07-27 09:41 UTC
   ([task record](https://tasks.cap-os.dev/p/capos/t/task-coordinator-caller-session-lease-binding)):
   every acquired coordinator lease records the opaque, service-scoped caller
   session reference already supplied by the
   [Session Context](../architecture/session-context.md) invocation boundary.
   The service checks the Endpoint ABI's live-session flag on every dispatch.
   `renewLease` and `releaseLease` require that same live caller session and
   return `callerSessionMismatch` separately from `staleGeneration`; read-only
   `lockStatus`/`lockList` surface the recorded actor through `LockInfo`, and
   the HTTP/JSON adapter projects that same service-scoped correlation as one
   fixed-width lowercase-hex `actor` field on its shared lock metadata shape.
   Only live, actor-bound leases reach those routes: persistent restart
   invalidates leases, state transfer omits them, and legacy records do not
   reconstruct them. The adapter rejects malformed internal lock metadata
   instead of emitting a plausible all-zero actor identifier.
   `make task-coordinator-logic-test` covers same-session success,
   cross-session refusal and refusal ordering, plus expiry/reclaim; `make
   run-task-coordinator` drives two broker-selected spawned callers in QEMU,
   while `make test-task-coordinator-api` distinguishes a direct Endpoint lease
   from an adapter-acquired lease on both HTTP lock read routes.
   Bounded HTTP API-token admission is now landed (2026-07-29 14:20 UTC)
   ([task record](https://tasks.cap-os.dev/p/capos/t/task-coordinator-api-token-actor-authentication)).
   `initConfig.taskCoordinatorApiTokens` optionally carries at most four
   SHA-256 token hashes paired with fixed-width actor labels. An absent set
   preserves the unauthenticated development behavior. A configured set
   protects every `/v1/tasks/*`, `/v1/locks/*`, `/v1/state/export`, and
   `/v1/state/import` route: missing, malformed, unknown, duplicate, or
   oversized authorization fails as the same typed `401` before routing or
   coordinator submission. Tokens are capped at 64 bytes, authorization
   field-values at 71 bytes, and hash comparison uses a constant-time
   primitive across the complete bounded set. Successful API lease acquisition
   records a bounded generation-keyed actor projection, and the existing
   `lock_metadata_json` shape uses it on both lock read routes. The adapter
   admits authenticated renew/release calls only when the presented token actor
   owns that task's API projection, refusing cross-token mutation before any
   coordinator call. Direct Endpoint leases retain their session actor and
   remain read-visible, but authenticated HTTP clients cannot renew or release
   them. The projection ledger has one slot per coordinator task (256), rejects
   an impossible excess before lease acquisition rather than terminating the
   service, and is cleared on import together with coordinator lease
   invalidation.

   Optional credential-surface admission landed 2026-07-30 04:02 UTC
   ([task record](https://tasks.cap-os.dev/p/capos/t/task-coordinator-api-credential-store-token-binding)).
   The static hash set remains the development default, including the prior
   unauthenticated behavior when it is absent. A declared
   `taskCoordinatorApiCredentialPath` instead requires the adapter's
   `CredentialStore`, one fixed `UserSession`, and a manifest-declared SHA-256
   prefilter. The prefilter rejects arbitrary bearer values before they can
   request Argon2 work, `CredentialStore` verifies the admitted bearer, and the
   fixed session must answer `info()` as live. Its opaque session/principal
   identifiers derive the service-scoped actor used by the existing
   generation-keyed projection. The bearer carries no session selector and the
   credential verifier returns no principal identity: deployment configuration,
   not client input, binds this one credential path to this one session.
   Missing session/hash grants, a denied credential, or a stale session returns
   typed `401`; verifier unavailability and transport failure return typed
   `503`, while verifier overload returns `503` with bounded `Retry-After` and
   millisecond guidance. Declaration never falls back to static hashes. The
   local QEMU gate runs the static two-token proof and the fixed-session
   credential proof separately, preserving cross-token mutation coverage while
   proving the credential admission and direct-Endpoint lease boundary.

   The host command adapter now carries that local credential contract
   ([task record](https://tasks.cap-os.dev/p/capos/t/task-backend-host-adapter-api-credential-support)).
   `CAPOS_TASK_BACKEND_API_TOKEN` is an environment-only input; when present,
   the adapter sends it as a bearer on every coordinator request without
   placing it in argv, command output, or the owner-only metadata ledger. It
   refuses redirects so the credential cannot be forwarded beyond the exact
   configured loopback endpoint. It enforces the coordinator's printable-ASCII
   grammar and 64-byte token / 71-byte authorization limits before opening a
   request, while an absent or empty value preserves the prior unauthenticated
   request shape. Typed `401` refusals use stable non-retryable exit 5. Typed
   `503` refusals retain transient exit 1 and expose only bounded numeric
   `Retry-After` and `retryAfterMs` guidance; malformed or oversized guidance
   is ignored and the adapter does not retry internally. `make
   task-backend-vibe-adapter-test` covers authenticated success, redirect
   refusal, missing and unknown credentials, reflected-label redaction, local
   oversized refusal, hostile retry guidance, the installed runtime-owned
   default lifecycle, and output / ledger redaction. The lifecycle proof no
   longer pins worker-owned compatibility mode: generation-bound `complete`
   and `reset` HTTP commands let the runtime close `active -> review -> done`
   after integration or settle `active -> ready` after failure. The adapter
   admits either command only when its owner-only ledger maps the runtime's
   environment-only opaque fence and run identity to the submitted coordinator
   worker/generation. Malformed, oversized, missing, stale, or cross-run hook
   claims fail before mutation. The adapter advertises the runtime's fenced
   reset capability. Its post-release fallback carries no fence, requires the
   task lease to be absent, and uses the coordinator's ordinary validated
   `active -> ready` edge; this lets a failed fenced settlement recover without
   treating an ambient or stale token as authority. The same private ledger
   provides the separate `main-integration` mutex required by runtime-owned
   integration. Because the runtime does not heartbeat that mutex, the adapter
   deliberately ignores command-lock lease durations: the record remains held
   until explicit release or the runtime's same-owner process-liveness recovery,
   so elapsed wall-clock time cannot grant overlapping integration authority.
   The mutex stores no task state and never enters the coordinator's task or
   conflict-domain model.
   Every validated coordinator status edge -- `ready -> active`, `active ->
   ready`, `active -> review`, `review -> active`, and `review -> done` -- is
   bound to the acquiring Endpoint caller session while the task has a live
   lease. The authenticated HTTP path additionally requires the lease's
   generation-keyed bearer/session actor projection before it submits any
   transition. In the deliberately unauthenticated posture, the API service's
   shared Endpoint caller session remains the authority check; therefore its
   own HTTP-acquired leases can use the hooks while a lease acquired by another
   direct Endpoint session remains read-visible but not HTTP-transitionable.
   An expired, released, or absent lease reserves no transition authority. The
   `active -> ready` edge still clears
   any attributed lease and advances the issued-generation high-water mark
   before the task can re-enter the dependency-derived runnable set, so
   pre-reset renew and release tokens are stale. No validated status edge
   remains lease-unbound; non-transition mutations such as task creation and
   dependency changes remain outside this lease-holder rule. Transitions retain
   the same durable write-through, conflict domains, dependencies, and
   runnable-set derivation, with no export/import format or status change. `make
   test-task-coordinator-api` executes both lifecycle hooks in capOS under the
   disabled, static-token, and credential-backed authentication postures. `make
   test-task-backend-vibe-adapter-9p` selects the static-token manifest posture
   and drives the existing two-boot fencing and workspace-metadata sequence
   through authenticated adapter calls.

   This remains bounded local proof-grade credential admission, not production
   authentication. The fixed session and public `capos` credential/hash are
   local proof fixtures. The console verifier is not an API credential registry
   and cannot attest a credential-to-principal or credential-to-session
   relationship; supporting multiple accounts or actors requires a different
   identity-bearing verifier/lookup contract. There is no credential issuance,
   rotation, revocation, persistent session lookup, TLS/public ingress, or
   production account-policy integration. It grants no public-ingress authority
   and does not close this gap. The coordinator remains authoritative for task
   state and fencing generations; the adapter receives no coordinator or
   storage authority beyond its existing narrow client endpoint. The Web UI
   login remains demo-grade.
6. **Client transport.** The bounded REST/JSON command adapter for vibe-loop's
   task-source and fenced-lock contracts is landed in `05324347`. A reusable
   typed client remains future work through the `capos` SDK `remote` transport
   ([capOS SDK And Dual Transport](capos-sdk-dual-transport.md)); the
   transitional host-backend remote transport (slice 4a there) can carry
   typed clients before the full `capnp-rpc` upgrade.
7. **Time quality and data portability.** The coordinator reads a granted
   `WallClock` for its boot-relative monotonic lease deadline and publishes a
   boot-unique lock epoch with bounded provenance. This gap is the authoritative
   current-state record for coordinator lease restoration. The done
   [`task-coordinator-seeded-wallclock-lease-expiry-domain`](https://tasks.cap-os.dev/p/capos/t/task-coordinator-seeded-wallclock-lease-expiry-domain)
   title overstates what finally landed: that task first added seeded expiry
   persistence, but its final remediation commit `ed22ca69` removed it and
   retained fail-closed boot-relative invalidation. The earlier
   [`task-coordinator-lock-clock-provenance-read-surface`](https://tasks.cap-os.dev/p/capos/t/task-coordinator-lock-clock-provenance-read-surface)
   delivered only the boot epoch and provenance fields on lock reads; it did not
   make deadlines comparable across boots.

   `kernelParams.seedUtcSeconds` selects an operator-declared UTC base at the
   boot monotonic origin for manifests that set it. The coordinator persistence
   manifest does not set it, and setting it would not establish elapsed time:
   Phase 1.x UTC is that fixed base plus monotonic-since-boot and restarts from
   the base on every reboot. Seeded and fallback readings both retain
   `untrusted` provenance. Durable coordinator records therefore keep the
   boot-relative lease shape: restore invalidates every lease, republishes the
   cleared record, and preserves its issued-generation high-water fence. `make
   test-task-coordinator-9p` proves that fail-closed invalidation and durable
   clearing across two boots of one host share. This is bounded local QEMU
   evidence, not trusted time or production clock authority; a progressing
   cross-reboot source and explicit domain contract remain Phase 2
   `ClockDiscipline`/NTP work. The
   bounded coordinator-local export/import contract landed 2026-07-27 05:08 UTC
   and its fencing and admission invariants were tightened 2026-07-27 05:33 UTC
   ([task record](https://tasks.cap-os.dev/p/capos/t/task-coordinator-state-export-import-local-proof)):
   `TaskCoordinator.exportState` emits one deterministic snapshot, bounded by
   `MAX_TASKS` and a 60 KiB payload below the Endpoint ceiling, containing
   statuses, dependency edges, conflict domains, and fencing generations in
   their observable stored order. Task creation, dependency addition, status
   transition, and durable restore refuse any mutation that would cross that
   byte bound, so every admitted live state remains exportable. `importState`
   treats the strict versioned field format as untrusted, builds a fresh
   candidate through the same `create_task`, `add_dependency`, and `transition`
   rules as live requests, and swaps it in only after complete validation.
   Unknown/malformed fields, invalid statuses, dangling references, cycles,
   duplicate keys, limit violations, invalid fields, and exhausted generations
   have typed refusals. Leases are absent from the format. The coordinator
   retains a `MAX_TASKS`-bounded per-key issued-generation high-water mark
   across replacement and temporary omission; import preserves the snapshot
   generation or raises it to that floor, so pre-import renew/release tokens
   are stale and the next claim cannot reuse a value issued before import.
   `make run-task-coordinator` proves the ordered round trip, stale-token rule,
   and replayed-snapshot high-water behavior in QEMU; the
   `make task-coordinator-logic-test` host gate covers hostile snapshots and
   atomic refusal.
   The optional writable-9p path admits import through a directory transaction:
   it validates and encodes the complete candidate before mutation, prepares
   task temporaries plus one canonical generation-floor record, syncs a
   rollback marker before the first destructive rename, backs up the prior
   set, publishes the replacement, and commits through the marker rename.
   Ordinary persistent mutations publish only the changed task record and the
   floor record in the same transaction, so they cannot split across a crash;
   complete-set replacement remains specific to import. The floor record
   retains at most the coordinator's 256 entries and is bounded at 18,695
   bytes. File reads and writes are split into requests of at most 4,096 bytes,
   so the logical floor record fits the writable-9p transport in at most five
   chunks. Keeping retired keys in one record avoids consuming the 20 live-task
   slots or one directory entry per key. The 64-entry directory ceiling is 60
   task current/temporary/backup slots, three floor-record slots, and one
   transaction marker. A new identity beyond the 256-entry floor capacity
   retains the existing typed `retained-generation-capacity-exhausted` refusal.
   Boot recovery uses the same
   current/temporary/backup planner to roll the whole set backward before that
   decision or forward afterward, then restores generation floors before task
   records or dispatch. The [resource-governance descriptor](../architecture/resource-governance.md#landed-task-coordinator-core-table-descriptor-inventory)
   is authoritative for persistence-work accounting and proof coverage. Keys
   omitted from the snapshot lose their live record
   but retain their coordinator-local floor; snapshots still contain no floor
   table. Rejected imports preserve the prior live and host-visible set. The
   two-boot proof recreates an omitted key above its pre-restart high-water and
   rejects the pre-restart token. This is a host-filesystem plus QEMU `Tfsync`
   proof, not the fenced `CAPOSRS1` WAL or a production-storage durability
   claim, and it adds no `BlockDevice` authority.
   The bounded HTTP transfer routes landed 2026-07-27 07:21 UTC
   ([task record](https://tasks.cap-os.dev/p/capos/t/task-coordinator-api-state-transfer-routes)):
   `GET /v1/state/export` returns the coordinator's snapshot bytes unchanged,
   and `POST /v1/state/import` forwards its opaque body to `importState`.
   Adapter request and response payloads use the coordinator's 60 KiB snapshot
   ceiling; the whole HTTP request remains capped at 64 KiB, and the protocol
   tests prove a 60 KiB snapshot's multi-segment Cap'n Proto import call fits
   below the Endpoint ceiling. Incomplete or oversized imports never reach the
   coordinator. Successful exports use `application/octet-stream`, while
   import results and typed refusals retain the API's existing JSON
   result/error shapes. The adapter does not inspect or repair the snapshot
   format. `make test-task-coordinator-api` proves a deterministic export/import
   round trip, a maximum-size opaque import reaching the coordinator, and
   atomic hostile `malformed` and `unknown-field` refusals through the
   host-forwarded port.
   Local host inspection and lossless round-trip tooling landed
   2026-07-27 18:09 UTC:
   `tools/capos-task-coordinator-snapshot.py decode` projects a coordinator
   snapshot into deterministic UTF-8 JSON while preserving task,
   dependency-edge, and conflict-domain order, and `encode` validates that
   exact documented JSON schema before recreating importable snapshot bytes.
   The tool accepts only the canonical five-field `TCS1` layout, reads snapshot
   input only up to the coordinator's 60 KiB ceiling, shares the
   `encode_record`/`decode_record` key and list bounds, rejects unknown fields,
   invalid statuses, dangling references, cycles, duplicate keys, exhausted
   generations, and limit violations with stable classified exits, and writes
   no output before full validation. `make task-coordinator-logic-test` pins
   the Python codec to a snapshot produced by `Coordinator::export_state` and
   covers deterministic JSON, byte-identical decode/encode, hostile inputs,
   and no-overwrite output behavior. The tool reads and writes local files
   only; it makes no coordinator network call.
   The local loopyard interchange projection landed 2026-07-27 20:35 UTC:
   `to-loopyard` maps snapshot JSON into the strict
   `loopyard.interchange/v1` record set, and `from-loopyard` validates that
   interchange before recreating snapshot-shaped JSON. Both directions
   preserve task, dependency-edge, and conflict-domain order. The interchange
   matches the installed adapter's task shape: coordinator conflict domains
   occupy the ordered `resources` array and `paths` must be empty because this
   coordinator does not model path domains separately. The outbound form
   declares its metadata loss explicitly by using each task id as the title
   and `normal` as the priority; inbound conversion validates and drops those
   fields because the coordinator has no title or priority state. The adapter's
   portable `[A-Za-z0-9][A-Za-z0-9._-]{0,63}` id grammar is narrower than the
   coordinator's UTF-8 key grammar, so outbound conversion explicitly refuses
   otherwise-valid coordinator keys that cannot become adapter task ids.
   Unknown fields, invalid statuses, non-empty paths, dangling references,
   cycles, duplicate keys, exhausted generations, oversized inputs, and limit
   violations fail before output is written. Both directions prove that the
   converted task set still encodes below the coordinator's 60 KiB TCS1 ceiling
   before publishing output. The projection remains local-file only and makes
   no coordinator, loopyard, or PostgreSQL call.
   The offline apply-plan step landed 2026-08-02 02:47 UTC:
   `plan-loopyard TARGET OUTPUT [--current CURRENT]` validates the target and
   optional current `loopyard.interchange/v1` documents under the same strict
   interchange rules, with the projection's 512 KiB bounded-JSON ceiling for
   each input and a separate proof that each decoded state still fits the
   coordinator's 60 KiB TCS1 ceiling, before creating any output. It emits
   deterministic `loopyard.apply-plan/v1` JSON in task-record, resource-set,
   dependency-edge, and status-transition order. Every desired or current
   entry is classified `new`, `unchanged`, or `conflicting`; resource arrays
   use set semantics while retaining input order as evidence, dependency
   additions/removals retain their source order, and the `dependency_edges`
   section is the sole dependency authority. Task-record payloads deliberately
   omit dependencies, so arbitrary valid interchange order cannot make record
   creation depend on a later record. Only direct coordinator-valid status
   edges are actionable. Unsupported task removal, non-plannable record
   metadata drift, and invalid status edges remain explicit operator-decision
   conflicts. The command is local-file only, deterministic for identical
   inputs, and never replaces an existing output path. Plan serialization is
   bounded by a structurally derived 8,519,680-byte ceiling that covers the
   maximum target/current task and dependency counts rather than reusing the
   smaller interchange-input ceiling.
   `verify-plan PLAN TARGET [--current CURRENT]` is the local-file-only
   verification companion. It strictly validates the supplied
   `loopyard.apply-plan/v1` document under the planner's 8,519,680-byte output
   ceiling and both interchange inputs under their 512 KiB input ceiling. It
   rejects missing, unjustified, or contradictory plan entries, reconstructs
   the post-plan target state from the plan's task, resource, dependency, and
   status evidence, and proves that re-planning from that state to the target
   is an all-unchanged fixed point with no actionable transition. It also
   re-plans the supplied inputs and requires the plan to match the deterministic
   projection byte-for-byte. Verification writes no output and makes no
   coordinator, loopyard, or PostgreSQL call.
   Still missing: authenticated loopyard-side ingestion tooling that applies
   this plan to PostgreSQL. That importer requires operator credentials and
   authorization; the offline planner neither authenticates nor mutates
   loopyard. Endpoint lease actor
   attribution is landed as described in gap 5, but leases remain deliberately
   absent from this export/import format, so the state-transfer contract is
   unchanged. The optional static API-token rule now covers both transfer
   routes before their bodies or snapshots can reach the coordinator. The
   fixed-session local proof using `credential_store` / `user_session` is
   described in gap 5. The host vibe-loop adapter can now authenticate its
   task-source and fenced-lock calls. Its `export-state OUTPUT` command now
   fetches the opaque snapshot through that same loopback-only,
   redirect-refusing, environment-bearer client and creates an owner-only
   output without replacing an existing path. Its
   `import-state --confirm-replace-all INPUT` command reads at most the
   coordinator's 60 KiB ceiling before opening the request and forwards those
   bytes unchanged. Both commands preserve the adapter's typed `401` and `503`
   exit behavior and bounded retry guidance. The adapter does not inspect,
   repair, or re-encode snapshots, and the coordinator remains authoritative
   for task state and fencing generations. This adds no loopyard/PostgreSQL
   ingestion, credential issuance or rotation, public ingress, or production
   backend authority. Production credential registry/session lookup,
   authenticated loopyard-side ingestion, and production ingress authority
   remain missing.

## Sequencing

Phases ordered so each lands on local QEMU evidence before touching the gaps
behind it:

1. **In-memory coordinator local proof** (landed): coordinator service plus
   client demo proving runnable-set computation, transition validation,
   dependency-cycle rejection, conflict domains, and fenced leases (expiry,
   generation increment, stale-token rejection) in QEMU, with the pure rules
   under host tests. Proofs: `make run-task-coordinator`,
   `make task-coordinator-logic-test`. Root task record:
   `task-backend-coordinator-inmemory-local-proof`.
2. **API surface local proof** (landed 2026-07-14 03:32 UTC): HTTP/JSON adapter over
   the userspace network stack exposing the coordinator to a host-side
   client speaking the task-source/lock command contracts against local
   QEMU. The `task_api_mode` of the Phase C network-stack server spawns the
   coordinator and the `task-coordinator-api` adapter; the host harness
   (`tools/qemu-task-coordinator-api-smoke.sh`) drives the JSON contracts
   through a SLIRP hostfwd port. Proofs: `make test-task-coordinator-api`,
   `make task-coordinator-logic-test`. Task record:
   `task-backend-api-surface-local-proof`. The bounded concurrent-connection
   extension landed 2026-07-14 13:00 UTC with per-connection socket service
   objects, readiness-driven multiplexing, and fail-closed table exhaustion;
   task record: `network-stack-concurrent-socket-objects-task-api-serving`.
3. **Durable record store**: the transactional record store over
   `BlockDevice`, then switch the coordinator's persistence from in-memory
   state to it, with reboot and crash-recovery proofs. First increment landed:
   the `capos-lib` `CAPOSRS1` WAL core (host logic, `task-backend-record-store-core-host-logic`).
   Second increment landed: the `BlockDevice` wiring proof — `demos/record-store-blockdevice`
   persists the WAL through the typed `BlockDevice` cap and proves reboot
   durability plus bounded torn-write crash recovery across three boots of one
   disk image (`make test-record-store`,
   `task-backend-record-store-blockdevice-wiring-local-proof`). Third increment
   landed (2026-07-15 17:00 UTC): the coordinator's persistence is switched onto
   the store — `demos/task-coordinator-persist-proof` drives the real
   `task-coordinator-logic` coordinator write-through over a granted
   `BlockDevice` cap, committing each mutation's task/status/lease snapshot as
   one atomic frame and rebuilding live state from the log on boot;
   `make test-task-coordinator-persist` proves reboot reload and bounded
   forced-poweroff crash recovery across three boots of one disk image
   (`task-backend-coordinator-persistence-record-store-local-proof`). Fourth
   increment landed (2026-07-15 17:52 UTC): WAL compaction — the
   compaction-capable CAPOSRS1 format (`RecordStore::format_compacting` /
   `compact`) reclaims append-only log space by folding the live set into a
   fresh generation over an A/B superblock plus two segments, with the same
   crash-consistency guarantee (an interrupted compaction recovers to the pre-
   or post-compaction state via the generation/epoch fence);
   `demos/record-store-compaction-proof` and `make test-record-store-compaction`
   prove reboot log-space reclamation and a bounded forced-poweroff in the
   compaction flip window (`task-backend-recordstore-wal-compaction`). Remaining
   at this level: a durable optional-`BlockDevice`-cap path in the
   Endpoint-serving `task-coordinator-service`. A parallel QEMU-only inspection
   increment landed in `task-backend-9p-persistence`: the service uses a
   spawn-granted writable 9p `Directory` and bounded current/temp/backup task
   records; `make test-task-coordinator-9p` proves restart reload and clean
   host-visible state. This shortcut does not replace the `BlockDevice` path.
   The dependency-ordered
   [`task-backend-9p-api-persistence`](https://tasks.cap-os.dev/p/capos/t/task-backend-9p-api-persistence)
   follow-on is implemented in commit `2c291abc`: the existing Phase C
   userspace-network topology grants the same bounded state cap only to the
   coordinator while its storage-blind HTTP/JSON adapter remains host
   accessible. `make test-task-coordinator-api-9p` proves API-visible reload,
   restored-lease invalidation, stale-renew rejection, a new fencing
   generation, and post-restart mutation across two boots over one host share.
   Its device-composition prerequisite is the explicit
   `virtio_9p_host_fixture` feature described in
   [Virtio-9p Host Directory Passthrough](../proposals/virtio-9p-host-directory-proposal.md).
   The feature carries only the 9p fixture into the `not(qemu)` Phase C build,
   and `make test-virtio-9p-net-coboot` proves a writable 9p `Directory` and
   the userspace `Nic` path serve in one boot. The final local integration
   increment, [`task-backend-vibe-adapter-qemu-proof`](https://tasks.cap-os.dev/p/capos/t/task-backend-vibe-adapter-qemu-proof),
   is implemented in commit `05324347`: a real vibe-loop-compatible host
   adapter drives the persistent API while the coordinator retains the only
   storage authority. `make task-backend-vibe-adapter-test` verifies the
   installed CLI's runtime-owned default lifecycle, and `make
   test-task-backend-vibe-adapter-9p` proves authenticated adapter calls plus
   fenced lock and workspace metadata behavior across two QEMU boots. This
   adds only the bounded local bearer path described in gap 5, not public
   ingress, production authentication, or production storage authority.
4. **Actors, time, and portability**: the coordinator-local bounded
   export/import contract and its bounded HTTP transfer routes are landed
   (gap 7). Direct Endpoint lock leases are now attributed to their acquiring
   caller session and reject cross-session renew/release separately from stale
   fencing tokens (gap 5). Bounded static API-token admission and HTTP actor
   projection, including both transfer routes, are also landed, and the host
   vibe-loop adapter can present a bounded environment-only bearer. Remaining
   work is production identity-bearing session/credential lookup, progressing
   trusted-time synchronization for cross-reboot lease and timestamp
   comparability,
   authenticated host-side loopyard/PostgreSQL ingestion tooling, and
   production ingress/authentication authority.
5. **Live deployment**: serve the backend behind the public ingress/TLS
   milestone once that separate track is authorized and closed.

Phases 1 and 2 can proceed against in-memory state while phase 3 is designed;
the coordinator's storage seam should keep the record-store swap mechanical.

## Design Grounding

- [Stateful Task and Job Graphs](../proposals/stateful-task-job-graphs-proposal.md)
  — the work-graph substrate this track exercises (Stages E and G) and the
  prior-art analysis, including loopyard.
- [capOS-Hosted Agent Swarms](../proposals/hosted-agent-swarm-proposal.md) —
  `AgentTask`, `ResourceLease`, `ResourceVersion`, and `ConflictReport`
  shapes the coordinator should converge toward.
- [Task State and Agent Telemetry](../proposals/task-state-and-agent-telemetry-proposal.md)
  — the current file-per-task ledger the development workflow uses inside
  this repository.
- [capOS SDK And Dual Transport](capos-sdk-dual-transport.md) — the remote
  transport option for typed clients.
- [Network Usability After smoltcp](network-usability-post-smoltcp.md) — the
  listener and socket readiness contract used by the bounded adapter loop.
- [Userspace Networking](../proposals/networking-proposal.md) — the
  capability-facing TCP model and Phase C service-object boundary.
- [Completion-Ring Threading](../research/completion-ring-threading.md) — the
  single-threaded ring ownership and explicit readiness discipline used by
  the multiplexed service loop.
- [Plan 9 and Inferno](../research/plan9-inferno.md) — the per-connection
  service-object model that keeps connection state isolated behind distinct
  capabilities.
