AI Agents Should Propose Infrastructure, Not Mutate It
Why agents holding cloud credentials undo a decade of infrastructure-as-code — and a design that keeps agent speed while putting a policy-checked gate between intent and effect.
Most major cloud vendors now ship an MCP server that can alter your infrastructure. This is easy to build and it demos well.
It also throws away the governance properties that a decade of infrastructure-as-code was built to provide, at the moment they matter most.
You have probably seen the incident reports already, so links only, as a recap:
- Replit’s agent deleted a production database during an explicit code freeze.
- A compromised release of the Amazon Q extension for VS Code shipped with an injected prompt telling the agent to wipe local filesystems and cloud resources through the AWS CLI. The payload reportedly never ran — a syntax error, not a control, stopped it.
The first is an agent doing its own job badly. The second is an agent doing a job an attacker gave it. The mistake is the same in both: destructive authority reachable from probabilistic execution, with credentials.
Mutations become emergent
“Agent XYZ, create me a database” is not the demo case that matters. That’s a voice-activated console: a human forms the intent, a model transforms it into the command. You could have built it with Alexa many years ago.
The more interesting question is what day-2 operation looks like.
A developer asks an agent for a new feature. The agent splits the work across sub-agents, and one of them decides the service needs a database user, ACLs, and a topic with specific retention. Or: “my queries are slow” — and three reasoning steps later, an agent concludes the instance is undersized and bumps it a tier.
In these chains, the infrastructure mutation is a side effect of reasoning. It’s emergent — several layers removed from the request that triggered it.
The arithmetic breaks. Agents reduce the cost of producing a change, so mutation volume grows with whatever agent throughput an organization buys. Review capacity doesn’t grow with it (yet?): it is still humans, at human cadence. The governance models we relied on assume those two rates are matched. When mutation volume outruns review capacity, the review either becomes the bottleneck or stops being real.
Status quo
The common chain today runs on a developer’s machine:
- The agent’s host launches the vendor’s MCP server as a local process
and gets the tools list:
create_database,update_acl,delete_topic— each with a JSON schema for its arguments. - The model picks a tool mid-task: it decides it needs a database, finds
create_databasein the catalog, and fills in the arguments from whatever context it has. - The MCP server translates the call to the cloud API — using the credentials it inherited from the developer’s environment. The API executes. The resource now exists.
- The result returns to the model as a tool result.
Note which identity did the mutating: the developer’s. The agent has no principal of its own, so the audit trail shows a human, and no IAM rule can even tell the agent’s calls from the developer’s. The host asks for per-call confirmation — until the human clicks “always allow.”
AWS, Google and Azure now host remote MCP endpoints behind OAuth and IAM: the agent gets its own identity, every call is authorized — may this principal use the protocol, may it touch this resource — and everything appears in the audit log. Request-level governance exists.
The regression
An agent calling cloud APIs mutates infrastructure the way an engineer clicking through a console did in 2014.
That model was retired because it didn’t survive scale. Infrastructure-as-code introduced a declarative, versioned, checkable artifact between intent and effect. Those properties did not become acceptable to lose because the caller is now an agent.
The thesis
Separate the powers. The authority to propose an infrastructure change, the authority to judge it, and the authority to execute it must belong to different actors — and the agent holds only the first.
This is constitutional design, applied to machines. Power is split this way for the old reason: no single actor can be trusted to be right every time, so the guarantee is placed in the structure, not in the actor’s character.
The alternative introduces no new capability. It’s the same governance IaC already gave us, rebuilt for a caller that is fast and concurrent:
- The agent produces intent, not API calls. “I need a Postgres instance for service X.”
- A server-side layer turns the intent into a declarative artifact — Terraform/OpenTofu HCL — based on the organization’s existing estate and approved modules.
- The artifact is planned against current state, and the plan is checked against organizational policies (OPA).
- Denial is a negotiation. Policy violations come back to the drafting step as structured, machine-readable hints. The artifact is regenerated and resubmitted until it passes or gets detectably stuck. Which violations are self-fixable and which need a human is declared in the policy’s metadata.
- The actor that proposes and the actor that applies are separate. The drafter can only produce an artifact. Only the gate, after a passing check, can run the plan and apply it.
- Every step is audited — intent, generated artifact, violations, fixes, outcome — append-only log.
One mutation, traced
The derived intent arrives over the transport:
intent: service payments-recon needs a production Postgres instance
The drafter, working from the estate snapshot and the module catalog, emits a first draft:
module "payments_recon_db" {
source = "registry.internal/platform/postgres"
version = "3.2.1"
service = "payments-recon"
environment = "production"
instance_class = "small"
deletion_protection = false
}
The gate denies it — but the denial is structured:
[
{
"policy": "prod_deletion_protection",
"message": "deletion_protection must be true in production",
"self_fixable": true,
"fix_hint": "set deletion_protection = true"
},
{
"policy": "prod_instance_class",
"message": "instance_class \"small\" is not allowed in production",
"self_fixable": true,
"fix_hint": "use one of: medium, large"
}
]
Both violations carry self_fixable: true — declared in the policy’s
metadata, so the loop regenerates the
artifact: deletion_protection = true, instance_class = "medium",
resubmit. It passes, the plan is produced and checked, apply runs.
Now suppose the same intent trips a different rule:
{
"policy": "data_residency",
"message": "no residency classification for a new production data store",
"self_fixable": false,
"info_needed": "data residency classification from the service owner"
}
self_fixable: false ends the loop: the job parks as
needs_input and escalates. No amount of regeneration fixes this one,
because the missing fact isn’t in the machine’s context.
Which violations converge and which escalate was decided when
the policy was written.
A few things to notice in the picture.
- Two trust zones. The drafter proposes from a sandbox with no credentials. Only the plan/apply stage — on the far side of a passing check — can touch the cloud.
- One door. The gate is not one mutation path among several — no side channel where one agent holds a CLI and another uses its own MCP server. Organization-level policy denies state-changing calls to every principal except the apply stage: agents, developers, and CI alike.
- Providers encode the operational knowledge. Most Terraform providers are years of handling API quirks: retry semantics, dependency ordering, eventual consistency, idempotency, partial-failure cleanup. An agent calling cloud APIs rediscovers all of it at runtime. An agent emitting HCL inherits it.
- Modules turn generation into vocabulary. A platform team has approved modules — its building blocks — and the drafter’s job narrows to “instantiate an approved module with typed parameters.” Smaller blast radius, and a place to encode some of your taste that policy alone can’t express.
- The existing estate is the context — and it compounds. Most organizations already have HCL repos and state describing what exists: naming conventions, existing networks, patterns in use, etc. And the layer feeds what it reads: every mutation it applies lands back in the estate, tied to the intent that asked for it. The more infrastructure goes through the loop, the richer the drafter’s world gets. A direct-API agent can read the same repo — but its writes never land there. Every mutation it makes falls outside the declared estate, so the context it read is a little more wrong each time. One loop compounds its context; the other erodes it.
- The drafter never sees state. It sees only the HCL declarations.
The gate checks twice
The policy gate in the picture is one box, but it runs two different checks to answer two different questions.
The first check runs on the generated HCL itself, before anything executes.
Terraform is an execution engine: init downloads provider
plugins — arbitrary binaries — and plan runs them; data sources fetch
over the network during planning, and an external data source executes
whatever local command the configuration names. A bad artifact doesn’t
need to reach apply to do damage — plan is enough. So static
configuration policy pins the allowed providers and their versions,
restricts modules and providers to approved sources, and bans the escape
hatches. We check the artifact is even safe to run.
The artifact then gets an init and a plan under
read-only credentials, and the second check runs on the plan itself: the
resources that would result, the deletions and replacements, cost, regions,
encryption, quotas, and how the change relates to the estate that already
exists. This check answers “does this change comply” — the one
that can only exist because there is a declarative artifact to plan.
The exact saved plan that passed gets applied. A saved plan is bound to the state version it was computed against, so if another apply lands first, the stale plan is rejected and regenerated — never applied against a state that has moved.
Notes
-
Every agentic change has three layers. Task intent: “implement feature X” — stated by a human. Derived infrastructure intent: “feature X needs a topic and a database user” — inferred by an agent. Concrete mutation: this module with these parameters. The gate validates the third layer against policy. It cannot certify that the second was the right reading of the first — no infrastructure-level decision was ever stated for it to check against.
That is the precise limitation: the gate guarantees compliance, not intent-fidelity. A plan can pass every rule and still be the wrong plan.
-
None of this makes HCL sacred. Pulumi or another successor could substitute. The point is that the declarative-IaC ecosystem is where the industry already banked its operational knowledge, and the guard layer spends that balance instead of rebuilding it.
-
This design governs desired-state mutations. Restarting an unhealthy service, forcing a failover, rotating an ephemeral credential are actions, and pushing them through a plan cycle would be theater. They need the same invariant — a server-side check nobody bypasses.
Prior art
While prototyping these ideas, I discovered — of course — that I was not alone. AARM — Autonomous Action Runtime Management, a Cloud Security Alliance project that defines runtime governance of agent actions: pre-execution interception, bound to identity, checked against policy. If this article’s premise holds for you, the spec is worth reading.
Antitheses
1. “Isn’t this just CI inside MCP? Have the agent open a pull request like everyone else — the pipeline runs as usual.”
Yes — and that’s most of the point.
Build the pipeline version: a checkpoint no agent can mess with, violations returned to the machine author in structured form, deterministic escalation, apply on pass. That’s this design, hosted in CI. The article argues an invariant, not a deployment topology.
But the PR half smuggles back the human. A PR path doesn’t govern agents — it queues them behind a reviewer who is already the bottleneck. Throttle agents down to human review cadence, and you’ve killed the reason to use agents.
Plenty of pipelines already treat authors as untrusted — fork PRs, plan/apply separation, restricted deploy credentials. What none of them were built for is a machine author: the red X replaced by a structured counter-proposal, the reviewer replaced by deterministic escalation, generated HCL treated as hostile input.
2. “Scope the credentials tightly enough. The clouds already ship server-side policy — SCPs and organization policies.”
An SCP evaluates a call in its request context — principal, action, resource, conditions, tags. It answers “may this principal call this API” — not “does this change comply.” The rule a platform team actually writes: production databases use customer-managed keys, sit inside approved networks, keep deletion protection, and are never replaced during a freeze window. That rule is about the resulting change as a whole.
“Every Kafka topic has at least three partitions.” “Every database user gets a generated password that lands in the secret store, never in config.” Ordinary platform rules, and request-level authorization has no vocabulary for them.
The SCP is repointed. The design keeps one identity rule, aimed inward: deny state-changing calls to every principal except the apply stage. Identity policy supplies the non-bypassability; plan policy supplies the semantics.
3. “Intercept the API call and check it — that’s what runtime middleware is for.”
Interception does check before execution — that’s AARM-style middleware. The difference is the action being checked. Policy over one call sees the shape of a request. Policy over a plan sees the whole proposed change against a snapshot of the estate. If the rules you need are per-call, middleware is enough. The rules organizations write about infrastructure often are not.
4. “Put the guardrails in the agent’s instructions — system prompts, hooks and skills.”
A prompt is not a boundary. Client-side enforcement runs inside the thing it’s supposed to govern: any agent update replaces it silently, and any successful injection may rewrite it. The Amazon Q incident is the case — the instructions were the attack surface. A checkpoint that sits server-side, outside every agent’s control, is structural rather than advisory.
5. “This adds a full plan cycle to every mutation, and Terraform state serializes concurrent applies.”
Conceded. A plan cycle per mutation is real latency, and state locking serializes applies within a stack. How much it hurts depends on how the estate is partitioned — many small states parallelize, one monolithic state does not — but partitioning is mitigation, not absolution.
It is an optimistic-concurrency problem. That is the honest price of the check.
6. “Everything reduces to the quality of the policy. Who writes the Rego?”
The organizations that can write policy to a high standard are the ones least likely to hand agents raw credentials, and the exposed ones can’t staff it — so in practice a model will draft the Rego: a probabilistic layer on top of a probabilistic layer, with “deterministic” written on the box. The gate’s guarantee is exactly as good as the policy; everything the policy fails to express is permitted.
But “a model will write the Rego” is not a regress, because the two artifacts live in different volume regimes. Mutations arrive continuously, at agent speed. Policies change rarely, and are code: versioned, diffable, unit-testable. A model drafts a rule; a human reviews it; that one review covers every mutation the rule ever gates. The design doesn’t remove the human from governance — it moves human review from the layer where it can’t keep up to the one where it can.
A prompt-injected agent can still use the loop as an oracle and land the worst change the policy permits. The ceiling on that damage is the policy’s quality.
7. “How do you guarantee intent quality?”
You don’t — the design already conceded this: the gate validates the concrete mutation, not the reading of the task that produced it.
What the concession looks like in practice is compliant sprawl. A fleet of agents passes every check while the estate fills with redundant, individually compliant resources, because no rule expresses “should this exist at all.” The design bounds this failure; it doesn’t solve it.
What this adds up to
Nothing in the mechanism is novel. Declarative artifacts, policy engines, gates, audit trails — this is the reference monitor, articulated in 1972. The composition is the contribution: a machine author treated as untrusted, denial made part of the machine’s interface, desired state as the representation of action, proposing separated from applying, and scarce human review spent on policies and modules instead of individual changes.
The protocols are incidental. MCP is a transport and may be replaced. Terraform is a substrate and could be, too. The whole argument compresses to one sentence: nobody bypasses the check, and the artifact is checkable against a declared picture of the world before it executes. We spent years learning why that sentence matters.