Multi-plugin marketplace of forced mechanical gates (the Turnstile): agents cannot end a turn with mechanically failing code. Verdicts are deterministic CLI results — never LLM-judged. - core/gate: stdlib-only dispatcher (Python 3.11+) with SARIF-lite finding protocol (content-based fingerprints, line-drift proof), policy loader with includes/tiers, tool-chain resolver with fallback order + explicit DEGRADED verdicts, verdict cache, ratchet baseline, expiring waivers, guard-the-guard, loop-guard, turn ledger, doctor, report - 14 tool adapters: py_ast, ruff_format/check, secret_scan, node_check, tsc, oxlint, biome_*, prettier, ty, pyright, mypy, gitleaks (pure parsers, unit- testable without the tools installed) - packs: gates-essential (engine + all enforcement hooks + commands), gates-polyglot/security/strict (declarative content packs, registration-only hooks, dependencies on the engine — multi-fire structurally prevented) - Quality Ladder wiring: L0 PostToolUse (format/syntax/secrets, unchanged-hash fast path), L1 PostToolBatch (lint+types), L2 Stop turnstile with loop-guard, L3 gate check for CI parity - 56 tests: unit + golden-stdin hook contracts + packaging invariants + registration idempotence; manifest validator; seed fixtures - Spike results: H4 ratchet PASSED (0 false blocks / 10-10 caught); H2 partial falsification recorded (unchanged-hash <15ms unreachable at ~51ms p95 with per-edit CPython spawn — escalation path documented) - Design history: .research/brainstorm_20260926T1835.md (dialectic + tool matrix) Co-Authored-By: Claude Code <[email protected]>
agentic-gates
Forced mechanical quality gates for Claude Code agents — the Turnstile.
Agents do not end a turn with mechanically failing code. Every verdict is a deterministic CLI result (exit codes + JSON), never an LLM judgment.
L0 PostToolUse ≤150ms format-in-place · syntax floor · secret scan → auto-fix / feedback
L1 PostToolBatch ≤2s lint · typecheck · cycles on the batch → block error-severity
L2 Stop ≤30s scoped tests · dep audit · diff security → decision:"block" (TURNSTILE)
L3 /gate · CI minutes full suite · SBOM/license → same policy as CI
Install
claude plugin marketplace add davila7/agentic-hooks # or your fork's path/URL
claude plugin install gates-essential@agentic-gates
# optional content packs (declarative tool-chain profiles):
claude plugin install gates-polyglot@agentic-gates
claude plugin install gates-security@agentic-gates
claude plugin install gates-strict@agentic-gates
Then in each project: /gate-init (creates .gate/ policy), /gate-doctor
(checks tool availability).
Packs
| Pack | Role | Enforcement hooks |
|---|---|---|
gates-essential |
Engine: dispatcher + all hooks + guard + ratchet + commands | yes (only here) |
gates-polyglot |
Python + TS/JS tool chains (ruff, ty, biome, oxlint, tsc) | registration only |
gates-security |
secrets, gitleaks, osv-scanner, semgrep/ast-grep profiles | registration only |
gates-strict |
scoped tests, dep audits, cycles, expired-waiver enforcement | registration only |
Enforcement hooks live in the engine pack alone — plugin hooks run in parallel
across packs, so two packs carrying a Stop handler would double-fire the turnstile.
Design laws
- Mechanical only. Deterministic tool exit codes. LLM-as-judge is banned as a verdict.
- Missing tool ≠ pass. A tool that is not installed produces an explicit DEGRADED verdict.
- Guard-the-guard. The agent cannot edit policy, waivers, baseline, hooks or
settings —
PreToolUsedenies it,ConfigChangeblocks it, and CI re-checks it. - Ratchet.
.gate/baseline.jsonregisters existing debt once; only NEW findings block. Fingerprints are content-based, so line drift never looks like new debt. - Waivers expire. Every suppression has an owner and an expiry date; expired waivers re-open their findings.
- Style is silent, semantics are strict. Formatting is auto-fixed with zero feedback; only error-severity semantic findings block.
- Feedback is compact. Findings are capped (~20 / 8k chars) — the platform's
10k
additionalContextcap spills to a file with only a 2k preview. - The loop guard releases. After
loopguard.max_blocksidentical blocks the turn is released with asystemMessageto the human — never wedged against the platform's 8-continuation cap.
Tool chains (v1: depth-first Python + TS/JS)
| Concern | Python | TS/JS |
|---|---|---|
| format | ruff format |
biome format → prettier |
| syntax | in-process ast.parse |
node --check (JS) |
| lint | ruff check |
oxlint → biome |
| types | ty → pyright → mypy |
tsc --noEmit |
| secrets | in-process pattern scan → gitleaks |
same |
Chains are declared in .gate/policy.toml with explicit fallback order and budgets.
CLI
python3 core/gate.py init # .gate/ skeleton
python3 core/gate.py doctor # tool availability / degraded chains / lock skew
python3 core/gate.py check # CI parity — exit 2 on blocking findings
python3 core/gate.py baseline # register current debt (ratchet)
python3 core/gate.py waive --rule ruff/E501 --path 'legacy/**' --owner [email protected] --expires 2026-12-31
python3 core/gate.py report # SARIF-lite dump
Repo layout
core/gate/ dispatcher source of truth (stdlib-only, Python 3.11+)
packs/essential/ engine pack (vendored core/ + hooks + commands)
packs/{polyglot,security,strict}/ declarative content packs
templates/ CI / pre-commit / gitignore parity templates
tools/ vendor_core, validate_manifests, build_packs
tests/ unit + hook-contract integration + fixtures + spikes
Development
PYTHONPATH=core python3 -m pytest tests/ -q
python3 tools/validate_manifests.py
python3 tools/vendor_core.py # before release
python3 tests/spikes/h2_latency/bench_l0.py
python3 tests/spikes/h4_ratchet/run.py
Spike results live in tests/spikes/*/RESULTS.md (honest verdicts, including
partial falsifications — see H2's unchanged-hash budget).
Status
v0.1.0 — engine, packs, guard, ratchet, turnstile, contracts all under test. H1/H3 spike runs (live agent sessions) are packaged as taskpacks ready to execute.