Manage the sequence-enforcer that gates skill execution. Check mode, change enforcement level, or kill-switch.
Instal·la
npx skillscat add daniel-ospina/agent-infra/enforcement Instal·la des del registre de SkillsCat.
⛔ This skill MUST be read in full — not skimmed. Formal review gates depend on its workflow.
Skipping steps silently bypasses quality checks. Missing gates = undetected breakages.
⛔ HUMAN APPROVAL REQUIRED for ALL operations except checking current mode. Do not change enforcement level, kill enforcement, or re-enable enforcement without explicit human authorization. The kill switch exists as a safety valve for humans — agents must NEVER engage it autonomously. If enforcement is blocking legitimate work, escalate to the human.
Enforcement
Manages the sequence-enforcer that validates agent tool calls against YAML skill step declarations.
Print / Background Sessions (pi -p)
Non-interactive pi -p sessions (epic-executor sub-agents, background workers) default to warn — never blocking. Post-#357 this covers ALL print sessions, env- OR argv-detected (bare-shell pi -p with no PI_MODE included) — see "#201 carve-out reversal" below. Explicit overrides always win: AGENT_SEQUENCE_MODE=gate|strict, PI_ENFORCER_MODE, or the mode file force full enforcement in print sessions. Interactive sessions keep the gate default.
⚠️ Verifier-gate trade-off of the reversal: the bare-shell pi -p class loses the verifier-step tool allow-list (task/subagent/read/loop_enforcer) — those calls become warn-only. The merge gate (review-enforcer) stays active; restore per-session with AGENT_SEQUENCE_MODE=gate. Monitoring: if warn_blocked volume on verifier gates from bare-shell sessions rises (enforcement.jsonl mode:warn + verifier steps), re-evaluate the default — the trigger is a spike, not a steady background.
Audit visibility (~/.pi/agent/audit/enforcement.jsonl) for background workers:
warn_blocked— a call that WOULD have been blocked under gate mode was allowed in warn mode; includesallowed+hintcontext.timeout_park— a GATED step was parked at step N in print mode (state preserved), not popped. Written ONCE per activation-step (silent-window repeats are coalesced; if the lane keeps working, this line is all you will see), carriesstep_name,gate,parks,cap, and the derivedcause/remedy.timeout_idle_human— a print lane idled at ahuman_review/human_approvalstep. Reported once per episode, never bounded: a person owns the stall. (Distinct fromtimeout_idle_ungated, which means NO gate is applied — a human gate blocks every tool except read/web/loop_enforcer.)timeout_idle_ungated— a print lane idled with NO gate holding it: agate: autostep, or acheckpointstep in a warn session (warn applies no checkpoint gate). Written ONCE per idle episode (coalesced; an episode ends at the next tool call). This is the liveness signal for a parked worker — no gate is applied, so it can never be a gate stall and never produces a verdict.sequence_gate_unsatisfiable— TERMINAL (#1770): a gated step stayed SILENT across the bound, so the lane emittedSEQUENCE-GATE-UNSATISFIABLEand exited 3. Carriesstep_name,gate,parks,cap, the MEASUREDidle_min(since the last tool call) andstep_min(since the step was entered), plus the derivedcause/remedy. One row per stall, never per window. A lane that ends here did not fail silently — grep the marker in its log.blockedentries now includeallowed+hintso orchestrators can see why a worker is stuck and what the exit is.
Park policy (#1770). Parks are BOUNDED and NAMED, and a step with no gate is never park-bounded:
gate: auto→ never parks; one coalescedtimeout_idle_ungatedline per idle episode, no verdict, no exit (nothing is blocked, so nothing can be unsatisfiable).gate: verifier/ empty gate → park ONCE per activation-step with the cause + remedy derived from that step's own gate, and exit 3 withSEQUENCE-GATE-UNSATISFIABLEafter 3 consecutive silent windows (30 min with no tool call at all). The owner is found by STACK-WALK, so a gate below a sub-skill frame (areadis an escape tool at a verifier step and pushes a frame) is still bounded.- ⛔ This bounds SILENCE, not busyness. A window only elapses after 10 min with no tool event (every call/result re-arms the timer), so a lane that keeps calling tools — including one polling a background reviewer, or retrying a refused call — is never parked by this mechanism. That is deliberate: such a lane is visibly working to the orchestrator, the fleet board and the lane reaper, and an enforcer-side "busy but stuck" bound cannot distinguish it from a lane legitimately waiting on a 40-minute dispatch.
gate: checkpointin a print lane → gate/strict sessions are bounded the same way (the gate blocks every git op, so it must never sit forever at rc 0), and an in-flight call suppresses it like any other; warn sessions — the default forpi -p— apply no checkpoint gate at all (the first call auto-advances), so they get the one coalesced idle line and no verdict.- The stall OWNER is resolved by one stack-walk, then classified: a
human_*frame anywhere on top wins (terminal — a person owns the stall, so a checkpoint or verifier below it is never bounded). - A tool call IN FLIGHT suppresses the idle clock while it is outstanding. The set is FAIL-SAFE BY INVERSION: only provably instant tools (
read,write,edit,grep,find,ls,todo_write,todo_read,loop_enforcer,task_status,task_collect,web_search,web_fetch) are excluded — everything else, including everymcp__*and any tool not named here, is assumed able to run for minutes (the opposite list silently missed this repo's owndesign_reviewer). It is tracked pertoolCallId(pi runs sibling calls in parallel, so one result must never clear another call's liveness) and cleared bytool_execution_end— which pi emits on EVERY path, including a call that never ran because ANOTHER extension blocked it (a blocked call produces notool_result, so a leaf listening only to tool_call/tool_result would hold a phantom entry and disarm the bound for the session).tool_call/tool_resultand this extension's own block returns clear the same slot as a belt-and-braces. The only ceiling is the fleet's own task hard cap (6 h): an entry older than that cannot be a running tool, so a leak can never be permanent. - The count and the one-shot announcements are keyed per ACTIVATION of a step (
path#stepIndex#activation), so a skill read again after being popped starts clean instead of being capped on its first silent window. The verdict reports measured times (step_min,idle_min), neverparks × interval. - A
human_review/human_approvalstep is never bounded: a person is the owner, so the lane gets the one coalesced idle line and no verdict. - A session escape or the kill switch bypasses this whole path: a bypassed session is never parked and never ended.
- Interactive (non-print) sessions are handled FIRST and keep main's behaviour: pop any stale non-checkpoint frame; park a checkpoint frame one-shot only when the session is not warn and the step has stalled on the wall clock; never bound, never exit (a human is present and can run the checker or the force-pass). Nothing above applies to them.
Checkpoint Gates (#357 post-fix reality)
Checkpoint steps (gate: checkpoint, e.g. parallel_check_* in writing-plans / commit-workflow / executing-plans) are token-gated (Enforcement A #5039): a fresh phase-correct CLEAR token from parallel_work_check is required to pass. Canonical phases (C1–C5): start (executing-plans), scope (issue-scoping), plan (writing-plans), implement (commit-workflow + executing-plans), merge. There is NO verify checkpoint — the verifier gate is a separate mechanism. issue-scoping's start/scope checkpoints are prose instructions (self-enforced; no frontmatter gate). Post-fix (#357) the contract is satisfiable and escapable:
- Warn mode (default for
pi -p): the FIRST call at a checkpoint auto-advances with acheckpoint_skipped_warnaudit entry (token_state: none/stale/missing-or-wrong-phase/corrupt/other/ok) — never blocks, never deadlocks. Advance is mode-independent: gate/strict advance on a valid token at tool_call; a producer-timing escape call advances at tool_result when the token transitions !ok→ok. - No-board skip (gate/strict): a no-board session (ALL board/card/agent/paths signal families empty — unset OR empty/whitespace; ANY non-empty signal → fail-closed NOT-no-board) gets a distinguishable
CLEAR no-board-skip: <advisory>verdict from the vendored checker and a token carryingmode:"no-board-skip". The enforcer emitscheckpoint_no_board_skip(distinct fromcheckpoint_force_pass) at token-driven advances; board sessions are unchanged (plain CLEAR, no skip audit). - The in-session escape (gate/strict):
read+loop_enforcerare ALWAYS allowed at a pending checkpoint;bashONLY for the sole-command checker invocation — the CANONICAL reference is$AGENT_INFRA_PATH/scripts/parallel_work_check.sh <phase>(the vendored checker; NOT any hardcoded swarm path) — resolved absolute path, optionalenvwith the allowlisted keysGH_TOKEN|CHECKOUT_GUARD_ENFORCE|AGENT_INFRA_PATH, optional baresudo, optionalpython3/uv run, mandatory.sh|.pysuffix + phase arg; tab/NBSP/zero-width/metachars rejected.PARALLEL_CHECK_BINis an INTERNAL bin-location override — never a gate-timeenvprefix (not in the escape allowlist, soenv PARALLEL_CHECK_BIN=…fails closed at the gate). Everything else fails closed. A checker re-run at an ALREADY-ok checkpoint is blocked (checkpoint_token_fresh— re-running can REMOVE the token on UNKNOWN); non-checker tools at an already-ok checkpoint are allowed (the step advanced on the fresh token). - Operator force-pass (human-only): write
/tmp/parallel-check-force.json—{"verdict":"CLEAR","phase":"<step token_phase>","operator":"<you>","origin":"shell","repo":"<git remote>","ts":"<ISO>"}. CONSUMED on ANY checkpoint advance (unlinked — including a real-token or warn-mode advance, so a leftover file can never pass a same-phase adjacent checkpoint), 60-min TTL (future timestamps rejected), phase + repo bound. New-session start unlinks lingering force files (machine-shared path — write it AFTER the checkpoint is reached; single-operator assumption); a mid-session/reloaddoes NOT unlink. It is AUDITED ascheckpoint_force_passONLY when it actually DROVE the pass — a real-token or warn-mode consume emits nothing (the event means "an operator CLEAR passed this step"). This is the operator path for gate-mode interactive worktree sessions where the checker CANNOT CLEAR by design (C1 DEFER on any non-main worktree branch).
⚠️ Trust boundary:/tmpis world-writable and the file carries no operator credential — this is workflow-enforcement, NOT a security boundary; the audit trail + 60-min TTL are the detections. The real token file has the same exposure — but IS repo-bound (gate/strict only; warn is binding-free). The token'srepois the URL form (git remote get-urlof the resolved ops target, userinfo-stripped); binding comparestoken.repoagainstbindingRepo() = resolveRepo(PARALLEL_CHECK_REPO) || currentRepo(). No-remote / non-git cwd → both sides "unknown" → binding passes. Cross-repo mismatch → named-reason BLOCK → re-run the checker from this checkout.PARALLEL_CHECK_REPOis honored as a PATH (resolved via the samegit remote get-url); set-but-not-a-git-dir → both "unknown".--repo Xwithout the env binds X's remote — and--repo Xfrom a different cwd with no env means the enforcer never learns the flag → named-reason BLOCK, remediation: "set PARALLEL_CHECK_REPO to the checkout path". SAME-REPO-DIFFERENT-URL-FORMAT also BLOCKs (https vs scp of the same origin) — re-run from the same checkout form. This is NOT "a token written in repo A fails repo B" — same repo does not guarantee a pass; phase, TTL, and URL format must all line up.
Same-repo concurrent sessions: the token file is last-writer-wins and machine-global; it is NEVER unlinked on a token-driven advance (only the force file is one-shot) — the phase + TTL + repo-binding bound the concurrency class to fail-safe outcomes. A shared-token advance may be audit-attributed to EITHER concurrent session — accepted, documented. Mode-attribution sub-case: a no-board session advancing on a concurrent board session's CLEAR token emits a BOARD-mode audit with NO skip event (the event stream carries no tenant info); the post-merge lag tripwire queries for same-repo/same-phase multi-advance anomalies alongside the existing no-board-lacks-event query (per-session nonce deferred). - Recovery (park-only, never pop): ≥3 consecutive blocked calls at a checkpoint OR a >5-min wall-clock stall → immediate PARK (state preserved) with one
checkpoint_block_recoveryevent per checkpoint. Blocked-call audit entries are coalesced (≤20/60s per step) to bound log volume. Conditional signature: per-call blocked + 1 recovery event + 1 park/10min. In an INTERACTIVE session a relentless retry loop re-arms the 10-min timer on every blocked call and produces no further recovery signal (the human present is the escalation). In a PRINT lane the #1770 bound applies instead (gate/strict sessions only — warn sessions are excepted, see the park policy above): a checkpoint that stays SILENT for 3 consecutive windows emitsSEQUENCE-GATE-UNSATISFIABLEand the lane exits 3. - Fail-closed diagnosis: a checkpoint step missing
token_phasein its frontmatter is unpassable-by-design (blocks +⚠️ F2warning at activation listing the step) — fix the skill declaration.
Audit event set (checkpoint): checkpoint_skipped_warn, checkpoint_force_pass, checkpoint_no_board_skip, checkpoint_block_recovery, checkpoint_token_fresh. Mutual exclusivity + mode domain: skip audits carry token_mode/mode (no-board-skip), force-pass carries viaForce — a skip-token advance emits NO force-pass audit and a force-driven advance emits NO skip audit. Audit-gap note (deploy window): tokens written PRE-deploy carry no mode → after the new enforcer deploys they advance as board-mode CLEAR with no skip audit — re-run the checker once. The audit is HUMAN-READ-ONLY — enforcement.jsonl has no automated consumer; a reader is a swarm-side follow-up.
#201 carve-out reversal (#357)
The pi -p → warn default is now argv-aware: resolveMode uses isPrintMode(env, argv) (bare-shell pi -p with no PI_MODE env included). The old carve-out (bare pi -p keeps gate because it can dispatch task) is REVERSED — the checkpoint gate is unsatisfiable/inescapable in exactly that class, so warn is the safe default for ALL print sessions. Explicit overrides (AGENT_SEQUENCE_MODE, ELDATO_SEQUENCE_MODE, PI_ENFORCER_MODE, mode file) still force gate/strict. --mode json/rpc spawns remain a documented gate-resolving residual.
Check Current Mode (agent may use)
cat /tmp/agent-sequence-modeModes: warn (log only), gate (block destructive without skill read), strict (block all unexpected).
Change Mode (⛔ HUMAN APPROVAL REQUIRED)
echo <mode> > /tmp/agent-sequence-modeEmergency Kill Switch (⛔ HUMAN APPROVAL REQUIRED — agents must NEVER use autonomously)
touch /tmp/agent-state-machine.kill # disables ALL enforcement
rm /tmp/agent-state-machine.kill # re-enablesThis is a safety valve for humans. If an agent is blocked by a false positive, escalate to the human — do NOT touch the kill switch.
View Audit Log (agent may use)
cat ~/.pi/agent/audit/enforcement.jsonlContinue following the workflow as mandated by this skill. Do not skip steps.