← Blog

Article — Engineering

Why we didn't build Cloudwright as an autonomous agent

Agentic AI is having a moment — give a model a goal, a toolbox, and let it reason its way there. For a system that rewrites and redeploys somebody else's cloud infrastructure, we built the opposite: a deterministic, phase-gated pipeline where AI proposes and a human decides. Here's the reasoning, and where it costs us.

Published
July 15, 2026
Read time
~7 min
Category
Engineering & architecture
Built on
Cloudwright case study

The pitch nobody needed convincing on

When we started Cloudwright, the obvious AI-native shape was a set of autonomous agents: an ingestion agent, a classification agent, a planning agent, a code-generation agent, coordinating through shared memory or natural-language handoffs. It's the pattern behind most AI coding tools people reach for first, and it demos beautifully — point it at a repo, watch it reason its way to a migration plan.

We built a prototype shaped like that. It worked, right up until we asked the question that actually matters for this kind of tool: run it twice against the same repository — do you get the same answer? We didn't. Not because the reasoning was bad, but because “flexible enough to handle a case you didn't anticipate” and “guaranteed to handle the case you did anticipate the same way every time” are in tension by design. That's disqualifying for a tool whose output is infrastructure code that gets applied against somebody else's cloud account.

What we built instead

Cloudwright runs migrations as a fixed, seven-stage pipeline — Ingest, Analyze, Graph, Classify, Plan, Transform, Validate — where every stage reads and writes a versioned artifact to a shared store, and three points in that sequence are hard stops: nothing proceeds past the migration plan, the IAM policy, or the deployment step without a human explicitly approving it. No stage decides on its own that it's done well enough to skip the next one.

The AI still does real work inside that structure — classifying what a function's intent actually is, drafting the migration plan and its rationale, generating the transformed code. What it doesn't get to do is decide what happens next, or hand back something different on a re-run of unchanged input. Every AI call is schema-validated against a strict contract, temperature-locked, and cached by a hash of its exact input — so re-running the pipeline on unchanged code never re-invokes the model at all. The second run just replays the first run's answer.

Six reasons this trade held up

DeterminismSame repo, same commit, same config produces the same plan, twice. An agent’s reasoning path can legitimately vary run to run; a pipeline’s can’t.
TraceabilityEvery mapping carries a rationale and a link back to the artifact it came from. You can walk backward from any output to the exact classification that produced it.
AuditabilityNothing "just happens." Every stage has a defined status and failure mode, and a persisted record — including the runs that failed, kept for debugging rather than discarded.
Predictable failureA bad AI response halts the pipeline at that stage; it never propagates forward and corrupts something two stages later.
Bounded costEach function gets exactly one classification call and one transformation call, ever, per unique input. No exploratory loop running up the bill.
A real place to interveneApproval gates are stages in the pipeline, not an afterthought bolted onto an autonomous loop. The system is built to stop and wait, not merely permit being stopped.

Where AI still carries the weight

None of this is “no AI.” It's AI that annotates rather than controls. If a classification comes back below a set confidence bar, we don't accept it — it's marked unresolved and routed to a human reviewer. If a generated transform doesn't parse, or reaches for something it shouldn't, it's marked failed, not force-fitted into the output. The deterministic pipeline isn't a cage around a weak model; it's the thing that lets us actually trust a capable one.

The trade-offs we accepted

We don't think this comes free, and it's worth being straightforward about the cost — the same reason we publish what we deliberately skip elsewhere on this site.

  • Less adaptive.Something that doesn't fit our existing intent categories gets marked unresolved and handed to a person, instead of the system reasoning its way to a creative answer.
  • More rigid. Supporting a new source pattern means writing a new parser, not hoping a general-purpose agent generalizes to it.
  • No persistence.A failed step is failed. We don't retry with “try a different approach” and hope — a human decides what happens next.

We took that trade deliberately. For a system whose output touches somebody else's production migration, an approach that's occasionally brilliant and occasionally subtly wrong — with no reliable way to tell which — is worse than one that's reliably competent and honest about what it can't do yet.

If you're evaluating a vendor's “agent”

A few questions worth asking before anything it touches becomes irreversible: Does it produce the same output on the same input, twice? Can you point to the exact step that produced a given decision, or only describe roughly what the system did? What happens when it's wrong — does it fail loudly and stop, or keep going? If those answers are fuzzy, the demo was the easy part.

Wrestling with the same “should this be an agent?” question?

Whether it's a cloud migration, an internal tool, or something that needs the same discipline — plan first, execute with checkpoints — we'd like to hear about it.

Let's discuss it

See the full build — architecture diagram and walkthrough video — in the Cloudwright case study →