🧩 Agentic Scaling how consumer AI scales · 1 → 1B

Reference Architecture — the build that proved it

Every other chapter explains a concept. This one describes the reference build those concepts were tested on: a Pi-style personal assistant for three fictional people (Maya, Raj, Lena), with a hand-built control plane you can watch, deployed for real on an edge platform plus one cloud region. It is described in a neutral voice and at the altitude of components and choices — not a tour of screens, not a code listing. The point of a reference build is that the ideas on this site were not asserted; they were run, measured and, in one case, honestly missed.

One sentence version of the thesis as this build states it: personalization is a context pack, scale is stateless workers plus a reconcile loop, and the same loop a managed autoscaler runs in production is the one drawn by hand on the dashboard.

📖 Story: why build it instead of drawing it

A diagram of an autoscaler is easy to nod along to and hard to believe. The reference build exists because one question kept surfacing: "fine, but how does this scale?" — and the only convincing answer is a control loop you can edit while it runs, a fleet that visibly grows and shrinks, and a live URL whose compute is scaled by the same mechanism. So the build was done twice at once: a teaching control plane inside the app, dialed to one user per instance so every change is visible, and a real deployment whose managed autoscaler does the identical job on aggregate load. The teaching layer explains the production layer; the production layer proves the teaching layer is not theatre.

What was built, in one picture?

The topology has five lanes. Requests enter at the edge, are served by autoscaled stateless compute, read and write shared state, defer slow work to a queue, and are watched and gated by cross-cutting services.

flowchart TB
  subgraph EDGE["Edge / UI (global CDN)"]
    UI["Chat UI + inspector"] --> GATE["Gate + proxy (edge)"]
  end
  subgraph COMPUTE["Autoscaling compute (managed autoscaler)"]
    API["API gateway"] --> WK["Agent workers · the fleet"]
    EV["Eval / controller service"]
  end
  subgraph STATE["State"]
    DB["Relational store + vector index"]
    HOT["Hot store · sessions, rate limits, fleet state"]
    VS["Managed vector search (at scale)"]
  end
  subgraph ASYNC["Async"]
    Q["Queue · write-back, eval runs"]
  end
  subgraph XC["Cross-cutting"]
    OBS["SLOs · canary · CI/CD"]
    MOD["Model service + secrets"]
    TR["Tool registry (MCP)"]
  end
  GATE --> API
  WK --> DB
  WK --> HOT
  WK --> Q
  WK --> MOD
  WK --> TR
  EV -. gates .-> OBS

The edge platform hosts the UI and an email-gated entry; the cloud region hosts everything that thinks or remembers. Vendor names are deliberately absent from the rest of this chapter: each box is named for the concept it carries, because the concept is what transfers.

Which component does what, and why?

Component Role Why this choice What it becomes at scale
Edge UI + gate serves the chat surface, gates entry, proxies API calls a global CDN and edge functions for free; identity resolved before compute is touched unchanged — already global
API gateway auth, rate limits, session lookup, streaming out one enforcement point for entitlements and abuse limits more replicas; regional gateways
Agent workers run the nine-stage turn: rewrite → retrieval → context pack → agentic loop → generate → write-back stateless worker contract: nothing per-user lives here, so scale is replicas ~100 users per replica, autoscaled on concurrency and queue depth
Eval / controller runs the golden set, scores substance + empathy, gates deploys; hosts the teaching reconcile loop quality and capacity are both control loops — same service shape a fleet of judges; policy per tenant
Relational store + vector index personas, memory namespaces, embeddings, audit, eval results in one store one durable store for facts and vectors keeps write-back transactional and the boundary simple read replicas → shard by user → dedicated vector service
Hot store sessions, rate-limit counters, fleet state, queue millisecond keyed lookup for the parts of the pack that must be exact and fast clustered, regional
Queue memory write-back and eval runs off the hot path the turn returns in seconds; compounding memory can take minutes fan-out pub/sub, per-region
Managed vector search the at-scale swap for episodic recall a plug-in seam behind the same retrieval interface billions of chunks, ANN indexes
Model service multi-provider calls, model routing, tiering, fallback, timeout the bill is tokens; routing is the lever prefix caching, canary by model version
Tool registry (MCP) self-describing tools the agentic loop may call, scoped per user N+M instead of N×M integrations a governed catalogue with per-tenant entitlements
Secrets manager provider keys and gate secrets, injected at runtime keys never in code, never in a prompt, never on screen workload identity, rotation
SLOs · canary · CI/CD p50/p95, availability, error budget; traffic-split rollouts operate what you can see; roll back before an error budget burns per-tenant SLOs, automated rollback

🪤 Misconception — "one store for facts and vectors is a toy choice." At demo scale it is the right choice, and the reason is not convenience. A memory write-back that stores the fact, its embedding and its audit row in one transaction is how the namespace stays a real boundary. The dedicated vector service is a later swap behind the same interface, made when the index, not the design, runs out of room.

How was the control plane made watchable?

The centrepiece is a control plane that implements the desired-state loop of a Kubernetes HPA from first principles, in a form small enough to read. One policy knob — users per instance — sets the ratio; the controller reconciles the running fleet toward the target every time active users or the policy changes.

TEXT
desired = ceil(active_users / users_per_instance)
while running < desired: start_worker()             # scale OUT
while running > desired: stop_idle_worker()          # scale IN
                        or consolidate_then_stop()   # drain the least-loaded, then reclaim

Two details make it honest rather than decorative. Scale-in is real: when the ratio is raised and every worker is busy, the controller drains the least-loaded worker onto others with spare capacity and only then reclaims it — and rolls the moves back if a drain cannot complete, so an assignment is never lost. And the loop is idempotent: running it twice does nothing the second time, which is the property that lets a real autoscaler run it every few seconds.

stateDiagram-v2
  [*] --> Observe
  Observe --> Compute: active users · policy · triggers
  Compute --> ScaleOut: running < desired
  Compute --> ScaleIn: running > desired
  Compute --> Steady: running == desired
  ScaleOut --> Observe: start worker
  ScaleIn --> Observe: reclaim idle · or drain then reclaim
  Steady --> Observe: tick

The ratio is dialed to one user per instance in the build so the fleet visibly goes 1 → 2 → 3 as personas are switched, and back down as the knob is raised. That is a visualization. The production truth, stated in the fleet chapter, is roughly a hundred users per stateless replica, scaled on aggregate signals — concurrency, CPU, queue depth — which the same policy object also carries as trigger thresholds. The managed autoscaler serving the live deployment is that loop with the ratio replaced by concurrency; a Kubernetes HPA manifest for the same fleet is kept as an artifact so the equivalence is literal, not rhetorical.

⚠️ Pitfall — a control plane that only scales out. Most first attempts add replicas and never remove them, because removing means moving live assignments. A loop that cannot scale in is not a reconcile loop; it is a ratchet, and the bill shows it.

What was measured?

Every turn writes one record — tokens in, tokens out, cost, latency, model, retrieved chunk ids, tool calls, worker id, memory namespace — and an observability panel computes the operating numbers from those records.

Measure How it was taken What it showed
Per-turn tokens and cost counted from the provider response, priced per model, on every turn the context pack, not the question, dominates prompt tokens; cost per turn is a fraction of a cent on a small model and moves with tier, not with users
Latency p50 / p95 ring of recent turns, percentiles recomputed live low single-digit seconds end to end on a small model; the model call is the long pole, retrieval is milliseconds
Availability and error budget success ratio against a 99.5% SLO target budget remaining is the number that decides whether a canary widens
Queue depth pending write-backs and eval jobs the async seam absorbs bursts without touching turn latency
Fleet reconcile three active users at ratio 1 → three workers; ratio raised to 2 → two workers, users re-packed 2 + 1 scale out and in, with consolidation
Eval run with a gate nine golden-set cases grounded in seeded memories, judged on two axes 4 of 9 cases passed their per-case bar, while the aggregate gate passed: average substance 0.724 against a 0.70 gate, average empathy 0.831

The eval result deserves a plain reading. The gate was set at 0.70 on both axes, and the run's mean substance score landed just under it. That is the correct outcome for a regression gate on a new build: it discriminated. Tone (the empathy axis, scored against each persona's tone template) passed comfortably; substance on a few cases did not, and those cases pointed at retrieval choices, not at the model. A gate that everything sails through the first time is usually measuring nothing.

flowchart LR
  GS["Golden set · 9 cases · 3 personas"] --> RUN["Run each case through the live turn"]
  RUN --> J["Judge · substance vs expected · empathy vs tone target"]
  J --> G{"both ≥ 0.70?"}
  G -- yes --> PASS["pass · record run"]
  G -- no --> BLOCK["block deploy · record run"]
🔍 See it happen — one turn through the five lanes

Edge. Maya (a fictional product designer with a severe peanut allergy) asks "what should I cook tonight?" The edge resolves her session from a cookie, applies the rate limit, and proxies the turn to the gateway.

Compute. The gateway hands the turn to an agent worker — in the build, the worker the control plane assigned to Maya; in production, any replica. The worker runs the loop: an input guardrail, a query rewrite ("dinner recipe, dietary constraints"), a keyed lookup of Maya's profile card (allergy, location, tone preference — exact, no similarity involved), then top-K retrieval inside her memory namespace, which returns the memory "always checks ingredient labels" among others.

State. The context pack is assembled under an allocation budget — three personal slots, two general — so a high-scoring general recipe document cannot evict the allergy memory. Sketch of the record:

TEXT
turn 3c9a · user maya · worker w-agent-001 · namespace maya
profile: {allergy: peanut, location: SF, tone: warm}
memory: [m12 allergy, m07 short-visual]  general: [g3 weeknight-recipes]
model: small tier · tokens 1.9K in / 0.3K out · latency 2.4 s · fallback: false

Generate. The small-tier model answers warmly, suggests a peanut-free dish, and cites the allergy memory inline. The output guardrail passes it; the stream goes back through the edge.

Async + cross-cutting. A write-back job is queued ("Maya asked for weeknight dinner ideas"), the turn record lands in the audit table, and the observability panel's p95 and cost lines move by one turn. Nothing about this trace changes when the fleet has a hundred workers instead of one — only which worker answers.

What are the honest limits?

🎯 At consumer scale, the build's shape holds and its numbers do not. A lab with 100M weekly users keeps the same lanes — edge, stateless compute, sharded state, async, cross-cutting — but replaces every single-instance box with a sharded, replicated, regional one, and turns every hand-tuned knob (ratio, tier, budget) into a policy that a control loop moves. The reference build's claim is narrow and defensible: from one user to a billion is more replicas, more shards and more regions — not a redesign.

🧭 Enterprise mapping. The same build becomes an enterprise reference by attaching the constraints from the previous chapter at seams that already exist: the edge gate becomes SSO against a tenant directory, the memory namespace gains a region and a role list, the eval run is retained as compliance evidence, the tool registry gains per-tenant entitlements, and a human-in-the-loop queue sits between the agentic loop and the stream. None of the lanes move.

Start at the beginning with Start Here, or go straight to One Turn to follow the nine stages this build runs. Every term above has a card in the glossary.