Production AI: The Field Guide

Not theory. This is the guide for shipping AI that real users touch — building workflows and agents, engineering context and memory, injecting your own data (RAG vs dumping it in the prompt), running evals, wiring up observability and human-in-the-loop, and walking into an AI engineering interview with confidence. All code is TypeScript on Mastra, with Nous Research's Hermes Agent as a production case study. Examples lean on fintech/payments because that's home turf.

1The Production Mindset

The gap between "cool demo" and "production system" is the entire job of an AI engineer.

Analogy A demo is cooking dinner for a friend once. Production is running a restaurant: hundreds of orders a night, allergies you must never get wrong, health inspectors (compliance), food costs (tokens), and reviews (evals). Same skill of cooking — completely different discipline.

What actually changes in production

The production AI stack (mental map)

┌──────────────────────────────────────────────────────────┐ │ PRODUCT LAYER chat UI · API · background jobs │ ├──────────────────────────────────────────────────────────┤ │ ORCHESTRATION workflows · agents · tool calling │ │ human-in-the-loop · state machines │ ├──────────────────────────────────────────────────────────┤ │ CONTEXT LAYER prompts · memory · RAG · MCP tools │ ├──────────────────────────────────────────────────────────┤ │ MODEL LAYER model choice · routing · fallbacks │ │ structured output · caching │ ├──────────────────────────────────────────────────────────┤ │ QUALITY LAYER evals · tracing · monitoring │ │ (crosscutting) guardrails · cost controls │ └──────────────────────────────────────────────────────────┘
Key idea Read this guide bottom-up when debugging ("why is quality bad?") and top-down when designing ("what should we build?"). The Quality Layer is crosscutting — evals and observability wrap everything, they're not an afterthought bolted on at the end.

Rules of thumb that survive contact with production

2LLM Calls, Production-Grade

Before workflows, agents, or RAG — the humble API call itself needs hardening. Most "AI bugs" in production are actually integration bugs at this layer.

2.1 Structured outputs — never parse free text

TypeScript + Mastra — structured extraction, schema-validated & typed
import { Agent } from "@mastra/core/agent";
import { z } from "zod";

const DisputeTriage = z.object({
  category: z.enum(["fraud", "duplicate", "service_not_received", "other"]),
  urgency: z.number().int().min(1).max(5),
  requiresHuman: z.boolean(),
  reasoning: z.string(),
});

export const triageAgent = new Agent({
  id: "dispute-triage",
  name: "Dispute Triage",
  instructions: "Triage card disputes for a payments company. When unsure, set requiresHuman = true — never guess on fraud.",
  model: "anthropic/claude-sonnet-4-5",   // pinned version, not "latest"
});

export async function triageDispute(disputeText: string) {
  const result = await triageAgent.generate(
    `Triage this card dispute:\n${disputeText}`,
    {
      structuredOutput: {
        schema: DisputeTriage,        // Zod schema = instructions + validation + type
        errorStrategy: "strict",      // throw on schema mismatch → caller retries/falls back
      },
      maxSteps: 1,
    },
  );
  return result.object;               // typed as z.infer<typeof DisputeTriage>, already validated
}

2.2 Reliability: retries, timeouts, fallbacks

Analogy Treat model providers like payment processors. You wouldn't run a payments company on a single acquirer with no failover, no idempotency keys, and no reconciliation. Same energy: fallback providers, idempotent side effects, and logs you can reconcile against.

2.3 The non-negotiables checklist for every LLM call site

3Context Engineering

"Prompt engineering" was about clever wording. Context engineering is the real discipline: curating everything that enters the model's context window — instructions, history, retrieved data, tool results — treating attention as a scarce resource.

Analogy The context window is a surgeon's tray. A great scrub nurse doesn't dump the entire supply closet on the tray "just in case" — they lay out exactly the instruments this operation needs, in the order they'll be used. Extra instruments don't help; they slow the surgeon down and cause mistakes. Every token you add competes for the model's attention.

3.1 Why "just add more context" fails: context rot

3.2 Anatomy of a well-engineered context

┌─ SYSTEM PROMPT ────────────────────────────────┐ │ 1. Role & goal (who am I, what's success)│ │ 2. Hard rules (never do X, always do Y) │ │ 3. Tool guidance (when to use which tool) │ │ 4. Output format (schema, tone, length) │ │ 5. Few-shot examples (2–5 canonical cases) │ ├─ DYNAMIC CONTEXT ──────────────────────────────┤ │ 6. User/session memory (who is this user) │ │ 7. Retrieved data (RAG results, ranked) │ │ 8. Conversation history (possibly compacted) │ ├─ CURRENT TURN ─────────────────────────────────┤ │ 9. The actual user message │ └────────────────────────────────────────────────┘

3.3 Managing long-running context: compaction & note-taking

Common failure Teams discover context rot the hard way: the bot is great in turn 1–5, mediocre by turn 20, and by turn 50 it's ignoring the system prompt entirely. If quality degrades with conversation length, suspect context bloat before suspecting the model. Fix: compaction + tool-result pruning + moving rules the model keeps forgetting to the end of the context.

4Memory

LLMs are stateless — every request starts from zero. "Memory" is entirely an engineering construct you build around the model. Design it in tiers, like a computer's memory hierarchy.

Analogy Think RAM vs notebook vs filing cabinet. The context window is RAM: fast, expensive, wiped on restart. A session summary is a notebook: condensed notes you carry between meetings. Long-term memory is the filing cabinet: everything you might need again, indexed so you can retrieve just the right folder — you'd never dump the whole cabinet on your desk.

4.1 The memory tiers

TierWhat it holdsWhere it livesTypical implementation
Working memoryCurrent conversation, current task stateThe context window itselfMessage history + compaction (Section 3.3)
Session memoryWhat happened this session: decisions, entities mentionedCache/DB keyed by sessionRolling LLM-generated summary, updated every N turns
User memory (semantic)Stable facts: preferences, account context, "prefers Hindi", "is a merchant not a consumer"DB row / profile storeStructured key-value facts extracted by an LLM pass, with provenance + timestamps
Episodic memoryPast interactions: "raised a dispute in March, resolved in their favor"Vector store / DBEmbedded summaries of past sessions, retrieved by similarity when relevant
Procedural memoryLearned ways of doing things: playbooks, style rulesPrompt/skill filesCurated instructions that evolve — effectively versioned prompt content

4.2 Design rules for memory that doesn't backfire

TypeScript + Mastra — the whole tier stack, declaratively
import { Agent } from "@mastra/core/agent";
import { Memory } from "@mastra/memory";
import { LibSQLStore } from "@mastra/libsql";

const memory = new Memory({
  storage: new LibSQLStore({ id: "memory-store", url: "file:./memory.db" }),
  options: {
    lastMessages: 20,              // WORKING memory: recent turns kept verbatim
    workingMemory: {               // SEMANTIC user memory: structured, persistent profile
      enabled: true,
      template: `- Business type:\n- Language preference:\n- Open issues:\n- Risk flags:`,
    },
    semanticRecall: {              // EPISODIC memory: embed history, retrieve by relevance
      topK: 4,                     // hard budget — irrelevant memories are distractors
      messageRange: 2,
    },
    observationalMemory: true,     // background compaction of old turns into dense notes
  },
});

export const supportAgent = new Agent({
  id: "orbitx-support",
  name: "OrbitX Support",
  instructions: SUPPORT_PROMPT,   // includes: "never write card numbers/OTPs to memory"
  model: "anthropic/claude-sonnet-4-5",
  memory,
});

// resource = the user (memory follows them across conversations); thread = one conversation
await supportAgent.generate(msg, {
  memory: { resource: `user-${user.id}`, thread: `session-${session.id}` },
});

Frameworks like Mastra give you these tiers as configuration — but the design rules above still decide whether memory helps or hurts: what the working-memory template captures, how tight the topK budget is, and what you forbid from ever being written.

Key idea Memory is a read path and a write path, and both need engineering. Most teams only build the write path ("save stuff") and then wonder why the bot brings up irrelevant history. The read path — what to inject, when, within what budget — is where the quality lives.

5Injecting Your Data: RAG vs Context Stuffing

Every serious AI product needs the model to know things it wasn't trained on: your docs, your policies, your customer's data. You have two levers — stuff it into the context or retrieve it on demand (RAG) — and the right answer is usually a hybrid.

5.1 The three ways to inject knowledge

ApproachWhat it isBest whenWatch out for
Context stuffing (long context)Put the documents directly in the prompt, every callCorpus is small (< ~50–100K tokens), stable, and needed holistically (contracts, one policy manual, a codebase file)Cost scales per-call; context rot; latency; can't scale past window
RAG (retrieval-augmented generation)Index the corpus; at query time retrieve top-k relevant chunks into the promptCorpus is large, changing, per-user/tenant, or needs access controlRetrieval quality becomes your ceiling; pipeline complexity; freshness of index
Fine-tuningTrain the model on your dataTeaching style/format/behavior, or narrow classification at scaleTerrible for injecting facts (stale immediately, no citations, no access control). Rarely the answer for knowledge.
Analogy Context stuffing is bringing every file to the meeting — fine if the case fits in one folder. RAG is having a librarian fetch the three relevant folders when a question comes up — necessary once you have a warehouse. Fine-tuning is sending the employee to a training course — it changes how they work, but it's a horrible way to tell them this morning's news.

5.2 The decision framework

Anti-pattern "Long context killed RAG" is a headline, not an architecture. Even with 1M-token windows: (a) cost per query scales with tokens, (b) effective recall degrades well before the window is technically full, (c) you still can't fit a 10GB knowledge base, and (d) you still need per-user permissions. What long context actually killed is aggressive chunking — you can now retrieve whole documents or large sections instead of 200-token fragments.

5.3 What "RAG" means in 2026: retrieval is a toolbox, not one pattern

6Building a Real RAG Pipeline

Everyone's RAG demo works. Production RAG is 90% data engineering and retrieval quality, 10% the LLM call. Here is the pipeline with the decisions that matter.

INGESTION (offline) QUERY TIME (online) ───────────────────── ───────────────────── docs → parse → clean user query → chunk (structure-aware) → rewrite/expand query → enrich (titles, metadata) → hybrid search (vector + BM25) → embed → index (vector DB) → permission filter → also index keywords (BM25) → rerank top-50 → top-5 → assemble context (+citations) re-run on change (freshness!) → generate answer → verify/ground-check (optional)

6.1 Ingestion: where most quality is won or lost

6.2 Query time: hybrid search + reranking is the default

TypeScript + Mastra — ingestion & query time
import { MDocument, rerank } from "@mastra/rag";
import { PgVector } from "@mastra/pg";
import { embed, embedMany } from "ai";

// ── INGESTION (offline, re-run on document change) ──────────────
const doc = MDocument.fromMarkdown(policyMarkdown);
const chunks = await doc.chunk({
  strategy: "markdown",            // split along structure, not char counts
  maxSize: 1024, overlap: 80,
});
// enrich each chunk before embedding: title, section path, date, ACL metadata

// ── QUERY TIME ──────────────────────────────────────────────────
export async function answer(query: string, user: User, history: Msg[]) {
  const q = await rewriteQuery(query, history);           // cheap model, standalone query
  const { embedding } = await embed({ model: EMBEDDER, value: q });

  const hits = await pgVector.query({
    indexName: "kb",
    queryVector: embedding,
    topK: 40,                       // retrieve generously…
    filter: { tenant: user.tenant, acl: { $in: user.groups } },  // permissions IN the query
  });
  const top = (await rerank(hits, q, RERANK_MODEL)).slice(0, 6);  // …then rerank hard

  const sources = top.map(c =>
    `[${c.id}] (${c.metadata.title}, updated ${c.metadata.date})\n${c.metadata.text}`
  ).join("\n\n");

  return ragAgent.generate(
    `<sources>\n${sources}\n</sources>\n\nQuestion: ${query}`,
    { structuredOutput: { schema: GroundedAnswer } },   // {answer, citations[], confidence}
  );  // ragAgent instructions: answer ONLY from sources, cite [id], else say "not in KB"
}

6.3 Debugging RAG: it's almost always retrieval

7Workflows vs Agents

The most important architecture decision in applied AI: is the path through the task predictable (workflow) or does it have to be discovered at runtime (agent)? Choose wrong in either direction and you pay for it.

Definitions (Anthropic's framing, now industry standard) Workflow: LLMs and tools orchestrated through code-defined paths — you wrote the control flow, the model fills in the steps.
Agent: the LLM directs its own process — it decides which tools to use and when, looping until the task is done.
Analogy A workflow is an assembly line: fixed stations, each worker does one step brilliantly, quality is inspectable at every station. An agent is a skilled contractor you hand a problem to: "the bathroom leaks, fix it." Far more flexible — but you can't fully predict what they'll do, so you need supervision, budgets, and inspections. Nobody should use a contractor to do assembly-line work, or vice versa.

7.1 The five workflow patterns (know these cold — they're also interview canon)

PatternShapeUse whenPayments example
Prompt chainingA → B → C, each step's output feeds the next; add programmatic checks ("gates") between stepsTask decomposes into fixed sequential subtasks; trading latency for accuracyDraft dispute response → check it quotes policy correctly → translate to user's language
RoutingClassifier directs input to specialized handlersDistinct input categories needing different prompts/models/toolsSupport message → route to billing / KYC / fraud / chitchat handlers, each with a focused prompt
ParallelizationFan out simultaneous calls; aggregate (sectioning) or vote (majority)Independent subtasks, or you want multiple perspectives/confidenceScreen a transaction simultaneously for: sanctions terms, velocity anomaly, device mismatch → aggregate risk
Orchestrator–workersA lead LLM decomposes the task dynamically and delegates to worker callsSubtasks can't be enumerated in advance but structure is known"Investigate this merchant's chargeback spike" → orchestrator spawns workers per hypothesis
Evaluator–optimizerGenerator produces, evaluator critiques, loop until passYou can articulate clear evaluation criteria; iteration measurably helpsGenerate customer email → evaluator checks tone/compliance/completeness → revise
TypeScript + Mastra — routing as a workflow with .branch()
import { createWorkflow, createStep } from "@mastra/core/workflows";
import { z } from "zod";

const Route = z.object({
  intent: z.enum(["billing", "kyc", "fraud", "chitchat"]),
  confidence: z.number().min(0).max(1),
});

const route = createStep({
  id: "route",
  inputSchema: z.object({ text: z.string() }),
  outputSchema: Route.extend({ text: z.string() }),
  execute: async ({ inputData, mastra }) => {
    const r = await mastra.getAgent("router")      // tiny, cheap model — routing is easy
      .generate(inputData.text, { structuredOutput: { schema: Route } });
    return { ...r.object, text: inputData.text };
  },
});

export const supportFlow = createWorkflow({
  id: "support-flow",
  inputSchema: z.object({ text: z.string() }),
  outputSchema: z.object({ reply: z.string() }),
})
  .then(route)
  .branch([
    [async ({ inputData }) => inputData.confidence < 0.7, clarifyStep],   // don't guess — ask
    [async ({ inputData }) => inputData.intent === "billing", billingStep], // RAG + account tools
    [async ({ inputData }) => inputData.intent === "kyc", kycChecklistStep], // workflow + HITL gate
    [async ({ inputData }) => inputData.intent === "fraud", escalateStep],  // never automate; page fraud team
    [async ({ inputData }) => inputData.intent === "chitchat", smalltalkStep], // tiny model, no tools
  ])
  .commit();

7.2 When you actually need an agent

7.3 Frameworks: a pragmatic take (why this guide uses Mastra)

8Production-Grade Agents

An agent in production = model + tools + loop + state + budgets + guardrails + checkpoints + observability. The first three make the demo; the last five make it shippable.

8.1 The agent loop, hardened

TypeScript + Mastra — a hardened agent with a HITL-gated, idempotent money tool
import { Agent } from "@mastra/core/agent";
import { createTool } from "@mastra/core/tools";
import { createWorkflow, createStep } from "@mastra/core/workflows";

// Tools are prompts: purpose, when to use, when NOT to, and risk level — in the description
const issueRefund = createTool({
  id: "issue-refund",
  description: `Issue a refund. HIGH RISK: moves money, irreversible.
Use ONLY after verifying the transaction and policy eligibility.
Refunds over ₹2,000 will pause for human approval.`,
  inputSchema: z.object({
    txnId: z.string(),
    amountInr: z.number().positive(),
    reason: z.string().describe("policy clause justifying the refund"),
  }),
  execute: async ({ txnId, amountInr, reason }) => {
    // code is law: the executor enforces the gate, not the prompt
    if (amountInr > 2000) return requestApproval({ txnId, amountInr, reason }); // suspends (Sec. 9)
    return refundsApi.create({ txnId, amountInr, idempotencyKey: `refund-${txnId}` }); // retried ≠ paid twice
  },
});

export const investigator = new Agent({
  id: "dispute-investigator",
  name: "Dispute Investigator",
  instructions: INVESTIGATOR_PROMPT,          // incl. how to report failure explicitly
  model: "anthropic/claude-sonnet-4-5",
  tools: { searchTxns, getMerchantProfile, fetchDisputeEvidence, issueRefund },
  memory,                                     // Sec. 4 — compaction handled by Memory config
});

const result = await investigator.generate(task, {
  maxSteps: 15,                               // hard ceiling on the loop — no runaways
  memory: { resource: userId, thread: taskId },
  abortSignal: AbortSignal.timeout(120_000),  // deadline
});
// Wrap in a workflow (createWorkflow) when you need durable checkpoints:
// crash mid-task → .resume() from the last completed step, not from zero.

8.2 Tool design — the highest-leverage agent skill

8.3 State, durability, and multi-agent reality checks

Field wisdom The three most common production agent failures: (1) runaway loops re-trying a broken tool forever (fix: budgets + error-aware prompts), (2) context bloat from raw tool dumps until the agent forgets its task (fix: truncation + compaction), (3) confidently completing the wrong task (fix: verification step + evals + HITL for high stakes). Design for all three on day one.

8.4 Case study: Hermes Agent — and how to build self-improving agents

Nous Research's open-source Hermes Agent ("the agent that grows with you") is the best public reference for a production pattern most teams will want: an agent that gets better with use instead of resetting to factory settings every session. Its architecture is a checklist of ideas from this guide, shipped:

The self-improvement loop, generalized

Strip away specifics and every self-improving agent runs the same four-stage loop:

ACT ──────→ REFLECT ─────→ COMPILE ────────→ REUSE do the task what worked? write it down as load the skill/memory with tools what failed? a skill / memory / next time the situation what took updated instruction matches — and refine it too long? after each use
Analogy A self-improving agent is an employee who keeps a personal runbook. After every gnarly incident they add a page: what happened, what fixed it, what to check first next time. Two years in, they're 10× faster — not because they got smarter, but because the runbook did. Hermes ships the runbook habit; evals are the editor who keeps wrong lessons out of it.

9Human-in-the-Loop (HITL)

Autonomy is a dial, not a switch. Production systems earn autonomy gradually — with humans positioned where errors are expensive or irreversible.

Analogy Like training a new employee at a bank: week 1 they draft, you send. Month 2 they send small stuff, you review a sample. Year 1 they operate solo within limits and escalate exceptions. You never hand day-1 hires the vault keys — and you never stop auditing samples, even for veterans.

9.1 The autonomy ladder

LevelPatternExample
1. DraftAI proposes, human edits & sends every itemAgent drafts dispute responses; agents approve/edit in their queue
2. ApproveAI acts, but gated actions need one-click human approvalRefund > ₹2,000 pauses for supervisor approval with full context shown
3. ExceptionAI acts autonomously within policy; escalates out-of-policy & low-confidence casesAuto-resolve known billing FAQs; escalate anything mentioning fraud, legal, or below confidence threshold
4. AuditAI fully autonomous; humans review samples + flagged outliers after the factTransaction-note categorization, reviewed weekly at 2% sample rate

9.2 Engineering the approval flow

TypeScript + Mastra — durable approval gate with suspend/resume
const approveRefund = createStep({
  id: "approve-refund",
  inputSchema: RefundRequest,
  resumeSchema: z.object({ approved: z.boolean(), reason: z.string().optional() }),
  outputSchema: RefundResult,
  execute: async ({ inputData, resumeData, suspend }) => {
    if (!resumeData) {
      await notifyReviewers(inputData);        // Slack / review dashboard, with evidence + undo cost
      return await suspend({ pending: inputData });   // durable pause — hours later is fine
    }
    if (!resumeData.approved) {
      await recordRejection(inputData, resumeData.reason);  // rejections feed the eval dataset!
      return { status: "rejected" };
    }
    return refundsApi.create({ ...inputData, idempotencyKey: `refund-${inputData.txnId}` });
  },
});
// later, from the review UI's backend:
// await run.resume({ step: "approve-refund", resumeData: { approved: true } })

10Evals: The Discipline That Separates Pros From Tourists

Evals are to AI what tests are to software — except the system is probabilistic, so you're measuring score distributions, not pass/fail. No evals = you are shipping vibes.

Analogy Running an AI product without evals is running a restaurant without ever tasting the food — you only find out from one-star reviews. Evals are the tasting spoon: sample every dish, every service, against a standard, before the customer does.

10.1 The eval stack — three layers

LayerHow it scoresCost/speedUse for
Code-based (deterministic)Assertions: valid JSON? cites a source? contains required disclaimer? regex/exact match? retrieval recall@k?Free, instant — run in CI on every commitFormat, structure, retrieval quality, safety keywords, tool-call correctness
LLM-as-judgeA (usually stronger) model grades outputs against a rubricCents per case, secondsFaithfulness to sources, helpfulness, tone, policy compliance — anything fuzzy
Human reviewExperts label/grade samplesExpensive, slowBuilding golden data, calibrating the judge, auditing high-stakes flows

10.2 Building your eval dataset (the real asset)

TypeScript + Mastra — scorers for offline CI gates and online sampling
import { createScorer } from "@mastra/core/scores";

// Layer 1: deterministic scorer — free, instant, runs in CI on every commit
const citesSources = createScorer({
  name: "cites-sources",
  description: "Answers needing a source must include ≥1 citation",
}).generateScore(({ run }) => (run.output?.citations?.length ? 1 : 0));

// Layer 2: LLM-as-judge — ONE dimension per scorer, evidence required
const faithfulness = createScorer({
  name: "faithfulness",
  description: "Every claim in the answer is supported by the cited chunks",
  judge: { model: "anthropic/claude-sonnet-4-5", instructions: FAITHFULNESS_RUBRIC },
})
  .analyze(({ run }) => `Sources:\n${run.input.sources}\n\nAnswer:\n${run.output.answer}\n
For each claim: supported / unsupported, quoting the evidence.`)
  .generateScore(({ results }) => supportedRatio(results));

// OFFLINE: gate every prompt/model change — aggregate BY TAG, compare vs baseline
for (const c of dataset) {
  const out = await supportAgent.generate(c.input);
  results.push({ ...c.tags, score: await faithfulness.run({ input: c, output: out }) });
}
saveRun(aggregateByTag(results), { promptVersion: PROMPT_VERSION });  // diffable across versions

// ONLINE: attach scorers to the agent itself — sampled scoring of live traffic
export const supportAgent = new Agent({
  /* ...as before... */
  scorers: {
    citesSources: { scorer: citesSources, sampling: { type: "ratio", rate: 1.0 } },   // cheap → 100%
    faithfulness: { scorer: faithfulness, sampling: { type: "ratio", rate: 0.05 } },  // judge → 5% sample
  },
});

10.3 Offline vs online: the flywheel

Key idea When leadership asks "how good is the AI?", the only professional answer is a dashboard: "92% faithfulness on billing questions, 97% format validity, 3.1% escalation rate, trending flat week-over-week — here are the 8 failures this week and the fix in review." Evals turn AI quality from a feeling into a number someone can own.

11Monitoring & Observability

You can't fix what you can't see. For LLM systems, "seeing" means traces — the full story of every request — plus metrics and alerts built on top of them.

Analogy A trace is a flight recorder, not a speedometer. When flight 4417 has a rough landing, you don't want the fleet's average altitude — you want that flight's full recording: every input, every decision, every instrument reading, in order. Aggregate metrics tell you that something's wrong; traces tell you why.

11.1 Trace everything, structure it as spans

11.2 The dashboard that matters

CategoryMetricsAlert when…
HealthError rate, timeout rate, fallback-model activation rate, guardrail trigger rateError spike; fallback rate > a few %; injection-detector spike
LatencyTime-to-first-token, total duration, p50/p95/p99 per featurep95 breaches SLO; TTFT degrades after a deploy
CostTokens & $ per request / feature / tenant / day; cache hit rateCost per request jumps (often = context bloat bug); daily budget breach
Quality (online evals)Sampled judge scores, thumbs-down rate, rephrase rate, escalation rate, "not in KB" rateAny quality metric trends down 3+ days; escalations spike after prompt change
Agent behaviorTurns per task, tool-error rate, loop-abort rate, HITL approval/rejection rateAvg turns creeping up (context rot or tool regression); rejection rate rising

12Security & Guardrails

LLM apps add a new attack surface: the model happily follows instructions, and attackers can put instructions in data. In fintech this section is not optional reading.

12.1 Prompt injection — the #1 threat

12.2 The guardrail sandwich

INPUT GUARDS MODEL OUTPUT GUARDS ──────────── ───── ───────────── injection classifier → → schema validation PII detection/masking → (your app) → PII/secret leak scan topic/scope filter → → policy check (claims, promises) rate limits, size caps → → grounding check vs sources tool-call allowlist enforcement

13Cost & Latency Engineering

Token costs are your new cloud bill, and latency is your new conversion rate. Both are engineering problems with well-known levers.

13.1 The levers, in order of typical ROI

13.2 The token diet — field techniques for every layer

Analogy Tokens are like checked-baggage weight on a flight you take a million times a month. Nobody notices 2kg of junk in one suitcase; at fleet scale it's millions in fuel. The fix is the same: weigh everything, then remove what the trip doesn't need.

13.3 Latency

Key idea Track cost per successful task, not cost per API call. A cheap model that fails 30% of the time (users retry, escalate, churn) is more expensive than a pricier model that succeeds. Optimize the fraction, not just the denominator.

14The Production Readiness Checklist

Print this. Before any AI feature ships to real users:

Quality

Reliability

Safety & security

Operations

15Team Playbook: Running AI Engineering as a Team

Conventions that keep a team's AI work reviewable, debuggable, and improvable — the AI equivalent of code style + CI + on-call.

15.1 Prompts and configs are code

15.2 Rituals that work

15.3 Who owns what (small-team version)

16The AI Interview Guide (Candidate Edition)

AI engineering interviews in 2026 test production judgment, not transformer math. If you've internalized Sections 1–15, you have the answers — this section shapes them into interview form.

16.1 What interviewers are actually probing

16.2 The system-design question — a reusable answer skeleton

You will get: "Design an AI assistant for X" (support bot, document Q&A, analyst copilot). Walk this ladder out loud:

  1. Clarify: Who are the users? What actions can it take vs just answer? Volume, latency budget, stakes of a wrong answer? Compliance constraints?
  2. Simplest baseline: single model + well-engineered context; state what it can't do (fresh data, large KB, actions).
  3. Knowledge: hybrid — stuff small stable core (cached), RAG the large/changing corpus (hybrid search + rerank + permission filters), tools/APIs for live data. Justify vs pure long-context (cost, ACLs, freshness — Section 5 framework).
  4. Orchestration: route intents; workflows for predictable flows; agent loop only for open-ended parts — with budgets, checkpoints, tool-risk tiers.
  5. Safety & HITL: guardrail sandwich; autonomy ladder per action class; lethal-trifecta check.
  6. Quality loop: offline eval dataset + CI gating; tracing; online sampled evals; flywheel from prod failures back into the dataset.
  7. Cost/latency: prompt caching, model routing, streaming; cost-per-successful-task as the north-star metric.
Interview tip Mentioning trade-offs unprompted is the difference between mid and senior. Practice sentences like: "I'd start with a workflow because the path is predictable; the trade-off vs an agent is flexibility, which we don't need yet — and if evals show >15% of cases falling outside the flow, that's my trigger to revisit."

16.3 Rapid-fire questions with strong answers

When would you use RAG vs putting documents directly in the context vs fine-tuning?
Decision by corpus size, change rate, and access control. Small (<~50K tokens), stable, needed holistically → stuff it in context with prompt caching. Large, frequently changing, per-user permissions, or needs citations → RAG. Fine-tuning is for style/format/behavior or high-volume narrow classification — almost never for injecting facts, because facts go stale, can't be access-controlled, and can't be cited. Production answer is usually hybrid: cached stable core + RAG for the long tail + API tools for live data.
Your RAG system gives wrong answers. Debug it.
Follow the pipeline backwards with a trace: (1) Is the right content in the index at all — parsing/chunking may have destroyed it? (2) Was it retrieved into the candidate set — check embeddings/hybrid search, try the query manually? (3) Did it survive reranking into the final context? (4) Did the model use it faithfully? Most failures are 1–3, so I'd check retrieval first, and I'd want a standing retrieval eval (recall@k on query→chunk pairs) so regressions surface automatically rather than via user complaints.
What's the difference between a workflow and an agent, and how do you choose?
A workflow is code-defined control flow where LLM calls fill in steps — predictable, debuggable, cheap. An agent is a model directing its own loop with tools — flexible, but higher cost, latency, and variance. Choose by path predictability: if I can draw the flowchart, I write the flowchart (routing, chaining, parallelization, orchestrator-workers, evaluator-optimizer). Agents earn their keep when steps genuinely can't be enumerated and the environment provides feedback the model can react to — and only with budgets, checkpointing, and verifiable outputs.
How do you evaluate an LLM application?
Three layers: deterministic code checks (schema validity, citations present, safety regexes, retrieval recall) run in CI; LLM-as-judge for fuzzy dimensions (faithfulness, tone) — one dimension per call, rubric anchored, and calibrated against human labels to ~85%+ agreement; human review for golden data and high-stakes audits. Dataset comes from real prod failures and HITL rejections first, synthetic second. Offline evals gate every prompt/model change; online sampled evals plus implicit signals (thumbs, rephrase rate, escalation rate) monitor prod; failures flow back into the dataset — that flywheel is the whole game.
What is prompt injection and how do you defend against it?
Attacker-controlled text that the model treats as instructions — direct (user message) or, more dangerously, indirect (hidden in documents, emails, web pages the system reads). No complete fix exists, so defense in depth: demarcate untrusted content and instruct the model never to follow instructions inside it, classify inbound content, least-privilege tools, and structurally avoid the lethal trifecta — private data + untrusted content + external actions in one agent. Anything consequential downstream of untrusted input gets human approval. And guards live in code at the tool layer, because prompts are suggestions, not controls.
How would you cut LLM costs by 80% without hurting quality?
In ROI order: prompt caching (restructure prompts stable-first — up to ~90% off cached tokens); model routing/cascades (small model for the easy 60–80% of traffic, escalate on low confidence — verified by per-slice evals); context diet (rerank harder, keep fewer chunks, truncate tool results, compact history — cost bugs are usually context-bloat bugs); cap output tokens; move async work to batch APIs at ~50% off. Crucially, I'd measure cost per successful task, not per call — a cheaper model that fails more can be net more expensive — and gate each change on the eval suite.
How do you handle hallucinations in production?
Prevention: ground generation in retrieved sources with mandatory citations, and explicitly instruct + reward "I don't know / not in the KB" (include such cases in evals). Detection: faithfulness judges comparing claims to cited chunks, both offline and on sampled prod traffic. Containment: confidence-based routing — low confidence triggers clarifying questions or human escalation rather than a guess; high-stakes outputs get HITL regardless. You don't eliminate hallucination; you engineer the system so it's rare, detected, and cheap when it happens.
Design memory for a long-running assistant.
Tiered: working memory (the context window, managed with compaction), session memory (rolling summary), semantic user memory (structured facts with provenance and timestamps, extracted by an LLM pass with add/update/skip reconciliation — facts change), episodic memory (embedded past-session summaries, retrieved by relevance). Both paths need engineering: write path extracts atomic facts, not transcripts; read path injects only relevant memory within a token budget, because irrelevant memory is a distractor. Plus user-visible, deletable memory for trust and compliance, and never store secrets — memory is future prompt-injection surface.
A new model version came out. How do you upgrade?
Never bump the string in place. Pin versions; run the full offline eval suite on the new model — checking per-slice, since upgrades often improve the average while regressing a slice; re-tune prompts if needed (prompts overfit to models); canary 5–10% of traffic with online eval comparison; promote or roll back based on the numbers. Also re-check cost and latency — a "better" model that doubles p95 latency may be a worse product.
When would you use multiple agents vs one?
Default to one agent with well-designed tools. Subagents earn their place for context isolation (each does read-heavy work in its own context and returns a summary, keeping the orchestrator clean) and for parallelizable independent work (research, review fan-out). They hurt when they share mutable state — coordination overhead and inconsistency explode. So: architecture driven by context and parallelism limits, not by mapping an org chart onto the problem.
How would you design an agent that improves over time?
The act→reflect→compile→reuse loop, as popularized by Hermes Agent: after complex tasks, a cheap reflection pass compiles what worked into versioned skill files (structured markdown playbooks) that get retrieved into context when a similar task appears, and refined after each use. Feed the same loop from HITL rejections and eval failures — they're free reflection material. The critical guard: self-modification is gated exactly like a human's prompt PR — every skill/instruction change runs the eval suite, regressions auto-reject, and a human approves skill diffs until the suite is trustworthy. Improvement without evals is just confident drift.

16.4 Questions YOU should ask (signal you've operated in prod)

If they can't answer these, you've also just learned what your first quarter of work would be.

Closing thought Everything in this guide compounds into one loop: engineer the context → constrain the outputs → trace everything → eval relentlessly → feed failures back. Teams that run this loop ship AI that gets better every week. Teams that don't, ship demos that decay. Be the first kind.