# CONTEXT — glossary & domain model

> **Authoritative for meaning.** When code, prose, or a plan disagrees with a definition here,
> **this file wins** — the term means what CONTEXT.md says it means, and the disagreeing thing is
> the bug. This is the *grill-with-docs* convention: plans are stress-tested against the language
> defined here, and **this file is updated inline** as decisions crystallize (don't let it lag
> the code).
>
> Keep entries short and cross-linked. A term defined here is the canonical name — use it
> verbatim in code, docs, and commits. ADRs (`docs/adr/`) record *decisions*; this file records
> *meaning*.
>
> **Rename (ruled 2026-07-26, requests #1/#4; revised same day):** the tool is **pacer**,
> formerly *rebound*. Full sweep, with a deliberate case split: the **adapter id is `PACER`**
> (caps, exactly) so the canonical instance id comes out `PACER-0` — visually distinct from any
> peer agent id wherever it appears as a sender. Everything filesystem/prose stays lowercase:
> repo `spt-pacer-tool`, binary `pacer-shell`, prose terms `pacer latch` / `pacer timer` /
> `pacer code`, req ids `REQ-PACER-*`. Anything still saying "rebound" is pre-rename residue,
> not a second concept.

## Glossary

<!-- [doc->REQ-PACER-SIGNATURE] -->
**pacer** — the tool, and its one behavior: a **pure inward nudge** that keeps an autonomous
agent from silently stalling. It is a `kind = "shell"` spt-core adapter bound to one endpoint;
while its **pacer latch** is raised, the **bound endpoint**'s busy→idle edge starts the
**pacer timer**, and if that elapses while the endpoint is still idle the shell fires a
**ping**. Deliberately distinct from a conventional "heartbeat": a heartbeat reports liveness
*outward*; pacer only ever pokes *inward* and reports nothing. (Repo/project: `spt-pacer-tool`;
formerly *rebound* / `spt-rebound-tool`.) Every message pacer sends — **ping**, **stretch
report**, education, **status** answer — signs `--from` with its **canonical instance id**
(`PACER-0`), so the agent always sees the same non-peer sender. The signature follows the
canonical id, not the owner's alias: an alias is not yet visible to the resident over the link
(spt-core ask filed; the signature adopts the alias when that lands).

**bound endpoint** — the single endpoint a pacer instance watches and nudges: its spt-core
**owner** (the endpoint that drives the shell). Pacer reads this endpoint's busy/idle activity
and, on a **ping**, sends *to* it. One pacer instance, one bound endpoint.

<!-- [doc->REQ-PACER-LATCH] -->
**pacer latch** — the on/off gate that governs whether pacer acts. Raised (on) = an idle
bound endpoint will be pinged after the quiet interval; lowered (off) = idle is left alone. The
agent lowers the latch only when it is blocked on something with **no time bound** (e.g. awaiting
a human). Every idle→busy edge re-raises the latch. Distinct from the **pacer timer** in that
the latch is the *policy* (may we beat at all) and the timer is the *countdown* (when).

<!-- [doc->REQ-PACER-TIMER] -->
**pacer timer** — the countdown from a busy→idle edge to a **ping**: a fixed quiet interval
armed while the **pacer latch** is raised, stopped by any idle→busy edge (which also re-raises
the latch). Its elapse while still idle is the sole trigger of a ping.

<!-- [doc->REQ-PACER-PING] [doc->REQ-PACER-BACKOFF] -->
**ping** — the nudge pacer fires when the **pacer timer** elapses on an idle **bound
endpoint** with the latch raised: a `spt send --idle-only --ephemeral` back to the bound
endpoint. Idle-only so it never wakes a busy agent; ephemeral so a missed window drops rather
than spools a stale poke. Strictly **one-way** — pacer consumes no reply; the ping's only job
is to reappear in the agent's inbound and re-provoke a turn (an idle→busy edge). (Not a
liveness probe, despite the name.) Not necessarily single: if the endpoint stays idle after a
ping, pacer re-arms and pings again on a **widening (backoff) interval** until an idle→busy
edge occurs (which resets the interval to its base) — a stalled agent gets repeated,
decreasingly-frequent pings, never a spam loop and never a silent give-up.

<!-- [doc->REQ-PACER-PAUSE] -->
**pause** — the control that **lowers the pacer latch**: the agent declaring it is blocked on
something with no time bound (e.g. awaiting a human), so an idle bound endpoint should be left
alone. Reachable two ways (same operation): the capability verb `spt shell cmd PACER-0 pause`,
or the **pacer code** `!!done!!`. Auto-undone by the next idle→busy edge (which re-raises the
latch), so a pause holds only for as long as the agent actually stays idle. **The auto-undo is the
intended meaning, ruled by the operator 2026-07-26:** a pause promises quiet for *this idle
episode*, not quiet until the human returns. Whatever flipped the agent busy — its operator, or a
peer message — is an equally likely chance the block has cleared, so no wake is treated as
"incidental" and none holds the latch down. A still-blocked agent simply re-emits the code.

<!-- [doc->REQ-PACER-DELAY] -->
**delay** — the control that keeps the **pacer latch** raised but pushes the next **ping** out
by a bounded interval: the agent declaring it is waiting on **a fellow agent or a task with a
known/estimated ETA**. Reachable as `spt shell cmd PACER-0 delay <m>` or the **pacer code**
`!!wait=m!!` (`m` in minutes). Distinct from **pause** in that delay is *time-bounded* (the ping
still fires, just later, freeing the agent to re-wait or escalate) while pause is *open-ended*
(no ping until the agent moves). A peer-wait is a delay, never a pause — a peer that never
replies must not strand the agent.
<!-- [doc->REQ-HAZARD-PACER-DELAY-PRE-IDLE] -->
A delay declared **before** the busy→idle edge — the normal case, since an agent writes
`!!wait=m!!` in the turn it is finishing and the code commits at Stop — is **held** and bounds the
idle stretch that edge starts. The declaration is about the wait the agent is *entering*, not
about whatever the machine had already observed; a control is never consumed without acting.
Since 0.5.0 (request #11) every **consumed** wait also mints the next **checkpoint** on the live
**board** — declaring a wait is the act that says "this quiet span is one wait", so the board
gains a sub-id to classify it by. Superseded waits (a later steering code in the same batch wins)
mint nothing: one consumed wait, one checkpoint.

<!-- [doc->REQ-PACER-STATUS] -->
**status** — the one **read** control: the capability verb `spt shell cmd PACER-0 status`, which
asks a running pacer to report its own live state and changes nothing. The answer is the whole
machine in the vocabulary defined here — **pacer latch** (raised / lowered), the **bound
endpoint**'s activity and how long it has been idle, the **pacer timer** (off, or the countdown
to the next **ping**), where the backoff sits on the curve, and the **pacer code** watermark
(the last `seq` acted on). Delivered the way **education** is — `spt send --active-only` to the
bound endpoint — because a broker-launched resident's stderr is discarded by design, so its own
owner is the only place it can answer. That channel is also why status does not breach the pure
inward nudge: the report goes to the endpoint pacer already serves, never outward to anyone else.

**idle stretch** — the span from a busy→idle edge to the next idle→busy edge on the **bound
endpoint**: one contiguous quiet period. The unit *pacing* is phrased over — the **pacer
timer** counts inside one, a **pause** covers exactly one, a **delay** bounds one — and the
**board payload**'s trigger: a **resume** ending an idle stretch ≥ 60s sends the board.
(Measurement is phrased over the **board**, which spans many idle stretches.)

**resume** — the idle→busy edge as the board sees it: the instant the bound endpoint went back
to work. Each resume ending a ≥ 60s **idle stretch** triggers a **board payload**, and the
latest resume is the instant every checkpoint delta (and every classify fold value) measures to.

<!-- [doc->REQ-PACER-BOARD] -->
**board** — the one live measurement record per **bound endpoint** (exactly one exists at a
time, by ruling — request #11): a **stretch id** plus the stretch's **checkpoint** list. Born at
a **board-clear** (a classification) or **shell-link** (resident bind) event — that event marks
**checkpoint-0** and mints the id — and it keeps extending until classified: a stretch is the
interval between classifications, wall-clock, busy time included. The old 0.4.0 close/reopen/
bridge machinery is gone; nothing "closes" a stretch but its classification. The board keeps the
last **10** checkpoints — the oldest rolls off and a rolled-off sub-id is dead. In-memory by
ruling: a shell-link event births a fresh board, so a restart resets it by design (the
**classification registry** stays durable).

**checkpoint** — one timestamped mark on the **board**, named by its **sub-id** (`:0`, `:1`, …
— the designator form is `<stretch id>-<sub>`, e.g. `x7au34-2`). Checkpoint-0 is the board's
birth; each consumed **wait** mints the next. A checkpoint's board number is the wall-clock
delta from its creation to the latest **resume** — spans overlap (`:0` contains `:1` contains
`:2` …), and classifying a sub-id folds exactly the number shown.

<!-- [doc->REQ-PACER-STRETCH-REPORT] -->
**stretch report** (the **board payload**) — the message pacer sends to the **bound endpoint**
on a **resume** that ended an **idle stretch** ≥ 60s (= the base interval; sub-minute
turn-boundary gaps are noise, not waits): ONE **stretch id** and its **checkpoint** board, each
sub-id with its wall-clock delta from this resume, newest first (dual time format: relative
deltas + the resume's absolute local timestamp; durations at second precision — `8m20s` — so
classified averages can serve as drift markers). Never multiple stretch ids per payload
(request #11). Purpose: let the agent learn how long its recurring waits actually take. Sent
`spt send --active-only` (surfaces on the agent's own poll, never wakes). Always sent when the
floor is met, including when the idle stretch contained **ping**s. The payload teaches the
**classify** designator form and shows the **classification registry** (each class with its
running average), inviting the agent to classify a recurring kind of wait.

**stretch id** — a short random handle (6 chars) minted at **board** birth, naming the live
stretch in every **board payload**, and referenced (with a sub-id) by a **classify** control.
Exactly one is live at a time; a classification retires it with its board, and a control naming
a dead id is refused (verb) or inert (code).

<!-- [doc->REQ-PACER-STATE-DURABLE] -->
**classification registry** — the durable per-owner table a **classify** control folds into:
`class name → {count, running mean, last classified}`; running mean, no decay; cap 50 classes,
evicting the least-recently-classified. It lives with the rest of pacer's durable state in
`<install_dir>/state/<owner>.json` — atomically rewritten (temp + rename) and reloaded on
spawn — beside the **pacer code** watermark rider (the persisted `seq`, so a restarted pacer
never re-acts a code it consumed before the restart). The 0.4.0 `pending` field is gone: the
**board** is in-memory by ruling, and an old state file's `pending` list is ignored on load.

<!-- [doc->REQ-PACER-CLASSIFY] -->
**classify** — the third agent control (after **pause** and **delay**), and the only additive
one: it designates one **checkpoint** on the live **board** — `<stretch id>-<sub>`, e.g.
`x7au34-2` — and folds that checkpoint's shown delta (its creation → the latest **resume**,
wall-clock) into the named class's running average in the **classification registry**.
Reachable as the capability verb `spt shell cmd PACER-0 classify <id>-<sub> <name>` or the
**pacer code** `!!classify=<id>-<sub>:<name>!!`. Class names are snake_case (`[a-z0-9_]`,
≤32 chars). **One classification consumes the whole board** (operator ruling, request #11):
the remaining sub-ids die unclassified and a fresh board is born on the spot (the clear event
is its checkpoint-0). Refused (verb) or inert (code): a wrong stretch id, a dead/rolled-off
sub-id, or a board no resume has reported yet. Pacer never auto-matches — an average moves
only when the agent explicitly classifies. Unlike the steering codes (where the highest new
`seq` wins), every new classify code past the watermark applies, in `seq` order (after the
first consumes the board, the rest target a dead id and fall inert).

<!-- [doc->REQ-PACER-EXTEND] -->
**extend** — RETIRED in 0.5.0 (request #11, operator ruling: agents weren't using it). The
0.4.0 fourth control bridged the most recent stretch across idle/busy cycles until
`now + minutes`; the one-live-**board** model makes that implicit — a stretch keeps extending
until classified, so there is nothing left to bridge by hand and **wait** subsumes the surface
(each consumed wait marks a **checkpoint** on the ever-extending board). The verb answers a
pointer-refusal (`extend was retired in 0.5.0 — wait now extends the live stretch`); the
`!!extend=m!!` code no longer parses and is inert text; the manifest declares no extend
capability. Breaking, flagged in the 0.5.0 changelog.

<!-- [doc->REQ-PACER-RETIRE] -->
**retire** — the registry-surgery verb, **CLI only** (deliberately no pacer code: removing
accumulated averages is an operator-scale act, not a turn-scale one). One positional of
space-separated class names — `spt shell cmd PACER-0 retire "ci_run peer_wait"` (snake_case
names cannot contain spaces, so the separator is unambiguous) — removes each named class from
the **classification registry** entirely. The answer names what fell (with its final average)
and what was unknown; naming *only* unknown classes is **refused**. State persists on any
mutation.

<!-- [doc->REQ-PACER-VERB-REFUSAL] -->
**refusal** — the one-line answer a MAC-valid but unusable capability verb earns:
`⟳ **pacer** refused <op>: <reason>`, sent `--active-only` like every non-ping answer (a refusal
never wakes anyone). Refused: unknown op (defense in depth behind spt-core's vocabulary check),
malformed or missing args, retired ops (the **extend** pointer-refusal), and semantically dead
targets — classify of a wrong **stretch id**, a dead/rolled-off sub-id, or an unreported board;
retire of names none of which exist.
The MAC gate itself stays silent: an unstamped or mis-stamped line is an attacker's probe, not a
confused operator, and it earns no oracle (`REQ-HAZARD-PACER-FRAME-MAC` is unchanged). Inline
**pacer codes** have no reply channel and are never "refused" — a malformed code is simply not a
code.

<!-- [doc->REQ-PACER-VERSION-STAMP] -->
**version stamp** — the `pacer vX.Y.Z` suffix on **education** and the **status** answer,
naming the version of the *running image* (compile-time `CARGO_PKG_VERSION`, never a read of the
installed manifest — a displaced image is exactly a disagreement between those two, and the
stamp must tell the truth about the code that produced it). Field origin: doyle's 2026-07-27
displaced-image incident (request #13) — a resident kept serving pre-0.5.0 behavior for hours
after the exe was swapped under it, and process forensics was the only discriminator. Education
stamped = onboarding provenance is one glance at scrollback; status stamped = the on-demand
query. **Stretch reports stay unstamped** (ruled, request #13): per-resume repetition is noise,
and **status** answers the question when asked. A unit gate pins `manifest.toml` version ==
`Cargo.toml` version so the two bump together.

<!-- [doc->REQ-PACER-IO-AGENT-OUTPUT] -->
**pacer code** — an inline control token an agent writes in a turn (`!!done!!` → **pause**;
`!!wait=m!!` → **delay**, `m` in minutes, clamped `[1,120]`; `!!classify=<id>-<sub>:<name>!!` →
**classify**) that pacer reads out of the **IO funnel** and applies. Codes arrive as
`AGENT_OUTPUT` **io frames** on the same MAC-stamped drain the capability verbs use — the
published record of what the **bound endpoint** said, delivered to pacer rather than dug out of
the endpoint's session digest by pacer (request #14, spt-core 0.64.0).

This is what makes the codes *functional* control rather than mnemonics. Note what the change
did and did not move: pacer no longer parses its owner's session log, but it is still no part of
the harness adapter's business — the funnel is a published surface, and reading a published
surface is not the same as being in someone's loop.

Three invariants are part of the term's meaning:

- a code **inside backticks or a fenced code block is ignored** (the quotation-guard — so an
  agent *discussing* a code does not trigger it). Unchanged, and unchanged deliberately: it moved
  across to the new source without being rewritten;
- **each frame is its own event**, so a code is acted on once per frame and repeating a code in a
  later turn means it again. The old "at most once per committed `seq`" watermark retired with
  the digest read — it existed because the digest *window* was re-read on every poll, and a
  destructive drain has nothing to re-read;
- a payload **cut at the 16 KB cap** (`truncated="1"`) is followed to the digest entry its `seq`
  names, because the cut lands at the *end* of a turn, which is exactly where a code sits.
