# 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.

::: info 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>`](/features/review) returns non-zero until the content-bound receipt validates |
| [Verify circuit-breaker](/harness/circuit-breaker) | Iterations | `forge verify --change <c>` returns non-zero after N consecutive failures |
| [Spend budget ceiling](/harness/budget) | 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](/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](/harness/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](/harness/isolation) | `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](/harness/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](/harness/clarify-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 |

::: warning `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.

1. **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.
2. **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.

::: warning 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.

```mermaid
flowchart LR
    subgraph Guides["Guides (feedforward — steer before)"]
        G1[Guardrails]
        G2[Constitution]
        G3[Interactive input callout]
    end
    subgraph Model["Agent = Model + Harness"]
        M[Model executes]
    end
    subgraph Sensors["Sensors (feedback — observe & correct after)"]
        S1[Verify circuit-breaker]
        S2[Spend budget]
        S3[Review delivery gates]
        S4[Session timeline]
        S5[Drift ceiling]
        S6[Clarify-gate — warns, never refuses]
    end
    Guides --> Model --> Sensors
    Sensors -. corrects .-> Model
```

In Forge terms: the guardrails, the constitution, and the [canonical
interactive-input callout](/harness/clarify-gate#the-guide-one-canonical-callout)
are **Guides** — they shape the run before it happens. The enforced gates
and the [session timeline](/harness/timeline) are **Sensors** that observe
the run and refuse to bless it when the evidence says it went wrong.
[`forge clarify-gate`](/harness/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](#the-enforced-set) for why it stays out of that
table.

## Next steps

- [The guardrails gate](/harness/guardrails) — the control that blocks dangerous actions and scans for leaked secrets.
- [Verify circuit-breaker](/harness/circuit-breaker) — the fuse that stops "retry forever".
- [Spend budget](/harness/budget) — the ceiling on cost.
- [Session timeline](/harness/timeline) — the auditable, attributed record every gate emits into.
- [Connect a live agent](/harness/live-agent-hooks) — how to feed the gates from your coding host.
- [Interactive Clarification](/harness/clarify-gate) — the warn-only Guide + Sensor pair that keeps agents from self-answering product decisions.
- [Harness metrics](/harness/metrics) — whether the gates are actually earning their keep.
- [Harness as MCP](/harness/mcp) — let an agent query the gates directly (queryable, not enforcing).
- [Isolated verification](/harness/isolation) — run the agent's code against an ephemeral copy, not your working tree.
- [Bounded autonomous run](/harness/autonomous-run) — the one command that drives the loop, and stops it at the first open gate.
