residency: a session is a ledger of residents
Everything that stays alive between runs — a worker, a sub-agent,
a schedule, a top-level binding — is a resident,
and a resident has exactly four facets: an identity in the session’s
ownership tree, a typed capability that reaches it, a lease, and a probe.
The session keeps one ledger of residents, in chapters that keep their own
representations, and every management surface — listing, the exit story,
cancellation cascade, /clear’s clear-epoch, /resources — is a fold over
that ledger, written once. Lifetime moves along one order of residency
grades, and the two halves of the system are its two traversal disciplines:
interactive work discovers its lifetime after the fact, agent work
declares it at birth. The full reasoning — the five registries that grew
independently, the wart this dissolves, why the deep fusion is refused — is
session-ledger.
The four facets
- Identity — an id, with an owner edge into the session’s tree (the agent tree, the shell that spawned the worker, the session that armed the schedule). Ownership is what cancellation cascades along.
- Capability — the typed value that reaches and controls the resident: a
Value::Handlefor a worker, anAgentIdfor an agent, a schedule id for a wakeup, a top-level name for a binding. Capabilities are deliberately not unified — they are the honest variance between chapters, never flattened into one value. - Lease — a clock, an idle bound, a renewal signal, an optional backstop — including the degenerate case “none; the owner is the bound” (a worker, which lives as long as the session, worker or call that owns it). leases-and-budgets is where the lease machinery itself lives; residency is the frame it sits inside.
- Probe — what the resident costs now, for the
/resourcesfold.
Nothing session-lived escapes this: a live thing with no chapter is a review defect, the resident-shaped completion of the probe convention (probe-convention).
Resident vs. accumulator
The line between a resident and a mere accumulator (a scrollback, the bus,
an inbox) is the capability: a resident can be reached and controlled by
id; an accumulator can only be measured and bounded. Both are probed — the
/resources fold spans both kinds — but only residents are listed and
cancelled. A scrollback has no capability, and pretending it does
to buy uniformity would buy it at the price of a lie.
The accumulator’s contents have a second characterisation, exact in scope:
a scrollback is always a fold’s memo. Resume seeds that memo from the session
record before the worker spawns; live Signal::Facts step the same fold, while
Signal::Transients touch only its provisional edge
(session-record). The scrollback owns presentation
state around the memo — scroll, disclosure, the open line, the bounded window —
but no independent account of what happened. None of that grants it a
capability; the distinction says where state comes from, never who may reach it.
The residency order
Residency states are graded by independence from the session — a graded partial order, not a lattice: two residents can sit at the same grade with incomparable capabilities, and no join is claimed. There is no stopped grade: ral does not suspend, so a stopped child is resumed at once by whoever waits on it rather than parked as a resident of its own (ral-does-not-suspend).
| grade | state | population | entered by | left by |
|---|---|---|---|---|
| 0 | foreground | the run in progress | evaluation | completion, the wall, interrupt |
| 1 | background | worker handles (spawn) | birth | completion, cancel, its owner’s end, /clear, exit |
| 2 | survives exit | detached processes | birth | outside the session’s story |
Leases are the downward pressure on the residents that carry one — a binding, a parked sub-agent drifts toward reclamation unless renewed; a worker carries none, and its owner’s end is its bound.
Two traversal disciplines, one order
- Interactive work discovers its lifetime. A human cannot know at
launch that this build wants backgrounding; intent is revealed late, so a
REPL user reaches for
spawnonce that becomes clear, or Ctrl-C and re-runs underspawnfor work that was already in the foreground. - Agent work declares its lifetime. A model states intent at birth —
spawnis born at grade 1,detachat grade 2 — and mid-flight movement (promote) stays deferred until a concrete need.
Birth, not promote is not a universal law; it is the declaration discipline. Promotion is not a rejected mistake; it is the discovery discipline’s native gait. Both disciplines walk the same order — they differ only in when a resident’s grade is decided.
The folds, written once
The chapters keep their own representations, locks, and homes
(no-core-repr-leak-into-exarch):
the worker registry beside core’s handles, the agent tree and schedules in
exarch. What is shared is the
small resident signature — identity, population, capability kind, lease
row, state label, cancel — a convention each chapter’s fold writes by hand
(core’s Resident trait was retired unused,
session-ledger), and the folds written
against it:
- list — project a set of chapters into one table, marked by kind in a
designator namespace of its own (a worker’s
#N) so different chapters can never collide. - warn — the exit story: what survives, what is named. Shell exit names any worker handle still running first — it dies with the process, so naming it is its only farewell.
- cascade — cancellation follows ownership edges regardless of
population. Cancelling a resident cancels what it owns: an agent’s
teardown reaches its own workers through its shell’s durable root (a
worker’s cancel scope is a child of that root, so cancelling it walks the
ancestor chain with no extra edge), and reaches its schedules and
children through the tree itself (
Role::Child/Agent::children). - clear-epoch — every session’s inbox counts its own clears, bumped
under the same lock the drain runs, and
/clearbumps only that inbox’s count. Anything settling across it is rejected at the inbox’s own pop — whether by arriving through abus::Stamp(an async agent’s result, a worker’s deferred surface batch, a scheduled wakeup) or by the stronger route of unconditional removal (a schedule, disarmed and dropped rather than tagged). The stamp is the addressed envelopeMailbox::stampmints — destination and epoch captured as one value at composition — so what a worker carries is structurally the envelope of the inbox that will read it, and the rejection is addressed, not global: one tab’s/clearleaves another tab’s outstanding work alone. - probe — the
/resourcesprojection, over residents and accumulators alike.
Enumeration is not observation is the ledger’s own restatement of a law each chapter already keeps: no fold renews a lease. Interest is naming a resident through its capability, never scanning past it.
The fusion refused
The interface is the unification; a single registry struct would be the
flattening. Fusing the chapters — one struct, one lock order, one capability
type — is rejected because the substrates genuinely differ: thread plus
result channel versus agent tree plus mailbox versus schedule plus wakeup,
each with its own ownership shape and its own probe. A prior draft of this
refusal also weighed fusing a pgid job chapter into the handle model — fg
becoming a handle’s await — but ral does not suspend
(ral-does-not-suspend), so there is
no pgid chapter left to fuse against; a handle’s own eliminators (await,
cancel) are simply what they are, never analogues of a job verb that no
longer exists.
Not every chapter implements the resident signature, either, and that is
the same refusal at a smaller scale: exarch’s agent tree, schedules,
and binding ledger each only ever hand out a bare snapshot type for
listing, built by cloning fields out from under a lock already dropped by
the time the snapshot reaches a caller. cancel takes &self alone, by
design, and none of those three snapshots carries a live handle back to its
own chapter — adding one would grow a listing snapshot into something that
either holds a lock past the call that built it or duplicates the
tree’s own cancel methods. The ledger is precisely what makes refusing
these fusions affordable: unity lives in the signature and the folds, so
the capabilities — and the chapters that decline to unify further — can
stay typed and distinct without the system falling into pieces.
See also
session-ledger (the ADR this page
graduates from, and its full context/alternatives),
concurrency-detached-vs-structured
(the handle model; the job-table separation this restates at the listing
layer), unify-turn-evaluation
(the deferred survivor warning), agent-binding-reaping
(the binding chapter), scheduled-wakeups
(the schedules chapter), terminal-lease
(why fg is a host affordance), no-core-repr-leak-into-exarch
(why the chapters keep separate homes), probe-convention
(the probe facet as a checkable rule),
ral-does-not-suspend (why there is
no stopped grade), agent,
shell-state.