AI Product Architecture: The Reference Design for Production AI Systems (2026)
- A production AI product has nine layers: gateway, model router, context assembly, agent runtime, tool/MCP layer, memory, durable execution, evals and observability. A prototype has three.
- The six missing layers are where cost, reliability and debuggability live. The one teams add last, per-request cost attribution, takes an afternoon and changes every decision after it.
- Agent work is long-running, bursty, retryable and partially failing. Queues exist for exactly that workload shape, so the reference design is event-driven, not request/response.
Auth, rate limit, tenant resolution, idempotency key minting, 202-with-run-id for async work.
+3-8ms · fails: all trafficPolicy table mapping request class to model, cache lookup, fallback on 429/5xx. This layer subtracts cost.
negative costRetrieval, memory read, tool-catalogue resolution, token budget enforcement, cache-prefix ordering.
sets 60-80% of spendThe loop, plus step budget, token budget, wall-clock budget, no-progress detection and the journal write.
fails: runaway loopGateway, per-agent allowlist, schema validation, credential injection, result bounding, audit log.
7,500 tok/turn at 50 toolsEpisodic, semantic and procedural. Postgres plus pgvector before anything specialised.
skippable in v1Journal, replay, timers, resumption after a worker crash. A queue plus a step table is a valid implementation.
fails: duplicate side effectCross-cutting. Offline suite in CI, online judges on sampled traffic, drift alerts on distributions.
cross-cuttingCross-cutting. OTel GenAI spans, cost meter with a price snapshot, tail sampling on error and cost outliers.
scales with stepsWhat are the layers of a production AI product architecture?
Nine: gateway, model router, context assembly, agent runtime, tool/MCP layer, memory, durable execution, evals and observability. A prototype implements three of them: a model call, a prompt, and a UI. The six that are missing are the six where cost, reliability and debuggability live. That is why prototypes are cheap and production systems are not.
This is not a taxonomy exercise. It describes, with the proprietary parts removed, a platform I architected and built solo: AccioMatrix, an AI assessment and interview platform on an event-driven backbone that orchestrates multiple LLMs, AI agents and MCP-based tooling, now serving 20+ enterprise clients. Every layer below has a cost line because I have seen the invoice for it.
The boundaries are not arbitrary either. Each sits where you will eventually swap a vendor. You will change model providers. You will change vector stores. You will change your tool transport: MCP shipped a breaking stateless rewrite on 28 July 2026, and every client written against the old handshake had to move. A boundary that does not match something you might replace is decoration. Something you might replace with no boundary is your next incident.
The most valuable early decision is the least glamorous. Per-request cost attribution, recording tokens, cache status, model version and a price snapshot on every call, takes an afternoon and changes every architectural argument afterwards, because it converts opinions into numbers. The full implementation is in the full cost-per-request breakdown for LLM routing and caching.
| Layer | What it does | Typical implementation | Cost per request | Primary failure mode | Skip in v1? |
|---|---|---|---|---|---|
| 1 Gateway / API | Authenticates, rate limits, resolves tenant, mints the idempotency key, returns 202 + run_id for async work | Fastify or FastAPI behind a load balancer | ~$0 (compute only) | Holds an HTTP connection open across a four-minute agent run and times out | No |
| 2 Model router | Maps request class to model, checks the cache, falls back on 429/5xx | A policy table and a switch statement; a router product only if traffic is unpredictable | Negative — subtracts 30-60% | Silent quality regression with no eval to catch it | No |
| 3 Context assembly | Retrieval, memory read, tool-catalogue resolution, token-budget enforcement, cache-prefix ordering | Your own code. This is the highest-leverage 200 lines in the system | Sets 60-80% of the bill | A timestamp at the top of the prompt zeroes your cache hit rate | No |
| 4 Agent runtime | The loop plus every guard around it: step, token and wall-clock budgets, no-progress detection, journal writes | In-house loop, or LangGraph / OpenAI Agents SDK / Claude Agent SDK | $0.04-$1.05 per run (worked example below) | Loop never terminates; cost detected on next month's invoice | Only if you have no tools |
| 5 Tool / MCP layer | Gateway, per-agent allowlist, schema validation, scoped credential injection, result bounding, audit log | MCP servers behind a gateway; plain function calls for tools you own | 7,500 tokens/turn at 50 tools = ~$0.30 per 20-turn run | A tool returns 40K tokens and blows the context window | Gateway: no. MCP itself: often yes |
| 6 Memory | Episodic (what happened), semantic (what is true), procedural (how we do it here) | Postgres + pgvector. Mem0, Zep or Letta only when you have measured a need | $0.0002-$0.002 per read | Stale memory contradicts fresh retrieval and both reach the model | Yes, usually |
| 7 Durable execution | Journal, replay, timers, resumption after a worker crash | A queue plus a run/step table. Temporal, Restate or Inngest when you need timers and signals | ~$0.0001 per step in storage | A resumed run re-executes a paid side effect | No — a journal, at minimum |
| 8 Evals | Offline suite in CI, online judges on sampled traffic, drift alerts on output distributions | Deterministic assertions first, Langfuse / LangSmith / Braintrust second | $0.002-$0.02 per judged trace | The golden set goes stale and passes while production degrades | Deterministic: no. Judges: month two |
| 9 Observability | OTel GenAI spans, cost meter with a price snapshot, tail sampling on error and cost outliers | OpenTelemetry GenAI conventions, then pick a backend | Scales with steps, not requests | PII retained in traces past policy | No |
What does one request actually do, step by step?
Two things, and they cannot share a code path. A simple completion is seven synchronous hops and resolves in under two seconds. An agentic task is an enqueue, an immediate 202 with a run identifier, then forty or more hops across a worker that may run for four minutes, retry twice, and partially fail. Serve both through one request/response handler and you discover the difference when your load balancer starts killing runs at sixty seconds.
The synchronous path: client, gateway (auth, rate limit, idempotency key), router (policy lookup, cache check), context assembly (retrieve, budget, order for cacheability), model call, response validation, write and return. Seven hops, one model call, one cost line. If this is your whole product, you do not need seven of the nine layers, so do not build them.
The agentic path is different in kind. The gateway writes a run row and returns immediately. A worker picks the run off a queue, assembles context, routes, calls the model, parses a tool call, validates it against a schema, checks an idempotency key, executes the tool through the MCP gateway, bounds the result, appends an observation, journals the step, checks three budgets, and goes round again. Every one of those verbs is a place to fail, and every model call carries a token count and a dollar figure you should be recording.
The habit worth stealing: annotate the sequence diagram with tokens and dollars on every model arrow. Almost nobody does it, and it changes the conversation immediately. A diagram where the third arrow says twelve thousand tokens and three cents is one a CFO can read.
How much does one request cost?
Cost equals uncached input tokens times the input price, plus cached input tokens at roughly a tenth of that price, plus output tokens at five to six times input, plus tools, retrieval, storage and human review. Write it down once, put it in middleware, and stop arguing about it. Below is that equation worked across four request shapes at August 2026 list prices.
Three cost lines surprise everyone: the tool catalogue, the retry, and the human in the loop. Fifty tool schemas at roughly 150 tokens each add 7,500 tokens to every turn. Across a twenty-turn run that is 150,000 tokens of pure catalogue before the agent has done any work. Retries are missing from most teams' cost data entirely, because the failed call never reached a success-path log. And human review, where it exists, is usually the largest line in the system by an order of magnitude.
The key structural fact about agent cost: it grows superlinearly with turns, because the entire history is re-sent every turn. On the model below, doubling a run from twenty turns to forty turns multiplies the cost by 3.3, not by 2. Budget an agent feature linearly in turns and you have under-budgeted it.
The highest-leverage number in the architecture is the cache hit rate on your static prefix, and it is also the easiest to set to zero by accident. A timestamp in the system prompt, a shuffled tool list, a non-deterministic JSON serialiser: each moves the cached prefix boundary to the first token and quietly triples your bill. The arithmetic and the fixes are in the full cost-per-request breakdown for LLM routing and caching.
| Request shape | Input tokens | Output tokens | Tier (in/out per 1M) | Uncached | With prefix cache | Per 1,000 requests |
|---|---|---|---|---|---|---|
| Simple completion | 1,200 | 300 | Cheap · $0.20 / $1.20 | $0.00060 | $0.00042 | $0.42 - $0.60 |
| RAG answer | 6,400 (4,000 static) | 400 | Mid · $2 / $10 | $0.01680 | $0.00960 | $9.60 - $16.80 |
| 5-turn agent run | 63,500 cumulative | 1,250 | Mid · $2 / $10 | $0.13950 | $0.06200 | $62 - $140 |
| 20-turn agent run | 501,500 cumulative | 5,000 | Mid · $2 / $10 | $1.05300 | $0.24400 | $244 - $1,053 |
| Assumptions | 9,000-token static prefix (800 system + 7,500 catalogue + 700 few-shot); history grows 1,650 tokens per completed turn | 250 tokens per turn | List prices checked 24 Aug 2026 | No cache | Cache read 0.10x input, write 1.25x, TTL not expired mid-run | Excludes tools, storage and review |
Where should state live?
In your database, in four distinct kinds, not in your framework. Conversation state, run state, memory and artefacts have different lifetimes, access patterns and privacy obligations. Collapse them into one blob and you get the reason so many AI systems become undebuggable at the exact moment they get customers.
Run state matters most and gets delegated most often. LangGraph checkpointing, Temporal histories and framework-native persistence are fine mechanisms, but none replaces tables you own. The acceptance test is one query: can you say what run 8f3a did at step 7, what it cost, and how long it took, in a single SELECT? If not, your on-call engineer cannot debug production at three in the morning, and neither can you.
The schema below is the one I would start every AI product with. Three details are load-bearing. Cost is stored as bigint micros, never a float, because floats accumulate error across millions of rows and finance will notice. Status is a check constraint, so an unknown state is a write error, not a mystery. And the unique index on idempotency_key is the entire duplicate-side-effect defence: one line that stops a retried step from sending a second email.
The layer above this is the agent runtime itself, and the guards around it deserve their own treatment. That is in how the agent loop actually works in production.
create table agent_run (
id uuid primary key,
tenant_id uuid not null,
kind text not null,
status text not null check (status in
('queued','running','waiting_input','succeeded',
'failed','cancelled','budget_exceeded','stalled')),
input jsonb not null,
output jsonb,
step_budget int not null default 25,
token_budget int not null default 400000,
wall_clock_ms int not null default 600000,
tokens_used int not null default 0,
cost_micros bigint not null default 0,
trace_id text not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table agent_step (
run_id uuid not null references agent_run(id) on delete cascade,
idx int not null,
kind text not null, -- model | tool | human | system
tool_name text,
idempotency_key text unique, -- persisted BEFORE the side effect
request jsonb not null,
response jsonb,
error jsonb,
input_tokens int,
cached_tokens int,
output_tokens int,
cost_micros bigint,
price_snapshot_id uuid, -- never compute cost at query time
started_at timestamptz not null default now(),
ended_at timestamptz,
primary key (run_id, idx)
);
create index on agent_step (tool_name, started_at desc);
create index on agent_run (tenant_id, status, created_at desc);
-- the acceptance test for any state design:
select idx, kind, tool_name, cost_micros,
extract(milliseconds from ended_at - started_at) as ms,
error
from agent_step
where run_id = '8f3a...'::uuid
order by idx;- Conversation state — the message list for an open sessionRedis with a TTL. It is a cache, not a record. Rebuildable from the journal.
- Run state — what the agent did, in order, with cost and timingPostgres, authoritative, never the framework. This is the layer that makes 3am survivable.
- Memory — episodic, semantic, procedural facts that outlive a runPostgres + pgvector. Almost always skippable in v1; add it when you can name the query it answers.
- Artefacts — media, generated documents, raw tool payloadsObject store, with a pointer in the journal. Never inline a 40K-token tool response into the context.
- Price snapshots — the rate card each cost row was computed againstOne small table. Without it, a vendor price cut silently rewrites last quarter's cost report.
Should the architecture be event-driven or request/response?
Event-driven for anything agentic; request/response for anything that resolves in under two seconds with a single model call and no tools. This is the most contrarian position in the post and the one I have lived with longest. AccioMatrix runs on an event-driven backbone because an interview is a long-running, multi-modal, partially-failing workflow that produces a dozen artefacts asynchronously. Modelling that as an HTTP request is the most common architectural mistake in the category.
The argument is about workload shape, not fashion. Agent work is long (seconds to minutes), bursty (a batch import creates a thousand runs at once), retryable (a 429 is normal, not exceptional) and partially failing (step 6 of 20 failed; the first five results are still valuable). That is exactly the workload queues were invented for. Request/response forces you to hold a socket open across an operation that may take four minutes and fail twice, and every layer between client and worker, browser, CDN, load balancer, ingress, has an opinion about how long that is allowed to be.
The cost of the queue is real and gets skipped in most advocacy for it. You now own message ordering, poison messages, dead-letter handling, at-least-once delivery semantics that force idempotency on you, a lease-and-heartbeat protocol so a dead worker's run gets retried rather than lost, and a queue-depth graph someone now has to watch. That is roughly a week of engineering and a permanent increase in the number of things that can page you.
So the rule is conditional, not universal. If your product is a chat box that answers in a second and a half, a queue is over-engineering; do not build one. The moment you add a tool call, a retry, or a step that can outlast your load balancer's idle timeout, the queue stops being optional and becomes the thing you should have had.
A queue here buys you nothing and costs you a week of operational surface. Stream the tokens and move on.
Keep the HTTP handler, but write the run and step rows anyway. You get debuggability without the queue.
Enqueue, return 202 with a run_id, stream progress over SSE. Anything else fights your infrastructure's timeouts.
The queue is the backpressure mechanism. Without it, a 1,000-document import becomes a provider rate-limit incident.
You need timers, signals and resumption across days. This is where Temporal, Restate or Inngest earn their operational cost.
How do you make an AI system reliable when the model is not?
With four mechanisms that have nothing to do with prompting: idempotency keys persisted before side effects, retries that mutate context, budgets that terminate into named statuses, and a journal you can query. None get easier with a better model, which is why they belong in the architecture, not the prompt.
The idempotency rule first, because it is the one that costs real money when you get it wrong. Derive a key from the run identifier, the step index, the tool name and a canonical hash of the arguments. Canonical means key-sorted with no whitespace, so two argument objects with the same content but different key order hash identically. Otherwise the whole scheme is decorative. Insert the step row carrying that key before you perform the side effect. On a unique-constraint violation, read the existing row back and return its stored response instead of calling the tool again.
Be clear about the window this does not close. If the worker dies between the successful external call and the response write, the row sits in flight and nobody knows whether the email went out. That gap needs either the provider's own idempotency key on the outbound request or a reconciliation job that queries the provider by key and resolves the row. Anyone who says a database-side idempotency key alone is enough has not had a worker OOM mid-call.
The second rule is one most teams have never heard stated: retrying an LLM call with an unchanged context reproduces the same failure. At low temperature a model is close to deterministic given identical input, so an identical retry of a malformed tool call produces the same malformed tool call and charges you twice. A retry must mutate the context, appending the validation error, the expected schema and the specific offending field. Otherwise it is not a retry, it is a way to pay twice.
import { createHash } from "node:crypto";
function canonicalJson(v: unknown): string {
if (v === null || typeof v !== "object") return JSON.stringify(v);
if (Array.isArray(v)) return "[" + v.map(canonicalJson).join(",") + "]";
const o = v as Record<string, unknown>;
return "{" + Object.keys(o).sort()
.map(k => JSON.stringify(k) + ":" + canonicalJson(o[k]))
.join(",") + "}";
}
export function idempotencyKey(
runId: string, stepIdx: number, tool: string, args: unknown,
): string {
return createHash("sha256")
.update(runId + ":" + stepIdx + ":" + tool + ":" + canonicalJson(args))
.digest("hex");
}
export async function executeTool(db: Db, ctx: RunCtx, tool: Tool, args: unknown) {
const key = idempotencyKey(ctx.runId, ctx.stepIdx, tool.name, args);
const claimed = await db.insertStepIfAbsent({
runId: ctx.runId, idx: ctx.stepIdx, kind: "tool",
toolName: tool.name, idempotencyKey: key, request: args,
});
if (!claimed) {
const prior = await db.getStepByKey(key);
if (prior.response) return prior.response; // already done, replay it
return reconcile(prior, tool, key); // crashed mid-flight
}
// provider-side key where supported: closes the crash window properly
const res = await tool.call(args, { idempotencyKey: key });
await db.completeStep(ctx.runId, ctx.stepIdx, res);
return res;
}- 1Transport error3 attempts
Socket reset, DNS failure, 5xx from the provider. Retry the identical request with exponential backoff and jitter. Nothing about the context needs to change.
- 2Rate limit (429)2 attempts then fallback
Backoff, and route to the fallback model on the second attempt rather than the fourth. State the quality delta you accept when you write the fallback into the policy table.
- 3Invalid model output1 mutated retry
Do NOT retry the same request. Append the validation error, the expected schema and the offending field as a tool result, then take the next turn. Charge the extra tokens against the run budget.
- 4Tool business error0 retries
Do not retry at all. A 404 from a ticketing API is information for the model, not an exception for the harness. Feed it back as an observation and let the agent choose again.
What breaks in production, and how would you know?
Ten things, reliably. Below is the failure-mode matrix I would put on the wall for any AI product: blast radius, detection signal and honest time-to-detect for each. Read the cost row twice, where the honest answer to how long detection takes is next month's invoice.
Four signals catch most of it: task success rate, tool error rate by tool, cost per request by request class, and p95 latency. Note what is not on the list. Total spend is not an alert, because it rises when you grow, which trains a team to ignore it within two weeks. Cost per request rising is always a signal, and it is a four-line SQL query away.
Alert on rates and distributions, never on individual traces. An agent that fails one run in two hundred is normal; an agent whose failure rate moved from 0.5% to 4% overnight is an incident, and only the second is worth waking someone for. Output-distribution drift deserves its own alert, because it is the only signal that catches a prompt edit that broke nothing and changed everything.
The three most expensive failures in this table share a property: they succeed. A wrong-but-plausible retrieval, a wrong-but-successful tool selection, and a cache serving a confident answer to a different question all return 200 OK. No error rate moves. That is exactly why trajectory evaluation exists, covered in how to build an eval suite for an agent.
| Component | Failure | Blast radius | Detection signal | Time to detect | Auto-recoverable? |
|---|---|---|---|---|---|
| Gateway | Holds a connection open across a 4-minute run; LB kills it at 60s | Single user | 5xx rate on the sync path; client-side timeout metric | Seconds | No — architecture change |
| Router | Routes to a cheaper model; quality regresses silently | All traffic | Judge score on sampled traffic; distribution drift | Days without an eval; hours with one | No |
| Context assembly | Timestamp in the system prompt sets cache hit rate to zero | All traffic | cached_input_tokens near zero on every call | Minutes if recorded; a month if not | Yes, once found |
| Retrieval | Returns plausible-but-wrong chunks; the answer is confident | Single request | Groundedness check; citation span validation failure rate | Only via eval | No |
| Tool call | Duplicate side effect on retry — two emails, two charges | Single user, externally visible | Duplicate idempotency_key attempts; customer complaint | Minutes to never | Yes, with the key |
| Tool call | A tool returns 40K tokens and blows the context window | Single run | result_size distribution p99; context-overflow errors | Seconds | Yes, with truncation at ingestion |
| Agent loop | Never terminates; oscillates between two tools for 90 turns | Single run, large cost | Step-budget-exceeded rate; repeated-call hash detector | Immediate with budgets; never without | Yes — terminate into stalled |
| Model provider | 529 / capacity error under load | All traffic | Provider error rate by model | Seconds | Yes — fallback route |
| Memory | Stale memory contradicts fresh retrieval; both reach the model | Single user | Contradiction rate in judged samples | Only via eval | No |
| Queue | Poison message re-delivered forever, burning tokens each time | One tenant, whole budget | Redelivery count; DLQ depth | Minutes | Yes — DLQ after N |
| Evals | Golden set goes stale; suite passes while production degrades | All traffic | Divergence between offline pass rate and online judge score | Weeks | No |
| Cost | A provider price change silently rewrites every dashboard | Reporting integrity | price_snapshot_id mismatch against current rate card | Next month's invoice | Yes, with snapshots |
| Signal | Window | Threshold type | Page or ticket? | |
|---|---|---|---|---|
| Task success rate | Success rate by request class | 1 hour rolling | Relative to trailing 7-day baseline | Page |
| Tool error rate by tool | Errors / calls, per tool name | 15 minutes | Absolute, per tool | Page if >20%, ticket otherwise |
| Cost per request by class | cost_micros / requests, per class | Week over week | Relative, +25% | Ticket |
| p95 and p99 latency | Never the mean — the distribution is bimodal | 15 minutes | Absolute, per class | Page on p99 |
| Output distribution drift | This week vs trailing 30 days, per criterion | Daily | Bucketed comparison | Ticket with a named owner |
How do you know the system works at all?
Three eval layers, built in a specific order, over about three months. Final-answer evaluation alone is nearly useless for agents, because a right answer reached by a wrong path is a system you can neither trust nor improve. Trajectory evaluation, scoring the ordered sequence of plans, tool calls and handoffs against a golden path, is where agent regressions actually show up.
The ordering matters more than the tooling. Week one: traces and cost attribution, nothing else. Week two: a forty-case smoke suite of purely deterministic assertions that runs in CI in under ninety seconds. Weeks three to six: a golden set mined from production traces, not imagination, targeting several hundred cases. Month two: a calibrated judge, shipped with its calibration report. Month three: online judges on ten to twenty per cent of traffic, plus drift alerts.
Every vendor page shows the full stack. Almost nobody sequences it, and sequencing is what an under-resourced team actually needs: a team with no evals cannot build all five layers at once and will build none if told to.
The budget nobody plans for is the one this section exists to name. Judge calls are a recurring cost line that grows with traffic. Trace storage scales with steps, not requests, so a twenty-step agent run producing twenty to sixty spans puts span volume one to two orders of magnitude above request volume. That is why observability cost ambushes agent teams specifically, and why it belongs in the architecture budget, not a footnote.
- Week 1Traces and cost, nothing else
OTel spans with run_id, tenant_id, tokens, cache status and a price snapshot. Explicitly skip the judge. Roughly two engineer-days.
- Week 2A 40-case CI smoke suite
Deterministic assertions only: schema conformance, required tool called, forbidden tool not called, budget respected, output parses. Under 90 seconds or it gets skipped. Two days.
- Weeks 3‑6A golden set from production traces
Target several hundred cases, stratified across failure classes rather than the happy path. This is the phase that cannot be shortcut and the one that gets cut. Eight to twelve days.
- Month 2A calibrated LLM judge
Rubric first, human-labelled set second, agreement report third. Ship the calibration report alongside the judge and re-run it whenever the judge's model version changes.
- Month 3+Online judges and drift alerts
Sample 10-20% of production traffic. Add per-segment distribution drift, which is the only alert that catches a prompt edit that broke nothing and changed everything.
What can you skip in year one?
A vector database, multi-agent, a framework, and specialised memory infrastructure. Each recommendation costs me consulting revenue, which is the main reason to believe the rest of this post.
You probably do not need a vector database in month one. Postgres with pgvector handles millions of chunks comfortably, keeps your ACLs in the same transaction as your data (which matters enormously for permission-aware retrieval), and removes a whole system from your operational surface. Reach for a dedicated vector store when you have measured a latency or scale problem, not when you have read about one.
You probably do not need multi-agent. Anthropic's January 2026 guidance is blunt: in their testing, multi-agent implementations typically use three to ten times more tokens than single-agent approaches for equivalent tasks, and they describe teams investing months in elaborate architectures only to find better prompting on a single agent matched them. The decision procedure is in when a multi-agent system is actually worth the token premium.
You probably do not need a framework. LangGraph, the OpenAI Agents SDK, the Claude Agent SDK and CrewAI all give you real things: persistence, handoffs, lifecycle hooks, protocol support. None gives you your budgets, your idempotency keys, your journal schema or your cost attribution, and those are the parts that decide whether the system survives contact with customers. What you do need from day one is cost attribution, and it takes an afternoon.
- Cost attribution middleware with a price snapshot — one afternoon, changes every later decision
- The run/step journal in your own database, queryable with SQL
- Three budgets: steps, tokens, wall-clock, each terminating into a named status
- Idempotency keys on every irreversible tool, persisted before the side effect
- A tool gateway with a per-agent allowlist, even if you have three tools
- Forty deterministic eval cases in CI
- A dedicated vector database — Postgres and pgvector until you have a measured limit
- Multi-agent orchestration — Anthropic reports 3-10x tokens for equivalent tasks
- A managed router product — a policy table and a switch statement first
- Specialised memory infrastructure — add it when you can name the query it answers
What does this cost to build, in engineer-weeks?
Ten to fourteen engineer-weeks for a two-person team to reach a production-grade version of the nine layers, of which roughly a third is the eval and observability work that gets cut first and regretted most. Dollars are rarely the constraint for the teams asking. Two senior engineers off the roadmap for a quarter is the constraint, and any estimate given in dollars but not headcount was written by someone who has never had to defend a plan.
The distribution is uneven and worth knowing in advance. The gateway, router and a working synchronous path are about a week. The agent runtime with all its guards is two to three weeks, and the guards are most of it. The tool layer with a gateway, allowlist and schema pinning is a week and a half. Durable execution on a queue plus a journal is a week; on Temporal or Restate it is faster to a demo and slower to a confident production posture, because you have also bought an operational dependency.
Then the part teams delete: three to four weeks of evals and observability. Instrumentation, a CI suite, a golden set mined from real traces, a calibrated judge, and the alerting that makes any of it actionable. This work has no demo, no screenshot and no visible output until the first time it catches a regression, which is exactly why it is the first thing cut when a date moves.
After launch, budget two to four engineer-days a month of steady-state maintenance: provider changes, model deprecations, dependency bumps, cost and latency review, plus an on-call rotation someone has to actually want. If nobody on your team wants that pager, that is a real signal, and it is what AI product development services exist for.
| Layer | Engineer-weeks (2-person team) | What it actually contains | Cut it and you get |
|---|---|---|---|
| Gateway + router + sync path | 1.0 | Auth, rate limit, tenant resolution, policy table, fallback routing | Nothing at first, then a provider outage takes you down |
| Context assembly | 1.5 | Retrieval, token budget, cache-prefix ordering, deterministic serialisation | A bill three times larger than it needs to be |
| Agent runtime + guards | 2.5 | The loop, three budgets, no-progress detection, validated tool calls, journal writes | A run that loops 90 times overnight |
| Tool / MCP layer | 1.5 | Gateway, allowlist, schema pinning, credential injection, result bounding, audit log | An unvetted tool description reaching your model |
| Durable execution | 1.0 | Queue, lease and heartbeat, retries, DLQ, resumption from the journal | Runs lost on every deploy |
| Evals + observability | 3.5 | OTel spans, cost attribution, CI suite, golden set, calibrated judge, drift alerts | No way to tell a prompt change from a regression |
| Total | 11.0 | Plus 2-4 engineer-days a month of steady-state maintenance | A demo with customers attached |
“Dollars are not my constraint. Two senior engineers off the roadmap for a quarter is my constraint, and any post that prices a system in dollars but not headcount was written for someone who has never had to defend a plan.”— The objection this section exists to answer
What do real products built on this look like?
The same nine layers with different boxes filled in, and wildly different cost shapes. That last part is worth internalising: there is no single cost-optimisation strategy, because the largest line item moves with the product.
In an AI interview platform, the dominant cost is real-time voice, not the scoring LLM, the inverse of what almost every team assumes when they budget one. The hard parts are not the models either: signal fusion for proctoring, calibrated rubric scoring with validated evidence spans, and getting the ordering right across three asynchronous artefact producers. The whole system is in the full architecture of an AI interview platform.
In a RAG system over private documents, the dominant architectural risk is permissions, and the dominant cost line is generation output tokens. Post-filtering an approximate-nearest-neighbour result for access control silently destroys recall: a top-50 retrieval where 8% of chunks pass the user's ACL leaves four usable chunks. That is why permission handling belongs in the retrieval design, not a wrapper. The teardown is at RAG over private documents, end to end.
In a voice product, the dominant cost is per-minute transport and speech, and the architecture is judged on tail latency, not throughput. I run production voice agents at about 2.5 cents a minute on a custom LiveKit stack, down from about 10 cents on a managed platform, and the full build-versus-buy arithmetic is in the voice AI build vs buy break-even. The pattern across all three: the reference architecture is stable, the cost profile is not.
AI PRODUCT ARCHITECTURE — REFERENCE DESIGN (Axionry, Aug 2026)
Nine layers. A prototype implements three.
1 GATEWAY auth, rate limit, tenant, idempotency key, 202+run_id
cost ~$0 | fails: holds a socket open across a 4-min run
2 MODEL ROUTER policy table, cache lookup, 429/5xx fallback
cost NEGATIVE (-30..-60%) | fails: silent quality regression
3 CONTEXT ASSEMBLY retrieval, memory read, tool subset, token budget, cache order
sets 60-80% of the bill | fails: timestamp zeroes the cache
4 AGENT RUNTIME loop + step/token/wall-clock budgets + no-progress detector
$0.04-$1.05 per run | fails: never terminates
5 TOOL / MCP gateway, allowlist, schema pinning, credential injection
7,500 tok/turn at 50 tools | fails: 40K-token tool result
6 MEMORY episodic / semantic / procedural, Postgres + pgvector
$0.0002-$0.002 per read | fails: stale memory vs fresh retrieval
7 DURABLE EXEC journal, replay, timers, resumption
~$0.0001 per step | fails: resumed run repeats a paid call
8 EVALS deterministic CI suite -> golden set -> calibrated judge
$0.002-$0.02 per judged trace | fails: stale set passes
9 OBSERVABILITY OTel GenAI spans + cost meter + price snapshot
scales with STEPS not requests | fails: PII past retention
RULE: no architecture claim without a failure mode and a cost number.AI product architecture: common questions
→What are the layers of an AI product architecture?
Nine: gateway and API, model router, context assembly, agent runtime, tool and MCP layer, memory, durable execution, evals, and observability. A typical prototype implements three of them: a model call, a prompt and a UI. Evals and observability are cross-cutting, not sequential, which is why bolting them on last is so expensive: you end up instrumenting a system you can no longer change cheaply.
→How much does it cost to run an AI product per request?
At August 2026 list prices, a worked example puts a simple completion at about $0.0006, a RAG answer at about $0.017, a five-turn agent run at about $0.14 and a twenty-turn agent run at about $1.05, all uncached on a mid tier of $2 per million input and $10 per million output tokens. Prefix caching cuts the twenty-turn run to roughly $0.24. Human review, where it exists, is usually larger than every model line combined.
→Do I need a vector database for my AI product?
Almost certainly not in month one. Postgres with the pgvector extension handles millions of chunks comfortably and keeps access-control data in the same transaction as the vectors, which matters a great deal for permission-aware retrieval. Add a dedicated vector store when you have measured a latency or scale limit you cannot solve in Postgres, not because a comparison table suggested it.
→Should an AI product use an event-driven or request/response architecture?
Event-driven for anything agentic; request/response for a single model call that resolves in under two seconds with no tools. Agent work is long-running, bursty, retryable and partially failing, which is the exact workload shape queues exist for. The honest cost of the queue is ordering, poison messages, dead-letter handling, at-least-once semantics that force idempotency on you, and a queue-depth graph someone now has to watch.
→How long does it take to build a production AI system?
About eleven engineer-weeks for a two-person team across all nine layers, of which roughly three and a half weeks is evals and observability. After launch, budget two to four engineer-days a month of steady-state maintenance plus an on-call rotation. The eval and observability block is the one most often cut when a date moves, and it is the one whose absence you notice first.
→What is the difference between an AI prototype and a production AI system?
Six layers. A prototype has a model call, a prompt and a UI. A production system adds a router, context assembly with a token budget, an agent runtime with step, token and wall-clock budgets, a tool layer with an allowlist and schema validation, durable execution with a queryable journal, and evals and observability that cross-cut everything. None of those six become unnecessary as models improve.