How Turn-End Guard and Sub-Agent Guard Backstops Work in Firstmate
The turn-end guard and sub-agent guard in Firstmate are safety backstops that prevent primary sessions from ending turns without supervision and from delegating work outside the fleet via untracked sub-agents.
Firstmate implements a robust supervision cycle through two critical safety mechanisms defined in the kunchenguid/firstmate repository. These guards ensure that no primary session can terminate a turn while a watcher remains unhealthy or tasks are in-flight without oversight, nor can they spawn hidden sub-agents that bypass fleet tracking. Understanding how these components inspect harness payloads, validate watcher health, and classify tool shapes is essential for maintaining operational integrity in multi-agent workflows.
What Is the Turn-End Guard?
The turn-end guard guarantees that no turn ends "blind"—that is, without proper supervision. Before a primary (or second-mate primary) can finish its turn, the guard verifies that the fleet's watcher is healthy and that no unsupervised work remains in flight. If these conditions are not met, the guard blocks the turn termination and forces a bounded follow-up turn, ensuring continuous oversight.
How the Turn-End Guard Works
The primary entry point for this backstop is bin/fm-turnend-guard.sh, which each harness invokes via its stop hook. The script executes a strict verification pipeline before allowing a turn to conclude.
Payload Verification and Scope Checking
The guard first validates that the payload originates from an expected harness—Claude, Codex, OpenCode, Pi, Grok, or Cursor. It then verifies the primary-scope marker by calling fm_primary_scope_matches from bin/fm-primary-scope-lib.sh to ensure the session is operating within a genuine primary checkout rather than a child worktree. This prevents the guard from misfiring on secondary or sandboxed contexts.
Watcher Health Validation
Once scope is verified, the guard calls fm_supervision_status, which delegates to fm_watcher_healthy in bin/fm-wake-lib.sh. If the watcher reports healthy, the guard exits silently with status 0, permitting the turn to end. If the watcher is unhealthy or tasks remain in-flight without supervision, the script prints a descriptive banner to stderr and exits with status 2, forcing the supervisor to create a bounded follow-up turn.
Harness-Specific Behaviors
Different harnesses integrate the guard with adapted workflows:
- Claude: The harness uses a co-operative auto-arm path that limits automatic continuations via a block-budget. If the budget exhausts, the guard provides a fail-open mechanism to prevent infinite loops.
- Cursor: The wrapper script
bin/fm-turnend-guard-cursor.shforwards the payload to the main guard. Exit status2is treated as a silent no-op, with the Cursor extension generating the follow-up turn directly.
What Is the Sub-Agent Guard?
The sub-agent guard prevents primary sessions from using delegation-shaped tools—such as Agent, Workflow, or Spawn—that would create work the fleet cannot track. By blocking these calls at the point of use, the guard ensures all delegated work flows through bin/fm-spawn.sh, which writes mandatory state/<id>.meta metadata required for supervision.
How the Sub-Agent Guard Works
The entry point is bin/fm-subagent-pretool-check.sh, registered as a PreToolUse hook in each harness. The script intercepts tool invocations before execution and applies shape-based classification to determine whether to allow or deny the call.
Tool Shape Classification
The guard receives the tool name from the harness and checks it against a list of delegation stems, including agent, subagent, task, workflow, and spawn. If the tool name matches these patterns, the script checks exclusion lists (OBSERVE_ONLY_TOOLS or PLAN_ONLY_TOOLS). Tools on these lists are allowed to pass; all others are treated as unauthorized delegation attempts.
Denial Logic and Escape Hatches
When the guard detects a prohibited delegation tool, it exits with status 2 and writes a JSON denial message to stderr (or stdout for Grok). This denial prevents the tool from spawning an undisclosed agent. For intentional exceptions, the environment variable FM_ALLOW_SUBAGENT=1 disables the guard entirely, allowing the primary to use delegation-shaped tools without restriction.
Key Implementation Files
The backstop layer relies on the following components in the repository:
docs/turnend-guard.md– Human-readable contract defining predicates, harness integrations, and fail-open trade-offs.bin/fm-turnend-guard.sh– Core script implementing the watcher health check, banner generation, and Claude auto-arm cooperation.bin/fm-turnend-guard-cursor.sh– Cursor-specific wrapper forwarding payloads with the--cursorflag.docs/subagent-guard.md– Specification of the sub-agent guard, exclusion lists, and escape hatch behavior.bin/fm-subagent-pretool-check.sh– Implements shape-based tool classification and enforcement logic.bin/fm-primary-scope-lib.sh– Shared library used by both guards to validate primary checkout scope.bin/fm-supervision-lib.sh– Providesfm_supervision_statusand related supervision predicates.bin/fm-wake-lib.sh– Contains low-level watcher health checks includingfm_watcher_healthy.
Summary
- The turn-end guard in
bin/fm-turnend-guard.shvalidates harness origin, primary scope viafm_primary_scope_matches, and watcher health viafm_watcher_healthybefore allowing a turn to conclude. - Exit status
2from the turn-end guard triggers a bounded follow-up turn, ensuring no blind turn endings occur. - The sub-agent guard in
bin/fm-subagent-pretool-check.shblocks delegation-shaped tools unless they appear on exclusion lists or theFM_ALLOW_SUBAGENT=1escape hatch is set. - Both guards ensure all work creation flows through
bin/fm-spawn.sh, maintaining durablestate/<id>.metarecords required for fleet supervision.
Frequently Asked Questions
What happens when the turn-end guard blocks a turn?
When the turn-end guard detects an unhealthy watcher or in-flight work without supervision, it prints a banner describing the condition to stderr and exits with status 2. According to the kunchenguid/firstmate source code, this exit code forces the supervisor to create a bounded follow-up turn, preventing the primary from ending its session blindly. For Claude, this integrates with an auto-arm budget system; for Cursor, the exit triggers a silent follow-up generated by the Cursor extension.
How does the sub-agent guard distinguish between allowed and prohibited tools?
The sub-agent guard classifies tools by shape, checking if the tool name contains stems like agent, workflow, or spawn as implemented in bin/fm-subagent-pretool-check.sh. If the name matches these stems, the script consults exclusion lists (OBSERVE_ONLY_TOOLS and PLAN_ONLY_TOOLS). Tools on these lists are permitted; all other delegation-shaped tools are blocked with exit status 2 and a JSON denial message.
Can these guards be disabled for testing or specific workflows?
The sub-agent guard supports an escape hatch via the environment variable FM_ALLOW_SUBAGENT=1, which disables shape-based blocking entirely. The turn-end guard does not provide a global disable switch, as it is fundamental to the supervision contract; however, the Claude harness implements a fail-open mechanism after exhausting a block-budget of automatic continuations, allowing graceful degradation rather than permanent blocking.
Why must sub-agents be spawned through bin/fm-spawn.sh instead of native delegation tools?
Firstmate requires all delegated work to be visible to the fleet's supervision cycle. Native delegation tools (like Agent or Workflow) create sub-processes without writing state/<id>.meta metadata, rendering them invisible to the watcher. The sub-agent guard forces primaries to use bin/fm-spawn.sh, which creates the durable metadata records that the turn-end guard later inspects to determine if work remains in-flight.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →