Reference Architecture · Enterprise LLM Platform
Inference Fabric & Agentic Platformv0.4
A governed enterprise platform where agents may reason autonomously, but access to models, data, code execution, and irreversible actions stays under infrastructure control.
§1–§10 — the problem, the design, the controls, the tradeoffs, what it costs to operate, and when it is the wrong choice. About ten minutes. You can stop at the divider.
§11 onward — zones, tenancy, serving, inference, observability, and the appendices that specify all of it precisely enough to build from.
§ 1Executive summary
An agent is software that decides what to do next. That is what makes it useful, and it is also the whole problem: a traditional application follows code paths someone reviewed, while an agent chooses its own tools, in its own order, based on text it was given at runtime.
This architecture accepts the autonomy and removes the authority. The agent may plan, reason, and decide freely. It cannot reach a model, an enterprise system, an execution environment, or an irreversible action on its own — each of those passes through a separate control that authenticates the person behind the request and is able to refuse it.
The platform is built as two independent estates. One runs GPU model serving and nothing else. The other runs the agents, the controls, the audit trail, and the evaluation suite. They fail differently, scale differently, and cost differently, so they are operated and funded separately.
What that buys is accountability: every model call, tool action, approval, and failure in a run is attributable to a person and an organization, and reconstructable afterwards. What it costs is infrastructure — more moving parts, a GPU operations practice, and a platform team to run both. §8 states that bill explicitly.
The design is settled. One delivery decision remains open, and it is a question of who writes one component rather than of how the platform is shaped (§9).
The agent can reason freely. It cannot act freely.
1.1At a glance
| What it is | A self-hosted platform for running enterprise AI agents, plus the model infrastructure they run on |
| Who operates it | A platform team, on the organization's own GPU hardware and its own or managed Kubernetes |
| Core principle | Agents reason freely; every action they take passes through a control that can refuse it |
| Shape | Two independent estates — model infrastructure, and the agent platform |
| Controls | Four — model access, enterprise access, code execution, irreversible actions |
| Models | Self-hosted open-weight models; vendor APIs optional, behind the same control |
| Data posture | Nothing leaves the estate without passing a brokered allowlist. No vendor telemetry. |
| Status | Design complete. One delivery decision open — §9 |
§ 2Why this architecture exists
Five problems show up whenever an organization moves agents from a pilot into shared production. Each one is solved by a specific structural choice rather than by policy or training, because the failures are not the kind a person can be careful enough to avoid.
2.1Agents expand the security boundary
Traditional applications follow predetermined code paths. Agents decide which tools to use, in what sequence, based on content they read at runtime — a ticket, a document, a web page. That content can be written by someone with an interest in what the agent does next. The architecture limits the consequences by placing authorization outside the agent, in components the model cannot influence.
2.2Model-generated code is an untrusted workload
Agents that are useful for real work will generate and run code. There is no prompt that reliably prevents a model from emitting arbitrary shell or Python, so the useful question is not whether the code is safe but what it can reach. Running it inside the application platform would put a bad generation or a successful prompt injection directly next to everything else the platform runs.
2.3Enterprise AI needs accountability
When an agent modifies a ticket, queries a warehouse, executes code, or spends money on a model call, the organization needs to know who initiated it, what happened, why, and what it cost. That is an audit requirement in regulated settings and an operational requirement everywhere else — without it, nobody can debug an agent, and nobody can defend one.
2.4AI infrastructure creates unpredictable cost
Inference is expensive and scales on a different curve from the applications consuming it. Mixed into a general application platform, GPU capacity becomes both the bottleneck and the budget line nobody can attribute. Separating model infrastructure lets capacity, routing, and spend be managed — and charged back — independently of the teams building agents.
2.5Long-running agents must survive failures
Business workflows span minutes, hours, or days, and the valuable ones pause for a human decision somewhere in the middle. The platform has to stop, resume, and recover across restarts and deployments without repeating irreversible actions — which is a durability problem, not an AI problem, and is solved with the same rigor as any other.
None of these five is about model quality. A better model makes an agent more capable at each of them and changes none of the controls, which is the test a reference architecture should pass: it should still be correct after the models are replaced.
§ 3The architecture at a glance
Four areas, and one rule about how they connect. Everything else in this document is detail beneath this picture.
| Area | What lives there |
|---|---|
| People and enterprise systems | Where requests enter, who approves decisions, and the business systems agents are permitted to reach |
| Agent platform | Trusted orchestration, the four controls, policy, evaluation, and the audit trail |
| Isolated execution | Where model-generated code runs, briefly, in an environment thrown away afterwards |
| Model infrastructure | GPU model serving, on its own estate, with no route to the internet |
The rule is that the agent sits inside the platform and reaches the other three areas only through a control built for that purpose.
The implementation divides these four areas into seven security zones, so that components with different trust levels are separated by a network boundary rather than by convention. That division is an implementation concern and is specified in §11; nothing in §1–§10 depends on it.
§ 4Four controls around every agent
These are the part of the architecture worth remembering. If you retain one thing from this document, it should be that an agent's authority is held by four brokers rather than by the agent.
4.1The concerns they answer
| Leadership concern | Architecture response |
|---|---|
| Can an agent access something it shouldn't? | Identity follows every action, and every model and tool call passes through policy enforcement that authorizes the person behind it |
| Can an agent leak credentials or sensitive data? | Agents receive no enterprise credentials at all; access is brokered, scoped, and short-lived, and nothing leaves the estate unbrokered |
| Can generated code damage the platform? | Generated code executes only in isolated, disposable environments with no network and no persistence |
| Can an agent take an action we can't undo? | Actions designated irreversible stop for a named human decision, which is recorded |
| Can we prove what happened? | Every action is traced, checkpointed, attributable to a person and an organization, and recoverable |
4.2The controls
AI Gateway — governs model access
Every call to a language model, whether self-hosted or a vendor API, is made by one service on the caller's behalf. Model access is at once a permissions question, a budget question, a data-leakage question, and a cost-attribution question; answering those four in one place is tractable, and answering them at every call site is not. Nothing else in the platform — not an agent, not the evaluation suite, not a debugging script — can reach a model.
Tool Gateway — governs enterprise access
An agent asks for an action against a business system; a tool service decides whether that identity may take it, and performs it. The agent holds no credential to anything, because an agent's behavior is only as predictable as the model driving it, and models can be steered by the documents they read. Agents also cannot see tools they are not permitted to call — an unavailable tool named in a prompt is an invitation to work around it.
Sandbox Broker — governs code execution
Code the model wrote runs somewhere that can be thrown away: a fresh isolated environment per invocation, seeded with only the files that task needs, with no ambient network, destroyed on completion, never reused across teams. Treating generated code as hostile turns an unanswerable question — is this code safe? — into an answerable one: what can it reach?
Approval Broker — governs irreversible actions
Some actions require a person, and the agent is not that person. Which actions those are is configurable, because it is a risk-tolerance decision rather than a technical one. When a run reaches a gated action it pauses, saves its position, releases the machine it was using, and waits — for minutes or for days — then resumes exactly where it stopped once someone decides. The deciding identity is part of the audit record.
4.3Engineering guarantees
Underneath the four controls sit six invariants. They are the load-bearing rules of the platform: every structural choice in the technical reference exists to make one of them enforceable, and every design review of a change should start by asking which of the six it touches.
| # | Invariant | What it removes |
|---|---|---|
| 1 | Identity follows every request — authorize the caller, never the session | Privilege escalation from stale session state, even when the access-control code is correct |
| 2 | All model calls pass through one gateway | A second, unmetered, unpoliced path to a model |
| 3 | Agents never own enterprise credentials | A compromised agent turning into a compromised credential |
| 4 | Generated code is treated as hostile | Blast radius from a bad generation or a successful injection |
| 5 | Every operation is traceable end to end | Unanswerable questions after an incident |
| 6 | Agent execution can safely resume | Work lost to a restart, or repeated by a retry |
The six invariants in full, with rationale
1Identity follows every request
Tenant identity comes from a verified token at ingress and is carried as a signed claim on every hop. No downstream component ever re-derives it from session or connection state. Authorize the caller, never the session.
Why under concurrency, reconnect churn, or async message interleaving, a request evaluated against a stale session context yields privilege escalation even though the role-based access-control code is correct. Binding identity to the request removes the class of bug rather than the instance.
2All model calls pass through one gateway
No component calls a model except through the AI Gateway — not the agent workers, not the evaluation runner, not a debugging script.
Why this creates one enforceable place for model permissions, budgets, redaction, routing, and cost attribution. A second path to a model is a second place all five have to be reimplemented, and the one nobody remembers to update.
3Agents never own enterprise credentials
Tool servers hold scoped, short-lived credentials issued by a secrets manager and enforce policy on behalf of the calling identity. The agent holds nothing.
Why compromising an agent process — through a prompt injection, a poisoned document, a bad tool — should not expose long-lived credentials to the ticketing system, the source repository, the warehouse, or a model API.
4Generated code is treated as hostile
Model-generated code executes only in ephemeral, kernel-isolated sandboxes with no ambient network, and never inside the agent worker.
Why a language model can emit arbitrary shell or Python, and standard container isolation was designed to separate cooperating workloads, not to contain a deliberately hostile one.
5Every operation is traceable end to end
One correlated trace identifier follows a request from ingress through the agent, the models, the tools, and the sandboxes, and back.
Why without it you cannot answer the four questions an operator actually asks — what did the agent do, why did it make that call, what did it cost, where did it fail (§16).
6Agent execution can safely resume
Run execution is at-least-once, and the checkpoint record is the idempotency mechanism: a redelivered run re-enters the graph at its last checkpoint rather than at the start. Irreversible tools are idempotent or gated through the Approval Broker — a contract on tool authors, not an implementation detail of the queue.
Why at-least-once delivery is what lets a run survive a node reboot; the checkpoint is what stops that survival from doing the same work twice.
§ 5The design decisions
Six decisions determine everything else. Five are settled and are stated here with what each one buys and what it costs; the sixth is a delivery question that is still open (§9).
| Decision | Why it matters | Position |
|---|---|---|
| Self-host models? | Cost, data control, latency | Yes — with vendor APIs optional, behind the same control |
| Separate model infrastructure from the agent platform? | Cost and operational blast radius | Yes — two independent estates |
| Centralize model and tool access? | Governance and audit | Yes — four controls, no bypass |
| Permit model-generated code? | Capability against security | Yes — only in isolated, disposable environments |
| Require a human for certain actions? | Risk tolerance | Configurable per action |
| Buy or build the agent serving layer? | Delivery cost and platform control | Open — evidence gate pending (§9) |
1Self-host the models
BenefitPrompts and completions — which will contain the organization's most sensitive text within a quarter of going live — never leave the estate. Cost per token becomes a capacity decision rather than a vendor invoice, and latency is a property of your own network.
CostYou now operate GPUs: capacity planning, driver and firmware upgrades, hardware failure, and a serving stack that changes quickly. Open-weight models trail the frontier on the hardest reasoning tasks, which is why vendor APIs stay reachable through the same control rather than being prohibited.
2Give model infrastructure its own estate
BenefitFive things become independent at once. Capacity — GPU investment scales with AI demand, not application demand. Performance — different model workloads are tuned separately. Availability — a model upgrade does not touch the agent platform, and a GPU node draining for a driver update does not evict the audit trail. Cost — expensive accelerators serve only workloads that need them, and spend is attributable per team. Security — models have no internet route and are reachable on exactly one path.
CostTwo estates to operate, two upgrade cadences, and a network boundary between them that adds a hop to every model call. Teams that would rather run one Kubernetes cluster will find this the least popular decision in the document.
3Centralize model and tool access
BenefitGovernance has somewhere to live. Quota, budget, model allowlists, redaction, per-team cost attribution, and tool authorization each exist in exactly one place that can be tested, and the audit trail falls out of the same chokepoints rather than being assembled from ten systems afterwards.
CostLatency on every model and tool call, and two components that are on the critical path of everything. They need the availability engineering and the hardening budget of an edge service, not of an internal one.
4Permit generated code, in isolation only
BenefitThe agents can do real work — analyze a dataset, convert a file, drive a browser — without that capability becoming a path into the platform. A bad generation costs one disposable environment.
CostCreating a fresh isolated environment is not free, and that cost lands on the critical path of every task that executes code. Warm pools reduce it and complicate the guarantee that no environment is ever shared, which is why the runtime choice is still measured rather than assumed (§9).
5Gate irreversible actions on a person
BenefitAutonomy becomes affordable. The organization sets where the line falls per action rather than choosing between a supervised assistant and an unsupervised one, and every decision at that line is attributable to a named person.
CostThroughput. A gated workflow moves at human speed, and the gates have to be chosen deliberately — gate too much and people approve without reading, which is worse than not gating at all.
§ 6One agent task, end to end
A single realistic example exercises almost the whole architecture. An engineer asks an agent to investigate an operational issue, analyze the data, and update the corresponding ticket.
- The user signs in and submits the task. Single sign-on establishes who they are and which team they belong to. That identity is attached to the request and travels with every step that follows — nothing downstream ever infers it.
- The agent plans its work. It decides what to look at and in what order. This is the part that is genuinely autonomous, and the part the architecture makes no attempt to constrain.
- Model requests go through the AI Gateway. The gateway checks the team's model permissions and budget, routes to the right model pool, records tokens and cost against the team, and returns the response.
- The agent requests data through approved tools. It asks for logs and metrics; the tool services authorize the requesting user against each system and use their own scoped credentials to fetch. The agent never sees a credential and cannot reach a system no tool exposes.
- Generated code runs in an isolated environment. To analyze the results the agent writes a script. The script executes in a disposable sandbox with no network access, which returns its output and is then destroyed.
- The ticket update stops for approval. Writing to the ticketing system is configured as irreversible. The run pauses, saves its position, and releases the machine it was using. The engineer gets a notification, sees exactly what the agent intends to write, and approves it.
- The run resumes and finishes. It picks up from where it paused — possibly on a different machine, possibly after the platform was deployed in the interim — and completes the write.
- Every step is on one record. The plan, each model call and its cost, each tool call and the authorization decision behind it, the sandbox execution, the approval and who gave it, and the final write are all part of a single reconstructable trace.
Steps 3 through 6 are the four controls, in order. Step 7 is the durability guarantee. Step 8 is the audit guarantee. If a component had failed at any point — a worker crash at step 5, a deployment during the wait at step 6 — the run would have resumed from its last saved position instead of starting over or half-completing.
The equivalent runtime paths, with their network flows, are specified in §17.
§ 7What the architecture guarantees
Stated as outcomes, with a pointer to the part of the technical reference that specifies each one.
| Concern | Guarantee | Specified in |
|---|---|---|
| Data isolation | One team cannot reach another team's conversations, memory, documents, or spend — enforced twice, in the application and again in the database, so an authorization bug returns nothing rather than someone else's data | §12 |
| Credential exposure | Agents hold no enterprise credentials. Every credential is scoped to one integration, short-lived, and held by a service the model cannot instruct | §11 |
| Unauthorized action | Every action against a business system is authorized against the identity of the person who started the run, at the moment of the call | §12 |
| Generated code | Model-generated code cannot execute inside the trusted platform, cannot reach the network, cannot persist, and cannot be reused across teams | §11 |
| Irreversible actions | Designated actions stop for a person; agents cannot self-approve; the deciding identity is recorded | §14 |
| Failure recovery | A run survives the failure of any machine involved, a platform deployment, and a multi-day wait for a human, and resumes rather than repeats | §14 |
| Audit | Every model call, tool action, approval, and failure in a run is reconstructable from one correlated record, attributable to a person and a team | §16 |
| Cost control | Model spend is metered at a single chokepoint, capped per team, and attributable per run. An unmetered path shows up as a reconciliation gap rather than a surprise | §13 |
| Data residency | Nothing leaves the estate except through a brokered allowlist that logs every request, and no component of the platform reports usage to a vendor | App. A |
§ 8What this costs to operate
Every architectural choice has a price. This section states the one attached to this design.
8.1What the enterprise has to own
Stated as capabilities rather than products — the architecture holds with any credible implementation of each row, and specific technology suggestions are in the technical reference (§11.3).
| Capability | Platform requirement |
|---|---|
| Identity | Enterprise single sign-on for people, and cryptographic workload identity for services |
| Model governance | A central AI gateway owning permissions, budget, routing, and cost attribution |
| Enterprise integrations | A controlled tool gateway with one least-privilege service per integrated system |
| Execution | An agent runtime and serving layer, plus an isolated code sandbox |
| Model serving | GPU capacity, a serving engine, and cache-aware request routing |
| State | Durable relational storage and a vector store, both tenant-scoped |
| Observability | Unified traces, metrics, logs, and an evaluation suite that gates releases |
| Secrets | Short-lived, dynamically issued credentials — no long-lived keys anywhere |
| Delivery | Git-driven continuous delivery reconciling both estates, and a mirror for everything pulled from outside |
8.2The ledger
What it buys
- Tenant isolation strong enough that one team's agents cannot reach another team's data
- Policy enforced in one place per capability, and testable there
- Predictable model governance, including for vendor APIs
- An audit trail that answers questions after an incident rather than during one
- GPU capacity that scales and is funded on its own curve
- A small blast radius for generated code and prompt injection
- Independence from any single model vendor or agent platform
What it costs
- More infrastructure components than a hosted agent product
- Two operational practices: GPU infrastructure, and the platform itself
- Broker and gateway latency on every model and tool call
- Sandbox startup cost on the critical path of code-executing tasks
- A standing platform engineering team — this is not a project that finishes
- Operating observability, evaluation, and policy infrastructure as products
- Possibly owning the agent serving layer outright (§9)
8.3Where the effort concentrates
Implementation effort is not spread evenly. Three areas hold most of it: the GPU estate, which is a hardware and capacity practice more than a software one; the tenancy boundary, which is cheap to describe and unforgiving to implement, and where the platform's credibility is won or lost; and the serving layer, which is the one component with no settled answer and is sized in §9 at somewhere between three and eight engineer-weeks depending on how one question resolves.
Everything else — the gateways, the sandbox broker, the approval flow, the observability stack — is integration and configuration of components that exist. Costly in aggregate, but not uncertain.
§ 9Open decisions
One decision gates delivery. The rest can be made after the platform is standing, and are listed so that nobody mistakes silence for consensus.
9.1The gating decision — adopt or build the serving layer
The question. The agent runtime is settled. What is not settled is the service wrapped around it — the component that accepts work, queues it, runs it durably, streams results back, and survives a machine failure mid-run. Nothing in the platform can be sized until this is decided.
| Commercial product | Rejected. Requires a runtime license check and reports usage to the vendor. Incompatible with an estate that permits no unbrokered outbound traffic — independent of price |
| Adopt an open-source server | A permissively licensed, forkable option exists. Cost: a one-week evaluation, then two to three weeks of integration |
| Build a thin one | Roughly eight engineer-weeks, five to six with two engineers |
| The gate | Adopt only if tenant isolation can be implemented through documented extension points, without patching the project's internal request handling |
Everything else about the candidate is recoverable with a wrapper. Tenant isolation is not — a boundary bolted on from outside cannot be trusted, which is the same reasoning that eliminated the single-user agent runtimes in the first place. The full rubric is §14.3 and the evaluation plan is §15.
9.2Decisions that can wait
Six choices are deliberately unmade. None of them changes the shape of the platform, and each has a stated default that applies if nobody decides — they are listed so that silence is not mistaken for consensus.
The six deferred decisions
1How hard the audit-data separation should be
Separate observability projects per team give hard separation and clean key revocation, at the cost of administration and cross-team platform dashboards. Tagged records in one project are far simpler and rely on query-time filtering. If any team's data is subject to contractual segregation, take the hard separation.
2Whether to self-host the trace store
Self-hosting at evaluation scale means operating an analytics database — evaluation runs generate far more trace volume than production traffic does. A managed alternative with gateway-side redaction is defensible, but it changes the data-residency answer in §7, so it is a decision for the same people who set that requirement.
3Which isolation technology the sandbox uses
The options trade startup cost against boundary strength. Startup cost lands directly on agent responsiveness, so this is measured rather than argued. A warm pool is the usual mitigation and complicates the one-team-per-sandbox rule — see §11.4.
4Which serving engine runs the models
One option has the strongest integration with the routing layer; another is faster to stand up and couples the estate to a single hardware vendor's stack. Deferrable — the contract the rest of the platform sees is identical either way (§13).
5How deep the separation between teams goes
Shared cluster with network policy and database-level isolation, or physically separate clusters for teams under regulatory constraint. This depends on whether any team sits outside the organization's compliance boundary — the assumption in §10.2 most likely to change.
6Whether vendor model APIs are permitted at all
If they are, the AI Gateway becomes the most sensitive component in the estate: it sees every prompt from every team and holds every key. That is an affordable position, but it needs an edge component's hardening budget rather than an internal one.
§ 10When to build this — and when not to
This is a substantial platform. It is worth being explicit about the conditions under which it is the wrong answer, because most organizations reach those conditions gradually rather than starting at them.
10.1Two lists
Probably excessive if
- you are supporting one trusted team;
- agents only read data and never modify enterprise systems;
- you use hosted models and do not operate GPUs;
- agents cannot execute arbitrary code;
- workflows are short-lived and never wait on a person;
- regulatory and tenancy boundaries are minimal.
Appropriate when
- multiple teams or business units share the platform;
- agents can modify enterprise systems;
- model and data access must be centrally governed;
- GPU infrastructure is operated internally;
- workflows persist across failures or human approvals;
- auditability is a requirement rather than a convenience.
The four controls are separable. An organization that meets only some of these conditions can adopt the ones that apply — a central AI gateway is valuable on its own, and so is an isolated sandbox — without taking on the two-estate topology or the durable serving layer. The architecture is a destination, not a prerequisite.
10.2Assumptions this design rests on
- Single organization, multiple internal teams that do not share a trust boundary with each other but are not mutually adversarial.
- On-premises or colocated GPU hardware; the agent platform on-premises or on managed Kubernetes.
- Not air-gapped, but default-deny egress with a brokered allowlist.
- Prompt and completion payloads are treated as sensitive at rest.
- Vendor model APIs optionally reachable, through the same gateway as local models.
- Single-sign-on authenticated internal users. No anonymous public entry point.
Change any of these — particularly the “not mutually adversarial” assumption — and several conclusions need re-deriving, starting with how deep the separation between teams goes (§9.2).
Technical reference
The remainder specifies how the guarantees above are implemented. It is written for platform, security, SRE, and AI engineering teams, and it assumes the vocabulary those teams already use. Nothing below changes the architecture described in §1–§10.
§ 11Trust zones and topology
The four conceptual areas in §3 are implemented as seven zones. The useful question about a zone boundary is not what sits inside it but why it is drawn at all — each of these separates two things that fail differently, scale differently, or deserve different amounts of trust.
11.1The zones
| Zone | Purpose | Why it is separate |
|---|---|---|
| Z0Internet | Third-party SaaS, registries, model sources, optional frontier APIs | Nothing outside the estate is trusted, including services we pay for |
| Z1Edge | Authenticate users, control what comes in and what may leave | External traffic should never reach platform workloads directly, and no workload should reach the internet unobserved |
| Z2Agent platform | Run agents, gateways, workflows, telemetry, CI/CD | Trusted application logic lives here — and only here |
| Z3Sandbox | Execute model-generated code | Generated code must be treated as hostile, so it gets a zone with a kernel boundary and no network |
| Z4Inference fabric | Serve models on GPUs | GPU workloads scale and fail differently from application workloads, and cost far more per hour to leave idle |
| Z5Enterprise data | Business systems and internal data | Agents should reach these only through tools that authorize the caller |
| Z6Operations | Privileged administration | Administrative access is a different threat model and needs its own boundary and its own recording |
The agent loop runs in Z2 as a trusted, code-defined workload. Z3 hosts no persistent agent process — only short-lived containers that execute model-generated code and are destroyed afterwards. That is the main structural benefit of the harness decision (§14.1) and a large reduction in attack surface: there is nothing in the untrusted zone for an attacker to persist in.
11.2What enforces each boundary
| ID | Zone | Trust | Boundary enforced by |
|---|---|---|---|
| Z0 | Internet / third party | Untrusted | Perimeter firewall |
| Z1 | Edge / DMZ | Semi-trusted | Web application firewall, ingress gateway, forward proxy, identity provider |
| Z2 | Platform services | Trusted | Namespaces + default-deny NetworkPolicy + mutual TLS + tenant row-level security |
| Z3 | Tool execution sandbox | Untrusted | Kernel isolation, ephemeral lifecycle, zero ambient network |
| Z4 | Inference fabric | Trusted, restricted | Separate L3 segment; only the AI Gateway may originate |
| Z5 | Data & enterprise systems | Trusted, sensitive | Per-service authentication, scoped credentials, no direct Z3 access |
| Z6 | Management & ops | Privileged | Bastion / privileged access management, admin VLAN, multi-factor auth |
11.3What runs where
The inventory below is a reference implementation, not a requirement. Named products are the ones we would reach for; the architecture holds if you substitute equivalents, and §8.1 states the same thing as capabilities. The inference fabric has its own section (§13).
Zone inventories — components, by zone
Z0Internet / third party
Corporate SaaS reachable via MCP (Jira, GitHub, Salesforce, calendar); package and container registries; Hugging Face / NGC model sources; optional frontier model APIs. All reached through Z1 brokers, never directly.
Z1Edge / DMZ
| Component | Role |
|---|---|
| Perimeter firewall / NGFW | L3/L4 segmentation between zones |
| Ingress Gateway Envoy Gateway / Istio | TLS termination, WAF, north-south routing |
| Identity Provider Keycloak / Okta / Entra | OIDC for humans; issues the tenant_id + groups claims the whole platform authorizes against |
| Egress Broker Envoy / Squid | The only path out of Z2 and Z3. FQDN allowlist, full request logging, TLS inspection where lawful |
| Artifact Mirror Harbor / Artifactory / Zot | Pull-through cache for OCI, PyPI, npm, model weights. Scanning and signature-verification chokepoint. No workload pulls from Z0 directly |
Z2Platform services (Kubernetes)
Agent plane
- Agent Serving Layer — the HTTP surface over the compiled Deep Agents graphs: threads, runs, state, resume, SSE streaming. Agent Protocol wire format. Stateless API pods; workers scaled by KEDA on queue depth. Adopted or built per §15; either way it is ours to operate and fork.
- Deep Agents application images — planner, sub-agent definitions, virtual-filesystem backend, skill sets. Topology is code; the image is the source of truth and Argo reconciles it.
- Checkpointer (Postgres) — thread state, interrupt / resume. Ownership is proven in platform-owned tables under row-level security before any thread ID reaches the checkpointer (§14.4).
- Store (Postgres / pgvector) — cross-thread long-term memory, namespaced per tenant and per user from token claims.
- Run queue (Redis / Valkey) — carries the wake-up signal and the KEDA scaling metric. Postgres, not Redis, is authoritative for run state.
Control plane
- AI Gateway LiteLLM Proxy / Envoy AI Gateway — single OpenAI-compatible endpoint. Owns virtual keys, per-tenant quota and budget, model routing and fallback, prefix / semantic cache, PII redaction hooks, and OTel emission.
- MCP Gateway + MCP server fleet — one least-privilege service per integration (
mcp-gitlab,mcp-jira,mcp-postgres,mcp-search). - Sandbox Broker — mediates all Z3 execution: allocates an ephemeral sandbox, injects only the working set, enforces wall-clock and resource ceilings, returns results, destroys the sandbox.
- Approval Broker — human-in-the-loop gate for irreversible actions, wired to LangGraph's native
interruptprimitive.
Observability & evaluation
- Langfuse — traces, sessions, scores, prompt management, datasets, experiment runs. Backing stores: Postgres + ClickHouse + Redis/Valkey + S3-compatible blob.
- OpenTelemetry Collector — fans out to Langfuse (GenAI semantics) and Prometheus / Loki / Tempo (infra semantics).
- Eval Runner Argo Workflows — dataset fan-out, experiment execution, scoring, CI gating.
- Prometheus / Grafana / Alertmanager / Loki / Tempo
Developer platform
- GitLab + registry; GitLab Runners on ephemeral K8s executors, with a GPU-tagged pool for eval and benchmark jobs
- Argo CD — GitOps reconciliation for Z2 and Z4
- Argo Workflows / Events — evals, nightly regression, model promotion, scheduled runs
Data services
- PostgreSQL (HA) — separate instances, or at minimum separate databases per consumer: checkpointer, store, serving layer, Langfuse, GitLab
- Vector store pgvector / Qdrant — retrieval corpora, tenant-namespaced
- Object store MinIO / Ceph RGW / S3 — artifacts, trace payloads, datasets, weights staging
Security & identity
- Vault — dynamic DB credentials, model API keys, per-tenant virtual keys, short TTL
- SPIRE / SPIFFE — workload identity; every pod-to-pod call is mutual TLS with a verifiable identity
- OPA / Kyverno — admission policy: signed images, no privileged pods, no hostPath, required NetworkPolicy
- Falco / eBPF — runtime detection, aggressive ruleset on Z3 nodes
Z5Data & enterprise systems
Enterprise Postgres / warehouse, document stores, Confluence / Jira, SharePoint / Drive, internal APIs. Reached only through MCP servers in Z2.
Z6Management & ops
Bastion / PAM, cluster admin endpoints, BMC / IPMI / Redfish, provisioning, backup targets, SIEM forwarder.
11.4Sandbox requirements — Z3 in detail
Dedicated tainted node pool. Nothing else schedules here. Nothing persists here. The broker allocates a fresh sandbox, seeds only the working set the tool needs, applies ceilings, pushes work in, pulls the result out, and destroys it (C9 → C11).
| Property | Requirement |
|---|---|
| Lifecycle | Created per tool invocation (or per short-lived session), destroyed on completion. No reuse across tenants, ever |
| Isolation | gVisor or Kata Containers. Standard runc is insufficient for executing model-generated shell |
| Contents | Code interpreter, shell tool, headless browser, file conversion utilities |
| Filesystem | emptyDir or ephemeral volume seeded with only the working set the agent needs. Destroyed with the sandbox |
| Network | No ambient egress. NET_RAW dropped, DNS pinned to a policy resolver, RFC1918 destinations blocked (server-side request forgery containment). Outbound only via the Z1 Egress Broker where the tool explicitly requires it |
| Limits | Wall-clock timeout, CPU / memory / PID caps, writable-layer disk cap, output size cap |
| Identity | Per-invocation SPIFFE identity scoped to one tenant, one thread, one tool call |
EvidenceDenied egress attempts and sandbox timeout / OOM rates are alerting signals, not debug output. A spike in either means generated code is trying to do something the design says it cannot.
§ 12Multi-tenancy and the security model
This is the section that justifies the harness decision. The tenant boundary is a chain, and it breaks at its weakest link — so it is specified link by link rather than asserted once.
| Layer | Mechanism |
|---|---|
| Ingress | OIDC token validated at Z1. tenant_id, user_id, groups extracted from verified claims |
| Propagation | Claims re-signed into an internal JWT and carried on every hop. Also set as OpenTelemetry baggage so telemetry inherits them without manual plumbing |
| Serving layer | Thread and run creation requires tenant_id from the verified token; the server authorizes the caller's claim against the resource owner on every read and write. Cross-tenant reads return 404, not 403 |
| Checkpointer | Ownership resolved against platform-owned tables under row-level security before the thread ID is handed to the checkpointer (§14.4). The checkpointer runs its own pool and does not inherit request context |
| Store (memory) | Namespace prefix (tenant_id, user_id, …); namespace derived from the token claim, never from a request parameter |
| Vector store | Per-tenant collection, or a mandatory metadata filter enforced server-side in the MCP retrieval service — not in the prompt |
| AI Gateway | Per-tenant virtual key, model allowlist, rate limit, hard budget ceiling. Cost attribution falls out of this for free |
| MCP Gateway | Per-tenant, per-tool authorization. Tool visibility is tenant-scoped — an agent should not see a tool it may not call, because an unavailable tool in the prompt is an invitation to work around it |
| Sandbox | One tenant per sandbox instance, destroyed after use. No shared writable layer |
| Langfuse | Project per tenant (hard separation) or session_id / user_id / metadata tagging with scoped API keys (soft). Choose deliberately — see §9.2 |
Two of those rows carry more weight than the rest. 404 rather than 403 on a cross-tenant read matters because a 403 confirms the identifier exists and turns the endpoint into an enumeration oracle. Row-level security as a backstop matters because it changes the consequence of an application-layer authorization bug from “another tenant's rows” to “zero rows”.
Authorizing the session instead of the caller. Under concurrency, reconnect churn, or async message interleaving, a request can be evaluated against a stale or wrong session context, which yields vertical privilege escalation even though the RBAC code “works.” Bind identity to the request, revalidate at each authorization point, and alert whenever ingress identity and execution identity disagree — that alert is the one in §16.3 that pages immediately.
The database-level half of this — row-level security policies, transaction-scoped tenant context, and the connection-pool constraint the checkpointer imposes — is specified in Appendix B.1.
§ 13Inference fabric
A stateless serving tier on its own estate. No agent logic, no business logic, no tool execution — the fabric answers inference requests and does nothing else. The business case for separating it is §5, decision 2; this section is the engineering contract.
13.1Architecture requirements
These are the properties the design depends on. They should outlive any particular serving stack.
- Model serving runs independently of agent workloads — separate estate, separate scaling, separate upgrade cadence, separate blast radius.
- Routing is cache-aware, not round-robin. Requests are steered to the replica most likely to already hold the relevant attention state. This is the single highest-leverage configuration decision in the fabric.
- Pools are sized per workload shape. An agentic tool-calling loop and a long-form chat session are different workloads and should not share a pool.
- Prefill and decode scale independently, so latency to first token and overall throughput are not traded against each other by accident.
- Weight distribution must not saturate the fabric network — a scale-up event should not degrade in-flight inference.
- GPU health is a first-class operational signal, not something inferred from application errors.
- Exactly one ingress. Only the AI Gateway may originate traffic into the fabric; GPU nodes have no internet route at all.
The tool-calling loop is chatty and latency-sensitive in a different way than long-form chat. Sizing an agentic pool separately — small context, high concurrency, a model tuned for repeated structured tool calls — is the difference between a responsive agent and one that feels broken.
13.2Reference implementation
Named products below are the current best answers, not requirements. The fabric's contract with the rest of the platform is a pool of OpenAI-compatible endpoints behind one gateway; substitutions are local decisions.
GPU fabric components — hardware, serving, and scheduling
| Component | Notes |
|---|---|
| GPU nodes | H100 / H200 / B200 class. NVLink / NVSwitch intra-node; InfiniBand or RoCEv2 (400G+) inter-node |
| Model servers | vLLM (primary), SGLang, or TensorRT-LLM / NIM. Tensor parallelism sized per model; --max-model-len and gpu-memory-utilization tuned per pool |
| Disaggregated serving | llm-d or NVIDIA Dynamo — separate prefill and decode pools so time-to-first-token and throughput scale independently |
| Inference Gateway | Gateway API Inference Extension: InferencePool + endpoint picker. Prefix / KV-cache-aware routing rather than round-robin |
| Model pools | pool-chat-large · pool-agentic · pool-embed · pool-rerank · pool-judge · pool-guard |
| Weights store | Lustre / GPFS / WEKA, or object store + local NVMe cache. Scale-up pulls must not saturate the fabric network |
| KV cache tier | LMCache or equivalent offload to CPU RAM / NVMe |
| GPU Operator + DCGM | Per-GPU utilization, streaming-multiprocessor occupancy, ECC, thermals, XID errors → Prometheus |
| Scheduler | Kubernetes + Kueue / Volcano for gang scheduling |
§ 14Agent serving layer
This is the one component with no acceptable off-the-shelf answer, and it sits directly on the critical path: nothing in Z2 can be sized until it is settled. This section specifies what the component must do; §15 decides who writes it.
14.1The harness, and why it is not the control plane
Deep Agents on the LangGraph runtime is the agent harness for the entire platform.
The requirement that drove this is multi-tenancy. Self-hosted personal-assistant runtimes assume one trusted operator per gateway; tenancy in those systems is simulated by running one full instance per user, and session identifiers route requests rather than authorize them. That is an operational dead end at organizational scale and a security boundary that does not exist.
Deep Agents gives the same open-ended capability surface — explicit planning, sub-agent delegation, a virtual filesystem, skills, long-horizon execution — on top of a runtime whose state model (threads, checkpointer, store) is ours to scope. Tenancy is enforced by our authorization code and our database, not by instance count.
The corollary matters as much as the decision: Deep Agents is an execution harness, not a control plane. Credential management, model policy, cost governance, tool authorization, and audit do not live in the harness. They live in the AI Gateway, MCP Gateway, Approval Broker, and Vault.
Why a harness that owned governance would have to be trusted. We would rather trust infrastructure we can test.
14.2What is in scope, and what is not
The harness and runtime are not in question. Only the server wrapped around the compiled graph is.
| Layer | Status | Consequence |
|---|---|---|
deepagents harness · langgraph runtime | MIT, unaffected | Use as-is |
| Postgres checkpointer · Store | MIT, unaffected | Use as-is; do not reimplement, do not migrate their tables |
| HTTP surface · run queue · streaming · scheduler | Commercial — excluded | License key at startup and vendor egress for verification and usage reporting. Denied by R7.1 |
create_deep_agent() returns a compiled LangGraph graph, and everything under it is permissively licensed. What is commercial is the Agent Server around it — HTTP surface, run queue, streaming layer, scheduler — which requires a license key at startup and egress to a vendor endpoint for verification and usage reporting unless run air-gapped. That egress requirement alone disqualifies it for Z2, independent of cost: a default-deny zone would need a permanent firewall exception to a third-party host, and usage telemetry would leave the estate.
Graph code, tools, MCP integration, and the AI Gateway are unchanged whichever path is taken.
14.3How a run actually executes
14.4Acceptance requirements
Eight guarantees the serving layer must provide. For an adopted server they are the evaluation rubric; for a built one they are the build scope. They are numbered so that spike findings can be recorded against them, and so a later re-evaluation starts from evidence rather than memory.
R1Runs survive infrastructure changes run lifecycle
- R1.1Create a thread; submit a run against a thread; retrieve run status; cancel a run.
- R1.2Retrieve current graph state for a thread, including pending interrupts.
- R1.3Runs execute asynchronously. Submission returns immediately; execution happens on a worker.
- R1.4A run survives the death of the pod that accepted it.
R2A crash must not lose work durability
- R2.1Postgres checkpointer wired; the graph resumes from the last completed node after worker eviction, OOM kill, or rolling deploy.
- R2.2Queue semantics are acknowledge-on-completion, not acknowledge-on-receipt.
- R2.3Redelivery is safe. At-least-once execution must not double-apply side effects — the checkpointer is the idempotency mechanism, so replay re-enters at the last checkpoint rather than the start.
- R2.4A worker that dies holding a run releases it within a bounded lease window; a reaper requeues it.
R3Clients can disconnect without breaking a run streaming
- R3.1Token-level streaming to the client over server-sent events.
- R3.2Node-level state updates and custom events streamed on the same channel.
- R3.3A client may reconnect mid-run and resume the stream without losing events.
- R3.4The streaming client need not be connected to the pod running the graph.
R4Humans can safely pause and resume agents human-in-the-loop
- R4.1
interrupt()inside a graph suspends execution and checkpoints state; the worker is released. - R4.2Interrupt payload is retrievable and routed to the Approval Broker (C10).
- R4.3Resume with
Command(resume=…)continues from the interrupt point — possibly on a different pod, possibly days later, possibly after a deploy. - R4.4Approval decisions are recorded in the audit trail with the deciding identity.
R5Tenants can never cross boundaries the gating requirement
- R5.1
tenant_idanduser_idare derived from a verified token only. Never accepted from a request body, query string, or client-controlled header. - R5.2Every thread and run is owned by exactly one tenant; ownership is authorized on every read and write.
- R5.3A caller from tenant A requesting tenant B's thread receives 404, not 403 — a 403 confirms the ID exists and turns the endpoint into an enumeration oracle.
- R5.4Postgres row-level security is enforced on the ownership tables as a backstop, so an application-layer authorization bug returns zero rows rather than another tenant's rows.
- R5.5Store namespaces are derived from token claims, not request parameters.
- R5.6Identity is bound to the request and revalidated at each authorization point. The system authorizes the caller, never the session or connection.
R6Everything is observable observability
- R6.1OTLP export to the Z2 collector; no forced vendor tracing backend.
- R6.2
tenant_id,user_id,thread_id,run_idpropagated as OpenTelemetry baggage so descendant spans inherit them without manual tagging. - R6.3Prometheus metrics: queue depth, run duration, lease expiries, worker saturation.
- R6.4Trace IDs correlate with AI Gateway spans (§16.1).
R7It can operate inside a restricted environment operations
- R7.1No outbound license or telemetry call. Runs in a default-deny egress zone with no exception.
- R7.2Horizontal scaling of API pods and workers independently.
- R7.3KEDA-compatible scaling signal (queue depth) — these workers are I/O-bound and CPU-based autoscaling under-scales badly.
- R7.4Graceful drain:
terminationGracePeriodSecondslong enough to finish or checkpoint in-flight work; pod disruption budget configured. - R7.5Alembic-style migrations, health and readiness endpoints.
R8We can own and fork it licensing
- R8.1Permissive OSS (MIT / Apache-2.0 / BSD). No copyleft in the serving path.
- R8.2No runtime license key.
- R8.3Full source available and forkable.
14.5Tenancy and the checkpointer
The architectural point is short: the platform must establish tenant ownership of a thread in its own tables before that thread ID is passed into LangGraph's independently-pooled checkpointer.
Why the checkpointer manages its own connection pool and executes its own SQL. It does not participate in the request-scoped session and will not carry the request's tenant context, so row-level security cannot be the thing protecting it. Authorize first, then delegate an already-authorized identifier.
This is path-independent: the same interaction has to be resolved whether the serving layer is adopted or written, because it arises from the checkpointer rather than from the server. It is also the reason a built server is an eight-week project rather than a four-week one.
The resolution has four parts:
thread_idis a uuid4, unguessable, so a leaked identifier is not enumerable.- Every request path resolves
thread_id → tenant_idagainst the platform's ownthreadstable under row-level security before the identifier is handed to the checkpointer. - Workers do the same: a leased run carries its
tenant_id, revalidated at pickup — never trusted from the queue payload. - Optional defense-in-depth: a connection factory for the checkpointer pool that sets the tenant setting per checkout. Adds complexity; only worth it if the threat model includes a compromised worker.
A test that opens two sessions in sequence for different tenants and asserts the second cannot see the first's rows is worth more than any amount of review here. It runs in CI on every commit (§15.5).
The transaction-scoping constraints that make this work — and the specific ways a pooled connection defeats it — are in Appendix B.1.
§ 15Adopt or build the serving layer
The commercial option is closed (§14.2). What remains is adopting a permissively licensed open-source server or writing a thin one, decided by a one-week spike against the §14.4 rubric on a single criterion.
15.1The comparison in one table
| Requirement | Aegra (adopt) | Custom server (build) |
|---|---|---|
| Async runs (R1.3) | Likely | Yes |
| Durable checkpointing (R2.1) | Likely | Yes |
| Stream reconnect (R3.3) | Likely | Yes |
| Human-in-the-loop resume (R4) | Verify | Yes |
| Multi-tenancy (R5) | Must prove | Designed in |
| Permissive OSS, forkable (R8) | Yes — Apache-2.0 | Yes |
| No telemetry egress (R7.1) | Yes | Yes |
| Initial effort | ~1-week spike + 2–3 weeks integration | ~8 engineer-weeks |
| Long-term ownership | Fork risk — young project | Fully owned |
Every row but one is recoverable with a wrapper. R5 is not, which is why it is the only thing the gate turns on.
15.2Path A — adopt Aegra
Aegra is an Agent Protocol-compliant server for LangGraph agents: FastAPI + PostgreSQL, drop-in compatible with the standard LangGraph SDK, Postgres checkpointing, a Redis job queue with lease-based crash recovery, streaming with reconnection, pluggable JWT / OAuth authentication, bring-your-own Postgres, and the tracing backend of your choice. On paper it satisfies most of §14.4. It is Apache-2.0 rather than MIT — permissive, with an explicit patent grant, which satisfies R8.1.
Spike plan — one week, one engineer
Timeboxed. The output is a filled-in rubric, not a working platform.
| Day | Work | Proves |
|---|---|---|
| 1 | Stand up Aegra + Postgres + Redis locally. Deploy a trivial Deep Agent. | R1, R3.1 |
| 2 | Wire the Postgres checkpointer against the real schema. Kill workers mid-run. | R2.1–R2.4 |
| 3 | The gate. Implement tenant_id binding through the auth framework, including row-level security. | R5.1–R5.6 |
| 4 | Human-in-the-loop: interrupt, route to a stub Approval Broker, resume on a different pod. Reconnect mid-stream. | R4, R3.3–R3.4 |
| 5 | OTLP to a local collector → Langfuse. Read the source for the worker lease and auth paths; record a fork-difficulty assessment. | R6, R8.3 |
Pass / fail criteria
Adopt if R5 is satisfiable through the documented auth extension points without patching Aegra internals, and R2 and R4 hold under induced failure.
Fall back to Path B if tenancy requires modifying core request handling, or the checkpointer / tenancy interaction (§14.5) cannot be resolved cleanly.
R5.4 is the specific thing to prove. Everything else in the rubric is recoverable with a wrapper; a tenancy boundary that has to be bolted on from outside is not — and that is exactly the failure mode that disqualified the personal-assistant harnesses in the first place (§14.1).
If adopted — integration work, 2–3 weeks
- Fork and vendor immediately. Pin to a commit in your own GitLab, build your own image via the Z1 mirror. Do not consume upstream tags directly into production. This is not distrust of the project; it is what makes R8.3 real.
- Auth adapter — validate the internal JWT from the Z1 ingress, map claims to Aegra's principal, reject any request whose token lacks
tenant_id. - RLS migration on the ownership tables, plus the thread-authorization interaction in §14.5.
- Approval Broker bridge — interrupt payload → broker; decision → resume call.
- OTLP wiring and baggage propagation.
- Helm chart — API Deployment, worker Deployment, KEDA
ScaledObjecton Redis queue depth, pod disruption budget, NetworkPolicy per Appendix A. - Failure test suite — kill workers under load, roll deploys mid-run, expire leases, assert zero lost or duplicated runs.
Fork insurance
Aegra is young. The mitigation is not to avoid it — it is to be structurally ready to own it.
- Keep 4–8 weeks of engineering capacity in reserve as fork insurance rather than spending it on Path B upfront.
- Maintain a rebuild-from-scratch estimate, reviewed quarterly.
- Keep all graph and tool code free of Aegra imports. Aegra is a deployment target, not a dependency of your agents. If the only Aegra-aware code is the serving layer and its adapters, replacement is a contained project rather than a rewrite.
- Build to the Agent Protocol wire format so clients are portable across serving layers.
15.3Path B — build a thin agent server
Only if Path A fails its gate. The estimate holds only if the scope does, so the scope is stated as a table rather than a paragraph.
| Feature | Build? | Why |
|---|---|---|
| Threads, runs, state, resume API | Yes | Core |
| Async worker + queue | Yes | Core |
| Streaming | Yes | Interactive UX |
| Auth + tenancy | Yes | You were building this anyway for §12 |
| Postgres checkpointer | No — use as-is | MIT, drop-in |
| Store | No — use as-is | MIT, drop-in |
| Assistants CRUD | No | Graphs are code, versioned in Git, reconciled by Argo. Runtime-registered assistants solve a problem you do not have |
| Cron / scheduled runs | No | Argo Events and CronJobs are already in Z2 |
| Studio integration | No | Use langgraph dev locally; production debugging is Langfuse |
| Double-texting policies | One | Implement reject (409 if a run is active on the thread). Add others only on demand |
Building to the Agent Protocol spec keeps LangGraph SDK and Agent Chat UI compatibility for free, and makes a later switch to Aegra — or back — a configuration change.
Milestones
| # | Milestone | Requirements | Est. |
|---|---|---|---|
| M0 | Scaffold, app factory, session / transaction wiring, health, CI | R7.5 | 0.5 wk |
| M1 | Threads + runs CRUD, graph registry, synchronous invoke | R1.1–R1.2 | 1.5 wk |
| M2 | Queue, worker, leases, reaper, checkpointer, failure tests | R1.3–R1.4, R2 | 1.5 wk |
| M3 | Streaming, event persistence, reconnect | R3 | 1.0 wk |
| M4 | Auth, tenancy, RLS, checkpointer authorization, cross-tenant tests | R5 | 1.0 wk |
| M5 | Interrupt / resume, Approval Broker bridge, audit | R4 | 1.0 wk |
| M6 | OTLP, baggage, metrics, Helm, KEDA, PDB, NetworkPolicy, load test | R6, R7 | 1.5 wk |
Total: ~8 weeks for one engineer; 5–6 with two — M3 and M4 parallelize cleanly. That is the upper bound of the effort, which is why Path A is evaluated first. The service layout, API surface, data model, queue, and streaming design for this path are specified in Appendix B.2.
15.4The decision gate
15.5Work common to both paths
Needed either way — schedule it in parallel with the spike, not after it.
1Agent Protocol conformance tests
Your own suite against the wire format. This is what makes the two paths interchangeable, and what protects you if either choice is revisited.
2Graph registry contract
How assistant_id maps to a compiled graph, how versions are pinned, and how Argo rolls a new graph version without orphaning in-flight runs. Needed in both paths and easy to under-specify.
3Tool idempotency guidelines
A direct consequence of at-least-once redelivery. Applies to an adopted server identically. Ship before the first non-trivial tool.
4Approval Broker interface
Interrupt payload schema, decision callback, audit record shape — including the deciding identity (R4.4).
5Failure test harness
Kill workers under load, roll deploys mid-run, expire leases, partition Redis, assert no lost or duplicated runs. This is the suite that says whether either path is production-ready, and it is reusable across both.
6Cross-tenant isolation test
Two tenants, sequential sessions; assert the second cannot read the first's threads, runs, checkpoints, or store namespaces. Runs in CI on every commit.
15.6Risks
| Risk | Path | Mitigation |
|---|---|---|
| Aegra maturity / maintainer churn | A | Fork and vendor from day one; keep the rebuild estimate current; no Aegra imports in graph code |
| Checkpointer / RLS interaction under-designed | Both | Resolve via thread authorization (§14.5) rather than forcing RLS onto vendor tables; test explicitly |
| Build overruns the 8-week estimate | B | Scope discipline in §15.3 is the control. Assistants, cron, and Studio are the features that will be argued back in — hold the line |
| Silent tenant leak | Both | RLS as backstop, not primary; cross-tenant CI test on every commit |
| At-least-once surprises a tool author | Both | Idempotency guidelines shipped before the first non-trivial tool |
| Agent Protocol drift | Both | Pin the spec version; the conformance suite catches divergence |
15.7Recommendation
Run the Path A spike. It costs one week against an eight-week alternative and has a plausible outcome of closing the item entirely.
The one thing to prove is R5 — specifically whether the tenant claim reaches checkpointer scoping without patching internals. Everything else in the rubric can be wrapped from outside; a tenancy boundary bolted on externally cannot be trusted.
- A run outlives the pod that accepted it, the pod that ran it, and the deploy that replaced both.
- A client can disconnect and reconnect without losing a token.
- A run can wait days for a human without holding a worker.
- A redelivered run resumes rather than repeats.
- A tenancy bug returns zero rows, not someone else's.
- The serving layer runs with no outbound call to anyone.
What is not yet settled is who writes it — and that is a one-week question, not an architectural one.
§ 16Observability and evaluation
An operator has four questions, and the observability design exists to answer them rather than to collect signals:
| Question | Answered by |
|---|---|
| What did the agent do? | The reasoning tree from the serving layer — graph nodes, sub-agent delegation, planning steps, interrupts |
| Why did it make that model or tool call? | Model spans from the AI Gateway and tool spans from the MCP Gateway, nested under the same trace |
| What did the run cost? | AI Gateway spans — authoritative, because every model call traverses it |
| Where did latency or failure occur? | One correlated trace spanning ingress, agent, model, tool, and sandbox |
Why each question maps to exactly one chokepoint. That is not a coincidence — it is the payoff for routing every capability through a broker in §4.
16.1Instrumentation strategy
Instrument at infrastructure chokepoints plus the framework callback, and correlate on one trace ID:
- AI Gateway emits a span per model call — model, tokens, cost, latency, cache hit, tenant. Authoritative for cost, and complete regardless of caller.
- Agent Serving Layer / Deep Agents emits the reasoning tree — graph nodes, sub-agent delegation, planning steps, tool-call decisions, interrupts.
- MCP Gateway emits a span per tool call with the authorization decision attached.
- Sandbox Broker / Z3 emits execution spans — exit code, duration, resource consumption, egress attempts.
Langfuse ingests OTLP over HTTP at /api/public/otel; spans carrying gen_ai.* attributes render as generations with model, token, and cost mapping, and everything else nests around them as regular observations. gRPC is not supported — use HTTP/protobuf.
Propagate trace attributes via OpenTelemetry baggage, not manual per-span tagging. tenant_id, user_id, session_id, thread_id, and experiment_id set once at ingress will then appear on every descendant span across process boundaries.
Why hand-tagging is where trace trees fall apart in practice: one un-instrumented hop and the tree becomes several disconnected fragments that no longer answer any of the four questions.
16.2Evaluation
Deep Agents makes evaluation tractable in a way a self-modifying daemon does not: the graph topology is code, so the trace shape is stable across runs and span-level assertions are meaningful.
Three tiers, each answering a different question, all recorded in Langfuse:
Did the component behave correctly?
A single node, deterministic inputs, assertions on output shape and content.
Did the agent take an acceptable path?
Full graph over a dataset. Assertions on tool-call sequence, sub-agent delegation, turn count, cost ceiling, and whether any gated action was attempted without approval.
Did it actually accomplish the task?
Deterministic checkers where possible — file diff, SQL result, API state; a judge model where not.
Reproducibility requirements
- Every eval trial runs as a fresh thread under a dedicated eval tenant — no checkpointer or Store state carries between trials.
- Pin model, model version, and sampling parameters explicitly.
temperature=0does not make an agent deterministic, but unpinned model routing makes results meaningless. - Version the prompt in Langfuse prompt management and record the version on the run, so a score regression can be attributed to a prompt change rather than guessed at.
- The judge model runs on a pinned pool separate from the model under test.
Scoring must be outcome- and trajectory-weighted, not span-diffed. Even with fixed topology, an agent's loop count varies by input. Metrics that survive: task success rate, cost per task, turns to completion, tool error rate, approval-gate violations, and p95 latency.
Evidence“Gated action attempted without approval” is an assertion in the trajectory suite, so a regression in the Approval Broker fails a pull request rather than surfacing in production.
16.3Operational metrics
| Signal | Source | Alert on |
|---|---|---|
| Cost per tenant per hour | AI Gateway | Budget burn rate |
vllm:num_requests_waiting | Z4 | Sustained > 50 → scale out |
vllm:gpu_cache_usage_perc | Z4 | > 0.90 → out-of-memory risk |
| Time to first token / per output token, p95 | Inference Gateway | SLO breach |
| Tool-call error rate | MCP Gateway | Regression after a tool deploy |
| Egress denials | Z1 broker | Spike → misconfiguration or exfiltration attempt |
| Sandbox timeout / OOM rate | Sandbox Broker | Runaway generated code |
| Queue depth | Redis | KEDA scaling signal; CPU-based autoscaling badly under-scales I/O-bound workers |
| Lease expiries / requeues | Serving layer | Sustained non-zero → grace period shorter than node execution (R7.4) |
| Identity mismatch (ingress vs execution) | Audit | Any occurrence — page immediately |
The last row is the only one on this list with no acceptable non-zero rate. Everything above it describes a system under load; that one describes a system whose central invariant has failed.
§ 17Request paths
Five runtime paths carry essentially all traffic; the narrative walkthrough in §6 is F1, F2, and F3 composed into one task. Read the semantic chain first — the C# references beneath each one are cross-references into the connection matrix in Appendix A, where protocol, port, and authentication for every flow are specified.
| Path | Purpose |
|---|---|
| User → Agent Platform | Submit and stream agent runs |
| Agent → AI Gateway → Models | Inference |
| Agent → MCP Gateway → Enterprise systems | Tool execution |
| Agent → Sandbox Broker → Sandbox | Generated-code execution |
| Platform → Observability | Traces, metrics, evaluation |
F1Interactive agent turn
- User
- Ingress + identity
- Run queue
- Agent worker
- AI Gateway
- Model
- Tool gateway or sandbox, when needed
- Response
The tenant claim is bound at the ingress hop and travels on every edge after it. The loop repeats until a terminal response; a checkpoint is written at each node, and a span is emitted at every hop.
Network references: C1 → C2 → C4 → C7 → C14 → C15, then C8 → C19 or C9 → C11; checkpoints via C5.
F2Long-running run with human approval
- Agent worker
- Gated node
- Checkpoint + release worker
- Approver notified
- Decision
- Resume from checkpoint
Same entry as F1. On reaching a gated node the graph raises interrupt(), state is checkpointed, and the worker is released — it is not held open waiting for a human. Resumption may land on a different pod, after a deploy, days later. This is why workers must be stateless and why the queue acknowledges on completion rather than on receipt.
Network references: C10 to the Approval Broker; resume re-enters via the checkpointer (C5).
F3Sandboxed code execution
- Agent worker
- Sandbox Broker
- Fresh sandbox, seeded + limited
- Execute
- Result returned
- Sandbox destroyed
No sandbox outlives its invocation, and none is shared across tenants. Cold-start cost is therefore on the critical path of every code-executing turn — see the runtime choice in §9.2.
Network references: C9 → C11; optional outbound via C12; telemetry via C13.
F4Model promotion
- Commit
- CI
- Weights via mirror
- Staged to fabric
- New pool applied
- Canary
- Eval suite
- Promote or roll back
Model changes ship through the same GitOps path as code, and the eval suite (F5) is the gate rather than a follow-up.
Network references: weights staged via C17; Argo CD applies the new InferencePool via C27.
F5Evaluation run
- Scheduled workflow
- Fetch dataset
- Fan out N trials
- Drive graph as eval tenant
- Traces land
- Score
- Gate the pipeline
Trials run against an isolated eval tenant, scored deterministically where possible and by a judge model where not. A regression beyond threshold fails the pipeline.
Network references: C24 dataset read and score write; C25 invokes the graph under test; traces via C21 / C22.
App. ANetwork matrix
Every permitted network flow in the platform, in initiator → responder direction. All internal hops are mutual TLS with SPIFFE workload identity unless noted. The C# identifiers are the cross-references used by the figures and by the flows in §17. Edge labels in any rendering of this architecture carry the C# only — protocol and port belong in a tooltip, or the diagram is unreadable at the zoom people actually use.
View all 32 network flows
| # | Source | Destination | Proto / port | AuthN / Z | Notes |
|---|---|---|---|---|---|
| C1 | Z0 user | Z1 Ingress Gateway | HTTPS 443 | OIDC | Only public entry point |
| C2 | Z1 Ingress | Z2 Agent Serving Layer | HTTPS 8443 | Internal JWT + mTLS | Tenant claim attached here |
| C3 | Z1 Ingress | Z2 web UIs | HTTPS 443 | OIDC + RBAC | Langfuse, GitLab, Grafana |
| C4 | Z2 Serving API | Z2 run queue | TCP 6379 | ACL + TLS | Admission decoupled from execution |
| C5 | Z2 Agent worker | Z2 Postgres checkpointer | TCP 5432 | Vault creds, TLS | Thread state, interrupt / resume |
| C6 | Z2 Agent worker | Z2 Store (pgvector) | TCP 5432 | Vault creds, namespaced | Long-term memory |
| C7 | Z2 Agent worker | Z2 AI Gateway | HTTPS 443 | Tenant virtual key + mTLS | Only inference path |
| C8 | Z2 Agent worker | Z2 MCP Gateway | HTTPS 8443 | Internal JWT + mTLS | Tool calls, tenant-scoped authz |
| C9 | Z2 Agent worker | Z2 Sandbox Broker | gRPC 8443 | mTLS | Code / shell / browser execution |
| C10 | Z2 Agent worker | Z2 Approval Broker | HTTPS 8443 | mTLS | Backed by LangGraph interrupt |
| C11 | Z2 Sandbox Broker | Z3 sandbox | gRPC 8443 | Per-invocation SPIFFE | Push work in, pull result out. Z3 never initiates |
| C12 | Z3 sandbox | Z1 Egress Broker | HTTP CONNECT 3128 | Proxy auth + allowlist | Only when a tool explicitly needs the internet |
| C13 | Z3 sandbox | Z2 OTel Collector | OTLP/HTTP 4318 | mTLS | Execution spans, exit codes, resource use |
| C14 | Z2 AI Gateway | Z4 Inference Gateway | HTTPS 443 | mTLS | The only flow crossing into Z4 |
| C15 | Z4 Inference Gateway | Z4 model server pods | HTTP 8000 | Cluster-internal | EPP selects on KV / prefix affinity + queue depth |
| C16 | Z4 model server | Z4 model server | RDMA (IB / RoCEv2) | Fabric-isolated | TP/PP collectives, KV transfer. Dedicated network |
| C17 | Z4 model servers | Z4 weights store | NFS / Lustre / S3 | Read-only | Cold start |
| C18 | Z2 AI Gateway | Z1 Egress → Z0 frontier APIs | HTTPS 443 | Vault-issued key | Optional; same policy surface as local models |
| C19 | Z2 MCP servers | Z5 enterprise systems | varies | Scoped short-TTL creds | Only Z2 → Z5 path |
| C20 | Z2 MCP servers | Z2 vector store | HTTPS / gRPC | mTLS, tenant filter | RAG retrieval |
| C21 | Z2 Serving / AI GW / MCP | Z2 OTel Collector | OTLP/HTTP 4318 | mTLS | Spans with gen_ai.* + baggage |
| C22 | Z2 OTel Collector | Z2 Langfuse OTLP | HTTPS 3000 | Basic auth (project keys) | /api/public/otel; HTTP/protobuf, not gRPC |
| C23 | Z2 Langfuse | Postgres / ClickHouse / Redis / S3 | 5432 / 9000 / 6379 / 443 | Per-service creds | Four backing stores |
| C24 | Z2 Eval Runner | Z2 Langfuse API | HTTPS 3000 | Project key | Dataset read, experiment run, score write |
| C25 | Z2 Eval Runner | Z2 Agent Serving Layer | HTTPS 8443 | Service JWT, eval tenant | Drives graphs under test |
| C26 | Z2 GitLab Runner | Z2 GitLab | HTTPS 443 | Runner token | |
| C27 | Z2 Argo CD | Z2 + Z4 K8s API | HTTPS 6443 | Scoped ServiceAccount | GitOps to both clusters |
| C28 | Z2 / Z3 workloads | Z1 Artifact Mirror | HTTPS 443 | Registry auth | No pulls from Z0 directly |
| C29 | Z2 / Z3 / Z4 | Z2 Vault | HTTPS 8200 | K8s auth / SPIFFE JWT | Dynamic secrets |
| C30 | Z2 Prometheus | Z4 DCGM + node exporters | HTTP 9400 / 9100 | scrape, mTLS | Pull direction Z2 → Z4 |
| C31 | Z6 bastion | Z2 / Z4 admin endpoints | SSH 22 / HTTPS 6443 | MFA + PAM recording | |
| C32 | Z2 Falco / audit | Z6 SIEM | TLS syslog / OTLP | mTLS | Z3 events at elevated severity |
Explicitly denied
- Z3 → Z2 except C13 telemetry — Z3 initiates nothing else
- Z3 → Z4, Z3 → Z5, Z3 → Z0 direct
- Z2 Agent worker → Z4 direct — must traverse the AI Gateway
- Z2 Agent worker → Z5 direct — must traverse MCP
- Z4 → Z0 — GPU nodes have no internet route
- Any workload → Z0 without the Egress Broker
- Any outbound license-verification or usage-telemetry call from the serving layer (R7.1)
A.1Diagram production notes
Figures 1–4 are the reading views. The full-fidelity version belongs in a layered draw.io document; these are the layers and the rules that keep it readable.
| Layer | Contents |
|---|---|
| L1 | Zones & boundaries — containers only; the executive view |
| L2 | Components |
| L3 | Control plane — C1–C4, C26–C32 |
| L4 | Inference data path — C7, C14, C15, C16, C17 (highlight) |
| L5 | Tool & sandbox path — C8, C9, C11, C12, C19 |
| L6 | Observability & eval — C13, C21, C22, C23, C24, C25 |
| L7 | Denied flows — red dashed, from the matrix above |
| L8 | Flow overlays — F1–F5, one sub-layer each |
Layout. Z0 top; Z1 as a horizontal band beneath; Z2 as the large center container; Z3 visually distinct (red / hatched) with its own border and a single bidirectional edge (C11) plus one telemetry edge (C13); Z4 as a separate container with exactly one edge entering it (C14); Z5 below or to one side; Z6 as a side rail.
Edge labels use C# IDs only. Protocol and port belong in the tooltip — otherwise the diagram is unreadable at the zoom people actually use.
What the diagram must communicate at a glance:
- One arrow enters Z4.
- Z3 is red, initiates nothing, and holds no state.
- The tenant claim originates at C2 and is present on every edge downstream.
App. BImplementation notes
Two subjects that belong in the hands of whoever writes the code, and nowhere near an architecture review: the database mechanics of tenant-safe checkpointing, and the internals of a built serving layer.
B.1Tenant-safe LangGraph checkpointing
The architectural rule is in §14.5: authorize thread ownership in platform-owned tables before the thread ID reaches the checkpointer. What follows is how that is implemented without the tenant context silently evaporating — which is the failure mode, and it fails quietly, returning empty results rather than errors.
RLS on platform-owned tables — satisfies R5.4
ALTER TABLE app.threads ENABLE ROW LEVEL SECURITY;
ALTER TABLE app.threads FORCE ROW LEVEL SECURITY; -- policies bypass the owner by default
CREATE POLICY tenant_isolation ON app.threads
USING (tenant_id = current_setting('app.tenant_id', true))
WITH CHECK (tenant_id = current_setting('app.tenant_id', true));
FORCE matters because the migration user usually owns the table. WITH CHECK matters because omitting it lets a tenant insert a row attributed to someone else and then lose sight of it.
Set the context transaction-scoped, never session-scoped
async with SessionFactory() as session:
async with session.begin():
await session.execute(text("SET LOCAL ROLE tenant_user"))
await session.execute(
text("SELECT set_config('app.tenant_id', :tid, true)"),
{"tid": principal.tenant_id},
)
yield session
SET SESSION persists on the pooled connection after the request ends; the next borrower inherits it, and a path that skips setup silently runs as the previous tenant with no error. An f-string here is also injectable from a JWT claim. Under PgBouncer in transaction mode, session-scoped settings are outright broken.
The transaction must be the request, not the repository method. Repositories flush(); the session dependency commits once after the handler returns. If methods commit individually, the first commit ends the transaction and takes the LOCAL settings with it — every subsequent query then runs with no tenant context, the policy matches nothing, and you get a silent empty list. Also set expire_on_commit=False; the SQLAlchemy default re-queries stale attributes after commit, which under asyncio raises MissingGreenlet.
Do not attempt to force row-level security onto the checkpointer's own tables. You do not own that schema, and a migration you do not control will eventually collide with policies you added to it.
B.2Built serving layer — service, API, data model, queue, streaming
Applies only if the gate in §15.4 resolves to Path B. Included because the eight-week estimate is only meaningful against a stated scope.
Service architecture, API surface, and data model
Service architecture
Single deployable, layered so it can split later without routes changing.
agent-server/
├── pyproject.toml
└── src/
├── common/ app.py settings.py database/ deps.py
│ repository.py pagination.py errors.py
│ security.py telemetry.py
├── routes/ threads.py runs.py stream.py health.py (HTTP only)
├── access/ threads.py runs.py (SQL only)
├── schemas/ threads.py runs.py events.py (Pydantic)
├── modules/
│ ├── graphs/ registry — assistant_id → compiled graph
│ ├── executor/ worker loop, lease management, reaper
│ └── events/ Redis pub/sub publisher + SSE relay
└── main.py mounts routers; worker entrypoint is separate
main.py is the only file that decides what a process serves. API pods and worker pods ship from the same image with different entrypoints.
Request flow is route → access → database and never skips or reverses. Routes contain no SQL; repositories contain no HTTP. That is what makes the tenant filter live in exactly one auditable place.
API surface
| Method | Path | Notes |
|---|---|---|
| POST | /threads | Creates a thread owned by the token's tenant |
| GET | /threads/{thread_id} | 404 if not owned |
| GET | /threads/{thread_id}/state | Current state + pending interrupts |
| POST | /threads/{thread_id}/runs | Submit run; body may carry command.resume for human-in-the-loop |
| GET | /threads/{thread_id}/runs | Paginated, cursor-based |
| GET | /runs/{run_id} | Status, error, timings |
| GET | /runs/{run_id}/stream | Server-sent events; honors Last-Event-ID |
| POST | /runs/{run_id}/cancel | Cooperative cancel via checkpointer |
| GET | /health · /ready · /metrics |
Auth is applied at router mount, not per handler, so a route added next quarter is authenticated by default:
app.include_router(threads_router, dependencies=[Depends(get_principal)])
Data model
You own the ownership and scheduling tables. LangGraph owns the checkpoint tables.
| Table | Columns |
|---|---|
threads | id (uuid4), tenant_id, user_id, assistant_id, status, metadata, created_at, updated_at |
runs | id, thread_id, tenant_id, status, input, config, attempt, lease_owner, lease_expires_at, created_at, started_at, ended_at, error |
run_events | run_id, seq, event_type, payload, created_at — stream replay; TTL-pruned |
| LangGraph-owned | checkpoints, checkpoint_writes, checkpoint_blobs, store — in a separate schema. Do not migrate these yourself |
Rules that carry weight:
tenant_idnever appears in a Create schema. It comes from the verified token and is passed as a server-owned field:repo.create(db, payload, tenant_id=principal.tenant_id). That makes cross-tenant writes a property of the type rather than of code review.- Never annotate a route with an ORM model. Separate Read schemas; list responses narrower than detail responses.
- Sorting is an allowlist, not
getattr(Model, order_by). - Order by a unique tiebreaker — the primary key appended automatically — or rows appear on two pages or none.
Worker, queue, and streaming design
Worker and queue design
Lease-based, acknowledge-on-completion.
claim: UPDATE runs SET lease_owner=:worker,
lease_expires_at=now()+interval '60s',
status='running', attempt=attempt+1
WHERE id = (SELECT id FROM runs WHERE status='queued'
ORDER BY created_at
FOR UPDATE SKIP LOCKED LIMIT 1)
RETURNING *;
heartbeat: extend lease_expires_at every 20s while executing
complete: status='succeeded'|'failed', lease released
reaper: lease_expires_at < now() AND status='running' → back to 'queued'
FOR UPDATE SKIP LOCKED gives correct concurrent claim without a separate broker. Redis carries the wake-up signal and the KEDA scaling metric; Postgres remains the source of truth for run state.
Why Redis can be flushed, evicted, or restarted without losing a single in-progress run. Making Redis authoritative trades that away for nothing.
Redelivery safety comes from the checkpointer, not from exactly-once delivery. A requeued run re-enters the graph at its last checkpoint. Tool side effects between the last checkpoint and the crash may repeat, so irreversible tools must be idempotent or gated through the Approval Broker. State this explicitly in the tool authoring guidelines.
terminationGracePeriodSeconds must exceed the longest expected node execution, or rolling deploys will manufacture lease expiries.
Streaming
The worker executing the graph and the API pod holding the client's connection are different pods (R3.4).
worker → graph.astream(stream_mode=["messages","updates","custom"])
→ publish to Redis channel run:{run_id}
→ also append to run_events (seq monotonic)
API pod → subscribe to run:{run_id}, relay as SSE with id: {seq}
→ on reconnect with Last-Event-ID, replay from run_events,
then subscribe
Persisting to run_events is what makes R3.3 work; pub/sub alone drops anything sent while the client was disconnected. Prune aggressively — these rows are large and short-lived.
App. CTerminology
Reference only. Nothing in §1–§10 requires any of it.
- TTFT
- time to first token — how long before a response starts arriving
- TPOT
- time per output token — how fast it streams once started
- KV cache
- the key/value attention state a model server keeps per in-flight request; reusing it across requests with a shared prefix is the largest single win in serving throughput
- MCP
- Model Context Protocol — the standard interface between an agent and a tool server
- HIL
- human-in-the-loop — a run that pauses for a person to decide
- RLS
- PostgreSQL row-level security — per-row access policy enforced by the database
- GUC
- a Postgres runtime setting; RLS policies read the tenant from one
- OTel / OTLP
- OpenTelemetry and its wire protocol — the vendor-neutral tracing standard used throughout
- OTel baggage
- key/value context that rides along a trace across process boundaries, so descendant spans inherit it without being tagged by hand
- SSE
- server-sent events — the one-way HTTP streaming channel used to deliver tokens to a client
- mTLS
- mutual TLS — both sides of a connection present a certificate
- SPIFFE / SPIRE
- a standard and its implementation for issuing cryptographic workload identity to every pod
- KEDA
- Kubernetes event-driven autoscaling — scales workers on queue depth rather than CPU
- PDB
- pod disruption budget — the guardrail that stops a node drain from evicting everything at once
- EPP
- endpoint picker — the component that chooses which model server pod receives a request
- TP / PP
- tensor and pipeline parallelism — the two ways a model is split across GPUs
- RAG
- retrieval-augmented generation — fetching documents and putting them in the prompt
- PAM
- privileged access management — brokered, recorded administrative access
- WAF
- web application firewall
- OIDC
- OpenID Connect — the single-sign-on protocol that issues the identity claims the platform authorizes against