AI-first is a posture, not a feature
Most platforms bolt AI onto a non-AI core. Treating it as foundational architecture — a single governed gateway, provenance on every artifact — changes what you can promise.
There are two ways to put AI into a product. You can add it as a feature — a panel here, a "summarize" button there, a model call wired directly into whichever service happened to need it. Or you can treat it as a posture: a decision that shapes every layer beneath it, the way multi-tenancy or offline-first does.
The distinction isn't pedantic. A feature can be toggled off. A posture you build into the architecture, and it constrains — usefully — everything that comes after.
What "AI-first" actually buys you#
When AI is a feature, each team integrates its own provider SDK. Within a quarter you have six different retry policies, three places PHI can leak into a prompt, and no single answer to "what did the model see, and which version produced this output?"
When AI is a posture, there is exactly one egress point to any model — an AI Gateway — and three properties become structurally true instead of aspirational:
- Model selection is a config change, not a code change. The application asks for a capability ("extract fields from this document"), not a vendor.
- Cost ceilings, redaction, and moderation are enforced once, at the boundary, where they can be audited in isolation.
- Every generated artifact carries provenance —
{ model, version, promptId, traceId, reviewedBy, local }— so any output is citable and reversible.
The one diagram that matters#
Every service that wants AI talks to the gateway. The gateway talks to providers. Nothing else does.
The shape is boring on purpose. Boring shapes are the ones you can reason about at 2am.
What it looks like in code#
The application layer never imports a provider SDK. It depends on a port:
// domain/ports/ai-gateway.ts — pure TypeScript, no vendor in the import graph
export interface AiGateway {
extract(input: ExtractRequest): Promise<Provenanced<ExtractResult>>;
}
export type Provenanced<T> = T & {
provenance: {
model: string;
version: string;
promptId: string;
traceId: string;
reviewedBy: string | null;
local: boolean;
};
};
The decision of which model satisfies extract lives in one adapter. Swapping Provider A for a cheaper Provider B — or a local WebGPU model when the device is offline — never ripples into application code.
When the posture earns its keep#
| Scenario | AI-as-feature | AI-as-posture |
|---|---|---|
| Regulator asks "what produced this decision?" | Grep six services | One provenance record |
| New cheaper model ships | Six migrations | One adapter |
| Field office goes offline | Feature breaks | Routes to local model |
A posture is a constraint you adopt early so you don't have to negotiate it forty times later.
None of this is exotic. It's the same instinct that makes you put authentication at the edge instead of in every handler. AI is no different — it's just newer, so the bolt-on temptation is stronger.
If you're building something where the answer has to be defensible — government intake, clinical decision support, anything regulated — decide the posture before you write the first prompt.
Keep reading
ULIDs over UUIDs: standardizing identifiers across 40 services
An implicit decision left unenforced becomes 40 services split 60/40 between two incompatible identifier shapes. Here's the standard I wrote to close the door — and why ULID won.
- Architecture
- PostgreSQL
- Platform engineering
- DDD
ADR: Outbox over dual-write for cross-context events
Why every state-changing event in the Ghasi suite goes through a transactional outbox instead of a direct publish — and what that costs.
- Event-driven
- DDD
- Reliability
- PostgreSQL
Stop calling new Date() in your services
Time and randomness are infrastructure. When every service rolls its own clock port, you get six file paths and three interface names for one concern. The fix is one platform port — and the testability it unlocks.
- Testability
- Hexagonal architecture
- DDD
- TypeScript