Imported from zkysar1/Claude-Mind (
.claude/skills/stop/SKILL.md). Install upstream withnpx skills add zkysar1/Claude-Mind --skill stop. Copyright stays with the author.
/stop -- Stop the Autonomous Learning Loop
USER-ONLY COMMAND. Claude must NEVER invoke this skill.
Syntax
/stop <agent-name> # Stop the named agent + drop to assistant (reconciliation-ready)
/stop <agent-name> --reader # Stop the named agent + drop to reader (read-only, walking away)
Agent name is REQUIRED. A bare /stop (no positional argument) is refused with a
clear error and the list of available agent directories. This prevents the cross-session
"wrong-agent stop" failure mode where a session's .active-agent-<SID> binding has
been silently overwritten or cleared (e.g., NO_AGENT window, or another /start rebinding
the SID), leaving /stop no safe default. Requiring the explicit name also enables stops
from any terminal — a side benefit, not a workaround. (Incident: 2026-04-24 — bare /stop
typed in a NO_AGENT window stopped the wrong agent because the binding fell back to a
different active agent's session.)
After stop, the agent lands in assistant mode by default so the user can immediately
reconcile state (mark a missed goal, edit a tree node, add a guardrail) without a mode
switch. Pass --reader for the read-only safe floor when walking away.
Step 0: Load Conventions -- Bash: load-conventions.sh with each name from the conventions: front matter. Read only the paths returned (files not yet in context). If output is empty, all conventions already loaded -- proceed to next step.
Step 0.5: Parse arguments — flag-parsing runs before positional-parsing so /stop --reader (no agent-name) is not mis-interpreted as /stop <agent-name=--reader>.
-
Flag parsing: If any argument is the literal string
--reader, settarget_mode = "reader". Otherwisetarget_mode = "assistant". This determines where the agent lands after the stop completes. Unknown flags are ignored (user will see the default output and can re-try). -
Agent name resolution (REQUIRED): Take the first argument that does NOT start with
--as<agent-name>. The positional argument is mandatory — there is NO fallback to current session binding.a. No positional argument present → REFUSE with the available-agents list, then DONE (no state mutation, no signal write): Bash:
ls -d */session/agent-state 2>/dev/null | awk -F/ '{print $1}' | paste -sd ", " -Output:"Error: /stop requires an explicit agent name. Usage: /stop <agent-name> [--reader]. Available agents: <list-from-bash>. (Why mandatory: the prior 'use current session binding' default silently stopped the wrong agent on 2026-04-24 when the binding had been overwritten. Explicit names also enable stops from any terminal.)"DONE.b. Positional argument present but agent directory missing → REFUSE with the same available-agents list: Bash:
ls agents/<agent-name>/session/agent-state 2>/dev/null(check existence) IF missing: Bash:ls -d */session/agent-state 2>/dev/null | awk -F/ '{print $1}' | paste -sd ", " -Output:"Error: Agent '<agent-name>' not found or has no session state. Available agents: <list-from-bash>."DONE.c. Positional argument present and agent directory exists → rebind this session to the named agent: overwrite
.active-agent-<SID>with<agent-name>so the PreToolUse[Bash] hook auto-injectsMIND_AGENT=<agent-name>on subsequent calls. If you need a deliberate cross-agent probe inside a single command (e.g., reading a third agent's state), writeMIND_AGENT=<other> <cmd>explicitly — the hook preserves explicit overrides.
Step 0.6: Worker-Body short-circuit (g-306-125) — MUST run before Step 1.
On a cross-box run this agent has TWO kinds of live Body: the REDUCER, which owns
the agent-wide state, and one or more WORKERS. Everything below Step 1 is an
AGENT-WIDE write — stop-target-mode, the stop-requested signal, agent-state,
agent-mode, the goal claim. A /stop typed on a WORKER box must perform none of
them. stop-requested is the sharp one: it is read by the REDUCER's Phase -1.4, so
setting it from a worker stops the wrong Body on a different machine while the user
believes they stopped only the box in front of them. A worker's own wind-down is
driven by the reducer-liveness poll, never by this file.
This check sits ABOVE Step 1 rather than inside the RUNNING branch on purpose. The
IDLE branch writes agent-wide state too (session-mode-set.sh, Step 2 there), and a
worker whose reducer has already stopped reads state=IDLE — so a RUNNING-only guard
would leak the mode write.
Detection uses the body-WM predicate: sessions/<SID>/working-memory.yaml exists
ONLY for a non-reducer Body. That is the invariant core/scripts/bash-agent-inject.py
documents and itself routes on, so deriving it here locally keeps the two predicates
identical and unable to drift. $MIND_SID is this session's own SID and IS available
here, because every step in this skill is a Bash TOOL call and the PreToolUse hook
injects it — the same basis Step 5's runner detection already relies on. Do NOT
rewrite this as a BODY_ROLE env check: that variable happens to be present in THIS
context for the same reason, but it is absent in every non-Bash-tool hook, and keying
a rail on it is the inert-rail class guard-2445 exists to prevent. COUPLING, stated
so it fails loudly: if a REDUCER ever gains a sessions/<sid>/working-memory.yaml,
this predicate misclassifies it as a worker and /stop becomes a no-op on the box
that owns the state.
THE EMPTY-MIND_SID CASE IS A THIRD ANSWER, NOT THE NEGATIVE ONE (g-115-9320,
guard-6178). Written as a single [ -n "$MIND_SID" ] && [ -f ... ], this predicate
returns the ELSE label when the variable is merely ABSENT — so a guard that CANNOT
EVALUATE reports reducer-or-single, the branch that writes the AGENT-WIDE
session/stop-requested a worker is forbidden to touch. The asymmetry is what makes
it a defect rather than a default: the two branches do not have equal blast radii, so
guessing toward the destructive one converts a failed hook into the dangerous action.
MEASURED 2026-09-07 (DESKTOP-O91DLK2, SID 1c4a1179): the PreToolUse inject hook fired
with BOTH MIND_SID and MIND_AGENT empty, and this step printed reducer-or-single
for a Body whose sessions/<SID>/body-manifest.yaml reads role: worker, body_state: active. Re-running with an explicit SID printed worker.
Per guard-341 an empty MIND_SID means the hook did not fire and is an ERROR — so
the correct third answer is to REFUSE, never to recover by guessing. Do NOT "fix" this
by globbing sessions/*/body-manifest.yaml for an active worker: with more than one
Body on a box that re-introduces the same guess one layer down, and the operator can
supply the SID in one keystroke.
Bash: if [ -z "$MIND_SID" ]; then echo "indeterminate"; elif [ -f "agents/<agent-name>/sessions/$MIND_SID/working-memory.yaml" ]; then echo "worker"; else echo "reducer-or-single"; fi
IF output is "worker":
-
Arm the SESSION-SCOPED stop signal so this turn can actually end (g-115-7309). FIRST, before the flush: the remaining steps are fire-and-forget, and a stop that cannot end its own turn is worse than one that skipped a push. Bash:
mkdir -p "agents/<agent-name>/sessions/$MIND_SID" && touch "agents/<agent-name>/sessions/$MIND_SID/stop-requested"WHY THIS FILE AND NOT
session/stop-requested: the stop-hook worker-net has four stand-down valves, and valve #2 (stop-hook.sh, gateworker-net-stop-requested) reads the AGENT-WIDEsession/stop-requested— which this very branch is forbidden to write, because the reducer's Phase -1.4 on another machine reads it. So the one actor that needs valve #2 was structurally barred from firing it: every worker/stopBLOCKed at turn-end, and the only escape was hand-writingbody-closing, which DURABLY retires the Body (closed-pending-merge, Phase -0 then refuses every further unit on that SID, and only a user-only/startreopens it). Stopping one box is not retiring that Body. This file is read by the paired valveworker-net-stop-requested-session, is keyed to THIS SID, and never reaches the reducer. (guard-4900 documents the trap; this step is its fix.) -
Write this session's summary. Same call graceful-stop D6.5 makes, SID-scoped, so a stopped Body leaves the same continuity artifact a stopped reducer does. Runs BEFORE the flush in step 5 because that flush must carry it -- graceful-stop D6.7 depends on every continuity file being written first. Bash:
bash core/scripts/session-summary-write.sh --sid "$MIND_SID" --agent <agent-name> --reason worker-stop || true -
Commit this box's agent-dir churn. Same call graceful-stop D6.62 makes. Bash:
source core/scripts/_paths.sh && bash core/scripts/iteration-commit.sh --goal-id worker-stop --title "worker Body stop on this box" --outcome deep --type chore --repo "$PROJECT_ROOT" || truesource core/scripts/_paths.sh &&IS LOAD-BEARING, not decoration:$PROJECT_ROOTis UNSET in a bare Bash call, so--repowould pass EMPTY and the script exits 1 naming all four flags you DID pass. That error reads as a broken script rather than a missing variable, and is how D6.62 sat inert for months (rb-9907).--outcome deepis also load-bearing:routineis a documented no-op that commits nothing. VERDICT ONgit status --porcelain, NEVER ON THE rc -- the|| truediscards it. -
Push what step 3 committed. Same call graceful-stop D6.65 makes. Bash:
bash core/scripts/iteration-push.sh --min-commits 0 --max-age-min 0 --fetch-interval-min 0 || trueAll three zeroes are required together: they convert iteration-push's rate-limited batch decision into "push whatever is ahead, now". D6.65 exists because a session whose final commits sit under both thresholds leaves them stranded with no later iteration to flush them -- and a STOPPED worker has no later iteration BY DEFINITION, so the case D6.65 was written for is strictly worse here than on the reducer. MEASURED 2026-09-10 on cc-09 (SID a30b1a3e): after the worker stop completed, agent store churn was still uncommitted and unpushed, and it took a user-invoked
/encode-sessionto ship it. Do NOT add--strict: without it soft_exit returns 0 on every path, so an rc-gated branch here would be dead code (guard-775); with it a transient network blip aborts the stop. -
Flush pending backend writes. Same call graceful-stop D6.7 makes, moved ahead of the sweep thread's next tick. Fire-and-forget: a flush failure must not block the stop. Bash:
bash core/scripts/owncloud-flush.sh || true⚠ THIS STEP DOES NOT PUSH THIS WORKER'S AGENT DIR, AND CANNOT (g-115-9319). It read "stage + push this worker's own per-session state so a machine-move right after the stop cannot strand it" until 2026-09-07, which is the one thing it is structurally unable to do. Per guard-1579, every write under
agents/<name>/is local-only from a box holding no live RUNNING claim for that agent — and a stopping worker IS exactly that box, since the reducer holds the claim elsewhere and this box reads agent-state IDLE. So the scenario the step named is precisely the scenario where it is inert. MEASURED THREE TIMES, two boxes, two OSes: DESKTOP-O91DLK2 (Windows)pruned_agents=12withalphaamong them,pushed=1; cc-07 (Linux 6.8.0-138-generic)pruned_agents=11withalphaamong them,pushed=0on 2026-09-07 butpushed=1on 2026-09-08 (three flushes, same box, same kernel,alphapruned every time).pushedIS NOT THE DISCRIMINATOR, IN EITHER DIRECTION. It counts OTHER owned paths and is box- and run-contingent, so a zero proves nothing and a non-zero does not mean the agent dir moved. This sentence used to claim the cc-07pushed=0"removes that ambiguity" — the 2026-09-08 re-measurement on that same box falsified it. The tell ispruned_agents=Nplus the WARN line naming the pruned agents; read those. (guard-6254 carries the same correction against guard-1579, whoserulefield is immutable.) NOT a data-loss report: the state is on local disk and a later/starton THIS SAME box resumes the SID. What is absent is OFF-BOX durability FOR THE PER-SESSION HALF only -- narrowed by g-306-477, which added steps 3-4 above: the GIT-TRACKED half of the agent dir (journal, experience, changelog) is now committed and pushed by those steps and does reach the remote.sessions/<SID>/is carried by**/sessions/in .gitignore, so that half is still untracked and still local-only, and a machine-move after a worker stop still strands it. If an artifact must reach the fleet, encode it to aworld/ormeta/store — those are not claim-gated. (rb-10330.) -
Park this Body instead of leaving it
active(g-306-477). Bash:py -3 core/scripts/body-manifest.py park --sid "$MIND_SID" --agent <agent-name> || trueReturns
parked|already-parked|no-forked-wm|not-active. Treat EVERY non-parkedreturn as a no-op and CONTINUE -- never fail the stop on it.WHY PARK RATHER THAN LEAVE IT ACTIVE, and why this does NOT stage the WM. A /stop-ed Body intends to resume by construction, so it must NOT be queued for merge -- staging here would lose every turn of divergence after the reducer marks it merged, which is exactly the argument park_body's own docstring makes. What parking buys over
activeis the thingactivelacks: a park clock and an EXPIRY path that runs the ORDINARY genuine close, which stages and pushes through the single existing writer. Anactivestopped Body that is never restarted stages its learning payload NEVER; a parked one eventually does. No new state and no new staging logic.A PARKED+STOPPED BODY DOES NOT RESUME POLLING, and both halves of that were VERIFIED in source rather than inherited from this text:
parkadvances the park orbit, but worker-loop Phase -0-stop readssessions/<SID>/stop-requestedFIRST (SKILL.md:114), ahead of the park-due gate (SKILL.md:145), so the stopped Body stands down instead of re-polling (g-115-9461); andstop-hook.sh:470carries the ALLOW gateworker-net-body-parked, so the turn-end is not trapped. -
Close this session's telemetry record. The worker got a WP1
activerecord at/startand never reaches the IDLE branch's WP2, so without this it orphans as permanently-activeand pollutes the live-sessions query. Keyed on the WORKER's own$MIND_SID. guard-165: SID/agent via ENV, python source single-quoted. Bash:TSID="$MIND_SID" TAGENT="$MIND_AGENT" py -3 -c 'import os,sys; sys.path.insert(0,"core/scripts"); from _session_telemetry import write_close; write_close(sid=os.environ["TSID"], agent=os.environ["TAGENT"], status="completed", ended_reason="user-stop")' >/dev/null 2>&1 || true -
Clean this session's SID binding so PROJECT_ROOT does not accumulate one file per stopped worker. Idempotent. Bash:
rm -f ".active-agent-$MIND_SID" -
Output:
"Worker Body stopped and PARKED on this box. The reducer was NOT signalled — its claim, canonical working memory, and agent-wide session state are untouched. This box's git-tracked agent state was committed and pushed by steps 3-4, so that half is durable off-box; the per-session state under sessions/<SID>/ is gitignored and remains on LOCAL DISK ONLY, so a later /start on THIS box resumes the SID and a machine-move still strands that half. The Body is now body_state=parked rather than active: it stays resumable, and if it is never restarted the park expires and the ordinary genuine close stages its learning payload. To stop the whole agent, run /stop <agent-name> on the reducer box."The "local disk only" wording is load-bearing and must track step 5. Until 2026-09-08 this string said the session state "has been staged and pushed", contradicting the ⚠ block directly above it — the block was corrected by g-115-9319 on 2026-09-07 and this user-facing sentence was not, so every worker
/stopreported a push that cannot happen (observed being repeated verbatim to the user, alpha/cc-07, 2026-09-08). A prose warning and the string the operator actually reads are two artifacts; fixing one is not fixing the other (guard-4282).
DONE. Do NOT continue to Step 1. Do NOT write stop-target-mode. Do NOT set the
AGENT-WIDE session/stop-requested. Do NOT chain into the aspirations loop.
The word AGENT-WIDE is load-bearing and was added with step 1 (g-115-7309): this
clause used to read "Do NOT set stop-requested" unqualified, which now reads as a
prohibition on step 1 itself. The two files are different objects with opposite
blast radii — session/stop-requested is agent-wide and stops the REDUCER wherever
it runs; sessions/<SID>/stop-requested is scoped to this Body on this box. Only
the first is forbidden here.
IF output is "indeterminate": STOP. Do NOT continue to Step 1, and do NOT treat this
as the single-box case — that is the whole defect (g-115-9320). MIND_SID is empty,
which per guard-341 means the PreToolUse inject hook did not fire; the role is
UNKNOWN, not "not a worker". Tell the operator verbatim:
/stopcannot determine whether this session is a worker Body or the reducer:MIND_SIDis empty, so the inject hook did not fire. Re-run with the SID set explicitly —MIND_SID=<sid> MIND_AGENT=<agent> /stop <agent-name>— where<sid>is the directory name underagents/<agent-name>/sessions/for this session. Proceeding blind would risk writing the agent-wide stop signal that stops the reducer on another machine.
Refusing is the safe direction here and a wrong guess is not: the operator recovers in one keystroke, whereas the agent-wide branch stops a DIFFERENT Body on a DIFFERENT box and nothing downstream detects it.
IF output is "reducer-or-single": continue to Step 1 unchanged. This is the ordinary
single-box case and behaves exactly as it did before this step existed. Note this now
means "evaluated, and this is not a worker" — it is no longer reachable by a failed
evaluation, which is what the indeterminate branch above took away from it.
Step 1: Check State -- Bash: session-state-get.sh
(Step 0.5 has already rebound this session to <agent-name>, so the PreToolUse hook auto-injects MIND_AGENT=<agent-name> and this read targets the correct agent.)
Behavior by Current State
RUNNING
Graceful two-phase stop. The agent finishes its current iteration's obligations (verify, state-update, learning checks) before stopping. No learning is lost.
How it works: This skill sets a stop-requested signal but does NOT change state
to IDLE. It then deterministically chains into the aspirations loop as its final action
(Step 5), so Phase -1.4 runs inside the same user turn: in-flight obligations from the
iteration checkpoint complete, then the full stop sequence (IDLE, consolidation, cleanup,
target mode) runs to completion. The Stop hook's BLOCK path remains as a safety net if
the in-turn chain is interrupted, but the normal path no longer depends on it — typing
/stop now self-completes without the user having to prompt "continue".
-
Write target mode (ALWAYS — runs before the idempotent guard so a user who types
/stop <agent-name>and then/stop <agent-name> --readercan change their mind before Phase -1.4 reads the file.target_modecomes from Step 0.5 flag parsing.): Bash:echo "<target_mode>" > agents/<agent>/session/stop-target-modeDo not move this below the idempotent guard — Phase -1.4 depends on this file existing when it runs (no fallback in D7). Any new caller of
session-signal-set.sh stop-requestedoutside/stopMUST also writestop-target-modefirst. -
Idempotent guard (do not re-set an existing signal, but still chain the loop): Bash:
session-signal-exists.sh stop-requestedIF exit 0 (signal already exists): A previous /stop set the signal but graceful-stop D3 never cleared it (if D1 had run, state would be IDLE and Step 1 would have routed us to the IDLE branch). Update the user, skip Step 3 to avoid a redundant set, and fall through to Steps 4 and 5 so this invocation still chains into the loop. Output: "Stop signal was already set — target mode updated to <target_mode>. Resuming graceful stop now." SKIP Step 3. Continue to Step 4. -
Set the signal (only if Step 2 did not skip): Bash:
session-signal-set.sh stop-requested -
Output: "Stop requested — finishing current obligations and stopping now. You'll see progress updates as each step completes. Will land in <target_mode> mode."
-
Chain into the aspirations loop — RUNNER SESSION ONLY.
Graceful-stop D1 writes
agent-stateand D7 writesagent-mode. Observer sessions (started via/start <agent> --mode reader|assistantwhile the loop was RUNNING) MUST NOT touch those files — that is the observer contract. So the chain is gated on session identity: only the session whose SID matchesrunning-session-idruns graceful-stop in-turn. Observer sessions stop here; the runner's Stop hook will pick up the signal at its next iteration boundary.Runner detection compares
$MIND_SID(exported on every Bash call by the PreToolUse hook —core/scripts/bash-agent-inject.py) against the savedrunning-session-id. MIND_SID is the ONLY authoritative "this session's SID" — reading stale per-agent files likelatest-session-idas a proxy is what caused the 2026-04-20 hang (observer clobber desynced the two files and/stoprouted the runner to the observer branch for 101 seconds).DO NOT add a heartbeat-fresh refresh-and-trust fallback here. The detection logic CANNOT distinguish "I'm the runner with stale running- session-id" from "another terminal is the runner ticking the heartbeat"; heartbeat freshness only proves some session is running, not which one. Autocompact rotation is handled at SessionStart by session-save-id.sh's four-witness gate — if running-session-id is stale, that gate failed upstream. Fix the gate, do not add a defensive write here.
Bash:
runner_sid=$(cat agents/<agent>/session/running-session-id 2>/dev/null | tr -d '\r\n'); if [ -z "$runner_sid" ] || [ "$MIND_SID" = "$runner_sid" ]; then echo "runner"; else echo "observer"; fiIF output is "observer": # Session-telemetry observer close (session-telemetry WP2, observer # variant, 2026-06-03): the observer got a WP1
activerecord at /start # (observer Step 0.5). It never reaches the IDLE branch's WP2 — Step 1 # saw state=RUNNING (set by the runner), so /stop took THIS RUNNING # branch, not the IDLE branch. Without a close here the observer's record # would orphan as permanently-activeand pollute the live-sessions # query. Placed FIRST in the observer branch so it fires for BOTH the # fresh and stale sub-paths below. Keyed on the OBSERVER's own $MIND_SID # (not the runner's running-session-id). status=completed, # ended_reason=user-stop. guard-165: SID/agent via ENV, python source # single-quoted.py -3(Bash-tool context). Fire-and-forget (|| true). Bash:TSID="$MIND_SID" TAGENT="$MIND_AGENT" py -3 -c 'import os,sys; sys.path.insert(0,"core/scripts"); from _session_telemetry import write_close; write_close(sid=os.environ["TSID"], agent=os.environ["TAGENT"], status="completed", ended_reason="user-stop")' >/dev/null 2>&1 || true# Three-way heartbeat probe (pure mtime; g-357-51): # fresh → runner is alive; leave the signal for its next iteration. # stale → heartbeat file aged out; runner presumed crashed → route # the user to `/start --recover`. # absent → no heartbeat file on this box. NOT a crash verdict; the # canonical gate decides (it needs positive death evidence). Bash: `bash core/scripts/heartbeat-stale.sh` IF output is "stale": Output: "Stop signal set, but runner session appears crashed (last heartbeat older than the staleness threshold in core/config/aspirations.yaml → runner_heartbeat.stale_minutes). The signal will not be picked up by the dead runner. Run `/start <agent-name> --recover` to force cleanup." DONE. Do not chain. IF output is "absent": Bash: `bash core/scripts/runner-dead-check.sh; echo "rdc_rc=$?"` IF rdc_rc == 0: same Output as the "stale" branch (crash confirmed by positive evidence). DONE. Do not chain. ELSE: Output: "Stop signal set. No heartbeat file exists on this box (absent, not stale) and the liveness gate does not read the runner as dead — the signal will be picked up at its next iteration." DONE. Do not chain. # output is "fresh" — normal observer path Output: "Stop signal set. The runner session will pick it up at its next iteration." # Plan v1 step 0.10 (2026-05-19): clean up this observer session's # SID binding at PROJECT_ROOT so it doesn't accumulate as cruft. # The runner has its own binding (cleaned by graceful-stop D7.1); # this one was created by /start --mode reader|assistant from the # RUNNING branch and has no other cleanup path. Idempotent — rm -f. Bash: `rm -f ".active-agent-$MIND_SID"` DONE. The signal is set; runner takes it from here. Do not chain.IF output is "runner": Skill:
aspirationswith argsloopThe aspirations skill enters, Phase -1.4 detects `stop-requested`, delegates to `/aspirations-graceful-stop`, which runs GS-1 (checkpoint recovery — typically a no-op when stop is typed between iterations) then GS-2 D1–D7 (state → IDLE, set `stop-loop`, consolidate, session cleanup, then apply `stop-target-mode` and emit the final stop-complete message in one merged step). Everything runs to completion before the turn ends; no "continue" nudge required. **Why chain explicitly instead of relying on the Stop hook BLOCK?** The Stop hook still BLOCKs when state is RUNNING and `stop-loop` is absent, but in interactive mode the CLI does not reliably trigger a new model turn when the preceding turn ended in text-only output. A direct Skill invocation is a deterministic handoff — the model follows the chain in-turn rather than waiting for the harness to re-invoke it.
IDLE (assistant or reader mode)
- Check current mode: Bash:
session-mode-get.sh - If current mode !=
target_mode(from Step 0.5): Bash:session-mode-set.sh <target_mode> - Output:
IF target_mode == "assistant":
"Mode set to assistant (reconciliation-ready). You can mark goals complete,
edit tree nodes, or add guardrails without ceremony.
/start <agent-name>resumes autonomous;/stop <agent-name> --readerfor walk-away safety next time." ELSE (target_mode == "reader"): "Mode set to reader (read-only)./start <agent-name> --mode assistantto make edits;/start <agent-name>to resume autonomous." 3.5. Finalize session telemetry (session-telemetry WP2, 2026-06-03): write the durable close record for THIS (non-runner) session. The IDLE branch handles/stopof an assistant/reader/observer session — it never reaches the graceful-stop D6.6 close (that's the autonomous runner's path), so without this step assistant/reader sessions would have an open WP1 record that never closes. status=completed, ended_reason=user-stop, mode_at_end=<target_mode>. The record lives at world/telemetry/session-records//$MIND_SID.json; the own-cloud sweep carries it to S3. Pure library module viapy -3 -c(no .sh wrapper / no daemon dependency — works even if the daemon is dead). guard-165: SID/agent/mode pass through ENV VARS, python source single-quoted. $MIND_SID and $MIND_AGENT are present here — the hook auto-injects MIND_AGENT and the binding is not cleaned until Step 4 below. Fire-and-forget (|| true): telemetry must never block the stop. Bash:TSID="$MIND_SID" TAGENT="$MIND_AGENT" TMODE="<target_mode>" py -3 -c 'import os,sys; sys.path.insert(0,"core/scripts"); from _session_telemetry import write_close; write_close(sid=os.environ["TSID"], agent=os.environ["TAGENT"], status="completed", ended_reason="user-stop", mode_at_end=(os.environ.get("TMODE") or None))' >/dev/null 2>&1 || true - Clean up this session's SID binding (plan v1 step 0.10, 2026-05-19): the
binding file at PROJECT_ROOT/.active-agent-$MIND_SID was created by /start
(or rebound by Step 0.5c above). The RUNNING branch's runner-session path
cleans its binding via graceful-stop D7.1; the IDLE branch never reached
D7.1 and accumulates a stale binding for each /stop. Delete it so the
PROJECT_ROOT doesn't bloat with one file per stopped session. The next
/startwill re-create as needed. Idempotent — rm -f is safe even if a prior /stop already cleaned it. Bash:rm -f ".active-agent-$MIND_SID"
Note: /stop <agent-name> (no --reader) from IDLE-reader PROMOTES to assistant (the
post-stop default). This is intentional — slash commands imply active user presence, so
CLI defaults favor the active mode. Use /stop <agent-name> --reader to explicitly stay
in / drop to reader.
UNINITIALIZED
Output: "Agent has not been started yet. Type /start <name> to begin."
Chaining
- Sets:
stop-target-modefile,stop-requestedsignal - Sets NEITHER of the above on the worker-Body path (Step 0.6): a
/stoptyped on a worker box arms its SESSION-SCOPEDsessions/<SID>/stop-requested(g-115-7309), does NOT push its per-session state, closes its telemetry, cleans its binding, and exits without touching any agent-wide file. The reducer is not signalled and keeps running (g-306-125). The session-scoped file is what lets the turn END: it fires the stop-hook'sworker-net-stop-requested-sessionvalve. It does NOT retire the Body —body_statestaysactive, so a later/startresumes this SID normally instead of needing the user-only reopen abody-closingclose would force. - Calls (RUNNING branch, runner session only):
Skill: aspirationswith argsloopas the final action, so Phase -1.4 runs in the same user turn and the graceful stop (D1–D7) completes before the turn ends. Observer sessions skip the chain and leave the work for the runner's Stop-hook re-entry path. - Does NOT call: /aspirations-consolidate directly (that's reached via Phase -1.4 D4)
- Called by: User only. NEVER by Claude.