Detailed setup, agent procedure, permissions, and operating limits.
Clone into a stable location and run the checks:
git clone https://github.com/HakAl/switch.git
cd switch
SWITCH_DIR="$PWD"
python3 switch.py --version
python3 switch_auto.py --version
python3 -B -m unittest discover -s tests -vKeep both scripts and the Python interpreter at those paths: installed hooks
refer to their absolute locations. In a new terminal, set SWITCH_DIR to the
absolute path of this clone again. Choose one of the following hook setups.
No live session is reset during installation.
From the project you want to manage:
cd /absolute/path/to/your/project
python3 "$SWITCH_DIR/switch_auto.py" install --dry-run
python3 "$SWITCH_DIR/switch_auto.py" install
python3 "$SWITCH_DIR/switch_auto.py" statusThe default limit is 25% of the reported context window and the default state
directory is $SWITCH_STATE_DIR, otherwise ~/.local/state/switch. Pass a limit
such as 40% or 250k, --project, or --state-dir to override them.
The installer merges hooks, wraps the effective command statusLine, and adds the
missing required_permissions to permissions.allow in the project's
.claude/settings.local.json, keeping existing entries, key order, indentation
and line endings. It warns before normalizing formatting it cannot reproduce. Review the
permissions_to_add that --dry-run lists before installing. Also permit reads of the original task instructions and authority sources, and
only the actions the task needs. See permissions below.
A Bash allow rule does not grant OS filesystem or socket access.
Start a fresh Claude session inside a Herdr pane in that project after setup.
Keep Herdr's own session-reporting integration enabled. Run herdr agent list
and check that the row for the pane identifies Claude and the current session.
If reporting is absent or names another process, fix Herdr's integration first.
Use the agent procedure for a manually triggered request;
the companion supplies preparation instructions when it observes the threshold.
For user scope, status, uninstall and retention details, see automatic setup. To remove this project installation:
python3 "$SWITCH_DIR/switch_auto.py" uninstallUninstall removes the permissions it added and preserves state and backups for recovery. Do not delete claims while an old session might be resumed, or change state roots to bypass a claim.
Skip this section if you installed the companion: it already records lifecycle hooks.
Set SWITCH_STATE to the same private state directory used by requests:
SWITCH_STATE=/absolute/path/to/private/switch-state
python3 "$SWITCH_DIR/switch.py" hooks --state-dir "$SWITCH_STATE"This prints a settings fragment without changing configuration. Add each entry to
the corresponding hook array in Claude's settings, preserving existing hooks,
permissions and statusLine. Alternatively save the fragment and launch Claude
with --settings /absolute/path/switch-hooks.json after checking the effective
configuration. Then configure the scoped permissions below and start a fresh
Claude session under Herdr to obtain its SessionStart record.
Both setups use HERDR_PANE_ID from Herdr and session_id from hook input.
Required events are SessionStart, UserPromptSubmit, Stop, PreToolUse,
PostToolUse, PostToolUseFailure, SubagentStart, SubagentStop and PermissionRequest.
Lifecycle snapshots contain identifiers, worker generations, the parent transcript
path and state, not tool arguments or prompt text; the companion separately stores
the inputs disclosed above.
Use a private local state directory; avoid shared, synced or network storage.
The launching Claude Bash tool needs permission to execute the installed script's
request and ack commands, and optionally preflight and status. The example
below uses manual-trigger setup paths; for the companion, use its printed
required_permissions, including its auto-checkpoints path. Substitute your
actual interpreter and installation paths:
{
"permissions": {
"allow": [
"Bash(/usr/bin/python3 /opt/switch/switch.py preflight *)",
"Bash(/usr/bin/python3 /opt/switch/switch.py request *)",
"Bash(/usr/bin/python3 /opt/switch/switch.py status *)",
"Bash(/usr/bin/python3 /opt/switch/switch.py ack *)",
"Read(//absolute/path/reset-state/requests/**)",
"Edit(//absolute/path/project/checkpoint.json)"
]
}
}Use the same interpreter spelling for invocation; bootstrap uses Python's
sys.executable, which can differ from python3 or a symlink. Verify the emitted
command and permit that exact installed-script prefix too when necessary. The
trailing * matches arguments; avoid pane-prefix patterns such as w8:*.
File rules use // for filesystem-root paths, and Edit covers Write. Grant
reads for the actual instruction/authority files and only the writes/commands
needed by the task's next action. An acknowledgment does not grant that next
action permission. Deny or managed rules still apply.
Run the bootstrap's ack command exactly as printed, alone in its own Bash call. Do not chain it with checksum checks or other verification; run those in a separate call. The installer's existing ack rule covers the generated interpreter and script prefix, not every command you might combine with it.
The helper and hooks need filesystem access to their state directory, the
checkpoint, instruction and authority sources, plus access to the local Herdr
socket and executable. The helper invokes only herdr agent list, herdr pane get, herdr pane read, and herdr agent prompt. Claude's Bash allow rule does
not itself configure an OS sandbox's filesystem or socket access. Test this
setup with preflight and a disposable seat first. No broad Bash/Herdr allow rule,
bypass permission mode, or permission editing by the agent is required.
For recovery, status and reading saved checkpoint/bootstrap files need the same
read permissions. Manual bootstrap submission uses the operator's normal Claude
input (or their existing authorized Herdr prompt channel); it is never retried
automatically. The new context still needs the scoped ack command permission.
- Finish useful foreground tools. Collect outstanding workers and their results. Finish waits, or deliberately stop disposable waits through the owning tool's normal interface. Do not cancel useful work to meet a reset deadline. Inspect Claude's background task list and any external worker/workflow system you used. Herdr idle and Stop alone cannot prove those resources are finished.
- Write a concise checkpoint based on examples/checkpoint.json.
Include task, role, absolute worktree, refs, completed and unfinished work,
concrete next action, instruction paths, original authority-source paths, and
an explicit preparation inventory.
quiescent: trueattests that active tools, workers and waits are all empty except the upcoming request command itself. Hooks also check tools/workers, but cannot discover arbitrary external jobs or a process detached by a completed Bash call. This attestation is necessary. - Run preflight, then request from the Claude session being reset. Herdr
supplies
HERDR_PANE_ID;--panemust match it, and--sessionmust be that pane's current live session. Missing or mismatched caller identity is refused. Do not set or override this variable to target another pane. Preflight is read-only and reports busy/input conditions without treating them as readiness. - After request reports
scheduled, end the turn immediately. Additional user input cancels the pending reset. Start no more tools, workers, or background waits. The helper waits for a subsequent Stop, no tracked active resources, fresh lifecycle telemetry, idle/done Herdr status, and a readable empty prompt. Keep this pane exclusively owned during reset: no typing, scrolling, manual session switching, or other automation.
python3 /opt/switch/switch.py preflight \
--state-dir /path/reset-state --pane PANE_ID --session OLD_SESSION \
--checkpoint /path/project/checkpoint.json
python3 /opt/switch/switch.py request \
--state-dir /path/reset-state --pane PANE_ID --session OLD_SESSION \
--checkpoint /path/project/checkpoint.json \
--resume 'Run the next action recorded in the checkpoint.' \
--idle-deadline 300 --clear-deadline 60 --ack-deadline 120
# From outside the resetting turn, using the returned request ID:
python3 /opt/switch/switch.py status \
--state-dir /path/reset-state --request-id REQUEST_IDThe caller-pane check prevents accidental cross-pane requests; it is not OS-level authentication. Code with arbitrary local execution can change its environment. Keep command permissions narrow and do not grant environment overrides.
All deadlines are finite, from 1 to 3600 seconds. clear-deadline gives clear
confirmation and subsequent replacement readiness separate intervals of that
length (60 seconds each by default, at most 120 seconds combined). The readiness
interval starts once, after correlated clear confirmation; both its loop and
Herdr subprocess limits use the new budget. Missing Herdr reads during readiness
or acknowledgment polling consume the existing interval; confirmed identity
changes still refuse immediately. No prompt is resent. Lifecycle evidence must
still be at most 120 seconds old: a longer deadline does not make stale evidence
usable, and slow startup without further hook events can exhaust that freshness
window. ack-deadline bounds confirmation
of resumption, not completion of the resumed task. Each Herdr call is capped at
five seconds and the remaining phase budget. A detached process survives the
launching tool/turn ending, but not arbitrary host shutdown or OS sandbox teardown.
Local checkpoints are capped at 32 KiB and copied to private request storage; the actual copy is reread and hashed before clearing and bootstrapping. Existing handoff systems can supply the content, but an identifier in terminal output is not a checkpoint. Export and verify the actual content first. Authority source paths reference the operator's real instructions/grants, with their original scope and expiry; agent-authored notes are separate and cannot extend a grant. Empty resource inventories mean explicitly none, not unknown.
A successful TaskStop for a local agent can close its tracked worker even when Claude emits no SubagentStop. A completed foreground Agent result must match an observed worker generation. For older stale entries, Switch reads at most 8 MiB of the parent transcript and requires paired structured TaskStop success, matching session identity, timestamps and transcript order. Resuming a worker invalidates earlier stop evidence, including when timestamps tie. Raw transcript content is never copied into lifecycle state. Missing, incomplete or changing evidence keeps the worker blocking. Refusal reasons identify remaining workers.
Parent Stop clears foreground tools but does not end workers. Useful live or unknown work still blocks reset; the 300-second default is unchanged. Finish work before requesting reset, or explicitly choose a longer bounded idle deadline. Do not delete worker state or cancel useful work just to meet the deadline.
A published request has request.json and the durable checkpoint.json.
helper.log is opened when the CLI launches the helper; allocation alone does
not establish it. bootstrap.txt is prepared only after the replacement session
passes readiness, not merely after clear. It contains the exact bytes bound to
the resume attempt. ack.json appears only when acknowledgment succeeds.
Request records include identity and stage; send results, timestamps and new
session evidence appear as those stages are reached. status derives recovery
advice from the recorded stage and the currently available bootstrap artifact.
| Outcome | Meaning |
|---|---|
resumed |
New session confirmed, bootstrap submission observed, and agent acknowledgment received. Task completion is separate. |
not_cleared |
This helper sent no clear. The original session should be inspected and preparation repaired. |
clear_uncertain |
Clear was attempted but not confirmed by both Herdr and a new SessionStart with source clear. Do not resend. |
cleared_not_resumed |
Clear was confirmed; bootstrap or acknowledgment failed/timed out. Inspect the pane and receipt before manual recovery. |
interrupted_uncertain |
Status found an abandoned helper, including one killed before its final write. Inspect saved intent and live evidence. |
Exit 0 means the command succeeded (scheduling is not reset completion); 3 is a
validation/setup refusal (including brief claim-lock contention); 4 is a recorded unsuccessful or uncertain outcome.
Only resumed is a completed reset-and-resume result. A successful Herdr send
alone is not proof of submission, and a working status is not an acknowledgment.
The agent's receipt attests that it read the checkpoint/instructions and rechecked
authority; it is not an independent proof of comprehension.
Bootstrap submission evidence survives ordinary follow-up prompts in the same
session. Ack accepts either matching session telemetry or the worker's saved
submitted_at confirmation for that request. The latter also supports recovery
from older hooks that erased telemetry on a follow-up. The input provenance
archive (auto/PANE/SESSION.inputs.json) excludes full Switch bootstraps bound
to a recorded resume in that pane/session, including recognized paste wrappers.
Unverified marker-prefixed inputs remain untrusted provenance. Fresh and
bootstrap-only sessions may have no archive; generated preparation text
advertises it only when a usable record exists. Retain real authority sources
from the prior checkpoint rather than listing an absent archive. Its absence
does not mean submission was missed, and its presence confers no authority.
After a timeout, a valid manual ack can write ack.json, but it does not restart
the helper or change its finished cleared_not_resumed outcome to resumed.
Inspect both records during recovery. Session/terminal identity, checkpoint and
permission-mode checks still apply; changing permission mode can produce a
separate refusal. No automatic denial retry or deadline extension is performed.
If a refusal occurred before allocation and inspection confirms no request or
session claim was allocated, an explicit user request to try again permits fresh
preparation and an ordinary request without --retry-of. No predecessor ID
exists in that case. An error without an ID is not itself proof of no allocation;
inspect the session claim and request state when uncertain. Published uncertain
or abandoned requests remain held. Helper-launch refusals return the allocated
request_id so its receipt can be inspected through status.
A finished not_cleared request can be retried explicitly if status reports
retry_eligible: true. Revalidate preparation and update the checkpoint, then
repeat request with --retry-of FAILED_REQUEST_ID. This creates a new request
and preserves the old receipt. A fresh Stop is required; new input cancels the
new attempt too. Status reports current_request_id when an attempt has been
superseded. Eligibility is advisory and is rechecked under locks at scheduling.
Never retry automatically, delete claims, or change state roots to bypass a claim.
ack acknowledges a resumed session; it cannot unlock a cancelled request.
If clear was attempted or its outcome is uncertain, inspect the live session and
saved evidence before recovery: a timed-out send can still arrive. If clear
succeeded, verify the recorded pane/session/terminal, preserve any draft, read
the durable checkpoint, verify its recorded hash, and recheck applicable
instructions and original authority sources for scope and expiry. Stop if those
checks fail. Then follow the reached stage:
- No resume attempt: after those checks, continue the checkpoint's
next_actionmanually in that session. No bootstrap or acknowledgment is required; do not invent resume evidence or runackfor an unattempted resume. - Resume attempted and the saved bootstrap matches its bound hash: only after checking it was not already processed, manually submit its exact contents once and follow the embedded acknowledgment command. Stop on refusal.
- Resume attempted but the saved bootstrap is missing or mismatched: do not resend. Reconcile live submission/ack evidence before continuing manually from the checkpoint.
Never clear that session again as a recovery shortcut.
Herdr has no atomic session-compare-and-prompt operation. The helper rechecks identity and input immediately before sending, but cannot exclude an external writer in the last instant. Exclusive pane ownership is a required operating condition. Scrolled views, unfamiliar prompt layouts, permission dialogs, and missing/stale telemetry are refused. Only actual SGR 2 dim runs are ignored; RGB/palette color alone never proves text is a suggestion. The full bordered input box is checked, including continuation lines.
Switch sends /clear in the existing process. It never restarts Claude, changes
its model/mode, or cancels work. See validation for what was actually observed in
the tested CLI; do not infer arbitrary resource survival from a successful clear.
State files and claims should be retained while an old session might be resumed.
Kernel locks disappear on process death; status uses those locks rather than a
PID that could have been reused. No process can promise a final disk write after
SIGKILL or power loss, so interrupted results remain explicit.
The base engine uses Claude's SessionStart hook as clear evidence. The companion uses fresh session-bound status-line measurements to request preparation. See the design and validation. Permission examples follow the Claude permission reference.