CLAUDE.md@packages/cap · git:20261010.0eda541 · 2026-10-10 · sha256 0b398b150e8468ca

CLAUDE.md@packages/cap git:20261010.0eda541A

Immutable. This exact content never changes and is served at /api/v1/blob/0b398b150e8468ca.

# cap

## Inferred LSP plans and question-shaped helpers

`lsp_sql.plan(outlines, targets)` asks the host to infer the configured server
and root from explicit workspace-relative sources. All sources must agree;
`Plan` retains explicit owner assertions. The SQL schema is carried in query
documentation, including `path` columns and the quoted `references` table.
Function examples remain in on-demand capability documentation. `report.list`
accepts structured values, so strings are mapped through `report.string`.

One question is asked with one expression. `target`/`target_at` build a
reference seed, `collect_files`/`collect_seeds` capture without hand-building a
`Plan`, and `query_text`, `query_one` and `references` answer without a
`RowDecoder` — `references` running the `targets`-join-`"references"` statement
the module doc already advertised, into the typed `Reference`. They are thin
compositions over the same `lsp.snapshot` call and the same satellite-local
SQL, so no budget moves: an over-limit statement is still refused rather than
truncated, and `query_text`'s doc says which of the coverage it does not carry.

`search.grep_text` and `search.glob_paths` are the same idea for the two
search reductions, each also naming the call to read when a bound matters.
Both modules open with a `//// ## Flow` spine and a `//// ## Examples`
block, since a rendered `## Examples` is what a model reads as its
instructions.

Production `proc.run` inherits the calling strand's shell directory, and
`proc.in_dir` overrides one command through the host wrapper.

## Purpose

The capability prelude: the language a code-mode program is written
against, plus the boot runtime that runs it. Every `cap/*` module a
submitted program may import is here. Its effectful calls use an RPC stub
— a typed local-looking call that marshals its arguments into a `cap_call`
frame, sends it over the one AF_UNIX channel to the satellite host, and
blocks for the `cap_result`. This package runs *inside* the jailed
satellite node, never in the harness VM, and is a separate build target so
it can one day be published on its own. WP-J, and WP-N for `cap/strand`.

The prelude serves three execution contexts (`codemode/vet/policy.Seam`).
On the default server, workspace and orchestration programs admit the same
effect and child-operation capabilities. An omitted mode selects workspace,
so a program can read files and spawn children in one run. An explicit
workspace-only host has no Agency custody and retains the effect-only subset.
The extension seam widens that effect subset with the `ext` vocabulary; it
does not gain `cap/strand` or `cap/workflow`. An installed extension's tool is
a workspace program with a different entry point. It widens by no new base
capability: `cap/ext.call` was phase 1's
"which call am I serving?" pull, and `protocol-change/012` deleted it,
because a satellite that lives for a session is *told* what to answer
over a `hook_call`.

This package therefore serves **two boot shapes**. `cap/runtime.run`
boots one program, writes one terminal `outcome` frame and dies — code
mode's disposable node. `cap/runtime.serve` boots the same channel over
the same socket and then waits, answering `hook_call`s until the harness
cancels or the channel closes — an installed extension's session-lived
satellite. The capability language beneath them is identical; what
differs is where a node's authority comes from, which is the invariants
below.

The disposable runtime and resident hooks render abnormal worker exits through
one bounded satellite-local formatter. Gleam assertion messages, failed values
and source locations survive as ordinary failure text; monitor stacks are
omitted. The failed value has its own formatting budget so unrelated metadata
cannot hide the capability error. This does not grant the program a new effect.

## Key Types

- `cap/report.{Outcome, Value}` — `Completed(value)` /
  `Errored(message, details)`. A program is a `fn() -> Outcome`;
  `to_msgpack` is the marshalling the boot runtime writes as the terminal
  frame, so a strand receives a structured value and never scrapes stdout.
  `Value` is a re-export of the wire's value type plus builders
  (`string`/`int`/`float`/`bool`/`list`/`object`/`null`) and readers
  (`field`/`as_string`/`as_int`/`as_float`/`as_bool`/`as_list`), none of
  which coerces: `as_int` refuses a float and `as_float` refuses an int,
  so the pair still answers which tag arrived. They live here
  because a program cannot name `core/msgpack`: the allowlist omits it and
  the hermetic build's `--warnings-as-errors` turns importing a transitive
  dependency into a compile error, so without them `report.text` was the
  only thing a program could say.
- `cap/mcp.{Content, ToolResult, DecodeError, McpError}` is the shared
  vocabulary for generated `cap/mcp/<server>` façades. `Text`/`Other`
  content blocks retain text and carry non-text kinds by name. Tools without
  `outputSchema` return `ToolResult` with optional structured content. Tools
  with it return generated records or other structural types, decoded by
  `cap/internal/mcp_codec` after `cap/internal/mcp.invoke_typed` reads the
  existing result envelope. `DecodeError(path, reason)` identifies a failure
  relative to the structured root; `ResultSchemaMismatch(error, result)`
  retains the original result for inspection. `ToolFailed`,
  `ServerUnavailable`, `McpDenied` and `ResultMalformed` keep their distinct
  tool, transport, policy and envelope meanings.
  These public types carry no authority. The internal invocation seam fixes
  the envelope `{tool, arguments}` and dispatches under `"mcp." <> server`;
  generated façades fix both names. Programs cannot import that seam or the
  codec module. The codec composes total readers for primitive types,
  records, enums, lists, nullable values and supported unions. It checks
  structural shapes, literals and closed objects; general JSON Schema
  refinements remain the server's admission duty. Missing structured content
  differs from present null, including inside optional nullable fields.
  MCP is absent from every static seam: a configured host adds `cap/mcp`
  and each generated module through `client/codemode.seam_allowlist`, and a
  host with none allows neither.
- `cap/strand.{Assignment, Handle, Waited, TerminalResult, StrandError}` —
  child operations in either default program mode. `assignment`/`within`/`detached`/
  `from_my_conversation`/`with_model`/`with_tools`/`expecting` build a spawn; `spawn`,
  `wait` (a list of handles against **one** deadline, sliced across rounds up to `max_wait_slice_ms`), `send`, `note`,
  `notes` and `roster` are the six calls, serviced by the same
  `client/agency` closures the `agent_*` tools call. Every `StrandError`
  variant but the last two is one of the harness's own refusal names
  carrying the harness's own sentence — except the two ceilings, which are
  the seam's own: `SpawnCeilingReached` and `AdmissionCeilingReached`, the
  second covering `send`, `note` and `notes` alike because a program at
  any of them does the same thing and the message names which. The named
  set matches `codemode/orchestration.refusal_code`'s range as of today,
  `map_error` being the other half of that contract: `NameAlreadyMinted`
  and `PlaneFailed` are there for that reason and not because a program is
  expected to recover from either. `StrandRefused` stays the arm for a
  code this module has not learned, so a new name still reaches a program
  as itself — which is also why the two sides can drift silently, and what
  the pins are for. **Nothing gates the pairing across the wire**: `cap`
  and `codemode` share no dependency, so no check in either package can
  see the other's list. What *is* gated is each end against its own type —
  `strand_test.named_code` and `orchestration_test.expected_code` are
  total `case`s, so adding a variant to `StrandError` or to
  `agent.Refusal` breaks that package's test compile and forces whoever
  adds it to write the code down and look at the paired list. Adding a
  variant on one side and not the other still compiles everywhere; the
  alarm is that you were made to read the sentence saying so.
- `cap/runtime.{Transport, BootError}` — the boot runtime's injected
  transport (`send`, `recv`, `outcome_sink`) and its four setup failures.
  `run(main)` is the production convenience the generated satellite entry
  module calls; `boot` is the testable core beneath it.
- `cap/runtime.{Invocation, Answer, Asked}` with `serve`/`serve_over` —
  the other boot shape, and a persistent satellite's whole main loop.
  `serve(answer)` reads the environment, boots the channel and waits;
  `serve_over(token, transport, answer)` is the same loop over an injected
  transport, the seam `boot` takes and for the same reason. `Asked(
  invocation, args, deadline_ms)` is one `hook_call` with its `kind`
  string already turned into a closed set — `Tool(name)` or `Event(name)`,
  anything else refused `bad_kind` — and `Answer` is `Answered(value)` /
  `Refused(code, message)`, mirroring the `hook_result` body. The three
  codes this loop mints itself are `bad_kind`, `busy` and `crashed`;
  every other code is the serving function's own vocabulary
  (`packages/ext`'s, in practice).
- `cap/schedule.{Schedule, Created, Wake, Bounds, ScheduleError}` — heartbeats a
  program sets for the strand it is running on or for a strand that
  strand spawned, the code-mode half of the door `tools/schedule` opens
  for a tool call. Both land on one
  implementation (`client/scheduleseam.Door`), so either door can cancel
  what the other created. `Created.wake` is what the host actually
  granted and is not always what was asked for — an operator policy may
  permit scheduling and forbid waking — so a program that needs waking to
  be meaningful must read the field rather than assume the request. It is
  a `Wake` (`WakesIdle | SteersOnly`) rather than a `Bool`, so the
  request and the grant read the same way at both ends; the capability
  wire still carries a boolean in both directions.
  **Four timings, and two of them are not conveniences.** `every` is a
  fixed interval, `at` a UTC instant, `cron` a five-field calendar
  expression, and `after` a one-shot a fixed while from now — one of the
  four per schedule. `after` exists because *nothing tells a program the
  current time*: the strand's prompt carries no clock and no date, so an
  absolute instant for `at` cannot be computed inside a program and a
  guessed one is either refused or fired at the wrong moment; the host
  resolves it against the session's own clock, bounded at 1..604800
  seconds. `cron` exists because an interval cannot express a *phase* —
  the grid is aligned to the epoch, so `every(…, 86_400, …)` is always
  00:00 UTC — and it is the standard five fields and nothing more: no
  seconds field, no month or day names, none of `L`/`W`/`?`/`#`, and the
  two day fields ORed rather than ANDed when both are restricted.
  `cron`'s fields are read in **UTC**, and `cron_at_offset(name,
  expression, offset, wake, body)` is the one door onto a different
  clock: it takes a **fixed offset** written `[+-]HH:MM` (`"+02:00"`,
  `"-05:30"`, between `"-14:00"` and `"+14:00"`) and **not a timezone**.
  That distinction is the whole of what the function costs, and it is
  what the doc comment has to state: a zone is a function from an instant
  to an offset, which needs a database Loom does not carry, so nothing
  here follows a daylight-saving change and an offset written in summer
  fires an hour out all winter. Write the offset in force now and say in
  the body which clock the schedule was set for. The offset is refused
  beside any timing but `cron`, which names no fields for it to shift,
  and it moves no durable identity: the host records an occurrence under
  the UTC second it fell on, so an offset added or changed later re-fires
  nothing. `every_within` and `cron_within` are the same two
  recurring shapes with `Bounds(max_fires:, expires_after_s:)` stated
  rather than defaulted; `DefaultBounds` is what the plain
  functions pass, and `Bounds` can only *narrow* — the host holds both
  numbers to the same ceilings it holds its own configuration to and
  denies anything above one as `invalid_schedule`.
  **A target is a request, never an instruction.** `every_on`, `at_on`,
  `cron_on`, `after_on` and `cancel_on` are the functions that name one
  (`cron_at_offset` deliberately does not: it is this strand's own
  schedule, and the smallest addition the offset needed), and the host
  admits only the calling strand itself or a strand it spawned, decided
  from its own lineage ledger and refused as `invalid_schedule`
  otherwise — a program cannot reach a sibling's or a parent's context by
  writing a name. The plain `every`/`at`/`cron`/`after`/`cancel` mean
  this strand. Two consequences a program has to read rather than
  assume: a schedule onto a subagent is always `SteersOnly`, because a
  subagent has one run and a fresh one after it would extend a child's
  life past its work; and `list`/`cancel` are keyed on the strand that
  **created** a schedule rather than the one it fires onto, which is what
  makes a heartbeat onto a subagent cancellable at all once that
  subagent has finished. `Schedule.target` and `Created.target` are how
  a program tells the two apart, since one name may be in use on this
  strand and on a child's at once.
  The default server admits scheduling from either program mode. A schedule
  creates durable work that can outlive a program and may wake an idle strand;
  its target and creator checks still run in the host. An explicit effect-only
  host also admits it because scheduling does not require child custody.
- `cap/job.{Started, State, StopCause, LostReason, Exit, Stream, Spill,
  Job, Row, Cursors, JobError}` with `start`, `start_within`, `start_for_session`, `poll`,
  `list`, `kill`, `send`, `send_last`, `from_start`, `after`,
  `is_pending` — background jobs a program starts, watches, feeds and
  stops, the code-mode half of the door `bash`'s `mode` argument and the
  `job_*` tools open for a tool call. Both land on one implementation
  (`client/jobseam.Door`), so a job is the same record with the same
  owner whichever door made it and either may poll or kill what the other
  started; ownership is the **strand**, never the caller.
  **A program's own `within_ms` is unrelated to a job's deadline.** The
  satellite ends when the program returns and the job keeps running under
  its own token, so a program that starts one and returns has neither
  leaked anything nor waited for anything — if the point was the output,
  either `poll` until `is_pending` is false or use `proc.run`. The
  finite deadline is fixed at `start` and never renewed. Session lifetime
  reports zero in `Started.wall_ms` and `Started.deadline_ms`; otherwise the wall is
  what the host actually granted after its own clamp and the session
  policy's narrowing, and is not always what was asked for.
  This is the thing `proc.run` cannot be: `run` blocks for the whole
  command and dies with the execution, so "start a server, poll until a
  line matches, run the tests, kill the server" is a loop here and is
  nothing there — which is exactly why the top-level tool surface has no
  "wait until the output contains X" argument.
  `poll` answers a bounded rolling tail *since a cursor*; `Stream.dropped`
  above zero means output left the window unread, never that it was lost,
  because `Job.spill` carries the whole of each stream content-addressed
  once the job is terminal. Cursors are the harness's tokens — take
  `after(job)`, never compute one. `send` leaves stdin open and
  `send_last` closes it, so the end-of-input boolean is converted once at
  the wire and never appears at a call site.
  `JobError` is split the way `cap/proc`'s is: `JobCeilingReached`,
  `JobNotFound` and `JobRefused` for the reasons a program branches on,
  `JobDenied` carrying any other host code verbatim, `JobUnavailable` for
  a channel that could not carry the call or a host with no jobs plane.
  Workspace seam only, like `cap/schedule`.
- `cap/proc.Command` — opaque, built through `command`/`in_dir`/`with_env`/
  `with_stdin`/`with_timeout`, so a non-empty argv holds by construction.
  `proc.run` is the one capability the harness's `default_router` services
  today. `proc.stdout` and `proc.stdout_accepting(command, exit_codes)` reduce
  a run to its stdout or one error string naming the command (argv cut to at
  most 160 bytes), the exit code and the end of stderr (at most 400 bytes);
  the ellipsis counts inside both limits, and `tools/proc_failure` keeps its
  own copy of the cut; a timeout is always
  an error. They are the reduction every hand-written `run` helper kept
  getting wrong by dropping stderr. `stdout_from` is the pure decision behind
  them, `@internal` so tests reach it without a channel.
- `cap/task.Failure(e)` — `Returned(index, error)` / `Crashed(index,
  reason)`. The result type of every combinator, and the reason a killed
  branch is distinguishable from a branch that returned an error.
- `cap/actor.{Address(state, msg), Next(state), Reply(value)}` — the
  unforgeable typed address, the handler's continue/stop verdict, and the
  reply channel for `call`. `Address` carries both type parameters, which
  is what makes `call` and `get` fully typed.
- `cap/internal/channel.{Channel, Handle, CapOutcome, CallError}` — the
  single seam every capability goes through. `Channel` is the one-function
  record stored in the VM-global slot; `Handle` is the boot module's
  private handle on the actor; `CallError` is `Denied` (the broker's
  in-band refusal) or `Unreachable` (transport).

## Relationships

`cap/lsp_sql` separates one host capture from repeated satellite-local SQL.
`Plan` names one server/root and explicit outlines and reference targets;
`collect` dispatches `lsp.snapshot` and decodes an opaque immutable
`Observation`. `metadata` returns scope, generation, interval and counts.
`query` materializes the four fixed fact tables in private memory, binds tagged
`Cell` values and uses a caller-owned `RowDecoder(a)`. `QueryResult(a)` keeps
typed rows and column names beside unchanged provenance. `QueryError` names
authorization, statement, budget, value and decoder failures without parsing
sentences. A SELECT or decoder issues no host calls. `schema()` returns the
four tables and their columns from one definition (the `query` doc repeats it,
because the prelude keeps only function docs, and a test runs every column
through the native boundary). A query that names an unknown table or
`sqlite_master` is refused with the table list in its reason. `error_text`,
`query_error_text` and `cell_text` render errors and cells for a report.

Every capability module whose public error type had no renderer ships
`error_text(Error) -> String`, exhaustive and one line, following
`strand.error_text`; `lsp_sql` has a second, `query_error_text`, for its second
error type. Programs used to hand-write these.

Native authorization and progress/heap enforcement cannot be implemented by
the pure Gleam API. `cap/internal/ffi_lsp_sql` delegates through
`loom_cap_lsp_sql.erl` to the existing SQLite dependency family. All handles
stay inside that bridge and every query closes its own database. The opt-in
heap ceiling is process-global and belongs only in this satellite; loading
these modules into the harness would violate Rule Zero. The detailed schema,
ownership and checked-interval limits are in
[`docs/architecture/lsp-sql.md`](../../docs/architecture/lsp-sql.md).

- **Depends on**: `core` (msgpack values and the corruption report),
  `gleam_erlang` (processes, monitors, subjects), `gleam_otp` (the actor
  behind `cap/actor` and the channel), and the standard library. The SQL
  observation bridge directly pins `esqlite_loom` 0.9.1, the same native SQLite
  family selected by the harness's `sqlight_loom` 1.2.1 consumers. The offline
  seed carries that native application into the satellite artifact.
- **Deliberately does not depend on `broker`.** The spec DAG (§0.1) puts
  WP-J at `J → G,I`, which holds for `codemode` but not here: `cap` is the
  untrusted far side of the effect-plane wire, not a peer of the broker, so
  the frozen Part 1.4 envelope it needs is reproduced over `core/msgpack`
  in `cap/internal/wire` (outbound) and `cap/internal/inbound` (the read
  half) rather than borrowed from `broker/framing`. Report the divergence;
  do not close it by adding the edge.
- **Depended on by**: nothing in the harness. `packages/codemode` is its
  counterpart and restates the shared names (`LOOM_CAP_SOCK`,
  `LOOM_CAP_TOKEN_FILE`, the `outcome` frame kind) rather than importing
  them, because linking model-facing code into the harness VM would break
  Rule Zero. A compiled program depends on `cap` by being built against it,
  vendored inside its own build root.
- **FFI**: the transport and registry modules own the channel's impurity.
  `cap/internal/ffi_registry` binds `persistent_term` — VM-global, readable
  at local-memory speed from every process a program spawns, which no pure
  alternative can do. `cap/internal/ffi_transport` binds `getenv`, a file
  read, and `gen_tcp` over AF_UNIX. Both go through `cap_ffi.erl`. The SQL
  bridge described above is separate because it owns no channel effect.

## Traffic

- **Actor messages** — `cap/internal/channel.Msg` (opaque):
  `Perform(cap, args, deadline_ms, caller, reply)` is the call every public
  `cap/*` function makes; `Deliver(id, outcome)` and `Fail(reason)` are
  casts from the boot runtime's read loop; `CallerDown(down)` is the
  monitor firing when a caller is killed; `Stop` is teardown.
  `SetToken(token)` is the serving loop's alone: it installs the
  invocation's token before the answer starts and clears it after.
  `cap/actor` and `cap/task` spawn their own processes but exchange no
  package-level protocol.
- **Wire** — one AF_UNIX stream, length-prefixed msgpack, protocol version
  1, 16 MiB cap. Out: `cap_call` (carrying the token, the capability name,
  the marshalled args, and a deadline) and `cancel`. `strand.wait` is the
  one call that sets its own deadline — the join window plus
  `wait_margin_ms` — because the harness answers `Pending` at the window
  rather than hanging, and the channel must outlast that. In:
  `cap_result`, the answer to something this node asked, and `hook_call`,
  the one frame the harness asks *it* (`protocol-change/012`) — a
  single-shot node's reader drops the second, and both shapes drop every
  other well-formed kind and keep the channel open. Out, once per
  invocation on a serving node: `hook_result`, carrying the frame `id` of
  the `hook_call` it answers. Out, exactly once per execution on a
  single-shot node: the terminal `outcome` frame
  (`{v: 1, id: 0, kind: "outcome", body}`) carrying
  `report.to_msgpack(outcome)`. A serving node writes **no** `outcome`
  frame at all, because it ends no execution.
- **Commits / registers**: none. This package never touches durable
  storage; it has no way to.

## Invariants

- **A program cannot name the seam.** `cap/internal/*` is an internal
  module, which the Gleam compiler forbids another package from importing,
  and no public cap function takes a channel or a token as an argument.
  Both are held in the channel actor and fetched per call through
  `cap/internal/dispatch`. The program can neither supply, read, nor
  replace them.
- **The token authenticates the channel; it does not confine an escaped
  `.beam`.** The boot runtime must read the token file, so a hand-written
  `.beam` with its own `@external` can read it too and present the genuine
  token. What the token buys is rejection of a peer that never read it and
  binding to one `{op_id, step_id, deadline}` so it cannot be replayed.
  Confinement is the kernel jail plus the broker's per-call policy check.
  Do not write it the other way round.
- **Installing over a live channel is refused.** The channel lives in a
  VM-global slot installed per execution, so a process surviving execution
  *N* would read *N+1*'s channel and act under *N+1*'s token.
  `dispatch.install_exclusive` refuses to overwrite a slot whose channel
  actor is still alive, making the executor's reaping obligation fail
  loudly instead of silently lending authority. `release` is a
  compare-and-clear, so a slow teardown cannot clear a later execution's
  slot. Under the single-shot shape that guard never fires, because every
  execution gets a fresh node; under the serving shape it is what would
  catch a host that started a second satellite for one extension without
  reaping the first, which is why `codemode/satellite` states the reaping
  obligation as *a host reaps its node before the session's next host for
  that extension starts*.
- **A serving node's authority belongs to the invocation, not to the
  node.** The token file `read_token` reads is deliberately not a working
  token for a serving satellite; it satisfies the boot sequence and
  nothing else. Every token that works arrives on a `hook_call`, is
  installed with `channel.set_token` before the worker starts, and is
  cleared to empty bytes after the answer ends — so a process the
  extension kept alive between invocations frames its `cap_call`s with
  bytes the harness has already revoked. **An extension may compute
  between invocations and may not act**, and the ordering is what makes
  that true: install, then spawn; answer, then clear.
- **One invocation at a time, and a crash is an answer.** A second
  `hook_call` arriving while one is open is answered `busy` on the spot
  and never queued — a queue would mean a second token installed under
  the first invocation's worker, and the harness serialises anyway, so a
  second is a fault to name rather than work to schedule. The invocation
  runs on a monitored child of the loop, so a `panic` in extension code
  becomes `crashed` on the wire and the loop goes on to the next
  invocation; that child is also what keeps the loop in its selector, so
  the `busy` answer is not stuck behind an invocation that may run for
  its whole deadline. The loop ends on a `cancel` frame or a closed
  channel, in both cases by returning.
- **Cancellation is real, not advisory.** The channel monitors the caller
  of every in-flight call. Killing a `race` loser therefore produces a
  `DOWN`, which emits a `cancel` frame for that call id, which makes the
  broker revoke the effect and kill its executor process group. No
  cooperation from the dying process is needed. The combinator signals every
  losing worker before it waits on the original monitors, then returns only
  after every Down has joined. This ordering prevents one slow exit from
  postponing cancellation of its siblings.
- **Fail-fast reports the failure which triggered cancellation.** Indices
  absent from the runner's completed-outcome map are tasks it killed after
  observing that failure. They do not become synthetic lower-indexed crashes,
  because doing so would make `both` and `parallel_map_fail_fast` depend on
  scheduler order and hide the error which actually stopped the group.
- **Structured concurrency holds only while the combinator does.** Workers
  are unlinked and monitored, and the combinator drives cancellation from
  its own loop, so a combinator killed from outside — most plausibly by a
  linked `cap/actor` crash — orphans its workers until the node dies. The
  guarantee to state is "no work outlives the satellite".
- **The actor link runs to the *spawner*, not to the program root.** An
  actor spawned by `main` fails the program as a unit; one spawned inside a
  `cap/task` branch is contained to that branch and surfaces as a
  `Crashed` failure. "All-for-one" (spec §1.6, WP-J) describes the first
  case only.
- **Mailboxes are bounded and `send` parks.** Admission is synchronous and
  handling is asynchronous, so a fast producer meets backpressure rather
  than growing the heap. Parked senders are bounded by how many processes
  push at once, never by message rate.
- **Filling the bounded queue to its bound costs O(bound), not
  O(bound²).** The internal queue is the two-list technique — push onto
  `back`, pop from `front`, reverse `back` onto `front` only when the
  latter runs dry — with its length tracked in a counter rather than
  recomputed, so `handle_admit`'s bound check and the push it gates are
  both O(1). Before issue #45 both were `list.length`/`list.append` over
  a single list, which made admitting a mailbox's worth of messages cost
  the square of the bound; the fix removed the factor rather than moving
  it, the same lesson `08cdbce` drew from `core/json`'s excerpt.
- **Four of the six calls carry a lifetime admission ceiling, and the
  numbers hold together.** `spawn` 32, `send` 128, `note` 256, `notes`
  64; `wait` and `roster` none, because a wait's cost is time and a
  roster's size is `session_strands`. The test a call has to meet is that
  it *mints something outliving the execution*. **`note` and `notes` are
  one decision**: a note/notes loop is quadratic in harness work and the
  quadratic needs both factors unbounded, so relaxing either alone puts
  it back. The ceilings are the host's (`codemode/satellite.CapCeiling`);
  this side only names what comes back.
- **A refusal keeps the harness's name, or arrives as itself.**
  `cap/strand.map_error` turns a broker code back into the variant of the
  same name, and the codes are `codemode/orchestration.refusal_code`'s
  plus the two ceiling codes.
  The two packages share no dependency — they are the ends of one wire,
  not peers — so each side pins its own half and the orchestration sample
  crosses the whole of it for real. A code neither side has learned yet
  comes back as `StrandRefused` carrying the code verbatim rather than
  being folded into a generic failure.
- **Nothing asked for is nothing, not an empty list.** A spawn with no
  declared result shape sends `nil`, not `[]`, so it is distinguishable
  from one that declared an empty shape — which is what keeps the
  harness's `NoResultAsked` verdict a separate fact from "the child
  answered nothing".
- **Deny-by-default for `cap/net` is a broker property.** Nothing in
  `cap/net` refuses anything; it marshals and dispatches exactly as
  `cap/fs.read` does and only labels the broker's refusal. The design's
  guarantee holds because no policy field exists for a program to flip, not
  because this module checks anything.
- **Every boundary decodes totally.** A wrong-shape `cap_result` field is a
  `String` fault a caller maps to its own typed error; an oversized length
  prefix, an unparseable payload, or an unsupported version is an
  `inbound.Fault` that settles in-flight calls in-band and closes the
  channel; a well-formed frame of any other kind is dropped and the channel
  stays open. Nothing here panics, and a crash inside the submitted `main`
  becomes an `Errored` outcome rather than a dead node.

## Deep Docs

- [docs/architecture/code-mode.md](../../docs/architecture/code-mode.md) —
  the two layers, the prelude as the capability system, and what each layer
  actually confines.
- [docs/architecture/effects.md](../../docs/architecture/effects.md) — the
  one door, the framed wire, the jail, Rule Zero.
- [packages/codemode/CLAUDE.md](../codemode/CLAUDE.md) — the harness side:
  the lint, the hermetic build, the launcher, and the host at the other end
  of this channel.
- [docs/review/m4-triage.md](../../docs/review/m4-triage.md) — the review
  wave this package's current shape answers.
- [Root CLAUDE.md](../../CLAUDE.md) — repo ground rules and the doc graph.

## Background collaboration (protocol 048)

`cap/execution.receive` reads the current background execution's durable input
by cursor. `cap/peer` discovers and messages operator-linked peers; its typed
receipts prove admission. The default server admits these modules from both
program modes, with host routers supplying identity. `cap/workflow.step`
requires a background execution and returns a `cap/strand.Handle` for a durable
named child operation. Programs can combine these calls with filesystem and
process effects; each effect still passes its broker check and the satellite
jail. Extensions retain a separate lifecycle and allowlist.

`execution.endpoint` couples a decoder and typed callback in an opaque endpoint.
`execution.serve` registers the immutable names and idle interval, reads one
ordered journal and continues after a rejected value. It reports callback
outcomes through `execution.delivery`; callback success is not proof of actor
completion. `execution.progress` submits bounded, coalesced volatile snapshots.
Raw `receive` publishes `default` readiness and cannot consume a typed service.
Idle expiry is host-owned cancellation, so the program cannot rely on reporting
a final result after an idle response. The original wall deadline always holds.

## Durable code-mode notes

The host-installed `cap/notes` data surface adds `put`, `get`, and `list`
to workspace and orchestration programs that have a notes door. It uses the
existing JSON blackboard, with relative read keys and caller-owned writes.
`cap/fs.read` dispatches `note://` reads as `notes.read`; virtual paths are
read-only and never mounted for shell processes. Extension and resident
policies do not admit the notes module. Static allowlists remain unchanged;
`client/codemode.seam_allowlist` installs this module per host.

See [protocol 045](../../protocol-change/045-code-mode-notes.md).

## Code-mode utilities

`report.decode_json` and `report.encode_json` bridge JSON text and Value
through core/json_wire. `strand.map` runs bounded batches with one output per
assignment; pending/failed joins and refused admissions stop further spawning
while retaining known handles and unstarted assignments. It introduces no
process machinery or new capability.
`strand.wait` slices long join windows into bounded requests of at most 30 s (`max_wait_slice_ms`) until handles settle or the deadline expires.


`cap/peer` also exposes caller-owned `inbox`, `inbox_get`, and `history` for
same-session and remote inputs, plus existing remote admission `received`,
`received_get`, and linked sender `sent_receipt` views. These are read-only typed
views; they neither acknowledge nor consume messages. Empty `roster` means no
outgoing remote links and says nothing about the caller's inbound messages.
Pending IDs and receipt keys paginate live sets; receipt hash cursors are not
arrival watermarks. Full bodies that exceed the response ceiling fail explicitly.

## Explicit session-lifetime background jobs

Protocol-change/058 records the lifetime contract. Finite jobs retain their
existing default and fixed deadline. Bash `mode: "background", lifetime:
"session"` and `cap/job.start_for_session` explicitly request no wall deadline.
Code-mode callers declare `permissions.wall_s: 0`; the launching action must
receive the missing wall grant before execution, and the job captures it.
Zero job wall/deadline denotes this authorized lifetime. Clearance and drain
remain bounded, other resource limits remain active, and session shutdown,
owner kill or originating-operation abort cancels the execution. Quiet waiting
has no completion or heartbeat wake unless the caller explicitly asks for the
existing idle heartbeat. A VM restart loses the job and never replays it.

## Typed capability boundaries (protocol 057)

Peer calls decode stable response envelopes inside the satellite; callers receive
pages, inputs, links and receipts rather than JSON strings. Optional lookups
use `Option`, and pending, transcript and receipt cursors have distinct types.
Open sender-owned values remain `cap/report.Value`. Peer, workflow and execution
errors distinguish host denial, channel unavailability and malformed responses.
Execution input cursors are nonnegative, and normal closure preserves its reason.

`cap/strand` re-exports `core/ids.OpId` and `EntryId` with total parsers and
renderers. Handles and deliveries use those identities; declared fields use
`Required` or `Optional` before marshaling the existing boolean. `cap/job.JobId`
validates the host's register-key grammar; stdout and stderr cursors are separate
opaque types, while `from_start` and `after` retain the normal polling workflow.

`cap/schedule.{Cadence, Expiry}` exposes the granted interval, cron expression
with fixed UTC offset, or resolved one-shot Unix time. `Created` and `Schedule`
retain their rendered `when` alongside `cadence`. Missing or malformed required
fields return `MalformedScheduleResult`, separately from transport failure.
These types grant no authority and change no queue, receipt or schedule custody.

See [protocol 057](../../protocol-change/057-typed-capability-results.md).

## Diagnostics scope

`lsp.diagnostics(None)` is an always-unsettled snapshot of the current package
server. `Settled([])` is meaningful for an explicit file scope, not as workspace
coverage. The wire variants and explicit-file behavior remain the same;
protocol 078 records the correction.