How to Grow an AI Application Architecture Without Building a Platform Too Soon
The most intimidating AI architecture diagram is often the one somebody copied before their product had a real request path.
It has retrieval, a vector database, a model router, a gateway, multiple guardrails, several caches, an orchestration framework, tracing, evaluation, tool calling, and an agent loop. Every box is plausible. The problem is that the diagram answers a different question from the one an early product needs answered.
An early product needs to know whether it can help a person complete one job safely and reliably enough to be worth using again.
That does not mean an AI feature should be a reckless call to a model API. It means its architecture should grow in response to an observed constraint. Add a component when it addresses a failure mode, cost, permission boundary, or product requirement that the current system can name. Otherwise, every new box adds state, latency, configuration, and debugging paths before it adds proven value.
The useful mental model is not a fixed stack. It is a sequence of increasingly consequential decisions.
Start With One Observable Request Path
The smallest useful AI application has a user input, a model request, and a response. It is not the final architecture, but it is a valuable baseline because every later addition has something to improve against.
For example, a support product might accept a question and produce a draft answer. Before adding retrieval or tool calls, the team can already observe important facts:
- Which questions are people asking?
- How long do they wait for an initial response and a finished response?
- Which answers need correction?
- What must the feature never expose or do?
- Is the suggested answer useful enough for an agent to keep?
The baseline should still have ordinary production controls: authentication, request limits, secret management, structured errors, and basic event records. The point is not to skip engineering. The point is to avoid hiding a simple product failure behind a complex framework.
For frontend engineers, the interface is part of that baseline. A streaming response needs a clear distinction between a request that is queued, one that is producing text, one that failed, and one that needs review. A single isLoading flag cannot tell a person whether they can cancel safely, retry without duplicating work, or trust a partial answer.
type AssistantState =
| { status: 'idle' }
| { status: 'preparing-context' }
| { status: 'streaming'; text: string }
| { status: 'needs-review'; draft: string }
| { status: 'failed'; retryable: boolean; message: string }
Those states become a contract between UI and backend. They also make product failures visible instead of leaving users to interpret an inert button or a spinning indicator.
Add Context When the Model Lacks Evidence
A general model can write fluent text without knowing the current order, policy, codebase, customer account, or company procedure that makes an answer correct. When that missing evidence is the dominant failure mode, context construction is the next architectural addition.
Context construction can include retrieval from approved documents, a query to a product database, a read-only API call, or a narrowly defined tool. It is tempting to describe all of this as "RAG," but the critical design work happens before the prompt is assembled:
- Decide which facts are necessary for the job.
- Establish who is permitted to see each fact.
- Select the smallest relevant evidence set.
- Preserve source identity, freshness, and policy version.
- Give the user a way to understand or challenge the answer when the feature warrants it.
This is data-system work. A search result is not permission. A semantic match is not an authorization decision. If an index mixes tenants, workspaces, or roles, a model can turn an access-control flaw into a polished explanation of somebody else's data.
The same rule matters in JavaScript applications that assemble context on the server. Do not serialize a broad internal record into a client payload because a model might need one field. Enforce access before retrieval, minimize the data passed to the model provider, and keep the provider-facing request separate from the UI-facing response. That separation makes it easier to audit what crossed each boundary.
Treat Guardrails as Policies, Not Decoration
Once an application handles private data, untrusted input, or consequential output, it needs explicit policies around both directions of the model call.
Input protections can reject a request, redact sensitive values, constrain a tool's parameters, or route a sensitive task to a different system. Output protections can check a required schema, evaluate whether citations support a claim, detect unsafe content, or stop a model from invoking an action outside its authority.
Where a guardrail belongs is a systems question. A provider may supply some moderation. A gateway may enforce organization-wide policy. An application may need rules that only make sense for one workflow. The right answer can involve more than one layer, but each layer should have a clear owner and failure response.
Streaming exposes the tradeoff especially well. If the product displays tokens as they arrive, a complete-response check cannot prevent an unsafe sentence that was already rendered. Buffering every answer makes complete validation easier but increases perceived latency. A product can choose among patterns such as:
- use lower-latency checks on input and incremental output,
- stream only low-risk portions while withholding a sensitive final action,
- generate a proposal and require review before release,
- disable streaming for workflows where complete validation is essential.
None is universally correct. The important thing is to make the reliability-versus-latency decision deliberately and measure false refusals alongside unsafe output. A guardrail that blocks legitimate work can quietly become its own product outage.
Add a Router and Gateway When Variation Becomes Real
One model endpoint is enough while every request has similar quality, cost, and context requirements. Routing earns its place when the product has materially different jobs.
A support assistant might answer a common password-reset question with a low-cost path, send an account dispute to a human queue, and use a stronger model only for complex diagnostic work. A router can classify intent, decide whether retrieval is needed, choose a provider, or choose a next action. It must be cheap and fast enough that its own errors and latency do not erase the benefit it was meant to create.
Model routing should not become invisible business logic. Keep the decision available in the trace, test it against representative inputs, and expose a useful fallback when confidence is low. A misrouted request is not merely a model-quality issue; it may be an incorrect permission, cost, or escalation decision.
A gateway is the companion boundary when several applications or providers need one controlled interface. It can centralize provider credentials, model allowlists, budget limits, rate limiting, retries, and version selection. It should simplify provider changes without pretending that every model behaves the same way.
An internal TypeScript boundary can preserve that honesty:
type GenerationRequest = {
task: 'support-reply' | 'incident-summary' | 'classification'
messages: Array<{ role: 'system' | 'user'; content: string }>
maxOutputTokens: number
userId: string
}
type GenerationResult = {
text: string
model: string
provider: string
requestId: string
finishReason: 'stop' | 'length' | 'content-filter' | 'error'
}
The abstraction is useful because it captures the product contract, not because it hides every provider-specific capability. Keep enough information to explain a result, measure a provider migration, and make a deliberate choice when a capability cannot be normalized.
Cache Only What Is Safe to Reuse
Caching is attractive because an AI request can include repeated retrieval, expensive tool calls, and costly generation. It can lower latency and cost dramatically when the same valid result is requested again.
It also creates one of the easiest ways to leak context. A question that appears generic can produce a personalized answer after retrieval. If that answer is cached without the identity, role, tenant, policy version, and relevant source version that shaped it, a later user might receive information they were never authorized to see.
Before caching, answer these questions:
- What exact result is reusable, and for whom?
- Which authorization claims must be part of the cache key?
- Which source or policy changes invalidate it?
- Is the result time-sensitive or tied to a conversation?
- What would be the consequence of a false cache hit?
Exact caching is usually easier to reason about. Semantic caching expands the possible hit rate by treating similar prompts as interchangeable, but similarity is not equivalence. The extra vector search adds work, the threshold can be wrong, and the result may be inappropriate even when two questions sound alike. Evaluate it against real traffic, including privacy-sensitive and authorization-sensitive cases, before presenting it as a free optimization.
Make Write Actions a Separate Class of Capability
The architecture changes character when model output can alter the world: send an email, change a record, issue a refund, place an order, or start a transfer.
Text generation can be incorrect and still be recoverable. A wrong write can be expensive, irreversible, or harmful. Do not treat a model's tool call as a privileged instruction merely because it matches a JSON schema.
An action-capable workflow needs boundaries such as:
- an allowlist of actions and fields the model may propose,
- server-side authorization independent of the model's claim,
- idempotency keys for retried or duplicated requests,
- confirmation or human review for consequential changes,
- audit records that preserve the proposal, actor, policy, and final outcome,
- clear rollback and escalation paths.
The frontend can reinforce those boundaries. Show an action as a proposal with affected records, not as a magically completed task. A confirmation dialog is not enough on its own, but it creates a useful moment to disclose impact and collect explicit approval. For high-risk actions, a user should be able to inspect the evidence the system used and route the proposal to a human instead.
Adopt Orchestration When the Flow Is Actually a Workflow
As request paths develop loops, branching, parallel work, fallbacks, and human handoffs, an orchestration layer can provide real leverage. It can coordinate a flow such as processing a query, retrieving authorized evidence, generating a draft, evaluating it, and escalating when the draft is unsuitable.
It should not be the first answer to a sequence of two function calls.
An external orchestrator brings dependencies, conventions, hidden behavior, and another runtime to debug. Start with explicit application code while the flow is still understandable. Add a tool when the product needs durable execution, visible step state, robust retry semantics, branching that no one should reimplement, or a shared workflow surface across multiple teams.
The decision is not "framework or no framework." It is whether the current complexity is more safely managed in ordinary code or in a dedicated workflow system. The answer should be visible in incidents, delivery speed, and operational burden, not in a fashionable diagram.
Grow by Evidence, Not by Layer Count
Each component in an AI application can make the product more capable, safer, faster, or cheaper. Each can also introduce new failure modes. Context needs data governance. Guardrails need calibration. Routers can misclassify. Caches can return stale or unauthorized results. Gateways can become bottlenecks. Tools can execute harmful changes. Orchestrators can obscure a simple flow.
That is not an argument for a tiny system forever. It is an argument for an architecture with a reason for every box.
Start with one observable user job. Identify the constraint that blocks it. Add the smallest component that directly addresses that constraint. Instrument the result. Repeat when the evidence changes.
That approach produces something more valuable than an impressive architecture diagram: a system whose capability, safety, cost, and complexity each have an accountable place in the product.
