07 / Journal / Artificial Intelligence

AI Agents in Production: Architecture, Tools, Memory and Guardrails

AI agents in production need clear tool boundaries, durable state, scoped memory and tested recovery. Build an architecture that makes failures understandable.

Start with the action boundary

An agent demonstration usually fails in production at the boundary between a plausible answer and a real action. A model can propose the right operation with the wrong account, repeat a successful request after a timeout, or treat an instruction inside a retrieved document as permission to change something.

This guide is for engineers and technical decision makers turning a useful prototype into a service. The aim is to make the execution path understandable: what the agent may observe, what it may propose, what the application will authorize, and how an operator can recover when the process stops halfway through.

Key takeaways

  • Keep identity, permissions and action budgets in application code.
  • Separate planning from execution through validated, narrow tool interfaces.
  • Store workflow state durably and make external effects safe to retry.
  • Treat memory as scoped data with provenance, expiry and deletion rules.
  • Evaluate completed tasks, refused actions and recovery paths together.

Design the smallest useful execution loop

Begin with one business capability, such as finding an order and drafting a support response. List the inputs, permissible reads, possible writes, completion conditions and escalation path. A workflow with a fixed sequence of steps may need only one model call. Introducing an autonomous loop is useful only when the next step depends on information that cannot be known in advance.

The application should own the loop. At each turn it supplies a bounded observation, accepts a structured proposal, validates that proposal and either executes an allowed tool or returns a clear rejection. The model does not receive a database password or a generic shell merely because those interfaces would make the demonstration shorter.

+--------------+     +-------------------+
| User request | --> | Identity + scope  |
+--------------+     +---------+---------+
                              |
                    +---------v---------+
                    | Bounded planner   | <---+
                    +---------+---------+     |
                              |               |
                    +---------v---------+     |
                    | Policy + approval |     |
                    +---------+---------+     |
                              |               |
                    +---------v---------+     |
                    | Tool executor     | ----+
                    +---------+---------+ observation
                              |
                    +---------v---------+
                    | State + audit log |
                    +-------------------+

Draw trust boundaries around these boxes. The request is authenticated but its text can still contain mistakes. Retrieved material and tool responses are evidence, not instructions. A policy decision must use server-side identity and resource ownership, even if the model supplies a confident explanation for why another identity would be more convenient.

An illustrative execution budget might permit four tool calls and one clarification. That is a design example, not a benchmark or universal default. Choose budgets from the task, then measure whether legitimate requests frequently exhaust them. Budget exhaustion should produce a resumable status or an escalation, not an invisible infinite loop.

Define completion before writing prompts

For an order lookup, completion might mean that a matching order was found within the authenticated customer's account and its status was explained. A generated sentence is not proof of completion. Persist the evidence references and a typed terminal status such as completed, blocked, needs_input or failed, so downstream systems can distinguish outcomes without interpreting prose.

Build tools as narrow application interfaces

A useful tool schema describes an operation, its required fields and its possible failures. Prefer get_order_status(order_id) over run_sql(query). The narrow form lets the service enforce tenant boundaries, hide internal fields and test failure conditions without asking the model to follow every database convention.

Validate the arguments after parsing, then authorize the resource using the current session. Reject unknown fields when they could change meaning. Check lengths, enumerations and numeric ranges. Return a stable error code and a short explanation that helps the planner recover without revealing private information about another customer.

The following runnable Python example shows a policy decision, not a complete authentication service. The trusted account and granted permissions would come from your server. The tool request is an untrusted model output.

from dataclasses import dataclass

@dataclass(frozen=True)
class Scope:
    account_id: str
    permissions: frozenset[str]

def authorize_lookup(scope, request):
    if set(request) != {"account_id", "order_id"}:
        raise ValueError("unexpected arguments")
    if "orders:read" not in scope.permissions:
        raise PermissionError("read not granted")
    if request["account_id"] != scope.account_id:
        raise PermissionError("account mismatch")
    order_id = request["order_id"]
    if not isinstance(order_id, str) or not order_id.strip():
        raise ValueError("order_id required")
    return order_id

scope = Scope("account-a", frozenset({"orders:read"}))
print(authorize_lookup(scope, {
    "account_id": "account-a", "order_id": "order-12"
}))

Write tools should expose a preview when practical. A draft email, proposed refund or planned database update can be reviewed as a concrete object. Approval should bind to that exact object, resource and action version. If the proposal changes after approval, invalidate the approval rather than treating it as an open-ended grant.

Credentials belong in the executor. Use a service identity with the minimum permissions needed for that operation and preserve the initiating user's scope. Restrict outbound destinations and avoid allowing the model to choose arbitrary URLs for authenticated requests. A small tool surface is easier to audit and usually produces clearer errors.

Make state and retries explicit

A conversation transcript is not a transaction log. Persist the workflow identifier, current step, proposed action, policy decision, tool attempt and observed outcome. Distinguish an operation that definitely failed before execution from one that timed out after the remote service may have accepted it.

Use an idempotency key for a logical action and reuse it across retries. Do not create a new key every time the network request is attempted. Where supported, the destination should atomically associate that key with the original result. Otherwise, combine a local durable operation record with a reconciliation query or a transactional outbox appropriate to the integration.

This runnable example creates a stable key from an action's canonical payload. It does not implement deduplication by itself. The receiving system or durable ledger must enforce uniqueness and reject reuse of the same key for a different payload.

import hashlib
import json

def action_key(workflow_id, step_id, payload):
    canonical = json.dumps(
        payload, sort_keys=True, separators=(",", ":")
    )
    material = f"{workflow_id}:{step_id}:{canonical}"
    return hashlib.sha256(material.encode()).hexdigest()

payload = {"order_id": "order-12", "action": "draft_reply"}
first = action_key("workflow-7", "reply-1", payload)
retry = action_key("workflow-7", "reply-1", payload)
assert first == retry
print(first)

A retry policy needs an attempt limit, backoff, a deadline and a classification of transient errors. A malformed request should not be retried unchanged. A rate limit may justify waiting within the remaining deadline. An uncertain write needs reconciliation before another attempt. These are different branches, even when they all appear as exceptions in a client library.

During recovery, reload the persisted step and verify whether its approval is still valid. The user's permissions or the underlying resource may have changed while the workflow was paused. Durable state makes resumption possible; it does not remove the need to recheck authorization at the moment of execution.

Give memory a purpose and a lifetime

Separate short-term workflow state from reusable knowledge and user preferences. Workflow state explains what has happened in this task. Retrieval supplies external evidence. A preference might record the user's chosen language. Combining these into one unstructured memory store makes access control, freshness and deletion difficult to reason about.

For each stored memory, record its owner, scope, source, creation time and expiry policy. A preference explicitly supplied by a user is different from a model's inference about that user. Require stronger evidence before storing inferred information, and avoid persisting secrets or sensitive personal details merely because they appeared in a conversation.

Storage choice Useful for Main trade-off
Request context Immediate reasoning Disappears when the request ends
Workflow database Recovery and audit Requires a state schema and migrations
Document retrieval index Versioned reference material Needs freshness and permission filtering
User preference store Explicit recurring preferences Needs consent, correction and deletion

Memory retrieval should be scoped before the result reaches the model. Filtering a final answer cannot undo disclosure in an earlier prompt or trace. Apply tenant and user permissions in the retrieval query, then verify the returned records belong to the intended scope. Revoke cached memory when permissions change.

Consider a user who corrects a shipping address. The current authoritative account record should outrank an old conversation summary. Define that precedence in code and data contracts. Otherwise, memory can make an agent less reliable by preserving outdated information with an appearance of personalization.

Evaluate guardrails and operate the service

Build a small, representative task set before expanding the agent's permissions. Include valid requests, ambiguous identities, missing resources, stale documents, adversarial tool output, expired approval and interruption after a successful write. Each case needs an expected decision and an explanation of what would count as a harmful failure.

Measure tool selection, argument correctness, authorization outcomes, successful completion and unnecessary escalation separately. A single aggregate success rate can hide an important failure class. Review cost and latency by task type, since an agent that resolves a complex request correctly may reasonably take longer than a simple lookup.

Guardrails should exist at several boundaries. Input handling can reject unsupported tasks. Tool validation controls execution. Output checks can catch unsupported claims or leaked identifiers. None of these makes untrusted text safe to treat as instructions. Keep the separation between evidence and authority visible throughout the workflow.

For a more detailed release process, see the LLM evaluation and observability guide. The practical link is a shared version record: prompt, model configuration, tool schemas, policy version and retrieval corpus should be identifiable in every trace so a regression can be reproduced.

Rehearse an uncertain outcome

Use a staging integration to simulate a timeout immediately after a remote write succeeds. The recovery procedure should locate the original operation through its idempotency key, confirm the result and continue without duplicating the effect. If the destination offers no way to reconcile, mark the workflow for human review instead of making an optimistic retry.

Run the same exercise after a worker restart, an expired session and a revoked permission. Record which state can be recovered automatically and which requires an operator. These exercises reveal gaps that a normal happy-path prompt evaluation cannot expose. They also produce a useful runbook for the first real incident.

Expose a kill switch that stops new write actions while allowing inspection of existing workflows. Alerts should link to a specific failure category and a documented response. Log identifiers and decisions by default, with sensitive payload capture limited by retention and access policies. Operators need enough evidence to investigate without creating a second uncontrolled store of customer data.

Common mistakes

  • Giving the model broad infrastructure access because narrow tools take longer to build.
  • Treating an approval message as authorization for every later version of a proposal.
  • Retrying a timed-out write without checking whether it already succeeded.
  • Storing every conversation as permanent memory without ownership or expiry.
  • Reporting answer quality while ignoring unsafe actions and failed recovery.

Checklist

  • Define one task, its completion criteria and its escalation path.
  • Authenticate the caller and scope every tool operation server-side.
  • Validate arguments and bind approvals to immutable action payloads.
  • Persist workflow state and reuse stable idempotency keys.
  • Specify memory provenance, access filters, expiry and deletion.
  • Test interruption, malicious evidence and permission changes.
  • Document write suspension, reconciliation and incident ownership.

FAQ

Does every AI application need an agent?

No. A fixed workflow with a few model calls is often easier to test and operate. Use an agent loop when the next action genuinely depends on observations and when its additional flexibility justifies the operational complexity.

Can a prompt enforce tool permissions?

A prompt can explain the policy, but the executor must enforce it. The application should reject unauthorized operations even if the model produces a perfectly formatted request and a persuasive reason.

How much memory should an agent keep?

Keep the information needed to complete and recover the task, plus explicit reusable preferences with a clear purpose. Retention should follow the data's sensitivity and useful lifetime, not the maximum capacity of a vector database.

What should happen when an agent reaches its budget?

Persist a clear terminal or paused status and explain what remains unresolved. Escalate or ask for missing information when appropriate, while keeping any further write action subject to a fresh policy decision.

Final thoughts

Production agents become manageable when their authority, state and recovery behavior are designed as ordinary application responsibilities. Start with a narrow task, make uncertain outcomes visible and expand permissions only after the evidence supports it.

For help shaping that architecture, explore my agentic AI development service and book a conversation.

  • #AI systems
  • #API integration
  • #Artificial Intelligence
  • #observability
  • #Python
Share

Comments

No comments yet. Start the conversation.

Leave a comment

Comments are moderated. Privacy policy.

Discuss this idea

Working on somethinglike this?

If AI Agents in Production: Architecture, Tools, Memory and Guardrails touches a problem you have, tell me about it - I’m happy to compare notes or scope the work.

Let’s chat