Skip to content

The Drift Log · Governing agents that write code

Governing Code Agents at the Build Intent Gate

1 September 2026 · 6 min read · 1309 words · established

A crystal shard held at a red-lit gateway in a black wall

Why prompt guardrails fail on coding agents, and how to govern mutations by placing a deterministic build gate between intent and disk writes.

A coding agent with raw filesystem access will eventually corrupt your repository.

It does not happen because the model is malicious. It happens because modern AI coding agents operate on token prediction rather than invariant verification. When an agent is given a tool like write_to_file or a shell execution primitive, the boundary between "generating a candidate diff" and "mutating the working tree" collapses.

The resulting failure mode is familiar to anyone running agents in production pipelines: an agent attempts a refactor, invents a plausible import that does not exist, pulls in an incompatible dependency with a restrictive license, or silently overwrites a critical concurrency invariant. By the time the code reaches a pull request, code review automation is forced to untangle hundreds of lines of hallucinated or legally toxic changes that should never have touched disk in the first place.

Prompt engineering cannot fix this. System prompts telling a model to "only use approved dependencies" or "never break backwards compatibility" are advisory. They degrade under long context windows and fail completely during multi-turn debugging loops.

To govern autonomous agents safely, you must move the control point. The agent should not write to the repository. The agent should register an intent to build, and a deterministic build gate must resolve that intent before any write occurs.

Why Output Filtering and Prompt Guardrails Fail

The industry's first reaction to rogue agent output was prompt-tuning: adding negative constraints to the system prompt. The second reaction was output filtering: running post-generation regex or AST parsers on the model's text response before passing it to the tool execution layer.

Both approaches share the same structural flaw: they treat agent governance as a text-classification problem instead of a state-transition problem.

Unsafe Agent Pipeline:
[Model Inference] -> [Advisory Prompt Rules] -> [Tool: write_file()] -> [Working Tree Modified]

When an agent enters a self-correction loop—such as when a compiler returns an error code—the model's internal attention prioritises satisfying the immediate compiler error over preserving system-wide constraints. If fixing a type error requires a helper function, and the model recalls a package that provides it, it will invoke a package manager to install it. If that package has an AGPL-3.0 license or introduces a known vulnerability, the text filter has no semantic understanding of your organisation's compliance rules to stop it.

Furthermore, output filtering operates on the stream, disconnected from current repository state. It cannot verify whether an interface modification breaks a contract across service boundaries until after the file is altered and the suite runs.

The solution is not to make the model smarter. The solution is to remove its write privileges entirely and place an explicit barrier between intent and execution.

The Build Intent Gate

A Build Intent gate treats every agent mutation as an untrusted proposal. Under this architecture, the agent does not possess tools that directly mutate the filesystem. Instead, it interacts with an intermediate protocol—often exposed through an MCP server or a restricted local daemon—that accepts a formal payload: the Build Intent.

Governed Agent Pipeline:
[Model Inference] -> [Tool: declare_build_intent()] -> [Build Intent Gate]
                                                               |
                     +-----------------------------------------+-----------------------------------------+
                     |                                         |                                         |
            [License Check]                           [Invariant Validation]                     [Isolated Harness]
                     |                                         |                                         |
                     +--------------------+--------------------+-----------------------------------------+
                                          |
                        [State: Terminal Verdict (Pass/Fail)]
                                          |
                      +-------------------+-------------------+
                      |                                       |
                  [Pass]                                  [Fail]
                      |                                       |
         [Deterministic Commit]                [Diagnostic Trace -> Agent Context]

A Build Intent is a structured declaration of work. It contains:

  1. The target subsystem or module identifier.
  2. The specific capabilities or interfaces being modified or requested.
  3. The raw candidate patch or configuration diff.
  4. The explicit assertions the agent claims the patch satisfies.

When the agent submits this intent, the gate intercepts the call. Before a single byte touches the project tree, the gate executes three deterministic stages:

1. Invariant and Contract Resolution

The gate checks the proposal against the project's invariant suite. If the patch alters an API signature that other modules depend on, the gate catches the breaking change statically. It does not ask an LLM if the change is backwards-compatible; it validates the schema using static analysis tools or contract linters running in native code.

2. Dependency and Licensing Provenance

If the intent introduces an external dependency or an isolated implementation artifact, the gate evaluates the provenance. In a rigorous governance model, unvetted external packages are blocked immediately. The gate verifies whether the implementation conforms to approved licenses and checks against known cryptographic signatures, such as verified artifacts verified through a deterministic root of trust.

3. Isolated Verification

The gate applies the patch inside an ephemeral, sandboxed environment. It executes the project's verification harness against the isolated change.

The harness returns one of four terminal verdicts:

  • CERTIFIED: The patch compiles, passes all regression and invariant tests, and introduces no license violations. The gate applies the write to the working tree.
  • PROVISIONAL: The patch is reproducible and passes basic compilation, but full behavioral contracts could not be proven. The write is held for human confirmation.
  • INCONCLUSIVE: The harness could not execute the test contract (due to environment or harness constraints). The gate treats this as a harness limitation, not an agent defect, and rejects the automatic write.
  • FAILED: The patch violates an invariant, fails a test, or introduces forbidden code. The gate rejects the mutation and returns a structured diagnostic trace directly to the agent.

Structural Implementation: The Intent Protocol

To implement a Build Intent gate, you must replace arbitrary file tools with an explicit transaction protocol. Consider a standard tool call schema in an MCP server configured for code modification:

{
  "name": "propose_build_intent",
  "description": "Registers a build intent to modify a module. Does not mutate the filesystem directly.",
  "parameters": {
    "type": "object",
    "properties": {
      "module_path": { "type": "string" },
      "proposed_diff": { "type": "string" },
      "invariants_checked": {
        "type": "array",
        "items": { "type": "string" }
      }
    },
    "required": ["module_path", "proposed_diff"]
  }
}

When the agent invokes propose_build_intent, the server does not execute a shell command. It executes a local validator:

def handle_build_intent(intent: BuildIntent) -> IntentResult:
    # 1. Check license compliance on any new dependencies
    if not licensing_gate.verify(intent.proposed_diff):
        return IntentResult(
            status="REJECTED",
            error="Dependency license violation: dynamic resolution failed."
        )

    # 2. Apply patch to ephemeral shadow workspace
    shadow_workspace = create_ephemeral_workspace()
    shadow_workspace.apply_patch(intent.proposed_diff)

    # 3. Run deterministic verification
    verdict = harness.run_suite(shadow_workspace)
    
    if verdict == Verdict.CERTIFIED:
        # Only now do we write to the active workspace
        apply_to_main_workspace(intent.proposed_diff)
        return IntentResult(status="COMMITTED", details="Verification passed.")
    else:
        # Return deterministic diagnostics to the agent context
        return IntentResult(
            status="REJECTED",
            error=f"Harness failed with verdict {verdict.name}: {verdict.diagnostics}"
        )

The agent never receives an open file handle. It receives either a confirmation that its intent was certified and committed, or an exact, reproducible diagnostic error explaining which invariant failed.

What Makes This Hard

Building an Intent Gate is technically demanding. It introduces real engineering trade-offs that simple tool-calling approaches ignore:

  • Latency and Feedback Loops: Running full test suites or static analysis passes inside a gate adds latency to every agent turn. If your test suite takes four minutes to run, the agent's iterative loop slows to a crawl. You must design targeted, modular test suites that run in milliseconds for local changes.
  • Side-Effect Leakage: If an agent's intent requires database migrations, external network calls, or configuration changes outside of source code, pure filesystem isolation is insufficient. Sandboxes must be strictly sandboxed at the OS or container level.
  • Inconclusive States: Sometimes the agent writes code that is correct, but your verification harness lacks the fixtures to prove it. Forcing a binary pass/fail can cause the agent to thrash in loops trying to "fix" valid code. The gate must support explicit INCONCLUSIVE states that cleanly escalate to human reviewers.

Conceding these difficulties is necessary. An intent gate requires investing in fast, deterministic local tooling. However, that cost is fixed, whereas the cost of debugging non-deterministic, corrupted code across an entire engineering team compounds infinitely.

What to Do on Monday

You do not need to rewrite your entire continuous integration infrastructure to begin governing coding agents. Start with these three concrete steps:

  1. Audit Agent Tool Configurations: Identify every tool exposed to your local and CI agents. Revoke permissions for raw shell execution (exec, bash), arbitrary file writes (write_to_file), and package managers (npm install, pip install).
  2. Implement an Intermediary MCP Proxy: Create a thin MCP server that wraps your file write actions. Expose a single propose_diff tool. Require the tool to apply changes to an isolated git branch or scratch directory rather than the active working tree.
  3. Bind Pre-Commit Checks to the Tool Response: Configure the proxy to run your local linter, license-checker, and unit test suite against the scratch directory before returning a response to the agent. If the checks fail, return the error output directly in the tool response string and drop the scratch changes.

Agents should be allowed to think freely, but they must never write freely. When you govern the gate between intent and execution, you transform AI coding agents from chaotic contributors into reliable, bounded components of your engineering pipeline.

Keep reading

Next in the log

The Strategic Master Library · written and reviewed under the house's own epistemic rules: nothing claimed that we cannot show.