Every production AI agent I've built follows a three-layer architecture. It's not fancy. It's not novel. But it works reliably at scale, which is what matters.
After 50+ deployments across 16 industries, I keep coming back to the same pattern: separate perception from reasoning, separate reasoning from action, and never let the LLM hold state. The architecture is boring on purpose. Boring is what survives the first incident at 2am.
3-Layer Agent Architecture
50+
Systems Deployed
Using this architecture pattern
3
Layers
Perception, Reasoning, Action
100%
Model-Agnostic
Swap LLMs without breaking the system
Why Most Agent Architectures Break in Production
Before I describe the pattern, I want to describe what it replaces. Almost every failed agent I've audited fell into one of three antipatterns. None of them are obvious in a demo. All of them are fatal in production.
Antipattern 1: The Single LLM God. One large prompt does everything. It parses the input, decides what to do, calls tools, writes the response, and updates state. It looks elegant in a Jupyter notebook. In production it's a black box that hallucinates a JSON field one call out of fifty and silently corrupts your CRM. You can't unit test it because the failure modes are non-deterministic. Anthropic's own guidance on building effective agents recommends decomposing reasoning into smaller, traceable steps for exactly this reason (see Anthropic's "Building Effective Agents" engineering post, December 2024).
Antipattern 2: No State Separation. The conversation history is the state. The LLM is asked to "remember" what happened earlier in the thread. This works until your context window fills up, until a user opens a new tab, until you need to resume after a 24-hour timeout, or until you want to A/B test a new model. State belongs in a database, not a prompt.
Antipattern 3: No Observability. When something goes wrong, you have one log line: "agent failed." You don't know which layer failed, what input it received, what tool call it tried to make, or what the LLM returned before parsing broke. You're left re-running the user's exact request in a sandbox and praying it reproduces.
The three-layer pattern fixes all three. Each layer has a single responsibility. State lives in a database with versioned schemas. Every layer logs its inputs, outputs, and decisions to a structured event store you can replay.
Layer 1: Perception
The perception layer handles everything between the outside world and your agent's brain. Its job is to take messy, unstructured input and turn it into clean, structured data.
This includes:
- Input parsing: Converting emails, form submissions, chat messages, or API calls into a standard format
- Data enrichment: Pulling context from your CRM, database, or third-party APIs
- Classification: Determining what type of request this is before routing to the right logic
The perception layer should be model-agnostic. If you swap Claude Sonnet 4.6 for Claude Haiku 4.5 or a fine-tuned model later, nothing in this layer changes.
Concrete Example: A Normalized Input Schema
Every input I've ever processed, whether it's a Typeform submission, a Slack message, an inbound email, or a webhook from HubSpot, gets normalized into the same shape before it leaves Layer 1. Here's the schema I use as a starting point on most projects:
{
"event_id": "evt_01HXY3...",
"source": "typeform | slack | email | webhook",
"received_at": "2026-04-26T10:14:33Z",
"actor": {
"email": "jane@acme.com",
"external_ids": { "hubspot_contact_id": "1234" }
},
"intent_hint": "demo_request",
"payload": { "company": "Acme", "team_size": 40, "use_case": "..." },
"enrichment": {
"company_size": 220,
"industry": "SaaS",
"tech_stack": ["HubSpot", "Stripe", "Segment"]
},
"classification": "qualified_inbound"
}
The benefit is downstream code never has to care where the event came from. A Slack message from a paying customer and a marketing form fill from a stranger arrive at Layer 2 in the same shape. That's what makes the system testable.
Concrete Example: Enrichment APIs I Actually Use
Enrichment is where most teams cut corners and pay for it later. The agent makes worse decisions because it didn't have the right context, and you can't debug why because the context was never logged. The vendors I keep coming back to:
- Clearbit and Apollo for B2B company and contact enrichment (firmographic, technographic)
- BuiltWith when I specifically need detailed tech stack data
- HubSpot CRM API as the source of truth for relationship history (last activity, deal stage, owner)
- Internal data warehouse (Snowflake or Postgres) for product usage signals like "has this user logged in this week"
The pattern: enrichment runs in parallel, has a hard timeout (I usually set it to 800ms), and degrades gracefully. If Clearbit is slow, the agent proceeds with what it has rather than blocking. The enrichment result is stored on the event so you can replay decisions later.
Layer 2: Reasoning
The reasoning layer is where decisions happen. But here's the critical insight: most of the reasoning should be deterministic, not LLM-generated.
The LLM handles:
- Understanding natural language intent
- Generating human-readable responses
- Handling edge cases that don't fit clean rules
The deterministic logic handles:
- Business rules (qualification criteria, routing rules, approval workflows)
- State management (where the workflow is in the process)
- Guardrails (what should the agent never do?)
This split is what makes the difference between a demo and a production system. LLMs are powerful but unpredictable. Business logic needs to be reliable.
| Feature | LLM Handles | Deterministic Logic Handles |
|---|---|---|
| Natural language intent | ||
| Human-readable responses | ||
| Edge cases outside clean rules | ||
| Business rules and routing | ||
| State management | ||
| Guardrails and constraints |
LLMs are powerful but unpredictable. Business logic needs to be reliable. The split between the two is what separates a demo from a production system.
Concrete Example: A State Machine, Not a Prompt
In every reasoning layer I've shipped, the workflow is encoded as a state machine, not a free-form conversation. For a lead qualification agent, the states might look like this:
intake -> awaiting_company_size -> awaiting_use_case ->
awaiting_timeline -> qualified -> meeting_booked
The LLM's only job at each state is to extract a structured value from the user's natural-language reply. The deterministic logic decides which state to move to next based on that value, plus the lead's enrichment data. This means the same model can power the conversation in English, Spanish, or German, and the routing rules don't change.
I lean on Claude Sonnet 4.6 for the natural-language extraction step. Its tool-use and structured-output reliability is the highest I've measured across the major frontier models for this kind of constrained extraction task.
Concrete Example: Guardrails as Code, Not Prompts
A "guardrail" written in a prompt ("never quote a price") is a suggestion. A guardrail written in code ("if response_text matches /$\d+/, escalate to human") is a guarantee. Every reasoning layer I build has a guardrail module that runs after the LLM and before any action. Common guardrails I encode:
- Never quote pricing or commit to deliverables
- Never share data about another customer
- Never proceed with a destructive action without an explicit human-confirmed flag
- Never escalate the same conversation more than twice without human review
These are unit tests, not vibes. Each one has a test case in CI that asserts the guardrail blocks the unsafe output.
Layer 3: Action
The action layer executes decisions. Every action should be:
- Logged: You need an audit trail
- Reversible: Where possible, actions should be undoable
- Confirmed: High-stakes actions get human approval
Common actions include:
- Creating or updating CRM records
- Sending emails or messages
- Booking meetings
- Creating support tickets
- Triggering downstream workflows
Action Layer Requirements
- Every action is logged with a full audit trail
- Actions are reversible where possible
- High-stakes actions require human approval
- Actions trigger downstream workflows automatically
- Each action has clear success/failure states
Concrete Example: Idempotency Keys on Every Action
The most common production bug I see in action layers is duplicate writes. The agent retries a failed call, the call actually succeeded, and now the customer has two HubSpot contacts or got the same email twice. Every action call I emit includes an idempotency key derived from the event_id plus the action name:
idempotency_key = hash(event_id + ":" + action_name + ":" + version)
HubSpot, Stripe, Calendly, and most modern APIs accept idempotency keys natively. For ones that don't, I keep a small Postgres table of completed actions and check it before firing.
Concrete Example: Action Approval Gates
Some actions are too high-stakes to fire automatically. For each project, I define a list of actions that require human approval before execution. The agent prepares the action, persists it as a draft, and posts a Slack message with two buttons: Approve and Edit. A human approves with one click, the action fires. This pattern is what lets the agent operate during the day at near-full autonomy without ever causing an embarrassing incident.
I run the orchestration for these flows on n8n v2.11.4 for most clients because the human-approval pattern is built in and I don't have to write the Slack interaction code from scratch.
Join AI Builders Club
Weekly AI insights, tools, and builds. No fluff, just what matters.
How to Test Each Layer Independently
Independent testability is the single biggest reason I use this architecture. When something goes wrong in production, I want to be able to ask "is the input wrong, the decision wrong, or the side effect wrong?" and get an answer in minutes. Here's the test pattern I use per layer:
Layer 1 (Perception) tests are pure functions. Given a raw webhook payload, the parser produces a normalized event. No network calls, no LLM, no database. These run in milliseconds in CI. I keep a fixture folder with a few hundred real (sanitized) inputs the system has seen and assert the normalizer never throws and always emits a valid schema.
Layer 2 (Reasoning) tests use recorded LLM responses. The deterministic part of reasoning is unit tested directly. The LLM extraction step is tested using recorded responses (essentially golden files): given input X, assert the LLM extracted value Y, then assert the deterministic logic moved to state Z. Once I trust the extraction, I freeze a few responses and run them in CI to catch regressions when I change the prompt or the model.
Layer 3 (Action) tests use a mock execution backend. In test mode, the action layer writes to a local store instead of calling HubSpot or Stripe. The assertion is "given decision D, the action layer attempted action A with payload P." This decouples action testing from third-party API availability.
The full integration test runs end-to-end against a sandbox HubSpot and a real LLM call, but only on the main branch and only a small smoke set. Most defects get caught at the layer level, where the feedback loop is seconds, not minutes.
Real Production Examples by Layer
The pattern is the same across every project, but the weight of each layer shifts depending on the problem. Three real systems from my case studies:
Perception-heavy: AI voice agent for real estate. Most of the engineering on this build went into Layer 1: speech-to-text accuracy, accent handling, real-time interruption detection, and pulling property data from MLS feeds in under a second. The reasoning layer was relatively simple (a structured qualification flow) and the action layer was a CRM write plus a calendar booking. When you're working with voice, the perception layer is where the entire system lives or dies.
Reasoning-heavy: SaaS support triage. The input was clean (text tickets via API). The actions were boring (assign to a queue, update status). The hard work was in Layer 2: classifying tickets across 47 categories, deciding which ones the agent could resolve autonomously, which ones needed human review, and which ones to escalate immediately. The reasoning layer used Claude Sonnet 4.6 for classification plus a deterministic policy module for routing.
Action-heavy: Enterprise workflow automation. The reasoning was straightforward business rules. The hard part was Layer 3: the system had to fire actions across 11 internal systems (SAP, Workday, Salesforce, plus a handful of legacy mainframes) with full transactional integrity. We spent more time on idempotency, retry logic, and rollback than on the LLM itself. In enterprise environments, this is the norm.
The three-layer architecture survives all three problem shapes because the boundaries are clean. You add weight where you need it without rebuilding the system.
Common Mistakes I See When Teams Implement This
Five antipatterns I keep seeing when teams try to adopt this pattern, with the fix for each:
1. Putting business logic inside the LLM prompt. "If the company has more than 50 employees, route to enterprise SDR." Don't write that in the prompt. Write it in code. Prompts are the worst place for business logic because they're not version controlled the same way, not unit tested the same way, and silently change behavior with model updates. Fix: anything that looks like a rule lives in a policies/ module with a test file next to it.
2. Storing state in the conversation history. Multi-turn workflows need a real state store. Treating the LLM's context window as memory works until the user pauses for 10 minutes, opens another tab, or your context window overflows. Fix: persist a state record in Postgres or a key-value store keyed by conversation_id, and load it at the start of every turn.
3. No retry strategy at the action layer. A flaky third-party API once caused a system I inherited to mark 1,200 leads as "unqualified" because the enrichment call timed out and the default branch was "fail closed." Fix: define retry budgets per action, define what "fail open" vs "fail closed" means for each, and never let a single transient error change a customer-visible outcome.
4. Logging only what failed. When something looks wrong, you need the full trace, not just the error. Every event the agent processes should write a structured log with the input, the enrichment result, the classification, the LLM extraction, the policy decision, and the actions fired. Fix: write to a dedicated event log table with one row per layer per event. Storage is cheap. Debug time isn't.
5. Treating Layer 2 as a single big prompt. "Decide what to do" prompts are hard to reason about and harder to test. Break Layer 2 into a sequence of small extraction calls plus a deterministic policy. Each small LLM call has one job and a contract. Fix: never write a prompt that asks the LLM to "decide and respond." Decide in code. Use the LLM only for the parts that need natural-language understanding.
Putting It Together
The flow is always: Perceive → Reason → Act. Each layer has clear responsibilities and clean interfaces. You can test each layer independently, swap components without breaking others, and debug issues by tracing through the three layers.
This isn't the only way to build AI agents. But after deploying 50+ systems, it's the pattern that consistently ships on time and works reliably in production.
The Production Flow
Perceive
Parse messy input into structured data, enrich with context, classify the request type
Reason
LLM handles language, deterministic logic handles business rules, state, and guardrails
Act
Execute the decision: update CRM, send messages, book meetings, create tickets, trigger workflows
Frequently Asked Questions
What is a 3-layer AI agent architecture?
A 3-layer AI agent architecture splits an agent into three components with clean boundaries: a perception layer that normalizes and enriches input, a reasoning layer that combines LLM-based language understanding with deterministic business logic, and an action layer that executes side effects with logging, idempotency, and approval gates. Each layer is independently testable and swappable.
How is this different from a single-LLM agent?
A single-LLM agent puts parsing, decisions, and actions inside one prompt. It looks simple but is hard to debug, hard to test, and unreliable in production because business logic is non-deterministic. The 3-layer pattern moves business rules into code where they're version-controlled and unit-tested, and uses the LLM only for what it's actually good at: natural-language understanding and generation.
Should the reasoning layer use one LLM or multiple?
For most workflows, one LLM is enough. I use Claude Sonnet 4.6 as the default. The split is not "different LLMs for different tasks," it's "LLM for language, code for logic." If you do need multiple models (for example, a smaller cheaper model for classification and a larger one for response generation), keep the boundary at the function level: one Layer 2 module calls model A, another calls model B, and the policy module is shared.
How do you handle errors across layers?
Each layer has its own retry and fallback policy. Layer 1 fails open: if enrichment times out, the event still proceeds with whatever data is available. Layer 2 fails closed: if the LLM returns malformed JSON twice, the agent escalates to human review rather than guessing. Layer 3 is idempotent: every action has a key, retries are safe, and partial failures get rolled back or flagged for replay. Logs at every layer make root-cause analysis fast.
What tools work best for each layer?
For Layer 1: Python or TypeScript for parsing, Clearbit or Apollo for enrichment, your CRM's native API for context. For Layer 2: Claude Sonnet 4.6 for language tasks, plain code for policy, a state store like Postgres or Redis. For Layer 3: n8n v2.11.4 or a similar orchestrator for the workflow, native API SDKs for high-stakes integrations, Slack for human approval gates. The architecture is intentionally tool-agnostic. Pick what your team already operates well.
How long does it take to build a 3-layer agent?
A focused, well-scoped agent (one specific workflow, one CRM integration, one action set) takes 2 to 4 weeks for the first version. Add one more week per additional integration. The reason it's not faster: 70% of the work is the perception and action layers, not the LLM. People underestimate this because demos focus on the LLM. In production, plumbing wins.
External references: Anthropic, "Building Effective Agents," anthropic.com/engineering, December 2024. OpenAI, "A practical guide to building agents," platform.openai.com, 2025. Wang et al., "A Survey on Large Language Model based Autonomous Agents," arXiv:2308.11432.
See this architecture in action: I used this exact pattern to build an AI lead qualification agent that books 2x more meetings, and it's the foundation behind every system in my AI workflow automation guide. If you're dealing with chatbots that aren't working, read why most AI chatbots fail, the fix is always in the reasoning layer.
Want me to build this for your business? Check out my Custom AI Solutions or see how the full system works.
Get architecture patterns like this every week. Subscribe to AI Builders Club.
