Pitfalls of AI System Design in Complex Systems
Where LLM pipelines, agent chains, and RAG systems actually break as complexity grows — silent failures, prompt drift, compounding cost and latency, and the temptation to reach for a model when deterministic code would do.
Most of the advice about AI system design is really advice about prompting: get the model to behave on a single call. That's a real problem, but it's not the one that takes a system down. The failure mode that actually matters shows up later, once you've chained a few LLM calls together, added a retrieval step, wired in a couple of tools, and the thing has grown past what any one person can hold in their head. The pitfalls at that point aren't the ordinary software ones — bad abstractions, tight coupling, missing tests. They're specific to systems where a probabilistic component sits in the middle of a pipeline that everything else treats as deterministic.
Reaching for a model when you need a function
The easiest mistake to make early, and the hardest to unwind later, is using an LLM for logic that has a correct answer. Parsing a date out of text, deciding whether a number is above a threshold, matching a string against a known list, routing based on a field value — these are all things a model can do, and will do correctly most of the time, which is exactly the problem. "Most of the time" is a regression waiting to happen in code that used to be a switch statement.
Anthropic's own guidance on agent design makes this point directly: most production systems don't need an autonomous agent, they need a workflow with clear steps, tight tools, and measurable outcomes. A model call belongs where the task genuinely requires judgment over unstructured input — summarizing a review, classifying an ambiguous support ticket, drafting language. It doesn't belong in the parts of the pipeline that were always going to be if/else logic if you'd designed it as code first and reached for the model second. On the lead-qualification work I've built, the scoring rubric is deterministic — weighted fields, explicit thresholds — and the LLM's job is narrower: extracting structured signal out of free-text notes that feed that rubric. The model never decides the score. It decides what goes into the inputs.
Silent failure is the actual danger, not visible failure
A crashed process pages someone. A wrong-but-confident model output does not. This is the pitfall that's distinct from ordinary software bugs: a traditional program that hits bad input usually throws, times out, or returns null in a way downstream code can check for. A model asked to extract a field from a document it can't actually parse will often just... answer anyway, with the same tone of confidence it uses when it's right. There's no exception to catch.
The only real defense is treating model output as untrusted input to the rest of the system, the same way you'd treat a value from an external API you don't control — validate shape, validate range, and where possible validate against a second, independent signal before it gets written anywhere durable. On a review-intelligence pipeline, that means a sentiment classification gets checked against the review's star rating; a five-star review scored as strongly negative is a mismatch worth flagging, not a result worth trusting silently. That kind of cross-check catches the exact failure mode — confident, plausible, wrong — that no amount of prompt tuning eliminates, because it's not a prompting problem. It's a validation-layer problem, and it needs to live in code, not in instructions to the model.
Prompt and context drift as the system evolves
A prompt that was carefully tuned against the cases you had six months ago degrades as the system grows new callers, new input shapes, and new edge cases nobody re-tested it against. This is drift in the boring, structural sense: someone adds a field to the context being passed in, someone else tweaks an instruction to fix one complaint, and three months later nobody can explain why the output changed for an unrelated case. Unlike a function signature, a prompt has no compiler to tell you what broke.
The mitigation that actually holds up is treating prompts like code that needs versioning and regression tests — a fixed set of representative inputs with expected output properties (not exact text, since exact text will never be stable), run against every prompt change before it ships. Without that, you're doing manual QA on a component that changes behavior every time it's touched, which doesn't scale past the first few iterations.
Cost and latency compound across a chain
A single LLM call with a two-second response time is a non-issue. Five calls chained sequentially — retrieve, rerank, generate, critique, regenerate — is ten-plus seconds and five times the token cost, and that's before anything retries. Each additional hop in an agent chain multiplies both the latency and the failure surface of everything before it, because a failure anywhere in the chain means redoing work downstream of it. Anthropic's engineering writing on long-running agent harnesses makes a similar point about coding agents specifically: if the model can't reliably recover from its own errors, each loop iteration compounds the failure rate of the weakest link in the chain rather than averaging it out.
This is where "just add another agent to handle that edge case" becomes an anti-pattern instead of a design choice. Every additional model call in a pipeline needs to earn its cost and latency budget against the alternative of a deterministic step that's faster, cheaper, and doesn't fail silently. A reranking step implemented as a cosine-similarity sort over embeddings you already computed is often good enough, and it costs nothing incremental and never hallucinates a bad rank.
Reproducibility, or the lack of it
Debugging a normal program means re-running it and getting the same output. Debugging an LLM pipeline means re-running it and, unless you've pinned model version, temperature, and the exact prompt and context that were live at the time, getting something similar — which is close to useless when you're trying to figure out why one specific request produced a bad result. The system prompt that shipped last Tuesday isn't the one that's live today, the context window included whatever the retrieval step happened to return at that moment, and the model provider may have updated the underlying weights without telling you.
The fix is logging enough to reconstruct the exact call: the full resolved prompt (not the template), the retrieved context, the model and parameters, and the raw response, all keyed to whatever downstream action it triggered. That log is the only thing that turns "the agent did something weird yesterday" into an actual bug report instead of a shrug.
The simpler pipeline that should have won
The recurring anti-pattern underneath all of the above is chaining agents or LLM calls to solve a problem that a deterministic pipeline already solves better. Multi-agent orchestration is genuinely useful when a task requires real judgment at multiple independent decision points that can't be collapsed into one call. It's a liability when it's been reached for because it's the interesting way to solve the problem rather than the reliable one. A system with three LLM calls chained together, each depending on the previous one having gone right, is a system with a compounding failure rate that a single well-scoped call — or no call at all — wouldn't have.
The discipline that holds all of this together is the same one that makes any system operable at all: keep the parts that have a correct answer in code, keep the parts that need judgment in the model, and put a validation layer at every seam between them. Complexity in an AI system isn't dangerous because it's complex. It's dangerous because it's the layer where "probably right" quietly replaces "verified," and nothing in the pipeline notices when that happens.
Sources referenced: Anthropic, Building Effective AI Agents; Anthropic, Effective Harnesses for Long-Running Agents.
Related Projects
Software
activeGoogle Review Intelligence
Single-admin internal tool that manually triggers Google review scrapes, imports them idempotently, and turns the result into rating trends, phrase analysis, and formula-safe CSV exports.
Next.js · TypeScript · PostgreSQL · Prisma · pg-boss · DataForSEO · Apify · Docker
Updated 2026-09-23
Software
maintainedLead Qualification System
Split-service pipeline that imports local business leads, scores each one on website opportunity, and syncs only the ones a human approves to HubSpot.
Next.js · TypeScript · PostgreSQL · Prisma · Redis · BullMQ · Docker
Updated 2026-05-16
AI / Agents
experimentalHomelab playground: Nous Research Hermes
Nous Research Hermes running in the homelab with terminal-level Proxmox access. It now picks up flaking Docker containers from Beszel monitoring, troubleshoots them, asks for approval over Telegram, and then fixes the root cause instead of just restarting the container.
Nous Research Hermes · Proxmox · Beszel · Telegram · Uptime Kuma · n8n · Docker
Updated 2026-10-23