# INSTANCE-AXES — consolidated grill rulings (operator, 2026-09-25)

Source: Round 1 / 2 / 3 grill pages in this directory; answers pasted by the operator in doyle's session.
A question left blank = the recommendation was taken.

## Settled before Round 1 (operator, doyle session 0fe9417f, 04:15–05:19Z)
- S1. Two separate state machines. **Controlled axis** = whether a controller is attached; only attach/detach change it; viewers never count. **Instance axis** = active / dormant / suspended / offline.
- S2. Active ordering: a per-id **activation counter**, replicated in the registry. Activating takes highest-seen + 1; ties break on node id. (New field; every existing epoch is per-node and not comparable.)
- S3. Triggers → active: adapter-reported user input (never rc keystrokes); an incoming message (per Q2/R2-3); a `--handoff`; wake/boot under the boot rule.
- S4. The only trigger → dormant: a sibling becoming active.
- S5. Auto-suspend modes: disable / dormant-enable / enable. Condition: uncontrolled AND agent idle (AND dormant, in dormant-enable), all held without break for the timeout; any break resets the timer.
- S6. Resolver (#345): node-qualified → the unique active instance → local → sole → refuse. Offline excluded; suspended ranks below dormant.
- S7. Handoff messaging: a dormant instance may message only its active sibling. Active → dormant sibling normal msg = no transition; `--handoff` swaps. A message from any other endpoint to a non-active instance makes it active.
- S8. Attach to a suspended endpoint still wakes it.

## Round 1
- Q1 landing state by wake cause: (a) attach → suspended lands **dormant** (amended by R3-1 when active is vacant); (b) `spt wake` → active; (c) active sibling's normal msg → dormant; (d) another endpoint's msg → active; (e) owned shell's wake-watcher → **ACTIVE** (operator changed this from the dormant recommendation).
- Q2 any message that is NOT busy-only-class steals active. spt-core builds NO rule around adapter ideas (pacer, alchemy, recharge wake).
- Q3 "agent idle" = the activity sentinel (busy|idle).
- Q4 boot: no time window; the rule is "no active sibling" (not "no siblings").
- Q5 the dormant send restriction covers peer msg AND its own shells. Refusal: `SEND_REFUSED_DORMANT`, naming the active sibling and the `--handoff` way out.
- Q6 handoff: (a) active-only sender; (b) to a suspended sibling → it wakes active, sender goes dormant; (c) to an offline sibling → refused, sender stays active; (d) carries a normal body; a flag on send.
- Q7 dropped (Instance.controlled already replicates, registry.rs:215).
- Q8 keep the knob chain (node default → endpoint override); value = {mode, after_ms}; **default {dormant-enable, 900000}** (15 min); every existing value migrates to the new default (no shipped value was > 0).
- Q9 requests A (axis split, counter, boot rule, auto-suspend modes) → #345 (resolver) → B (handoff + dormant restriction). Milestone INSTANCE-AXES.

## Round 2
- R2-1 **shells follow active**: shell→owner traffic (text, file, sensory) resolves to the owner's ACTIVE instance, cross-node when remote. Shell traffic therefore never steals active. **A suspended instance is never active.** A shell never messages a non-active instance; with no active owner instance, the payload spools until one comes online / is detected. CONTEXT.md line "A linked Shell counts as a driver" is amended: a linked shell sits on neither axis.
- R2-2 superseded by #346 (R3-4).
- R2-3 a same-id message (sibling or self-send) never steals; only handoff rules move active between siblings.
- R2-4 the user-input trigger = the busy report carrying a USER_INPUT payload (broker.rs:6398). `api presence` is retired or documented as not a trigger.
- R2-5 into request A: `spt daemon config auto-suspend <disable|dormant-enable|enable> [--after 15m]` (node default); `spt endpoint auto-suspend <id> <mode> [--after …] | --inherit` (override); bare form prints effective value + source; public docs section in instances/overview. Release note states plainly that auto-suspend turns ON fleet-wide at upgrade (a sole instance is never dormant so never auto-suspends under dormant-enable).

## Round 3
- R3-1 invariant **active ⇒ warm and online**. Suspend or offline of the active instance VACATES active. **No auto-pass** to a dormant sibling (operator: a deliberate vacate most likely means stay inactive; a needing agent can wake an instance). The "no active sibling ⇒ take active" rule fires at EVERY transition into warm (boot, wake, attach); during a vacancy an attach takes active (Q1(a) amended for that case only).
- R3-2 shell outbound spool lives on the **shell's node** (sender side); every shell→owner kind spools, sensory included, in send order, under existing spool bounds, no new TTL; drains when an active claim for the owner becomes visible to the shell's node (claim announced subnet-wide as an edge transition), to that instance, local or remote.
- R3-3 shell routing is **new request C**, after #345, parallel to B: A → #345 → {B, C}. Own two-host acceptance (shell on node A, active on node B).
- R3-4 **#346 joins INSTANCE-AXES** (operator greenlight), parallel to everything. New now-signal category `TRUST_WARNINGS`; each entry names sender + msg-id; cadence/override rules unchanged, cadence "delivered" claim moves to render time (first now-signal poll showing it); surfaces on the receiving instance's session(s); envelope `trust-warning` attribute AND the fail-safe standalone delivery (wan.rs:1073) both removed in the same release; the receiver-composed class strip keeps stripping inbound `trust-warning`; public envelope docs + adapter note (perri, emphasys). Reverses the releases#170 carrier ruling.

## Post-grill confirmations (operator, 2026-09-25)
- Shell wake-watcher: an owner instance is active anywhere → just bring the shell online (its traffic routes there). No active instance, a dormant/suspended one reachable → the watcher is an explicit wake: that instance wakes ACTIVE, then the shell comes online. (doyle's reading, operator-confirmed.)
- #351 INSTANCE-AXES greenlit.

## Round 4 — W0 SIBLING COEXISTENCE (operator, 2026-09-25, after todlando's W1 int refer)
Trigger: REQ-INST-9 `join_endpoint` refuses a same-id row from any other node on EVERY advertise round, and a same-id instance on a new node was the deferred instantiate-anywhere. So multi-node siblings could not exist, and #351 presumed them. Refer file: .spt/preserved/351/w1-refer-sibling-collision.md.
- R4-0 Add W0 (sibling coexistence) to #351, FIRST in order. W1 impl+units land; the W1 int waits on W0.
- R4-1 BIRTH = `spt endpoint span <id> [--node <NODE>]` (default: this node). The resume picker gets a "Span to node" option → a secondary menu listing the nodes the endpoint can span to, which calls the same verb. `spt live <id>` for an id held elsewhere still refuses and names `span`. Nothing makes a sibling implicitly.
- R4-2 IDENTITY = a random 128-bit endpoint uid, minted at creation, stored in the TRACKED MIND (so it replicates with a-<id>), and carried on every registry row. A same-id row with an equal uid joins as a SIBLING; a different or absent uid COLLIDES (REQ-INST-9 kept for the real name clash). Existing endpoints mint theirs on first boot of the new version. `span` refuses unless the target node holds the replicated mind. Spannable nodes = sync scope (sync-list subnets where the endpoint is visible).
- R4-3 REMOTE span allowed, gated by a new control surface `ENDPOINT_SPAN`, evaluated on the TARGET node (node-sovereign). Subjects = the spanned endpoint, its node (all its endpoints), or its subnet (all endpoints across its nodes). Targets = the destination node (node-scope rule or per-endpoint rule there); "all nodes of subnet S" = the per-surface subnet MODE (empowered engine room). No new subnet-target rule kind. Default posture = the standard chain (a closed node with no rules cannot be spanned to from a remote node).
- R4-4 A span run on the target node itself (node-local span of a remote endpoint) is NOT blocked unless a rule prohibits it (the intra-node self-referential rule, RULES-ONLY).
- R4-5 SIBLING BYPASS: sibling↔sibling traffic between an endpoint's inter-node instances bypasses access rules. (doyle recommends the engine-room inbound lock, step 0, still applies; builder confirms at census.) NO operator proof on span.
- R4-6 OPERATOR-ACCEPTED RISK (stated to the operator before the ruling): sibling admission and the bypass both reduce to "node holds the mind", which by default is every node of the anchor subnet. An agent on such a node can span the endpoint locally, become active on its next trigger, and receive its bare-id traffic. Accepted as within sync-scope trust: those nodes already hold the whole mind. State it in CONTEXT.md as the trust boundary, not as an unknown.
- R4-7 (operator, 2026-09-26, reviewing doyle's W1 pre-gate "blocker"): USER INPUT activates only when the instance's session is CONTROLLED (a controller attached). This is a prerequisite of the trigger, not an extra; the W1 lane at 6680af98 omitted it. Operator also notes: only messages deliberately pointed at an instance can steal active; most peer traffic resolves to the active instance (#345). So the injected-peer-text case is low-risk, NOT a blocker (doyle's severity was overstated). The own-injection matcher already built at 6680af98 stays (harness-agnostic insurance, reusing existing evidence). The operator's alternative insurance (block user input starting with `<` or `/`) is NOT adopted: it keys on claude-spt's input shapes, and the harness boundary rules out core catering to one adapter.
- R4-7a (operator, 2026-09-26): a harness-only seat (a harness session the broker does not host; structurally never stamped controlled) COUNTS AS CONTROLLED for the user-input trigger. Gate = controlled OR harness-only, using the registry's existing harness_only derivation (controllable != Some(true)). Does NOT change the controlled axis itself, nor auto-suspend's "uncontrolled" condition. (doyle flags for W1: under auto-suspend a harness-only instance reads uncontrolled; check that is intended at the gate and name it in the docs.)
- R4-7b (doyle, consequence of R4-7a, 2026-09-26): a harness-only seat also counts as controlled in auto-suspend's facts, so it never meets "uncontrolled". Today it never auto-suspends only incidentally (a missing controlled_changed_ms anchor fails warm). Make it explicit: one line in suspend_facts + a unit. The docs name it.
- R4-8 (doyle, 2026-09-26, from the W0 rig): the mind-sync BOOTSTRAP leg (pump/sync.rs:96-104) is unreachable. The requester gates bootstrap refs on its OWN visibility entry, which a node that never hosted the id lacks. CONTEXT.md §Cross-node Psyche sync makes the OWNER's sync list the scope, and R4-6 presumed "every anchor-subnet node holds the mind". So this is a defect against the contract, not a new design: for bootstrap refs (no local vis entry) the requester gates on roster membership only, and the RESPONDER's allows() (owner vis) decides. Held-locally refs keep the symmetric gate. Rides #354 (span needs it). Field effect: members begin pulling minds the owner exports, as documented; measure and state the size cost.
- R4-9 (doyle, 2026-09-26, #345 condition 5): a bare send whose target id == the sender's own id resolves to the SENDING INSTANCE (local), never to a sibling. A bare self-id from an instance of that id means "me". Mechanism that forbids the cross: the recharge wake is a self-send; crossing it to an active sibling would leave a dormant recharging instance unwoken. Reaching a sibling = `id@node` or `--handoff` (#349). Permanent, not a stopgap; #349 may refine the dormant send restriction but not this.
