pins: a read/write noticeboard, the matrix’s dual
A pin is state a kit publishes to a keyed slot on a persistent register, the
in-place dual of an event appended to scrollback. The transcript is an
append-only log of things that happened, sealed once written; a pin is what is
currently true — a task rollup, a build’s status, the file under edit — and
state changes by being overwritten, not appended. exarch already redraws
host-authored state in place (the agent matrix, the ctx% gauge, the phase
bar — fixed marks, never streamed, never logged); a pin is the missing fourth
cell, the same graphic grammar authored by the kit instead of the host. The
register started write-only — a kit could publish state but never read it
back — and is now read/write: pin-read and pin-list answer from the same
mirror the nudge already kept, so the model (and a kit) can survey the board
it writes to. The full reasoning for the wire and its taxonomy is
surface-pins-state; the read side is
register-is-read-write.
The vocabulary
A pin rides the same surface channel as everything else, as a disposition
wrapper around an ordinary “ `card “ — the wrapper carries
only placement, the card vocabulary and its decoder reused verbatim, and
this wire is unchanged by the read side:
`pin [key, body]— writebodyto register slotkey, overwriting in place on re-pin. Thekeyis model-chosen and is the datum’s identity — the thing the host cannot guess. No key is reserved: the write side has no guard at all, and every slot in the register is model-authored.`unpin [key]— drop the slot (a finished plan clears its gauge). An absent body is the same as`unpin.
The register once carried one host-owned exception, a services slot the
host reconciled and ordinary surface writes could not touch
(reject_protected_pin); that mechanism is deleted outright, on no
technical rationale beyond the operator’s own — protected pins should not
exist. Durable services lose the register as a
legibility channel with it: what a service <desc> <thunk> birth still
shows is the same worker-birth trail card an ordinary spawn gets
(worker #id cmd durable, builtins) plus an
aggregate running count on /resources — no per-service listing survives.
See names-and-schedule-labels’s
2026-08-27 amendment.
On top of that wire sit four model-facing commands, one pin-* family:
pin-set/pin-clear are ral wrappers over `pin/`unpin;
pin-read/pin-list are the two Rust enquiries that make the register
legible back (builtins). “Set” says a pin overwrites a
slot, “clear” says it empties — not “add”/“remove”, which would say the
register is a collection rather than one card per key.
So a kit holding evolving state pins one rollup and overwrites it, rather than
marching tasks 0/3, tasks 1/3, … down the scrollback — the streaming the rail
doctrine forbids. exarch/data/agent.ral (the tasks section) is the first client: transition reads the
list back, computes the next one, and pins the gauge that fills in place —
a per-task open → done move appends nothing to the transcript, and now
appends nothing to a bound list either.
How it flows
One decoder, one new arm, one viewport field, and one enquiry class over the same mirror — no new concurrency invariant, because a pin is still emitted in-run through the live foreground sink, the exact place a card is already safe:
- Decode.
value_to_pinis tried first on the surface channel, ahead of theioandcardarms (shell-eval); they cannot collide —iois aMap, the rest distinctVariantlabels. It resolves toSurface::Pin { key, card }orSurface::Unpin { key }; the applier records a forensic breadcrumb and publishes the corresponding liveTransient. - Register. The slots live on the `Viewport` as an
ordered
key → Cardmap; a pin isset_pin(overwrite or insert, first-seen order), an unpin isdrop_pin— the in-place analogue ofpush_card, touching neither the flatten nor the log.resetclears it on/clear, so pins are generation-bounded exactly as scrollback is. - Mirror. The session keeps its own copy alongside the viewport’s, per
agent: a
key → PinDigestmap (PinDigests, shell-eval) holding the full decodedCard, written on every accepted pin/unpin. It was born to let the nudge name what is pinned without parsing rendered text; the read side reuses the same store rather than adding a second one —pin-readandpin-listare enquiries answered straight from it. - Render. The register is a reserved right-hand column for the focused
session — a flat strip glued to the right edge, never a floating overlay that
would occlude the yank-able scrollback. It claims only dead margin past the
READ_Wreading cap, so the transcript never narrows; below a width threshold it collapses to a one-row pin band beside the matrix. The frame reads symmetrically: the rail owns the left edge (what happened), the register the right (what is). - Headless. There is no drawn register to overwrite, so a pin renders nothing.
The record log retains
Pin/Unpinas forensic breadcrumbs (one-seam-one-log) that no fold draws — the live register follows the shell boundary and is not restored on resume. Pinned state is ambient, like the matrix.pin-read/pin-liststill answer headless, since they read the mirror, not the drawn column.
Reading the register back
pin-read <key> answers the card stored under key, canonically
re-encoded, or () on a miss; pin-list answers the occupied keys. The
encoder (encode_card, cards) is value_to_card’s
inverse on the decoder’s image, so what comes back is never the authored
bytes — a bare-string span or a bare mark, sugar the decoder accepts, comes
back tagged, and an unknown mark comes back as the plain text it already
degraded to. Canonical, not verbatim, closes the one hole a write-only
register never had to worry about: an authored value could otherwise carry
state the decoder discards, and the register would hold a truth the rail does
not show. Reading the canonical card makes storage and display one thing by
construction — the WYSIWYG invariant: only what the card renders can be
read back, and two states that render identically are the same state to
pin-read.
Read-after-write within one run is sound for free: under the identity
binding IdentityDesk::enquire drains queued surface frames before answering
a request (core/src/protocol.rs), and under the wire frame order does the
same, so a pin written earlier in the same script is
already in the mirror when the read is answered. Both enquiries are per-agent,
same as the mirror they read — a sub-agent’s register is its own, and
pin-read never crosses that line; a foreign-key read is deliberately out of
scope (see the ADR’s open questions).
The model is the register’s default mutator
Because the register now reads as easily as it writes, the model can survey
the board (pin-list), read a slot (pin-read), judge it, and revise it
(pin-set) with no kit in the loop — a stray fact, a warning, a note is
pinnable with zero apparatus, the same way any card is surfaceable with zero
apparatus. A kit that owns a key instead treats its card as a
serialization: read it back, destructure against its own schema, mutate,
pin-set again. The tasks kit is the worked example
(register-is-read-write, §4): its
mutators take no list argument and thread none — add-task, transition,
tag-task each read pin-read "tasks", decode it against the kit’s own row
shape, and write the new rollup back through one sync-tasks write point,
which clears the slot once no task remains open. A card under "tasks" the
model wrote directly, in a shape the kit’s decoder does not recognize, fails
the next kit call with a didactic fail naming the expected shape — the
price of one keyspace shared between a schemaless mutator and a schema’d one.
The model watches its own pins
Because pinned state is something the user is watching on the rail, the
nudge facility tells the model when it changes. The
agent keeps the same small key → one-line summary mirror pin-read answers
from, while the session is otherwise pin-blind and the
events go straight to the frontend. There is one pinned-state nudge,
uniform for every pin kind (a task, a goal, any other pinned state alike) and
every agent role (the interactive trunk and a returning sub-agent alike):
edge-triggered and budget-free, it fires once, on the first quiet
completion after the register’s one-line digest differs from the one last
told, and stays silent on every quiet completion that repeats an
already-told digest. An emptied register is itself silent — unpinning only
re-arms the edge, so a later re-pin fires again even at a digest told long
before, and there is no fallback reminder that nudges an empty register
toward set-goal/add-task: that advertisement is what let a completion,
its own reminder, and the next completion cycle forever, and it is gone with
the livelock it caused. The exception is actionability: while the agent has
live descendants, the pin reminder waits for their results, because the
agent has already delegated the next move. Restlessness in a returning
agent is not this nudge’s doing — that is its separate, budgeted obligation
to call reply, re-issued while unmet. The pin reminder itself speaks once
per change and then falls silent, so a sub-agent that finishes without
replying right as its pinned state changes is told of both in the same
completion, but only the reply obligation is told again next time. This is
the discipline pinning earns: a kit that publishes state to a slot the user
watches is told once when that state moves, whether the agent holding it is
the trunk or a sub-agent.
Why this shape
- It is the next honest cut on the road already taken. documents → (operation vs appearance) → (render vs control) → (event vs state) → (write-only vs read/write): each step refined the surface taxonomy by one distinction, and this is the first that looks inside the state cell rather than beside it.
- It is the dual of a thing already on screen. The register adds no new visual vocabulary — it lets a kit author what the host authors in the matrix (transcript-as-graphic), and now read it back the same way the host’s own gauges are always legible.
- Position carries the distinction. Rendering state in a reserved column makes the plane’s horizontal axis — Bertin’s strongest variable — the event/state partition, legible in where the mark sits rather than implicit in which list it lands on.
- It makes the doctrine expressible, and now enforceable. “Encode the changing datum as a fixed-position magnitude, never streamed” was enforceable only on host marks; a write-only pin let a kit obey it in appearance while a bound list drifted out of step behind the scenes. Canonical read-back removes the second copy: the card is the datum, so there is nothing left to drift.
See also
surface-pins-state (the original
decision — wire format and taxonomy, write-only as first drafted),
register-is-read-write (the read
side this page now describes: pin-read, pin-list, the encoder, the
canonical-form rule, and the tasks/goal kits as preludes over the family),
surface-carries-documents
(the `card body a pin reuses verbatim),
transcript-as-graphic (the matrix
this is the model-authored dual of, and the encode-don’t-stream doctrine),
cards (the render document the body decodes through, and
the encoder that inverts it), builtins (the
pin-set/pin-clear/pin-read/pin-list family and the tasks kit built over
it), frontend (the viewport register and the draw
layout), shell-eval (the host sink, the pin-first
decode, and the mirror pin-read answers from), agent
(the nudge that reminds the model of its pins, from the same mirror
pin-read reads), names-and-schedule-labels
(the commitment keyspace retired, and its 2026-08-27 amendment records the
later removal of services, the one protected slot that survived it),
and exarch/data/agent.ral (the tasks section — the first client, now a pure
prelude over the family).