The eight pillars of a production-grade agent
July 1, 2026 · 7 min read · fnlabs
An agent that works in a demo and an agent you can run in production are not the same artifact. The demo needs three things: a loop, a model, and a few tools. Production needs everything around the loop — the parts that let you see what happened, prove a change is safe, bound the cost, and hand a clean interface to whatever calls it next.
We wrote down what that "everything around the loop" actually is. It comes to eight operational pillars. Each one is the kind of thing you can skip on a Friday and not notice — until it surfaces as an incident three weeks later instead of a clean failure now.
A demo has a loop. Production has eight pillars.
| # | Pillar | What it buys you |
|---|---|---|
| 1 | Observability | You cannot debug, audit, or improve what you cannot see. |
| 2 | Evaluation | You cannot trust a change you haven't measured. |
| 3 | Reliability | Real tools and networks fail; the agent must degrade safely. |
| 4 | Security | Untrusted input and tool access are a real attack surface. |
| 5 | Cost & latency | Unbounded loops and tokens become unbounded bills and blown SLAs. |
| 6 | State & memory | In-process memory does not survive a crash, a redeploy, or an audit. |
| 7 | Versioning | Prompts and tools change; you need rollback and reproducibility. |
| 8 | Interface contract | The seam that orchestration — and multi-agent — plugs into. |
Notice what none of these are about: the agent's reasoning. They are the operational shell. And building that shell — traces, eval gates, retries with idempotency keys, token budgets, durable state, versioned prompts, a stable I/O schema — is where the sprints actually go. The loop takes an afternoon. The shell takes a quarter.
fn is the shell
fn is an AI function runtime. You bring the prompt, agent, or multi-agent workflow. fn wraps it in the eight pillars and hands back a function you call from your code. You write the behavior; the runtime owns the operational surface.
Here is what that looks like for the four pillars engineers feel first.
Evaluation is a gate, not a vibe
The reason a prompt change is scary is that nobody knows if it worked. So evaluation is a first-class command that exits non-zero below a threshold and drops straight into CI:
fn eval classify --dataset tickets.jsonl --threshold 0.95
A 2% regression that would have slipped past a human reviewer fails the build instead. Nothing reaches production without clearing the bar. This is the single highest-leverage practice for preventing silent regressions — and it is the one teams most often leave for "later."
One honest caveat, because the pillar demands it: model-graded evaluation (LLM-as-judge) carries positional bias, self-preference bias, and run-to-run drift. Treat those scores as a signal, not ground truth, and keep a human review cadence as the calibration layer. fn tracks scores per version so that drift is visible instead of invisible.
Rollback is a pointer move, not a redeploy
Every change produces a new immutable, content-addressed version. That turns the questions that used to be unanswerable into one-liners:
fn diff classify@v2 classify@v3 # what actually changed
fn rollback classify --to v2 # production is on fire, undo it
Rollback is instant because nothing is rebuilt — v2 still exists, and you are
only moving a pointer. Prompts, tool definitions, and model choice are versioned
artifacts, not strings edited in place, so behavior stays reproducible after the
fact.
Observability you didn't have to instrument
Every run emits a structured, OpenTelemetry-compatible trace on its own: one span per model call, one per tool call, each recording inputs, outputs, latency, token count, and cost.
run 8f3c··· classify@v3
├─ llm claude-opus-4-8 142ms 1,204 tok $0.003
├─ tool lookup_account 31ms ok
└─ llm claude-opus-4-8 88ms 512 tok $0.001 → status: success
You get success rate, retry rate, latency percentiles, and dollar cost per request without wiring up a single exporter. The trace is the ground truth you reach for when something looks wrong at 2am.
The interface is the seam
Each fn function exposes a stable, versioned I/O contract: a typed task in, a
structured result out — status (success / partial / failure / escalated),
payload, a quality signal, and the trace ID for that run. That contract is what
makes the function callable statelessly by task ID.
It is also where multi-agent comes from. A multi-agent system built on unreliable single agents inherits and multiplies that unreliability — so you earn orchestration by making each unit production-grade first. Once every function clears the eight pillars, an orchestrator can compose them without knowing their internals. Your prompt, your agent, or a whole multi-agent workflow — each one is just a function you call.
What we're not pretending
Being honest about the hard parts is part of being production-grade. Prompt injection is an active, unsolved research problem; the goal is defense in depth — treat tool output as untrusted data, scope permissions, and add every confirmed injection attempt to the eval set as a regression case, not to claim it's solved. Long-term shared memory is deliberately out of scope for now. And fn itself is pre-launch: the commands above are the shape of the product, and we're onboarding design partners rather than claiming numbers we haven't earned.
The eight pillars aren't a fn invention — they're what production has always demanded of software. The bet is that an AI feature deserves the same, and that you shouldn't have to rebuild the shell for every function you ship.
Want the runtime to own the shell? Join the waitlist, or read the Quickstart to see the function model up close.