Skip to Content
ConfigurationCompletion Gate

Completion Gate (stop_completion)

Configures the completion gate — the stop_completion op that runs on Stop and enforces Navigator’s finish protocol from observable evidence, not model self-reports. When a codebase-mutating turn ends with unfinished work and no explicit exit signal, the gate forces one continuation so the work actually finishes (commit, docs, ticket, marker) instead of trailing off.

New in v7.0.0. Forced continuation ships off (new blocking features seed off); you opt in explicitly.

Config block

In .agent/.nav-config.json:

{ "stop_completion": { "enabled": false, "continue_enabled": false, "max_continues": 2 } }

Keys

  • enabled (default false) — Evaluate completion indicators on Stop. Both this and continue_enabled must be true for the gate to act; a missing block is off.
  • continue_enabled (default false) — Allow the forced continuation. This is the blocking behavior and the gate’s primary off-switch.
  • max_continues (default 2) — Hard cap on forced continuations, tracked in runtime state and reset at the next clean turn end.

The 6 completion indicators

Indicators are derived from observable turn evidence — the git tree, test runs, docs/marker paths, ticket state, and the simplification pass — using the same exit_gate.evaluate_exit vocabulary Loop Mode uses:

IndicatorEvidence
code_committedgit status --porcelain shows no tracked change. Untracked ?? paths are skipped unless the turn itself touched them, so a repo that keeps scratch files untracked on purpose can still meet it (v8.3.2).
tests_passingA test command (make test, make mod-test, pytest, unittest) ran this turn and did not error.
code_simplifiedThe simplification pass ran (when enabled).
docs_updatedA *.md file was touched: an Edit/Write path, or a mutating Bash command that names one (sed -i … README.md, v8.3.2).
ticket_closedThe PM ticket was closed (when PM integration is configured).
marker_createdA completion/context marker was created.

Forced-continuation semantics

The gate forces a continuation only when both conditions hold:

  1. Completion indicators are unmet, and
  2. No explicit exit signal is present in the final assistant message.

When it fires, exactly one continuation is injected per turn (the reason appears as Stop hook feedback), and the total is capped by max_continues. If you genuinely want to stop mid-task, emit the exit signal — the gate never overrides an explicit exit.

Circuit breakers

The gate carries a layered breaker set so it cannot loop or fire on the wrong turns:

  • Single-shot fuse — at most one forced continuation per turn; re-armed only at the next clean turn end.
  • max_continues cap — held count is capped (default 2) and reset on clean turn end.
  • Mutating-turn requirement — never fires on conversational or read-only turns. Bash-only turns are classified by a read-only command allowlist and, above that, by working-tree digest evidence: an unchanged git status digest across consecutive Stops overrules the classification, so ops work never reads as “mutated the codebase”. The allowlist knows cd and resolves absolute-path heads such as /bin/ls to their basename (v8.2.6); unknown heads still count as mutating, so the gate may over-fire but never under-fire. A turn whose only action is a read-only subagent (Explore, Plan, claude-code-guide, navigator-research, task-planner) is not mutating either; unknown agent types still are (v8.2.8). Quoted spans are masked before the command is split, so a | or > inside a grep pattern or a --jq filter never makes a turn mutating (v8.3.1). A redirect target ends at the operator (2>/dev/null; is /dev/null), a subshell, group or function body is classified by what runs inside ((cd x && git status) reads, (rm x) writes), and gh run watch, claude plugin list|validate|test|update, python3 -m json.tool, awk without > and make with only test-shaped targets (test|check|typecheck|validate) are read-only, so a turn that only runs the suite is not a task action (v8.3.3).
  • Stop-hook re-entry guard — short-circuits when the harness reports an active stop hook.
  • Kill switches — enabled and continue_enabled must both be truthy; PILOT_EXECUTOR disables the gate unconditionally, regardless of config.

Enabling

{ "stop_completion": { "enabled": true, "continue_enabled": true, "max_continues": 2 } }

Start with the default cap of 2; raise it only if you routinely see legitimate multi-step finishes being cut short.

When it fires wrongly

Every block the gate makes is one line in .agent/.nav-rejects.jsonl (v8.2.0, Reject Log) with evidence.mutating_tools, the indicators met and unmet. A gate that fires on a read-only turn shows up as "mutating_tools":["Bash"] in one grep — the diagnosis that took a transcript read before.

When NOT to enable

Do not enable the gate under an autonomous executor (Pilot or any external loop supervisor). Two loop supervisors fighting over continuation produces livelock, not throughput. Navigator enforces this: when the PILOT_EXECUTOR environment variable is set, the gate is off unconditionally — but if you run a different executor, leave continue_enabled: false yourself. Also skip it in exploratory/research sessions where turns intentionally end without commits.