I spent months watching AI agents fail at a distributed system — before realising the architecture itself was the problem. Here's what I found, and what I built instead.
I am not a developer. I run Steller — a B2B gift card middleware that sits between integration partners and gift card vendors — and I build it entirely with AI agents. Cursor, Qwen, Claude. No engineering team. No CTO. Just me and a context window.
For the first year, Steller ran on a textbook microservices architecture. RabbitMQ for messaging. Separate services for orders, wallets, catalog, webhooks. The kind of system that looks impressive in an architecture diagram and makes senior engineers nod approvingly.
It nearly killed the company.
Not because microservices are bad. They're not — for large human teams. But for a team of one human and several AI agents, microservices are a disaster. It took me months of failed sessions, broken deployments, and a lot of money in compute to understand why.
"The unit of architecture for agentic development is not the service boundary — it's the context window."
That insight changed everything. Let me explain what I mean, what I changed, and what I'd do differently from the start.
Every session with Cursor or Qwen started the same way. I'd describe a bug — say, orders completing successfully but wallets not being debited. The agent would load the order service, trace the code, make a change, and report success.
Then I'd discover the wallet service still wasn't debiting. Or worse: it was debiting twice.
The problem wasn't the agent's intelligence. It was the agent's visibility. In a microservices architecture, the order service publishes an event to RabbitMQ. The wallet service subscribes. These are two separate codebases, two separate context windows, two separate agents effectively operating blind to each other.
When I asked the agent to fix the order flow, it could only see the part of the flow that lived in the file it was reading. The rest — the consumer, the wallet debit, the Bamboo vendor call, the webhook dispatch — was invisible.
Here is the thing nobody tells you about building with AI agents: the context window is an architectural constraint, not just a technical one.
Every software architecture decision is ultimately about managing complexity — deciding what needs to know about what, what can be decoupled, what must be coupled. Microservices were designed to manage complexity for human teams. Each team owns a service. They develop, deploy, and scale it independently. The message queue is the contract between them.
But AI agents don't have teams. They have context windows. And the message queue — the thing that makes microservices powerful for human teams — is exactly what makes them hostile to agents.
An event published to RabbitMQ is an invisible dependency. The agent editing the publisher has no signal that a consumer exists, no visibility into what contract it expects, no way to trace what happens downstream. Every RabbitMQ change was a gamble.
Microservices optimise for team independence. Agentic architecture must optimise for context visibility. These are different things — and they often require opposite design decisions.
I want to be careful here: I am not saying microservices are wrong. I am saying they are wrong for small teams using AI agents as the primary development force. For a team of 30 engineers, microservices remain the right answer. For a team of one human and several AI agents, they are architectural poison.
What I replaced them with is embarrassingly simple:
Fat controllers. Instead of spreading the order flow across four services connected by message queues, I put the entire flow in one controller. Wallet debit, Bamboo vendor call, PIN return, webhook dispatch — top to bottom, readable in one file.
Hangfire for background work. Hangfire replaces RabbitMQ for everything that needs to happen asynchronously. Instead of opaque event names and invisible consumers, Hangfire jobs are named C# classes. PlaceBambooOrderJob is a file the agent can grep for, open, and read in full. The compensating transaction — wallet rollback on Bamboo failure — is in the same file as the forward path.
// OrderService.cs — publishes an event // Agent has no idea what subscribes public async Task PlaceOrder(OrderRequest req) { // somewhere a wallet gets debited... // somewhere Bamboo gets called... // the agent cannot see where await _bus.Publish( new OrderPlacedEvent { OrderId = order.Id }); // Agent: "What consumes this?" // Answer: another service, another context window. }
// OrdersController.cs — entire flow visible // Agent reads top to bottom, no surprises public async Task<IActionResult> PlaceOrder(OrderRequest req) { // 1. Validate (partnerId from JWT only — never from body) var partnerId = User.GetPartnerId(); // 2. Debit wallet — explicit, synchronous, traceable await _wallets.DebitAsync(partnerId, req.Total, req.ReferenceId); // 3. Enqueue Bamboo call — named, greppable, rollback included _hangfire.Enqueue<PlaceBambooOrderJob>( j => j.ExecuteAsync(order.Id)); // Agent: grep PlaceBambooOrderJob → finds rollback, // rate limits, compensating transaction. All in one file. return Ok(new { orderId = order.Id, status = "Processing" }); }
The pivot took 27 days and cost $672 in Cursor usage. That is the full cost of transforming a microservices system into an agent-ready monolith — architectural planning, code transformation, documentation, and the protocol layer that lets Qwen operate the system day-to-day.
| Dimension | Microservices | Fat Controllers |
|---|---|---|
| Agent orientation | 15–20 min per session | 2 min. Read AGENTS.md → open controller |
| Flow visibility | 4+ files, 4+ context windows | 1 file, top to bottom |
| Background job tracing | Implicit event names | Named C# class, grep-able |
| Compensating transactions | Saga across services | Rollback in same file as forward path |
| Deploy command | N services, N commands | docker compose up -d |
| Daily AI cost (operations) | High — constant re-orientation | ~$15/day, 95% cache hit rate |
I want to be honest about the tradeoffs, because this approach is not universally correct.
Fat controllers break when your team exceeds ~5 engineers. At that scale, multiple people modifying the same controller creates merge conflicts and coordination overhead that microservices were designed to solve. The context window argument disappears when you have humans who can hold context themselves.
Independent scaling becomes impossible. If your Bamboo vendor integration needs 10× the resources of your catalog sync, a monolithic controller cannot scale them independently. Microservices win here.
Very large domains get unwieldy. A controller that handles 40 different flows is not readable to anyone, human or agent. The approach works when the domain is bounded — Steller has one golden flow, not forty.
The principle is not "always use fat controllers." The principle is: design your architecture so that an AI agent can hold the entire relevant flow in a single context window. For Steller at this stage, that meant fat controllers. For a larger system, it might mean domain-bounded services with explicit, synchronous interfaces — not event-driven, not invisible.
Write AGENTS.md before you write a single line of code. It is the most valuable file in the repository. It tells the agent where to start, what to protect, and what commands to run. A well-written AGENTS.md cuts session orientation time from 20 minutes to 2.
Make every background job a named class with a greppable name. PlaceBambooOrderJob, ReconciliationJob, SendWebhookJob. An agent that can find the job in one grep can read its rollback logic, its retry behaviour, its preconditions — all without switching files.
Put the golden flow in one file. Whatever your system's most important path is — the thing that has to work for money to change hands — make it readable in a single sitting. If a new agent can trace it from request to response in three minutes, you've won.
Require test output before "done." An agent that shows you dotnet test → 72 passed, 0 failed is verifiably correct. An agent that says "looks good" is guessing. Make it a non-negotiable exit condition.
Cache your context. At 95.2% cache hit rate, my documentation pays for itself across hundreds of sessions. Every hour spent writing a clear runbook saves days of re-orientation. Docs are not overhead for agentic teams — they are the primary leverage point.
The context window is the new architecture constraint. We spend enormous effort optimising for team independence, deployment velocity, and horizontal scaling — all valid concerns for human engineering teams. But if your primary development force is AI agents, the constraint that matters most is the one nobody designs for: can the agent see the whole picture?
Steller is now operated by Qwen Coder. It reads AGENTS.md at the start of every session, runs tests before saying done, and logs what it changed. One non-coder founder. Several AI agents. Zero developers. And a system designed from the ground up for the intelligence that actually runs it.
Muhanad Abdelrahim is the founder of Steller, a B2B gift card middleware. He writes about agentic architecture and non-coder product development.