Starting another SaaS in 2026 is a strange thing to do. The discourse says the category is saturated, that AI flattened every moat, that the only things left worth building are wrappers around a frontier model with a nice landing page. I read those takes and I mostly agree with the diagnosis and completely disagree with the conclusion. The wrappers are exactly what dies first. What's left is the boring, hard-to-copy infrastructure underneath — the multi-tenancy, the billing, the cost control, the secrets handling, the idempotency across redelivery — and that work has not gotten cheaper just because the model got smarter.
This is the story of building one of those. It's called Aulinq. It's a multi-tenant, multilingual AI agent platform: the thing that answers a customer on Telegram, WhatsApp, or web chat, routes the conversation through the cheapest model that can actually handle it, charges the tenant per token in fractions of a cent, and keeps every tenant's data and secrets walled off from every other tenant. Three product tracks sit on top of that plumbing — a white-label path for agencies reselling agents to their clients, an API for startups embedding agents in their own product, and a single-tenant onramp for a solo founder who just wants a working bot without the infrastructure PhD. The production system is several Go services, one Postgres, one Redis, and a NATS JetStream bus. I am the only engineer, and I have a full-time job.
That last part is the thing people actually want to know about. How do you ship a distributed platform solo while employed? The honest answer is two things, and neither of them is "I code fast." The first is that I stopped coding solo and started orchestrating a team of AI agents — a planner, an implementer, a reviewer, a verifier, each with its own context and its own tools. The second is that I stopped trying to do this in daily one-hour increments. A weeknight hour is enough to review a diff, approve a plan, or watch a verifier run. It is not enough to research a stack decision, draft an architecture, or pull a failing service out of a drifted state. Those need a block of uninterrupted attention, and for me that block is Saturday, and the occasional Sunday when the family is out.
So the method and the operating model depend on each other. The weekend batch is where the thinking work happens — the research loops, the architecture iterations, the library gap-filling, the agent rules. The weeknights are where the small verify-and-merge tasks happen. Mixing them up is how you end up with a half-researched stack choice made at 11 PM on a Tuesday, which is the exact failure mode this method is designed to avoid.
This article is that method, written down. Not the polished version — the version with the dead ends and the rollbacks left in, because those are the parts that actually teach it. It assumes you're already a competent engineer. The agents don't replace that. They amplify whatever judgment you bring, and they amplify bad judgment just as fast.
The stack question isn't "what's popular"
The first thing an AI coding assistant will do when you ask "what stack should I use for a distributed SaaS" is give you the most popular answer. Python. FastAPI. Postgres. Redis. Maybe Kafka if you say "event-driven." This is the correct answer for a tutorial and frequently the wrong answer for a system that other AI agents are going to extend for the next two years.
The popular stack is popular because it's legible to the most people. Legible to the most humans is not the same as legible to the most agents. Agents don't benefit from a large ecosystem of tutorials — they've already read them. They benefit from a type system that rejects their mistakes, a runtime that fails loudly, and a deployment artifact with the fewest moving parts to get wrong.
I ran the stack question through deep research, not a chat. Four or five iterations: first a broad survey, then narrowing on the two or three options that survived, then a deep read on each, then a cross-check against a second model to catch the bias of the first. The pattern I followed for every consequential decision in this project, not just the stack. It's slower than asking once. It's faster than asking once and rebuilding in month four.
Go won. Not because it's popular in the AI-agent-tooling sense — it isn't, Python dominates that — but because a Go compiler turns a class of agent mistakes into build errors instead of 3 AM stack traces. When the agent adds a field to an event struct and forgets to update a consumer, the build fails. In the Python equivalent, the consumer silently deserializes to None and you find out in production.
The trade-off is real and I want to name it: I gave up direct access to the Python ML ecosystem. Every model call in the platform goes through an HTTP API rather than an in-process library. That's fine for an LLM-routing platform — the models live behind providers anyway — and it would be a real cost for a team doing local inference or heavy preprocessing. I'll write the full Go-vs-Python argument separately, including the cases where Python would have been the right call.
When the library doesn't exist, write it
After the stack, the same research loop for every layer. Event bus: NATS JetStream over Kafka, because subject-based routing is a string an agent can reason about and topic-based routing with a schema registry is a build step an agent will get wrong. Persistence: one Postgres with schema-per-domain, because "one database an agent can read in full" beats "ten databases each with their own migration tool" for agent legibility.
Then the part that surprised people when I described it later. Several of the libraries I needed didn't exist in a form I wanted to use. A typed JetStream publisher that wraps every event in an envelope and exposes a PublishInbound instead of raw Publish(subject, []byte). A circuit breaker with a sliding failure window rather than a naive consecutive-failure counter. A multi-tenant secrets vault with envelope encryption that doesn't leak plaintext to the tools layer.
The reaction from other engineers was usually "just use library X." Sometimes that was right. Sometimes the existing library was a thin wrapper that would have forced the agent to write the same boilerplate around it in every service. When the gap is real, writing the library is not the biggest problem — it's a few weekends with an agent, and you end up with exactly the interface the rest of the system expects. The platform now has a set of internal Go libraries (go-events, go-ai-providers, go-resilience, go-secrets, and a dozen more) that every service imports. Each one exists because the public alternative would have made the agents write more glue, not less.
The breaker is a good example of the shape. The public Go circuit-breaker libraries I found counted consecutive failures and reset on a timer. What I wanted was a sliding window — count failures in the last sixty seconds, open if they cross a threshold, half-open to probe, close on a single success. It's sixty lines of Go:
func (cb *SlidingWindowCircuitBreaker) Call(fn func() error) error {
cb.mu.Lock()
switch cb.state {
case cbOpen:
if time.Since(cb.openedAt) >= cb.cooldown {
cb.state = cbHalfOpen
} else {
cb.mu.Unlock()
return ErrCircuitOpen
}
}
cb.mu.Unlock()
err := fn()
cb.mu.Lock()
defer cb.mu.Unlock()
if err != nil {
cb.failures = append(cb.failures, time.Now())
// prune failures older than the window...
if cb.state == cbHalfOpen || len(cb.failures) >= cb.maxFailures {
cb.state = cbOpen
cb.openedAt = time.Now()
}
return err
}
if cb.state == cbHalfOpen {
cb.state = cbClosed
}
return nil
}
An agent wrote the first version of that in one pass. I reviewed it, found the pruning loop was off by one, and had the agent fix it. The point is that this library now has exactly the interface the rest of the system calls, with no adapter layer, and every service that imports it gets the same failure semantics. If I'd wrapped a popular library, each service would have a different ad-hoc wrapper and the agents extending those services would have to learn each one. There's a whole series coming on the libraries themselves — the events publisher, the AI-provider interface, the secrets vault, the RAG selector — each one a case where the public option would have meant more agent glue, not less.
The architecture plan, in iterations
Before development, I planned the whole architecture with the agents. This is the step most people skip, and the step that saved the most time later — even though it felt like the slowest part.
The key thing I learned: you have to give the agent as much initial context as you have, because the agent's default is to do as little as possible. Ask an agent to "design the architecture for a multi-tenant AI platform" and you get a three-tier diagram with a box labeled "AI Service." Ask it with the constraints — here are the messaging platforms, here's the cost-tier routing requirement, here are the tenancy rules, here's the event flow I expect, here's what I want to avoid — and you get something you can actually build.
I iterated. Four, five, six rounds. I'd get a plan, read it, push back on a specific decision, ask for the revision. The agent would defend a choice, I'd counter with a concrete scenario where it broke, the agent would revise. Boring and annoying. I'd sometimes rather have been coding. But a plan that survives five rounds of "what about this case" is a plan an agent can implement without inventing the architecture as it goes.
The thing I came to value most in those iterations was thinking about directions I wasn't taking yet. The current plan only has to cover what I'm building now, but the architecture has to bend toward the things I might build next. One example: I designed the agent configuration as an immutable template plus a per-tenant override, even though at the time I had one tenant and didn't strictly need the split. Six months in, when the second tenant arrived and the white-label case showed up, the override model already handled it. Had I built the simpler "one config per agent" the first pass, the agent extending the system later would have had to migrate the data model under load, which is the kind of task agents do badly. The architecture decisions themselves — Go over Python, ten microservices over a monolith, one Postgres over a database per service — each get their own writeup later. This article is the process, not the defense.
The development loop
Once the plan was stable, development was one large top-level plan broken into concrete, small tasks. Not "build the messaging service" — "add the PublishInbound method to the events library, here's the struct, here's the subject pattern, write the unit test." Then the next small task. Then the next.
The order mattered. Libraries first, with small examples proving each one worked and unit tests locking the contract. Then the core services, cross-testing against each other once two existed. Then the initial UI, which meant another research loop — this time on frontend frameworks, because the agent's first answer was React with a pile of boilerplate and what I wanted was the smallest thing that compiled to static and handled i18n without a build-step fight.
This is where the role separation lives. One agent reads the codebase and writes a plan for the task. A second agent implements against the plan. A third reads the diff and looks for bugs. A fourth builds, restarts, and runs the tests — and is not allowed to call the work done until it observes the system healthy. No agent promotes its own work. The implementer doesn't review, the reviewer doesn't verify, the verifier doesn't approve.
I won't re-derive it here — the next article is the agent team in detail, with the specific bugs each role caught and the one time the pipeline was overkill. The point for this article is that the loop only works if each task is small enough that a single agent can complete it without losing the thread. "Refactor the billing service" is not a task. "Extract the credit-hold RPC into its own function and add a test that a held-then-released credit returns the balance to the original value" is a task. The agent finishes it, the verifier confirms the test passes and the service log is clean, and the loop moves on.
Agent rules, written down, kept short
Halfway through the build I noticed the agents were drifting. A service that should have routed by tenant ID was routing by account ID. A migration added a column without the privilege grant, so the new role couldn't read it. Small things, each fixable, but the same shape of mistake kept coming back across sessions because the agent starting a new session didn't know the rules.
I wrote them down. A CLAUDE.md at the repo root, kept deliberately short. Eight non-negotiable rules, each one sentence. Multilingual means no language-specific code in the backend — the LLM is the sole authority for language, so the backend uses English keys only. Fix tests you touch or break. Production-ready code, no stubs. Schema changes reset the local stack. After code changes, build, restart, and check logs. Use sub-agents for parallel work and loops for verification.
Short on purpose. I tried a longer version first — every rule spelled out with examples and edge cases — and the agents started dropping the rules at the bottom of the list. A rule the agent doesn't follow is worse than no rule, because it gives you false confidence. The short version fits in the agent's active attention and gets followed. The long version lives in linked sub-documents that an agent reads only when a task touches that area.
The rule that paid for itself ten times over was "the LLM is the sole authority for language." Before that rule, the agents kept adding Cyrillic-counting heuristics and keyword-matching language detection to the backend, because that's the pattern in every chatbot tutorial. Every one of those heuristics broke on the first Russian-speaking tenant who code-switched to English mid-conversation. After the rule, the backend stops trying to detect language entirely, passes the text to the model, and the model answers in whatever language the user wrote in. One sentence in the rules file, and a whole category of bugs stopped appearing.
What I'd undo
I'd undo the temptation to start the UI early. I built a first dashboard pass before the core services were cross-tested, because it felt good to see something on screen. It wasn't worth it. The UI had to be rewritten when the service contracts settled, and the agent doing the rewrite kept trying to preserve the old UI decisions instead of matching the new contracts. Building the boring middle first and the visible UI last would have saved a week.
I'd also write the agent rules file before the first service, not halfway through. The drift in the early sessions was small, but every one of those early decisions is now baked into code that the agents extend, and the agents extending it don't always know which decisions were deliberate and which were drift. A rules file from day one would not have caught everything, but it would have given the early agents a clearer target.
This article is the method. The next one is the agent team in detail — the specific bugs the roles caught and the one time the pipeline was overkill. After that: the architecture decisions (Go vs Python, microservices vs monolith, NATS vs Kafka, one Postgres vs a database per service), then the libraries we had to write, then model optimization and cost tiers, and eventually the go-to-market side. The point of writing it all down is that the only distribution channel that's worked for a technical product like this is engineers forwarding the real build to other engineers.
Top comments (0)