Request a callbackBook a call
← All posts

AI Product Architecture: The Reference Design for Production AI Systems (2026)

TL;DR
  • 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.
The nine-layer reference stack
1 · Gateway / API

Auth, rate limit, tenant resolution, idempotency key minting, 202-with-run-id for async work.

+3-8ms · fails: all traffic
2 · Model router

Policy table mapping request class to model, cache lookup, fallback on 429/5xx. This layer subtracts cost.

negative cost
3 · Context assembly

Retrieval, memory read, tool-catalogue resolution, token budget enforcement, cache-prefix ordering.

sets 60-80% of spend
4 · Agent runtime

The loop, plus step budget, token budget, wall-clock budget, no-progress detection and the journal write.

fails: runaway loop
5 · Tool / MCP layer

Gateway, per-agent allowlist, schema validation, credential injection, result bounding, audit log.

7,500 tok/turn at 50 tools
6 · Memory

Episodic, semantic and procedural. Postgres plus pgvector before anything specialised.

skippable in v1
7 · Durable execution

Journal, replay, timers, resumption after a worker crash. A queue plus a step table is a valid implementation.

fails: duplicate side effect
8 · Evals

Cross-cutting. Offline suite in CI, online judges on sampled traffic, drift alerts on distributions.

cross-cutting
9 · Observability

Cross-cutting. OTel GenAI spans, cost meter with a price snapshot, tail sampling on error and cost outliers.

scales with steps
Layers 8 and 9 are drawn last but they run last in nothing. They cross-cut every other layer. Bolt them on at the end and you end up instrumenting a system you can no longer change cheaply. Layer 2 is the only layer with a negative cost contribution: a router and a cache subtract from the bill instead of adding to it.

What 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.

LayerWhat it doesTypical implementationCost per requestPrimary failure modeSkip in v1?
1 Gateway / APIAuthenticates, rate limits, resolves tenant, mints the idempotency key, returns 202 + run_id for async workFastify or FastAPI behind a load balancer~$0 (compute only)Holds an HTTP connection open across a four-minute agent run and times outNo
2 Model routerMaps request class to model, checks the cache, falls back on 429/5xxA policy table and a switch statement; a router product only if traffic is unpredictableNegative — subtracts 30-60%Silent quality regression with no eval to catch itNo
3 Context assemblyRetrieval, memory read, tool-catalogue resolution, token-budget enforcement, cache-prefix orderingYour own code. This is the highest-leverage 200 lines in the systemSets 60-80% of the billA timestamp at the top of the prompt zeroes your cache hit rateNo
4 Agent runtimeThe loop plus every guard around it: step, token and wall-clock budgets, no-progress detection, journal writesIn-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 invoiceOnly if you have no tools
5 Tool / MCP layerGateway, per-agent allowlist, schema validation, scoped credential injection, result bounding, audit logMCP servers behind a gateway; plain function calls for tools you own7,500 tokens/turn at 50 tools = ~$0.30 per 20-turn runA tool returns 40K tokens and blows the context windowGateway: no. MCP itself: often yes
6 MemoryEpisodic (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 readStale memory contradicts fresh retrieval and both reach the modelYes, usually
7 Durable executionJournal, replay, timers, resumption after a worker crashA queue plus a run/step table. Temporal, Restate or Inngest when you need timers and signals~$0.0001 per step in storageA resumed run re-executes a paid side effectNo — a journal, at minimum
8 EvalsOffline suite in CI, online judges on sampled traffic, drift alerts on output distributionsDeterministic assertions first, Langfuse / LangSmith / Braintrust second$0.002-$0.02 per judged traceThe golden set goes stale and passes while production degradesDeterministic: no. Judges: month two
9 ObservabilityOTel GenAI spans, cost meter with a price snapshot, tail sampling on error and cost outliersOpenTelemetry GenAI conventions, then pick a backendScales with steps, not requestsPII retained in traces past policyNo

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.

The async path
One agentic request, end to end, with tokens and cost on every model callClientGatewayQueueWorkerRouterLLMMCP GWPostgres
POST /runs + Idempotency-Key
INSERT agent_run (status=queued)
SQL
publish run.created
202 Accepted + run_id
worker leases run.created
resolve policy for class=agent.plan
turn 1: 9.4k in / 0.25k out
$0.021
tool_call search_docs(args)
INSERT agent_step + idempotency_key
before the side effect
tools/call · Mcp-Name: search_docs
result, truncated to 1.4k tokens
full body stored in journal
turn 2: 11.05k in / 0.25k out
$0.025 uncached, $0.004 cached
final answer
UPDATE agent_run (succeeded, cost_micros)
SSE push run.completed
Two turns of a run that would normally take twelve. Note where the idempotency key is written: before the tool executes, not after. Note also the gap between $0.025 uncached and $0.004 cached on turn two. That gap is the whole argument for ordering your context so the stable parts come first, and it compounds on every later turn.

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 shapeInput tokensOutput tokensTier (in/out per 1M)UncachedWith prefix cachePer 1,000 requests
Simple completion1,200300Cheap · $0.20 / $1.20$0.00060$0.00042$0.42 - $0.60
RAG answer6,400 (4,000 static)400Mid · $2 / $10$0.01680$0.00960$9.60 - $16.80
5-turn agent run63,500 cumulative1,250Mid · $2 / $10$0.13950$0.06200$62 - $140
20-turn agent run501,500 cumulative5,000Mid · $2 / $10$1.05300$0.24400$244 - $1,053
Assumptions9,000-token static prefix (800 system + 7,500 catalogue + 700 few-shot); history grows 1,650 tokens per completed turn250 tokens per turnList prices checked 24 Aug 2026No cacheCache read 0.10x input, write 1.25x, TTL not expired mid-runExcludes tools, storage and review
Where a $1.05 agent run actually goes
$ per 20-turn agent run, uncached, mid tierlower is better
Tool catalogue (50 tools x 7,500 tok x 20 turns)28.5% of the run$0.300
Conversation history re-sent each turngrows quadratically$0.627
System prompt + few-shot1,500 tok x 20$0.060
Task input400 tok x 20$0.016
Output tokens250 tok/turn at $10/1M$0.050
Total$1.053
The single largest controllable line is not the model and not the output. It is the tool catalogue and the re-sent history, together 88% of this run. That is why tool subsetting and cache-prefix ordering outrank model selection as levers, and why the first question to ask about an expensive agent is how many tools it can see.
The superlinear fact, stated four ways
3.3x
cost multiplier when a run goes from 20 turns to 40 turns
77%
cost reduction on the same 20-turn run with prefix caching held warm
-$0.81
150,000
tokens of pure tool catalogue in a 20-turn run with 50 tools attached
85%
context reduction Anthropic reports from on-demand tool discovery (77K to 8.7K tokens)
vendor-measured
The last figure is Anthropic's own, published with its advanced tool-use features: deferring tool definitions and letting the model search for them cut context from 77,000 to 8,700 tokens, an 85% reduction, while tool-selection accuracy on their internal evaluation rose from 79.5% to 88.1% on Claude Opus 4.5. A rare case where the cost lever and the accuracy lever are the same lever.

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.

001_agent_runs.sql
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;
Postgres DDL you can run today. Deliberately boring. cost_micros as bigint means a run that costs $1.0530 is stored as 1053000 and never drifts. The status check constraint means an unrecognised state fails the INSERT instead of surfacing in a dashboard three weeks later. And the unique index on idempotency_key is the whole duplicate-side-effect story in one line.
Checklist
The four kinds of state, and where each belongs
  • 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.
Only the third row is genuinely optional in year one. The other four are the difference between a system you can reason about and one you can only restart.

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.

Pick the backbone in ten minutes
Should this AI feature be event-driven or request/response?
Single model call, no tools, resolves under 2s
Request/response

A queue here buys you nothing and costs you a week of operational surface. Stream the tokens and move on.

One or two tool calls, resolves under 20s
Request/response with a journal

Keep the HTTP handler, but write the run and step rows anyway. You get debuggability without the queue.

Multi-step agent, tools with side effects, minutes not seconds
Event-driven

Enqueue, return 202 with a run_id, stream progress over SSE. Anything else fights your infrastructure's timeouts.

Bursty batch work — imports, bulk scoring, nightly runs
Event-driven

The queue is the backpressure mechanism. Without it, a 1,000-document import becomes a provider rate-limit incident.

Long-running with human approval in the middle
Event-driven plus durable execution

You need timers, signals and resumption across days. This is where Temporal, Restate or Inngest earn their operational cost.

Two of the five answers say do not build the queue. That is roughly the distribution I see in real conversations: most AI features described as agentic are a single model call with a retrieval step in front, and should be built as such.

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.

idempotent-tool-call.ts
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;
}
The whole pattern in thirty lines. The only subtle part is canonicalJson: if the argument object serialises differently on two runs, every key is unique and the table becomes a very expensive log. The reconcile() branch closes the crash window between the external call and the response write, and it is the part every blog post on this leaves out.
Retry strategy by error class
  1. 1
    Transport 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.

  2. 2
    Rate 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.

  3. 3
    Invalid 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.

  4. 4
    Tool 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.

Four error classes, four different strategies, and only two involve sending the same bytes again. The fourth is the one teams most often get wrong: wrapping a tool failure in a try/catch that terminates the run throws away the agent's ability to recover from an ordinary business condition.

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.

ComponentFailureBlast radiusDetection signalTime to detectAuto-recoverable?
GatewayHolds a connection open across a 4-minute run; LB kills it at 60sSingle user5xx rate on the sync path; client-side timeout metricSecondsNo — architecture change
RouterRoutes to a cheaper model; quality regresses silentlyAll trafficJudge score on sampled traffic; distribution driftDays without an eval; hours with oneNo
Context assemblyTimestamp in the system prompt sets cache hit rate to zeroAll trafficcached_input_tokens near zero on every callMinutes if recorded; a month if notYes, once found
RetrievalReturns plausible-but-wrong chunks; the answer is confidentSingle requestGroundedness check; citation span validation failure rateOnly via evalNo
Tool callDuplicate side effect on retry — two emails, two chargesSingle user, externally visibleDuplicate idempotency_key attempts; customer complaintMinutes to neverYes, with the key
Tool callA tool returns 40K tokens and blows the context windowSingle runresult_size distribution p99; context-overflow errorsSecondsYes, with truncation at ingestion
Agent loopNever terminates; oscillates between two tools for 90 turnsSingle run, large costStep-budget-exceeded rate; repeated-call hash detectorImmediate with budgets; never withoutYes — terminate into stalled
Model provider529 / capacity error under loadAll trafficProvider error rate by modelSecondsYes — fallback route
MemoryStale memory contradicts fresh retrieval; both reach the modelSingle userContradiction rate in judged samplesOnly via evalNo
QueuePoison message re-delivered forever, burning tokens each timeOne tenant, whole budgetRedelivery count; DLQ depthMinutesYes — DLQ after N
EvalsGolden set goes stale; suite passes while production degradesAll trafficDivergence between offline pass rate and online judge scoreWeeksNo
CostA provider price change silently rewrites every dashboardReporting integrityprice_snapshot_id mismatch against current rate cardNext month's invoiceYes, with snapshots
The four alerts that catch most incidents
 SignalWindowThreshold typePage or ticket?
Task success rateSuccess rate by request class1 hour rollingRelative to trailing 7-day baselinePage
Tool error rate by toolErrors / calls, per tool name15 minutesAbsolute, per toolPage if >20%, ticket otherwise
Cost per request by classcost_micros / requests, per classWeek over weekRelative, +25%Ticket
p95 and p99 latencyNever the mean — the distribution is bimodal15 minutesAbsolute, per classPage on p99
Output distribution driftThis week vs trailing 30 days, per criterionDailyBucketed comparisonTicket with a named owner
The mean latency of an agent is meaningless because the distribution is bimodal: a cache hit and a cold twelve-step run are not the same population. Quote p95 and p99 or quote nothing. The last row is the alert almost nobody has, and the only one that catches a prompt change that broke no tests.

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.

The eval maturity ladder, sequenced
  1. Week 1
    Traces 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.

  2. Week 2
    A 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.

  3. Weeks 3‑6
    A 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.

  4. Month 2
    A 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.

  5. 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.

Most teams stop after week two. That is a defensible choice for a small team and vastly better than stopping at week zero. What is not defensible is claiming a quality bar you have never measured.

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.

Build it now, or build it when you have evidence
pick
Build in v1
Six things, none of them glamorous
  • 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
Defer until measured
Four things you will be sold early
  • 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
The left column is roughly three engineer-weeks and the difference between a system you can operate and a demo with customers attached. The right column is where most AI infrastructure budgets go in month two, before anyone has a number that justifies it.

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.

LayerEngineer-weeks (2-person team)What it actually containsCut it and you get
Gateway + router + sync path1.0Auth, rate limit, tenant resolution, policy table, fallback routingNothing at first, then a provider outage takes you down
Context assembly1.5Retrieval, token budget, cache-prefix ordering, deterministic serialisationA bill three times larger than it needs to be
Agent runtime + guards2.5The loop, three budgets, no-progress detection, validated tool calls, journal writesA run that loops 90 times overnight
Tool / MCP layer1.5Gateway, allowlist, schema pinning, credential injection, result bounding, audit logAn unvetted tool description reaching your model
Durable execution1.0Queue, lease and heartbeat, retries, DLQ, resumption from the journalRuns lost on every deploy
Evals + observability3.5OTel spans, cost attribution, CI suite, golden set, calibrated judge, drift alertsNo way to tell a prompt change from a regression
Total11.0Plus 2-4 engineer-days a month of steady-state maintenanceA 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.

architecture-summary.txt
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.
A plain-text summary of the reference design, deliberately formatted to be quotable. Copy it into your own architecture doc and replace the figures with your measured ones. The point of the exercise: every layer ends up with a number next to it.

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.

Ready to talk numbers?

Twenty minutes, straight to the engineer. No sales rep, no deck.