03.02Overview
Software architecture with AI coding agents
How AR Labs designs software that AI coding agents build: design before spec, deep modules, C4 diagrams, decision records, risk spikes and tracer-bullet slices.
On this page
At AR Labs, a system is designed before any AI coding agent (an AI agent that writes code, such as Claude Code or Codex) is asked to specify or build part of it. The design names the kinds of work the system does, separates them into parts with narrow interfaces, records each significant choice and why it was made, and retires the riskiest unknown with a throwaway spike before anything else is built. Agents then implement against that design one thin end-to-end slice at a time. The design is what keeps many short agent sessions building one coherent system, and what keeps a person in charge of its shape.
The problem it answers
An AI coding agent sees only a slice of a project in any one session. Inside that slice it adds code without knowing that an equivalent exists elsewhere, and left alone it tends to build layer by layer and to produce many small, shallow modules with tangled call graphs. Each session's work can look reasonable while the system as a whole drifts. The design record is the holistic view that no single agent session has, and keeping it current is how a person stays the owner of the system's shape while agents write its code.
How we design a system
flowchart TD I["Design interview"] --> Q["Goals, constraints, quality attributes"] Q --> K["Decompose by kind of work"] K --> C["Containers and interface contracts"] C --> A["Record each choice as an ADR"] A --> X["Spike the riskiest unknown"] X --> V["Vertical slices, tracer bullet first"] V --> R["Reconcile the design as slices land"] R --> C
- Interview before writing. For any campaign, an agent questions a person one decision at a time until every branch of the design is settled, and writes a brief of decisions, scope, decomposition and acceptance scenarios. The technique comes from Matt Pocock's open-source
grill-meskill; our version is described under Grill Me. Design is a separate stage from specification because a per-task spec is grounded in today's code, and a design decision made inside one task's spec is invisible to every other task. - Three inputs, not one. Architecture follows from the goals (what the system must do, stated without technology), the constraints (what cannot change: infrastructure, team size, required providers) and the quality attributes. We pick the one or two attributes that dominate and lets them drive the biggest decisions. For a voice system, latency almost always dominates.
- Decompose by kind of work. The central question is which responsibilities have different performance characteristics, different failure modes or different rates of change. Those become separate parts. A quick test of the result: if one external provider changed, how many parts would have to change? One is good. Three is a coupling problem.
- Deep modules behind narrow interfaces. From John Ousterhout's A Philosophy of Software Design: a deep module offers a small interface over substantial behaviour. We design the interfaces and delegate the bodies. Deep modules suit agents in particular, because an agent can call one correctly with little context, and tests written at its boundary survive changes to its internals.
- Record each choice. The design lives in one
DESIGN.mdper system, which describes what the system is now, not how it got there. Each significant choice gets a one-page architecture decision record in Michael Nygard's format, and a reversed decision is superseded by a new record rather than edited. C4 diagrams show the system context and its containers. The formats are described in architecture documentation. - Spike the riskiest unknown. When one untested assumption could collapse the design, a throwaway spike proves or disproves it before any slice is built.
- Cut work into vertical slices. Each task crosses every layer to deliver one thin piece of behaviour end to end, and the first slice is the tracer bullet: the thinnest path through the whole system, which proves the architecture works. The term comes from Andrew Hunt and David Thomas's The Pragmatic Programmer. Layer-by-layer plans defer all integration feedback to the end; slices give it after the first task, and slices with explicit dependencies can be built in parallel. The mechanics are under To Issues.
- Reconcile as work lands. After the design interview, after the plan's research, and when a group of slices finishes, the design record is brought up to date from what was actually built. Each revision lands as a diff a person approves.
Designing the seams where agents and people meet
Many of our systems contain an AI model as a component, and the same design rules apply with three additions.
- Separate the brain from the hands. The model decides; deterministic code acts. Nothing the code can do reliably is left to the model.
- Draw state boundaries first. Decide what state is per turn, per session and persistent, and where each lives, before writing code. State that must survive a crash or a context summary is written down outside the conversation.
- Make tool contracts strict. A model calling a tool has read only its schema. Enumerations instead of free text, required fields marked, descriptions written as instructions, one job per tool.
Interface contracts between parts are written into the design document as stable boundaries: request and response shapes, and the error answers a caller can receive. Internals can change freely while the contracts hold, which is what lets an agent rework one part without reading the others.
A worked example: the Salesbot voice agent
Our Salesbot is a browser voice agent that runs a short sales discovery conversation. Its design follows the method above, and we use it as the method's worked example.
Decomposition. The design separates four kinds of work: hearing and speaking (real-time audio, which must never block), thinking (conversation logic, which can tolerate tens of milliseconds), showing (the progress display in the browser) and remembering (durable storage). Latency is the dominant quality attribute, so audio processing and database writes never share a code path. The result is two application containers, a browser front end and a Python voice server built on Pipecat, plus a SQLite store written only asynchronously.
A decision that removed a container. An early design assumed the language model would sit in its own service behind an HTTP bridge, so that it could call tools. Research during the next design interview found that Pipecat supports tool calling inside the voice pipeline. Because the tools were simple one-shot actions, a decision record chose in-process tool calling: no new service, no network hop, one log stream. The record states the cost as well: a slow tool handler would now stall audio, and multi-step autonomous tools would force a revisit.
A spike before the slices. The next decision was to model the conversation as a state machine using the pipecat-flows library. One risk could collapse it: the state machine and the channel that drives the browser's progress display had never been run together. A throwaway two-node spike proved three checkpoints against the installed library versions: the pipeline started with both in place, the model moved the flow from one node to the next, and a custom message sent from inside a flow handler reached the browser. Only then did slice work begin, and the spike code was marked for removal.
Contracts. The design document lists each boundary between containers, including the exact response for every error a front-end request can receive, so either side can change behind it.
What exists today
Systems built through our coding pipeline have, or are getting, a design document, a decision-record index and C4 diagrams at levels 1 and 2; Salesbot's record holds more than twenty decisions. Diagrams are written as text and validated through Structurizr before rendering (see C4 Diagram). The coding pipeline runs the design operation at the start of each campaign and at each container review.
Referenced by 04
- EngineeringHow AR Labs engineers software with AI coding agentslinks here in its text
- EngineeringDesign principles for AI-generated codelists this as related
- EngineeringDesign docs, ADRs and C4 diagrams for AI coding agentslinks here in its text
- ResearchCloudflare Workers, D1 and R2 as a default for new web appslists this as related