kunchenguid/firstmate/.agents/skills/afk/SKILL.md
afk
Enter away-mode supervision when the captain invokes /afk, says they are going afk, `state/.afk` exists, an incoming message starts with `FM_INJECT_MARK`, or any `state/.subsuper-*` marker is involved. It sets a durable away-mode flag so the sub-supervisor daemon can self-handle routine wakes and escalate captain-relevant events plus bounded declared-external-wait rechecks as batched digests during walk-away stretches, then exits automatically when any real unmarked message returns firstmate to
- Source repository stars
- 4,070
- Declared platforms
- 0
- Static risk flags
- 0
- Last source update
- 2026-08-24
- Source checked
- 2026-08-25
Decision brief
What it does: where it fits
Away-mode supervision. When invoked, /afk makes the daemon's token-saving tradeoff consented and explicit: the captain is stepping away, so the sub-supervisor may triage routine wakes in bash instead of waking firstmate's LLM for each one. Escalations still reach the captain, bu…
Not for
- Tasks that require unconfirmed production actions or broad system permissions.
- Environments where the pinned source and install steps cannot be inspected.
Compatibility matrix
Platform support, with evidence labels
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
Inspect first. Install second.
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/kunchenguid/firstmate --skill ".agents/skills/afk"Inspect the Agent Skill "afk" from https://github.com/kunchenguid/firstmate/blob/038d0f7ec6ba7238a151722931434dcf06ff37c4/.agents/skills/afk/SKILL.md at commit 038d0f7ec6ba7238a151722931434dcf06ff37c4. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
What the source asks the agent to do
- 01
How to exit afk
No /back is needed. The first genuine message is the return signal:
A message without the current operational prefix or a legacy bare marker, and not starting with /afk - the captain is back.A message with the current operational prefix (FMOPERATIONALPREFIX, U+2063 INVISIBLE SEPARATOR followed by FIRSTMATEOP: ), or a legacy bare FMINJECTMARK daemon escalation - stay afk and process it.Re-invoking /afk while already away - stay afk (refresh the flag); this - 02
What it does
1. Enter the lifecycle through bin/fm-afk-launch.sh. This owns the durable state write, session-scoped stale-artifact clearing, terminal record, and rollback. The flag survives a firstmate restart, so recovery re-enters afk when it is present.
Enter the lifecycle through bin/fm-afk-launch.sh.Ensure the sub-supervisor daemon is running as a tracked background process.Harness WITH a native in-pane tracked-background tool (e.g. claude's - 03
Orthogonal to approval authority
afk changes how aggressively firstmate surfaces things, not who approves what. "Away" never means "approves more" or "approves less." A PR ready for merge keeps the merge authority from AGENTS.md section 7, and a needs-decision finding keeps the ask-user-authority policy; anythi…
afk changes how aggressively firstmate surfaces things, not who approves what. "Away" never means "approves more" or "approves less." A PR ready for merge keeps the merge authority from AGENTS.md section 7, and a needs-… - 04
Operational prefix contract
The daemon constructs every current injection as the away-supervisor kind owned by bin/fm-operational-input.sh, beginning with FMOPERATIONALPREFIX: FMINJECTMARK (U+2063 INVISIBLE SEPARATOR) followed by the stable FIRSTMATEOP: label. The bare FMINJECTMARK form remains accepted fo…
The daemon constructs every current injection as the away-supervisor kind owned by bin/fm-operational-input.sh, beginning with FMOPERATIONALPREFIX: FMINJECTMARK (U+2063 INVISIBLE SEPARATOR) followed by the stable FIRSTM… - 05
Busy-guard and composer guard
The daemon never injects into an in-use pane. Two checks run before every injection, dispatched through bin/fm-backend.sh for the supervisor's own backend (tmux or herdr; see "Auto-discovered supervisor pane" below):
Primary-pane busy guard - paneisbusy trusts Herdr native busy when available, otherwise matches rendered output against only the detected primary harness's signature.Composer-state guard - injectmsg reads the full empty/pending/pending-unproven/unknown verdict from fmbackendcomposerstate and injects only when it is affirmatively empty.The daemon never injects into an in-use pane. Two checks run before every injection, dispatched through bin/fm-backend.sh for the supervisor's own backend (tmux or herdr; see "Auto-discovered supervisor pane" below):
Permission review
Static risk signals and limitations
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
Why each signal appears
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 90/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 4,070 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Provenance and original SKILL.md
- Repository
- kunchenguid/firstmate
- Skill path
- .agents/skills/afk/SKILL.md
- Commit
- 038d0f7ec6ba7238a151722931434dcf06ff37c4
- License
- MIT
- Collected
- 2026-08-25
- Default branch
- main
View the original SKILL.md
afk
Away-mode supervision. When invoked, /afk makes the daemon's token-saving
tradeoff consented and explicit: the captain is stepping away, so the
sub-supervisor may triage routine wakes in bash instead of waking firstmate's
LLM for each one. Escalations still reach the captain, but as one pre-read,
batched digest rather than per-wake injections.
What it does
-
Enter the lifecycle through
bin/fm-afk-launch.sh. This owns the durable state write, session-scoped stale-artifact clearing, terminal record, and rollback. The flag survives a firstmate restart, so recovery re-enters afk when it is present. -
Ensure the sub-supervisor daemon is running as a tracked background process. Its hosting differs by harness. Pick the right path:
- Harness WITH a native in-pane tracked-background tool (e.g. claude's
background bash, grok's background tool): first run
bin/fm-afk-launch.sh start-native, then runFM_AFK_STATE_PREPARED=1 bin/fm-afk-start.shthrough that native tool. This is a deliberate no-separate-terminal exception because the harness-hosted job creates no terminal or layout mutation, and a shell launcher cannot invoke a harness-native background tool. The launcher still owns lifecycle state and records the no-terminal mode, while the daemon inherits and auto-discovers the captain pane. If the native launch fails, runbin/fm-afk-launch.sh stopto roll back the prepared lifecycle. Do not wrap it innohup ... &(Codex/herdr can reap fire-and-forget shell children after a tool call returns). - Harness WITHOUT one (e.g. pi): run
bin/fm-afk-launch.sh start. It is the single owner of the daemon terminal: it creates a NON-VISIBLE tracked terminal for the current backend (a herdr dedicated--no-focusworkspace, a detached tmux session), records its exact id, and passes the captain pane in asFM_SUPERVISOR_TARGETso the daemon injects into the captain, not its own new pane. Never manufacture a terminal by splitting the captain's active pane (herdr pane split): a split co-tenants the tab and visibly shrinks the captain's pane (docs/herdr-backend.md "Away-mode supervisor support"). Both paths sharebin/fm-afk-start.shas the daemon entry. The native path tells it that the launcher already prepared lifecycle state; the terminal-backed path lets the entry perform its existing state setup inside the new terminal. It exits immediately if the identity-backed daemon lock already names a live process, otherwise it execsbin/fm-supervise-daemon.shin the foreground. The daemon is presence-gated: it injects escalations only whilestate/.afkexists, and stays quiet otherwise.
- Harness WITH a native in-pane tracked-background tool (e.g. claude's
background bash, grok's background tool): first run
-
Do not separately arm
fm-watch.sh. The daemon manages the watcher as its child; the singleton lock no-ops a stray arm harmlessly. -
Acknowledge in
AGENTS.mdsection 9 language: "Captain, away mode is active; I will batch routine updates and surface only decisions, failures, credentials, or review-ready work until you return."
How to exit afk
No /back is needed. The first genuine message is the return signal:
- A message without the current operational prefix or a legacy bare marker, and not starting with
/afk-> the captain is back. Runbin/fm-afk-return.shbefore acting on the message that brought the captain back. That script owns correct-ordered daemon shutdown, durable wake presentation and post-handling acknowledgement, escalation and wedge evidence, and the return-catch-up gate. If it reports a firstmate-actionableblocked:event, remediate it immediately through the normal lifecycle, or explicitly reclassify it with a durable reason and close its decision key withresolved [key=...], then runbin/fm-afk-return.sh check. Once the daemon stops, resume full per-wake responsiveness through the emitted primary-harness supervision protocol while blocker handling proceeds, so the gate never creates a blind wait. Do not answer a Bearings request or perform any other ordinary captain work until the check exits successfully. - A message with the current operational prefix (
FM_OPERATIONAL_PREFIX, U+2063 INVISIBLE SEPARATOR followed byFIRSTMATE_OP:), or a legacy bareFM_INJECT_MARKdaemon escalation -> stay afk and process it. - Re-invoking
/afkwhile already away -> stay afk (refresh the flag); this does not trigger an exit.
Bias ambiguous cases toward exit: a present captain beats token savings, and
a false exit is self-correcting (the captain re-runs /afk).
Orthogonal to approval authority
afk changes how aggressively firstmate surfaces things, not who approves what.
"Away" never means "approves more" or "approves less."
A PR ready for merge keeps the merge authority from AGENTS.md section 7, and a needs-decision finding keeps the ask-user-authority policy; anything requiring the captain still waits for the captain's explicit word.
The daemon only batches the notification.
Operational prefix contract
The daemon constructs every current injection as the away-supervisor kind owned by bin/fm-operational-input.sh, beginning with FM_OPERATIONAL_PREFIX: FM_INJECT_MARK (U+2063 INVISIBLE SEPARATOR) followed by the stable FIRSTMATE_OP: label.
The bare FM_INJECT_MARK form remains accepted for legacy daemon escalations during rollout.
U+2063 has no normal keyboard keystroke and survives terminal transport as UTF-8 text.
This is how firstmate tells a daemon escalation apart from a real message in the same pane.
The operational prefix travels with the message text; it does not rely on harness-level typed-vs-injected detection, which is not portable across claude, codex, opencode, pi, pi-signed, grok, and kimi.
Busy-guard and composer guard
The daemon never injects into an in-use pane. Two checks run before every
injection, dispatched through bin/fm-backend.sh for the supervisor's own
backend (tmux or herdr; see "Auto-discovered supervisor pane" below):
- Primary-pane busy guard -
pane_is_busytrusts Herdr nativebusywhen available, otherwise matches rendered output against only the detected primary harness's signature. This narrow delivery guard never classifies a recorded worker task and never uses a global union of vendor patterns. - Composer-state guard -
inject_msgreads the fullempty/pending/pending-unproven/unknownverdict fromfm_backend_composer_stateand injects only when it is affirmativelyempty. Every other or future verdict defers, including an unreadable pane, ambiguous geometry, a blank unidentified row, and a bare shell prompt left after the agent exits. Each adapter contributes only capture and capability facts to the fleet-wide screen classifier inbin/fm-composer-lib.sh, which owns every shape and verdict. It preserves proven idle composers as empty but requires a genuine container around shell glyphs; seedocs/herdr-backend.md"Composer and injection safety" for the operator contract.pane_input_pendingis the tested fail-closed predicate for callers that need to know whether the composer is unsafe: it treats every result except exactemptyas pending.
A busy primary pane, or any composer verdict other than empty, defers the injection; the buffered escalation survives in state/.subsuper-escalations and is retried on the next housekeeping tick.
In afk mode the composer guard is belt-and-suspenders (no human is typing), but it protects against the race window between the captain returning and their message landing, a dead shell, and the daemon's own previous injection sitting unsent.
Max-defer escape (the daemon must never silently wedge).
If anything stays buffered past FM_MAX_DEFER_SECS (default 300), the daemon
attempts one normal flush, which still requires an idle pane and an affirmatively empty composer.
The alarm is defense in depth rather than a substitute for keeping every genuinely idle supported composer injectable.
If that submit cannot be confirmed, it raises a loud, rate-limited wedge alarm:
an ERROR in the daemon log, a durable
state/.subsuper-inject-wedged marker (surface it on the "while you were out"
catch-up if present), a tmux status-line flash when applicable, and a configurable backend-independent active alert.
docs/wedge-alarm.md owns the alert channel setup, and docs/verification/supervision.md "Wedge-alarm channels" owns active evidence.
So a guard false-positive becomes a visible stall, never an unbounded silent no-op.
Submit model
The digest is typed once (send-keys -l on tmux, pane send-text on
herdr - both literal, non-submitting sends), then submitted with Enter and
verified through the selected backend's submit primitive.
Enter is retried (Enter only, never a retype) until the backend confirms the
submit landed.
For tmux that confirmation is normally a proven cleared composer from the shared classifier; an idle baseline transitioning to busy across this submit's own Enter also confirms that the turn started when a working harness hides its composer.
Without that baseline, busy state never converts an unknown composer into confirmation.
For herdr, idle-baseline submits first seek native agent-state showing a real turn started, then use the shared classifier when native state remains idle: a cleared composer confirms delivery, while pending text retries Enter and reaches the shared busy-queue verdict only after the retry budget.
A bordered-empty or ghost-only composer is recognized as empty where that backend uses composer confirmation, rather than mistaken for a swallowed Enter.
fm-send.sh uses the same primitive only on its typed plane and exits non-zero when that plane's Enter is positively swallowed; ordinary local text steers use the durable inbox and do not treat doorbell submission as delivery proof.
Busy-queued Enter exception (opencode 1.18.4). OpenCode keeps queued text visible while it is mid-turn, so tmux and herdr delegate the final delivery decision to fm_composer_queued_enter_verdict in bin/fm-composer-lib.sh rather than treating visible text alone as a swallowed Enter.
The daemon still clears its buffer only on the backend's empty success verdict; docs/tmux-backend.md and docs/herdr-backend.md own the backend-specific confirmation signals.
Classification policy
The daemon wraps fm-watch.sh, runs the watcher as a child, presents every durable wake after each actionable watcher close, classifies each presented record in bash, and acknowledges the presented generation only after routing completes.
It self-handles the routine majority without consuming a firstmate turn.
Captain-relevant events, plus a bounded recheck of a declared wait that remains idle, escalate to firstmate's context as one pre-read, single-line, batched digest.
The classification predicates (the captain-relevant verb set, declared-wait vocabulary, signal/stale tests, and fleet-scan) live in the shared bin/fm-classify-lib.sh, the same library the always-on watcher uses for its own triage when afk is off, so the two modes apply one identical policy.
While state/.afk exists the daemon owns the watcher, so the watcher reverts to one-shot and lets the daemon do the triage - the two never run their triage at the same time.
Classify each wake this way:
signalwith a terminal captain verb (done:,needs-decision:,blocked:, orfailed:) -> escalate. A nonterminal progress verb remains nonterminal even when its prose contains a legacy free-text token such asPR ready,checks green,ready in branch, ormerged; only a bare legacy line with such a token escalates. Other signals with no captain-relevant status -> self-handle.signalorstalefor a declared wait, either apaused:external wait or a verifiedcaptain-heldtransfer -> self-handle and track the pause rather than a wedge. If it remains declared and idle pastFM_PAUSE_RESURFACE_SECS(default 3600s), housekeeping sends one recheck and resets the pause window. That recheck names which human the wait is on: the external dependency forpaused:, and the captain themself for acaptain-heldtransfer, who can answer the held decision or release the hold.check-> always escalate. Check scripts print only when firstmate should wake.stalewith a terminal status or bare legacy captain-relevant line -> escalate. Nonterminal progress remains transient even when its prose contains a legacy free-text token or its seen-status marker already matches, so record a marker and self-handle. If the pane is still idle pastFM_STALE_ESCALATE_SECS(default 240s), housekeeping escalates it as a possible wedge. This bounds wedge-detection latency to the threshold plus a tick: a delay, never a loss. Healthy crewmates are autonomous and do not wait on firstmate mid-task.heartbeat-> self-handle. The daemon runs its own cheap bash fleet scan everyFM_HEARTBEAT_SCAN_SECS(default 300s) as the catch-all for a captain-relevant status line the per-wake classifier might miss.- Unknown reason, or any uncertainty -> escalate fail-safe.
Escalations are buffered up to FM_ESCALATE_BATCH_SECS (default 90s; 0 =
immediate) and flushed as one single-line digest prefixed with the current
operational prefix, carrying pre-read status summaries and a recommended action.
The single-line format makes the submission unambiguous across harnesses, and
the operational prefix lets firstmate distinguish it from a real captain message.
Injection hardening
- Single-line digest - embedded newlines are collapsed to a literal separator before injection, so submission is unambiguous regardless of harness.
- Busy and composer guards on the supervisor pane - before injecting, the daemon runs the detected-primary-harness rendered busy guard and reads
fm_backend_composer_statedirectly. Onlyemptypermits injection;pendingprotects half-typed or swallowed input, andunknownprotects unreadable panes and bare dead-shell prompts. Every other result preserves the buffer for retry, so the daemon never merges its digest into the captain's half-typed line or types it into a shell. - The active backend passes its capture plus declarative styled, cursor, identity, and row capabilities to the shared screen classifier; all structural recognition and verdict logic remains in
bin/fm-composer-lib.sh. Styled captures let that owner remove dim/faint and dark-TRUECOLOR ghost or placeholder text while shape detection uses the ANSI-stripped screen, so a dark border is not lost with ghost content. A ghost-only or idle bordered composer such as claude's│ > ... │therefore reads empty without allowing an unbordered shell prompt to do the same.FM_COMPOSER_IDLE_REoverrides the shared idle-placeholder regex, but a match alone never bypasses the classifier's shape-specific position and ANSI de-emphasis safety gates.FM_BUSY_REGEXoverrides the rendered delivery guards plus Grok's isolated task-state fallback. A blank or otherwise unidentified input row carries no positive container proof and defers injection, so a modal dialog or a mid-redraw pane is never an injection target. - Max-defer escape - the daemon must never silently wedge. If anything stays
buffered past
FM_MAX_DEFER_SECS(default 300s), the daemon attempts one normal flush, which still requires an idle pane and an affirmatively empty composer. If that cannot confirm a submit, it raises a loud, rate-limited wedge alarm: ERROR log, durablestate/.subsuper-inject-wedgedmarker, a tmux status-line flash when applicable, and a backend-independent active alert. A composer false-positive surfaces as a visible stall, never an unbounded silent no-op. - Verified type-once submit model - the digest is typed once (
send-keys -lon tmux,pane send-texton herdr), then submitted with Enter and verified. Enter is retried, Enter only and never a retype, until the backend submit primitive reportsemptyas its caller-facing success verdict. For tmux that verdict normally means the shared classifier proved the composer cleared; a baseline-gated idle-to-busy transition may instead prove this Enter started the turn. For herdr's idle-baseline path it means native agent-state observed a turn start, the shared classifier proved the composer cleared, or the shared queued-Enter verdict proved delivery while busy. This lets ghost-only or bordered-empty composers count as empty where a composer read is the active confirmation signal. - Marker strip -
strip_injection_markerremoves the current operational prefix or legacy bare marker before classification or relay, so the digest text firstmate sees is clean. - Portable singleton lock - the daemon uses the repo's portable lock helper
(
fm-wake-lib.sh) instead offlock, which is absent on macOS. - Dedupe across signal/stale/scan -
classify_signaland terminalclassify_stalepaths check the seen-status marker before escalating, so a captain-relevant status escalated by one path is not re-escalated by another in the same digest. The marker does not clear or suppress possible-wedge aging for a nonterminal progress line. - Auto-discovered supervisor pane - the daemon resolves its own BACKEND
(tmux vs herdr) and TARGET independently, mirroring
bin/fm-backend.sh's own runtime auto-detection. Backend:FM_SUPERVISOR_BACKENDoverride, then$TMUX_PANEset (tmux), then$HERDR_ENV=1with$HERDR_PANE_IDpresent (herdr), then a tmux fallback. Target:FM_SUPERVISOR_TARGEToverride (a tmux target or a herdr"<session>:<pane-id>"target), then$TMUX_PANE, then"${HERDR_SESSION:-default}:${HERDR_PANE_ID}"under herdr, then afirstmate:0fallback with a warning. Both resolution sources are logged at startup so a wrong-but-resolving fallback is detectable. Other runtime backends, including zellij, orca, and cmux, are not yet supported as supervisor backends; the daemon refuses loudly at startup instead of misapplying tmux primitives to a pane that isn't one (docs/herdr-backend.md "Away-mode supervisor support").
Stale-artifact lifecycle
Treat state/.subsuper-escalations, its .since sidecar, and state/.subsuper-inject-wedged as session-scoped delivery artifacts, not as the durable work record.
Always enter through bin/fm-afk-launch.sh, which clears prior-session artifacts only for a fresh entry and preserves the current session's buffer on refresh.
Always exit through bin/fm-afk-launch.sh stop, which keeps state/.afk present through the daemon's shutdown flush and clears it last.
docs/herdr-backend.md "Away-mode supervisor support" owns the current mechanism, and docs/verification/runtime-backends.md "Away-mode transport" owns active evidence.
Reliability properties
These properties must hold:
- Nothing is lost after queue publication. The daemon leaves every presented wake durable until routing completes and post-handling acknowledgement succeeds, so interruption replays the same work to the daemon or its successor.
- Wedge detection is bounded-latency, not lossy.
- Declared external waits are rechecked on a separate, bounded cadence rather than being mislabeled as wedges.
- The catch-all scan backs up the keyword classifier.
- The daemon preserves a single-instance portable lock, crash-loop backoff, a pane-gone guard, and a signal-trapped shutdown that flushes buffered escalations before exit.
FM_INJECT_SKIP (default heartbeat) force-self-handles matching kinds,
overriding classification.
Use it sparingly.
Frequently asked questions
What to verify before installation and use
What does the afk source document cover?
Away-mode supervision. When invoked, /afk makes the daemon's token-saving tradeoff consented and explicit: the captain is stepping away, so the sub-supervisor may triage routine wakes in bash instead of waking firstmate's LLM for each one. Escalations still reach the captain, bu…
How do I install afk?
The source record exposes this install command: npx skills add https://github.com/kunchenguid/firstmate --skill ".agents/skills/afk". Inspect the command and pinned source before running it.
Alternatives