First launch on an alt profile SETS ITSELF UP: a launch whose account root is MISSING runs the one-shot `alt init` itself and proceeds, and the account name defaults to "alt" — the user is never asked to run a binary command. Request BigscreenVR/claude-spt-bs#15 (requester discord:reavo, 2026-08-22): v0.29.0 shipped `claude-spt alt init ` as a user-facing setup step, but a user launching the shipped `claude-spt:alt` profile has no straightforward path to the `claude-spt` binary (it lives in the adapter dir, not on PATH), so asking them to run it is unacceptable — setup must happen automatically on the first launch of an endpoint via the alt profile. THE FAST PATH IS PRESERVED, which is what keeps this compatible with the ADR-0010 rejection of per-launch maintenance (the ccs cost this feature exists to remove): init runs at launch in EXACTLY ONE case, Preflight::MissingRoot — every launch after the first pays only the preflight stat it already paid. A FAILED auto-init fails the launch loudly and names `claude-spt alt init ` as the manual repair (the observation is reported, not a diagnosis); a successful one falls through to the normal credential preflight, so the first launch ends at Claude Code's own login prompt exactly as v0.29.0 documented. The default account name "alt" is a shared constant with the shipped profile's literal `--account alt`, so `alt init` bare and the profile agree by construction. The manifest is UNCHANGED — the shipped profile already bakes `--account alt` into every role's command; what this removes is the human step, not a profile knob. The plugin reconcile is VERIFY-THEN-NOTIFY (the documented [update.post] pattern; the 2026-07-16 fresh-install incident proved exit codes alone lie): after the marketplace/plugin steps, post-update RE-READS installed_plugins.json and asserts sptc@cplugs is present — claude-primary verification failure is LOUD stderr + exit nonzero with an empty stdout (no happy notice can fire); verified success prints the ✔-VERIFIED custom stdout notice (custom supersedes the static [update].message); the static [update].message is reworded failure-aware because core's emit_fallback fires it even after a failed post-step (deliberate, documented core design — NOT a core bug; doyle classification 2026-07-16). ccs-primary trees are per-account and unverifiable from this process — they keep the sentinel/static path rather than a false FAIL. Perched sessions are steered to BACKGROUND long-running work so the perch stays reachable — the spt reachability notice, ported from legacy owl's (sibling claude_skill_owl hook_prompt.rs + hook_check.rs) and renamed to spt semantics (, 'active spt perch'). Two injection sites, both perch-gated (a plain CC session is never told it has a perch): (1) UserPromptSubmit — EVERY perched turn's additionalContext closes with the general notice ('when spawning subagents or running long tasks, use run_in_background: true so you stay reachable for incoming messages'), appended AFTER any skill injection + drained messages (a notice never displaces a delivery); (2) PreToolUse — when the tool about to run is a subagent spawn (tool_name Agent, or its older payload name Task — matched both ways so a CC rename in either direction keeps the nudge alive), the targeted launch-this-in-background nudge rides the same emit, messages first, nudge last, and emits EVEN when the drain is empty (the point is catching the foreground spawn before it happens); and (3) PreToolUse — when the tool is `Bash`, `run_in_background` is not true, and the call is LONG-RUNNING by its own declaration (an explicit `timeout` of 60s or more) or by command shape (cargo, npm/pnpm/yarn, gh, pytest/jest, make, cmake, docker, sleep, `find`, `rg`, recursive `grep`), the same nudge rides the emit with a Bash-specific body — CAPPED AT THREE PER TURN (operator's number), the counter reset by each UserPromptSubmit, so the steering is early enough to shape the turn without becoming wallpaper. Non-spawn, non-long-Bash tools with an empty drain stay silent. Both ride the existing once-capped emit (spill guard unchanged). ADVISORY ONLY, deliberately: PreToolUse returns `additionalContext` and the tool runs anyway, so a nudge cannot convert the call it fires on — only the next one. Deny-and-steer (PreToolUse denying the call so the agent re-issues it backgrounded) is the only lever that would convert the current call and was RULED OUT by the operator 2026-08-04. This buys coverage, not enforcement. WHY (field, 2026-07-07 operator): every claude-spt agent ran subagents/long tasks in the FOREGROUND, going unreachable for the task's whole duration — the legacy-owl notice was the missing steering pressure. WHY THE BASH ARM (2026-08-04 grill): both legacy-owl legs were already ported and shipped, so the honest finding was not 'the feature is missing' but 'the feature does not cover the common case' — long foreground Bash (cargo, gh, test suites, recursive greps) is the ordinary way a perch goes unreachable and fired NOTHING. Observed in the grill session itself: a foreground `grep` blocked 120s and the harness backgrounded it, with the general notice already in context and no targeted nudge existing for that call. Subagent working perches follow the WORKER-TRUTH wave-1 contract (spt-core v0.27.0, doyle freeze 2026-07-06) and NEVER leak silently. SubagentStart (parent context) calls `api worker-start --session-id --agent-id --agent-type ` — NO worker-id positional (core hard-mints `{parent}-w{N}`), NO token (operator ruling: sid-symmetric auth, registration stores the sid, stop accepts stored OR parent-current sid — /clear rotation covered from both ends). The MINTED id is read from worker-start's BARE STDOUT (machine-readable result; empty/refused = LOUD log, never silent) and persisted in adapter-owned state keyed by the CC agent_id (state/worker/.wid — the SubagentStop payload carries only the CC agent_id, so the mapping is the adapter's to hold; same contract-clean carrier as the F-024 sid proof). SubagentStop looks up the minted id and calls `api worker-stop --session-id ` STRICTLY: a refusal is LOUD (stderr reason surfaced — the pre-0.27.0 silent-swallow leaked 100% of workers, the 2026-07-06 six-worker leak), a missing mapping is LOUD (WORKER_STOP_SKIP breadcrumb), and the state row is cleared on success. Missing agent_id on either hook = LOUD skip, never a silent no-op. Floor: min_spt_core 0.27.0 (older cores clap-reject the id-less worker-start — the doyle-accepted skew; core's registration-time floor enforcement guards the reverse direction). The session carrier (state/session/.sid) never advances past a FAILED strict registration. Pre-fix, handle_session_start persisted the current sid on EVERY SessionStart even when the bind/boundary spt_strict call was refused (error only logged) — which (a) poisons the NEXT rotation's auth proof (the perch still records the PRIOR sid, so the next boundary presents a sid the perch never accepted → AUTH_REFUSED → stranded perch, the F-024 class reached via carrier skew) and (b) would let the verified-identity fast path (REQ-UPS-IDENTITY-FASTPATH) trust a session the perch never bound. INVARIANT: carrier(eid, sid) is authoritative only when written immediately after a SUCCESSFUL strict bind/boundary registration this SessionStart; on failure the carrier keeps the prior sid (still the true proof for the retry). The seed leg (no endpoint id) never writes. 2026-07-10 UPS-timeout RCA prerequisite (hertz), converged 2026-07-15. A completed /compact must not leave an spt-hosted endpoint stuck ACTIVE with its parked messages undelivered until a human types. Field-proven (deployah 2026-07-21, hertz RCA): doyle's RELEASE GO was SENT (live listener delivery) 79-88ms before the operator submitted /compact; the stub the translation binary typed did not survive the compact collision, the body stayed committed in adapter msgpark, and SessionStart(source=compact) rebound the boundary but never transitioned ACTIVE->IDLE or re-drove the park — so the GO sat 46m45s until the operator's next UserPromptSubmit drained it (same black-hole class as REQ-HAZARD-STUCK-ACTIVE-NO-IDLE / -STOPFAILURE- / -INTERRUPT-, reached via /compact; the interrupt watcher cannot heal it — no interrupt marker). TWO SEAMS: (1) source-certain — the compact arm of handle_session_start must, after a successful boundary rebind, assert `state idle` and re-drive a non-empty msgpark by self-sending a redrive message through the OWN translation binary (--force-native), whose stub opens the turn whose UserPromptSubmit drains the park (custody unchanged: SessionStart never drains/commits the park itself — only a UPS/PreToolUse drain may stage, only post-emit commit destroys). (2) TRIGGER-GATED (the negative leg): an AUTO compact fires the same SessionStart(source=compact) MID-TURN — the agent is genuinely working, so marking idle or injecting a redrive there is the busy-injection hazard, and the continuing turn's next PreToolUse drains the park anyway (REQ-STUB-PARK-NO-STALL). The hook payload carries no trigger; the transcript's compact_boundary record does (`"subtype":"compact_boundary"`, `compactMetadata.trigger` = manual|auto, ground-truthed against live transcripts 2026-07-21). Read the LAST such record via the payload's transcript_path: trigger=auto -> skip (loud log); trigger=manual OR undeterminable (no path / no record / parse fail) -> heal, because the black-hole direction (46min unreachable) beats the noise direction (one stub turn CC queues behind the running turn, self-corrected at its next busy mark). An empty msgpark redrives NOTHING (no dead prompt: an operator compacting an idle session must not wake the agent). The redrive body must be self-explanatory to the receiving agent. The digest extractor's projects-root chain is SESSION-AWARE and never silent: it walks the precedence rungs (--config-dir capture → $CLAUDE_CONFIG_DIR env → legacy --in → ~/.claude/projects) and uses the first rung that actually CONTAINS the requested session's transcript — a rung that resolves to a real directory without the session must NOT end the search. When no rung holds it, the extractor says so on stderr (roots tried + session id) instead of exiting 0 with empty stdout. Rationale (field 2026-07-25, perri): the perch's bind-time {CLAUDE_CONFIG_DIR} capture is STALE the moment a later session boots under a different config root (a ccs-captured `~/.ccs/instances/` perch re-bound by a plain `~/.claude` session). The old first-rung-wins chain pointed the locator at the ccs tree, found no .jsonl, and returned empty-and-quiet — so the endpoint's digest carried spt-injected Context rows ONLY, its newest Agent entry frozen at the pre-reboot session, with no error anywhere on either side. Every digest consumer goes blind together: peers reading the endpoint, the Psyche's context sync, and any tool whose control channel is the digest (rebound's `!!done!!`/`!!wait=m!!` codes were unreachable for the whole session). The psyche host's resident loop is PACED and TERMINAL on persistent instant cycles: a healthy iteration blocks (perch poll or a real claude turn); when iterations complete sub-2s repeatedly (claude dying at spawn — the F-h untrusted-cwd trust prompt — or a thrashing poll), the host backs off exponentially (500ms doubling, 5s cap) and after 8 consecutive instant cycles exits LOUD + NONZERO (PSYCHE_HOST_GIVE_UP stderr, exit 3) so the wrapper DEATH becomes visible to spt-core's psyche_host_error/residency machinery — a silent internal loop is invisible to it (field: ~3 boots/sec for 30min, ordinal 5358, F-h). Threshold coupling (doyle-ruled): core's C3(b) backstop trips at >=10 ledger boundaries/60s; for the pure instant-death class this guard fires FIRST (<=8 boundaries, <60s wall clock); failure modes that dodge the streak (just-over-threshold cycles resetting it) legitimately reach the core backstop — a core trip on a guarded wrapper is correct backstop behavior, not a bug. A session-limit 429 must not leave an spt-hosted endpoint stuck ACTIVE with the limit banner read as agent activity. When a turn ends on the account session limit, CC files a SYNTHETIC assistant message (model ``, `isApiErrorMessage: true`, `error: rate_limit`, text `You've hit your session limit · resets