Skip to content
51 changes: 51 additions & 0 deletions .agents/skills/primary-session-handoff/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: primary-session-handoff
description: >-
Agent-only procedure for scanning primary-provider captain actions and safely transferring or restoring the live Firstmate primary session.
Use after a live session-lock refusal when the current provider is idle but unavailable, or whenever primary takeover, suspension, restoration, or a fleet-wide permission scan is requested.
user-invocable: false
metadata:
internal: true
---

# primary-session-handoff

Use this procedure after a live session-lock refusal when the current provider is unavailable, or whenever primary takeover, suspension, restoration, or a fleet-wide permission scan is requested.

Load `harness-adapters` before this skill performs a provider suspend or resume.
Read `bin/fm-primary-session.sh --help` before the first command because that script owns supported providers, exact mechanics, receipt states, and flags.

## Read-only triage

Run the command's fleet-wide scan before considering a transfer.
The scan reports structured pending permissions and captain-action markers without prompt, title, or permission prose.
Treat every listed action as unresolved until the owning session or captain resolves it.

Identify the exact external session requested for takeover from the visible provider inventory.
Do not infer identity from a title, transcript, terminal label, provider name, or working directory.

## Takeover

Invoke the owned takeover command with the exact external session id.
Do not call a provider archive command separately, pass a force flag, delete `state/.lock`, or edit the owner descriptor.

Only `idle` and `paused-rate-limited` are eligible outcomes.
`waiting-on-captain`, `busy`, `wedged`, `unknown`, and `unsupported` are refusals, not invitations to approximate the operation manually.

After success, read the ordinary session-start digest once and follow its emitted supervision block.
Preserve the printed receipt path so the suspended provider can be restored later.

## Restore

Restore only from the receipt created by the owned takeover command.
Ensure the successor primary has exited normally before requesting restoration, because a live or ambiguous lock owner is a hard refusal.

Invoke the owned restore command and do not call the provider reload command separately.
The reloaded provider remains mutation-ineligible until its native session-start nudge runs the ordinary lock-acquisition contract.
If another primary wins that acquisition, the restored provider stays read-only and reports the normal lock refusal.

## Unsupported or failed operations

Report an unsupported combination with the command's exact classification.
Leave the provider, numeric lock, owner descriptor, receipt, fleet state, task worktrees, and wake queue untouched after a refusal.
Never turn an incomplete suspension receipt into permission to delete or overwrite a live lock.
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,11 @@ data/ personal fleet records; LOCAL, gitignored as a whole
secondmates.md secondmate routing table; firstmate-private, maintained by fm-home-seed.sh (section 6)
<id>/brief.md per-task crewmate brief, or per-secondmate charter brief when kind=secondmate
<id>/report.md scout task deliverable, written by the crewmate; survives teardown
primary-session-handoffs/ privacy-safe takeover and restore receipts created by bin/fm-primary-session.sh
projects/ cloned repos; gitignored; READ-ONLY for you
state/ volatile runtime signals; gitignored
.lock numeric primary session lock; remains the sole orchestration authority
.lock.owner replaceable primary identity descriptor published after verified lock acquisition
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
<id>.turn-ended touched by turn-end hooks
<id>.grok-turnend-token firstmate-owned grok hook registry token for the task; removed by teardown
Expand Down Expand Up @@ -132,6 +135,7 @@ An `ABSENT` captain, shared-captain, secondmate, or learnings file means the fir

If the session lock cannot be acquired and verified, report its exact diagnostic and remain read-only; another active session is only one possible cause.
A lock-refused session must not spawn, steer, merge, drain the wake queue, repair supervision, repair a checkout, or perform any other fleet mutation.
When an explicit primary-provider takeover or restoration is required, load `primary-session-handoff`; only its owned command may cross this read-only boundary, and it never authorizes deleting or weakening a live owner's lock.

1. **Lock** - acquires the per-home session lock first, before anything mutates shared state.
2. **Bootstrap** - detect-only checks (tool/version problems, GitHub auth, the worktree-tangle check, harness override, dispatch-profile validation, backlog-backend status) always run, but routine confirmations stay silent by default.
Expand Down Expand Up @@ -483,6 +487,7 @@ These skills are not captain-invocable; load them only at their precise triggers
- `fmx-respond` - load on an `x-mention <request_id>` `check:` wake to handle the mention, on an `x-mode-error ...` `check:` wake to report the X-mode configuration blocker, and on any milestone or terminal wake for an X-mode-linked task before posting its completion follow-up; relevant only when X mode is on.
- `firstmate-codexapp` - load before coordinating a visible Codex Desktop thread, evaluating a Codex App backend request, or reconciling Codex Desktop host-tool smoke evidence for Firstmate work.
- `firstmate-coding-guidelines` - load before changing firstmate's shared, tracked material, as defined by section 1's list, whether editing directly or briefing a crewmate for a firstmate-repo task.
- `primary-session-handoff` - load after a live session-lock refusal when the current provider is unavailable, and before primary takeover, suspension, restoration, or a fleet-wide provider permission scan.

## 14. X mode

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Launching a supported harness inside it instantiates your first mate - and makes
- **Optional X mode** - opt in with one local `.env` token so firstmate can answer your public `@myfirstmate` mentions, act on normal reversible mention requests through the same lifecycle as chat requests, acknowledge spawned work, and post up to three public-safe completion follow-ups within seven days for genuine milestones and the final outcome without changing non-X behavior; dry-run preview records would-be replies and dismissals locally before go-live.
- **Guarded by construction** - the first mate is read-only over your projects except for the guarded paths authorized by [hard rule 1](AGENTS.md#1-identity-and-prime-directives), with fleet sync's safe branch pruning remaining part of the fleet-sync exception; crewmates make every project change behind the configured merge authority.
- **Restart-proof** - all state lives on disk and in the active session backend (tmux by hard default, herdr or cmux when selected or auto-detected, zellij/orca when explicitly selected); kill the session anytime and the next one reconciles, including confirmed-dead secondmate agents, and carries on.
- **Recoverable primary handoff** - safely transfer the live lock away from an idle local Paseo-hosted Claude or Codex provider, then explicitly restore its preserved provider session after the successor exits.

Full detail on every feature lives in [docs/architecture.md](docs/architecture.md).

Expand Down Expand Up @@ -160,7 +161,7 @@ It routes each request to a crewmate in its own session endpoint and git worktre
Optional secondmates extend this to persistent second mates, dispatch profiles let you steer which harness handles which task, and an opt-in X mode lets the same fleet answer public mentions.
`codex-app` is not a runtime backend yet; [docs/codex-app-backend.md](docs/codex-app-backend.md) owns the Codex App boundary.

Full architecture - the supervision engine, worktree isolation, secondmates, dispatch profiles, project modes, optional X mode, fleet sync, and self-update - is in [docs/architecture.md](docs/architecture.md).
Full architecture - the supervision engine, recoverable primary handoff, worktree isolation, secondmates, dispatch profiles, project modes, optional X mode, fleet sync, and self-update - is in [docs/architecture.md](docs/architecture.md).

## Built-in skills

Expand Down Expand Up @@ -203,6 +204,8 @@ Firstmate's skills live in two separate places with different audiences:
- [docs/gitlab-merge-watch.md](docs/gitlab-merge-watch.md) - maintainer verification for GitLab merge watching on arbitrary instances.
- [docs/turnend-guard.md](docs/turnend-guard.md) - the primary session's current "no turn ends blind" backstop, scope, loop safety, and compatibility limits.
- [docs/verification/supervision.md](docs/verification/supervision.md) - active maintainer verification for session-start, guard, continuity, and wedge integrations.
- [docs/primary-session-handoff.md](docs/primary-session-handoff.md) - current safe takeover, restoration, permission-scan, and supported-provider contract.
- [docs/verification/primary-session-handoff.md](docs/verification/primary-session-handoff.md) - deterministic and incident-derived maintainer verification for recoverable primary handoff.
- [docs/supervision-protocols/](docs/supervision-protocols/) - rendered primary-harness watcher protocols for Claude, Codex, OpenCode, Pi and `pi-signed`, Grok, and unknown harness fallback.
- [docs/scripts.md](docs/scripts.md) - the `bin/` toolbelt reference.
- [docs/documentation-audiences.md](docs/documentation-audiences.md) - documentation audiences and the machine-checked placement boundary.
Expand Down
21 changes: 21 additions & 0 deletions bin/fm-lock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
# PID of any one tool call, which is dead moments after it is written.
# Usage: fm-lock.sh acquire; exit 1 unless ownership is verified
# fm-lock.sh status print holder and liveness; always exits 0
# A successful acquisition also atomically publishes state/.lock.owner.
# That privacy-safe descriptor records the numeric owner, process-identity
# hash, verified harness, visible-session manager, external session id, and
# provider-native session id when available.
# The numeric state/.lock remains the sole orchestration authority.
set -u

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Expand All @@ -22,6 +27,8 @@ mkdir -p "$STATE" 2>/dev/null || {
# same identity contract.
# shellcheck source=bin/fm-session-lock-lib.sh
. "$SCRIPT_DIR/fm-session-lock-lib.sh"
# shellcheck source=bin/fm-primary-session-lib.sh
. "$SCRIPT_DIR/fm-primary-session-lib.sh"

if [ "${1:-}" = "status" ]; then
if [ ! -f "$LOCK" ]; then echo "lock: free"; exit 0; fi
Expand All @@ -34,6 +41,7 @@ if [ "${1:-}" = "status" ]; then
fi

me=$(fm_harness_ancestry_pid) || { echo "error: cannot locate harness process in ancestry" >&2; exit 1; }
incomplete_reason=
probe=$(mktemp "$STATE/.lock-write.XXXXXX" 2>/dev/null) || {
echo "error: cannot write session lock; operate read-only until resolved" >&2
exit 1
Expand All @@ -57,6 +65,11 @@ trap 'exit 1' HUP INT TERM
fm_lock_acquire_wait "$CLAIM_LOCK"
CLAIM_LOCK_HELD=1

if ! incomplete_reason=$(fm_primary_suspend_incomplete_clear); then
echo "error: ${incomplete_reason:-unresolved suspend-incomplete primary-session handoff}; operate read-only until resolved" >&2
exit 1
fi

if [ -e "$LOCK" ] || [ -L "$LOCK" ]; then
if [ ! -f "$LOCK" ] || [ -L "$LOCK" ]; then
echo "error: session lock is not a regular file; operate read-only until resolved" >&2
Expand All @@ -83,5 +96,13 @@ if [ ! -f "$LOCK" ] || [ -L "$LOCK" ] || [ "$written" != "$me" ]; then
echo "error: session lock ownership verification failed; operate read-only until resolved" >&2
exit 1
fi
if ! fm_primary_owner_publish "$STATE" "$me"; then
if fm_primary_lock_rollback_if_exact_owner "$STATE" "$me"; then
echo "error: cannot publish verified primary-session owner metadata; rolled back this session lock claim; operate read-only until resolved" >&2
else
echo "error: cannot publish verified primary-session owner metadata; could not prove safe rollback of this session lock claim; operate read-only until resolved" >&2
fi
exit 1
fi
release_claim_lock
echo "lock acquired: harness pid $me"
Loading
Loading