How the Away-Mode (/afk) Sub‑Supervisor Functions in Firstmate

The away‑mode sub‑supervisor is a presence‑gated bash daemon that temporarily replaces the standard fm-watch.sh watcher to buffer captain‑relevant escalations while the user is away, flushing periodic digests and shutting down gracefully upon detecting a non‑injection message.

Firstmate’s /afk command triggers a sophisticated sub‑supervisor architecture designed to minimize token consumption while preventing information loss. Implemented entirely in portable bash within the kunchenguid/firstmate repository, this system combines three tightly‑coupled components—bin/fm-afk-start.sh, bin/fm-supervise-daemon.sh, and bin/fm-operational-input.sh—to create a durable, state‑driven presence gate. The implementation relies on atomic flag files and lock‑based singleton guarantees to ensure only one daemon instance operates at any given time.

Entering Away‑Mode via fm-afk-start.sh

When the captain invokes /afk, the entry point script bin/fm-afk-start.sh performs atomic state initialization. It writes the durable flag file state/.afk using the helper fm_afk_flag_write, clears any stale escalation artifacts via fm_afk_clear_stale_artifacts, and handles the daemon launch sequence.

The script distinguishes between foreground‑capable environments and background‑only backends. In standard configurations, it executes fm-supervise-daemon.sh directly via exec, replacing the shell process. For backends lacking native foreground tracking, it delegates to a detached terminal wrapper.


# bin/fm-afk-start.sh – atomic flag write and daemon exec

fm_afk_flag_write "$FM_AFK_STATE" || { 
  echo "afk: failed to write away-mode flag" >&2; 
  return 1; 
}
...
exec "$FM_AFK_DAEMON"

Upon launch, the daemon acquires an exclusive lock at state/.supervise-daemon.lock, ensuring singleton enforcement across the Firstmate session.

Presence Gating in fm-supervise-daemon.sh

The daemon implements presence gating through the afk_active helper function. Every operational loop begins with this check, which simply tests for the existence of state/.afk:


# bin/fm-supervise-daemon.sh – presence validation

afk_active() { [ -e "$1/$AFK_FLAG_NAME" ]; }

If the captain removes the flag (implicitly, by sending a non‑injection message), the daemon detects the absence during its next iteration and initiates graceful shutdown, flushing any pending escalation buffers before exiting. This state‑driven approach eliminates the need for process signals or complex IPC mechanisms.

Wake Classification and Escalation Buffering

While active, the daemon reuses the shared classification library bin/fm-classify-lib.sh to evaluate every durable wake event. The classifier returns structured strings in the format <action>|<distilled>, distinguishing between routine self‑handle events and escalate events requiring captain attention.

Classification examples include:

  • Self‑handle: Heartbeat signals and routine status updates that require no intervention
  • Escalate: Terminal states like done, needs‑decision, or blocked

When an event requires escalation, the daemon appends it to state/.subsuper-escalations via escalate_add. Rather than injecting immediately, the implementation batches items and periodically flushes a single‑line digest via escalate_flush, significantly reducing supervisor pane noise.

The Housekeeping Loop

The daemon runs a perpetual housekeeping cycle configured by FM_HOUSEKEEPING_TICK, performing several maintenance operations:

Job Function Purpose
Batch Flush escalate_flush Injects digests when buffered items exceed FM_ESCALATE_BATCH_SECS
Max‑Defer Escape Retry logic Retries injection after FM_MAX_DEFER_SECS; raises wedge alarm on failure
Stale Re‑check stale_window_is_busy Verifies idle panes marked in state/.subsuper-stale-* files older than FM_STALE_ESCALATE_SECS
Pause Re‑surface Pause validation Re‑checks external pauses in state/.subsuper-paused-* files older than FM_PAUSE_RESURFACE_SECS
Heartbeat Scan scan_captain_relevant_statuses Greps all *.status files every FM_HEARTBEAT_SCAN_SECS for missed relevant lines

This comprehensive sweep ensures no captain‑relevant event remains undetected, even when the primary wake classifier misses edge cases during high‑frequency updates.

Exiting Away‑Mode

Return detection occurs in bin/fm-operational-input.sh through the should_exit_afk function. The logic defines away‑mode exit conditions as any message that:

  1. Does not start with the internal injection marker (FM_INJECT_MARK or the Unicode prefix U+2063 FIRSTMATE_OP)
  2. Is not the /afk command itself
should_exit_afk() {
  afk_active "$state" || return 1
  message_is_injection "$msg" && return 1
  case "$msg" in /afk*) return 1 ;; esac
  return 0
}

When these conditions are met, the primary Firstmate instance invokes afk_exit, which removes state/.afk. The daemon detects the missing flag during its next afk_active check and terminates, yielding control back to the standard fm-watch.sh watcher.

Wedge Alarm Handling

To prevent silent failure when the supervisor pane becomes unresponsive, the daemon implements wedge detection. If escalate_flush repeatedly fails to inject digests beyond FM_MAX_DEFER_SECS, the system writes a durable marker to state/.subsuper-inject-wedged and invokes inject_wedge_alarm:

inject_wedge_alarm() { 
  # ... marker creation ...

  wedge_alarm_notify "away-mode escalations WEDGED …" "$marker"; 
}

This fallback mechanism fires OS‑level or Herdr notifications, guaranteeing that critical escalations reach the captain even when the primary injection pathway is compromised.

Summary

  • Atomic Entry: fm-afk-start.sh writes state/.afk and execs the daemon with singleton locking via state/.supervise-daemon.lock.
  • Presence Gating: The daemon uses afk_active to monitor state/.afk, shutting down gracefully when the flag disappears.
  • Smart Buffering: Captain‑relevant wakes are classified via fm-classify-lib.sh and batched in state/.subsuper-escalations before digest injection.
  • Continuous Maintenance: The housekeeping loop performs stale‑window re‑checks, pause re‑surfaces, and heartbeat scans on configurable intervals.
  • Reliable Exit: Normal messages trigger should_exit_afk, causing flag removal and daemon termination.
  • Failure Safety: Wedge alarms via inject_wedge_alarm ensure no escalations are lost during supervisor pane outages.

Frequently Asked Questions

What triggers the away‑mode sub‑supervisor to start?

The /afk command triggers bin/fm-afk-start.sh, which atomically writes the state/.afk flag file and executes bin/fm-supervise-daemon.sh. The daemon acquires an exclusive lock at state/.supervise-daemon.lock to prevent duplicate instances.

How does the daemon know when to shut down?

The daemon polls for the existence of state/.afk using the afk_active function. When the captain sends any message lacking the internal injection prefix (FM_INJECT_MARK or U+2063 FIRSTMATE_OP), the primary Firstmate process removes the flag file. The daemon detects this absence during its next housekeeping tick and exits gracefully after flushing buffers.

What happens if the supervisor pane freezes while the daemon is running?

If the daemon cannot inject escalation digests for longer than FM_MAX_DEFER_SECS, it writes a marker to state/.subsuper-inject-wedged and calls inject_wedge_alarm. This fires alternative notifications (OS‑level or Herdr) to alert the captain that away‑mode escalations are wedged, ensuring no events are silently dropped.

How are events classified during away‑mode?

The daemon imports bin/fm-classify-lib.sh to evaluate wakes. Each event returns an action string like escalate|<distilled> or self-handle. Escalations are buffered in state/.subsuper-escalations and flushed as single‑line digests, while routine heartbeats are suppressed to conserve tokens.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →