The Enforcement Doctrine
In plain words: almost everything Forge does is gentle guidance the AI agent can, in theory, talk its way around. This page is about the small handful of things Forge refuses to let slide no matter what the agent claims — dangerous commands, runaway loops, unbounded spend, and unreviewed delivery. Those five are checked mechanically, with hard evidence, every time.
Maistik Forge is a single Go binary (forge, config dir .forge/) that unifies a spec-driven methodology with an enforced runtime. Most of what Forge does is configuration: it renders injected instruction text that the agent's model then executes. This page explains the one place where that model changes — the small, named set of controls that enforce rather than advise.
Forge is a configurator, not a step-firer
By default, Forge does not fire or block workflow steps. It renders instructions and the agent's model carries them out. That is exactly right for the methodology: proposing changes, writing specs, producing designs, triaging with trigger-rules. These are judgment tasks, and judgment belongs to the model. Forge steers; it does not pull the trigger.
The advisory default is a feature
An advisory system is the correct shape for anything that needs interpretation. A spec body, a persona, adaptive review depth, phase instructions — none of these can be reduced to a deterministic check, and pretending otherwise would only make Forge brittle. The default stays advisory on purpose.
But some failure modes cannot be left to narration
A harness exists to stop the specific ways an autonomous coding loop goes wrong:
- a runaway retry loop that never stops failing,
- unbounded spend,
- an irreversible or destructive command,
- unreviewed delivery.
An advisory version of these controls stops nothing. If the only thing standing between you and a runaway loop is a paragraph asking the agent to please stop, the agent can narrate compliance and keep going. For this small set, Forge itself checks deterministic evidence and returns a non-zero exit — regardless of what the agent says it did.
The enforced set
These five controls enforce. Each checks evidence the tool can verify on its own and refuses to bless a run that has gone off the rails.
| Enforced control | What it bounds | How it enforces |
|---|---|---|
| Destructive-action / secrets gating | Dangerous actions | A real PreToolUse-class hook that blocks the action before it runs |
| Review delivery gates | Delivery | forge review validate --gate <gate> returns non-zero until the content-bound receipt validates |
| Verify circuit-breaker | Iterations | forge verify --change <c> returns non-zero after N consecutive failures |
| Spend budget ceiling | Spend | forge budget returns non-zero when a configured ceiling is crossed |
| Drift ceiling | Spec-vs-code divergence | forge drift scan --enforce returns non-zero on unwaived, high-confidence (Certain) drift |
Everything else stays advisory: trigger-rules triage, adaptive depth, every SDD phase body, and persona.
The companions that do not enforce
Five controls enforce. Several other parts of the harness are built around them and are routinely mistaken for gates — they are not. None of them can refuse a run on its own, and adding one to the table above would be a category error.
| Companion | What it actually is | Why it is not a gate |
|---|---|---|
| Harness metrics | A read-only rollup of state the gates already record, across every change | It measures the gates; it never blocks anything. forge metrics reports, and returns |
| Harness as MCP | A stdio server exposing read-oriented harness commands as MCP tools | Makes the harness legible, not binding — an agent that can call a tool can ignore what it says. Enforcement stays in the non-zero exits used by hooks, pre-commit, and CI |
| Isolated verification | forge verify --isolated, running tests against an ephemeral copy of the tree | It changes where verification runs, not whether it can refuse. The verdict, the breaker streak, and the timeline event all still belong to the real repo |
| Bounded autonomous run | forge run — an orchestrator that drives an executor you supply and stops when a gate opens | It consumes the gates, it is not one. It records no gate state of its own; the breaker, budget, and secscan gates it polls are the same always-on gates, read-only |
| Interactive clarification gate | forge clarify-gate — a Sensor reading two persisted clarifying-question marker scopes | It reads state and returns non-zero like a gate, but it can never refuse a run: it is wired only to lifecycle-end events (Stop, session.idle) that fire after the agent's turn already ended, it deliberately never returns the blocking PreToolUse exit code, and no delivery gate consumes it. It is also wired for only 2 of 12 supported harnesses |
forge run is the exception, not another gate
Everywhere else forge is a configurator, not a step-firer. forge run is the one command that drives the loop itself — a deliberate, opt-in, bounded exception. It is dry-run by default (--yes to execute), --max-iterations defaults to 5 with a hard ceiling of 50, and it refuses to start at all when a gate is already open. Finishing the iteration cap is an escalation, not a success.
It also has no model. Forge cannot write code; the executor is an agent command you pass with --cmd. Forge is the harness in that loop, never the agent.
The admission rule for a new enforced control
A control earns enforcement only when both conditions hold. If either fails, it stays advisory.
- The failure mode is runaway, irreversible, or unbounded. A one-off mistake the human will notice does not qualify; a loop that never exits, a command that cannot be undone, or a cost with no ceiling does.
- It is deterministically checkable by the CLI from persisted per-change state (
.forge/changes/<c>/*.yaml) or live Git evidence — with no LLM judgment inside the gate.
No model judgment inside a gate
The moment a gate needs the model to decide whether it passed, it is no longer enforcement — it is advice wearing a hard-coded exit code. Enforced gates read counters and evidence, nothing else.
Drift earns enforcement under the unbounded-accumulation reading of criterion 1: unchecked spec-vs-code divergence grows monotonically across merges and never self-corrects — a cost with no ceiling, like spend. It satisfies criterion 2 because the gate blocks only on Certain findings (deterministically resolved by drift.Classify); Candidate ("needs review") findings require judgment and can never block. Enforcement is opt-in via --enforce — off by default, so bare forge drift stays read-only (exit 0).
Shared shape
Every enforced gate is the same object: an idempotent, re-runnable check over a persisted per-change counter or live evidence that returns a non-zero exit. It never fires the agent — it only refuses to bless a run that has gone off the rails.
- The breaker bounds ITERATIONS.
- The budget bounds SPEND.
- The review gates bound DELIVERY.
- The guardrail hook bounds DANGEROUS ACTIONS.
- The drift gate bounds SPEC-CODE DIVERGENCE.
Because they are idempotent and re-runnable, you can invoke them from a hook, from a script, or by hand, and get the same verdict every time.
The harness model: Guides and Sensors
Martin Fowler frames an agent as Model + Harness, and splits the harness into two kinds of control:
- Guides are feedforward — they steer the agent before it acts.
- Sensors are feedback — they observe what happened and correct after.
In Forge terms: the guardrails, the constitution, and the canonical interactive-input callout are Guides — they shape the run before it happens. The enforced gates and the session timeline are Sensors that observe the run and refuse to bless it when the evidence says it went wrong. forge clarify-gate is also a Sensor — same feedback shape, persisted state, non-zero exit — but it is the one Sensor in this diagram that, by design, can never refuse anything; see the enforced-set boundary for why it stays out of that table.
Next steps
- The guardrails gate — the control that blocks dangerous actions and scans for leaked secrets.
- Verify circuit-breaker — the fuse that stops "retry forever".
- Spend budget — the ceiling on cost.
- Session timeline — the auditable, attributed record every gate emits into.
- Connect a live agent — how to feed the gates from your coding host.
- Interactive Clarification — the warn-only Guide + Sensor pair that keeps agents from self-answering product decisions.
- Harness metrics — whether the gates are actually earning their keep.
- Harness as MCP — let an agent query the gates directly (queryable, not enforcing).
- Isolated verification — run the agent's code against an ephemeral copy, not your working tree.
- Bounded autonomous run — the one command that drives the loop, and stops it at the first open gate.