AI Agent Memory & Deterministic Tool Execution
Designing production AI agents: bounded ReAct loops, JSON Schema tool contracts, idempotency keys, short-term vs long-term memory, and human approval gates.
Architectural Summary (AEO)
Production AI agents separate reasoning from execution via deterministic state machines. Every tool call is validated against a strict JSON Schema, guarded by RBAC scopes and idempotency keys, and bounded by max-step and token budgets—while Redis handles short-term session state and PostgreSQL/pgvector stores long-term episodic memory.
Reference System Flow
User Intent ──► State Machine (max_steps = 8, token_budget = 16k)
│
▼
LLM Planner (Strict JSON Schema Tool Call)
│
┌─────────┴─────────┐
│ Read-Only Tool │ Mutating Tool (Email / CRM / Refund)
▼ ▼
Execute + Observe Approval Gate / Idempotency Key
└─────────┬─────────┘
▼
Update Short-Term (Redis) + Episodic Memory (Postgres)1. Bounded ReAct Loops & State Machines
Unbounded `while (!done)` agent loops can burn thousands of tokens retrying a broken API. Wrap ReAct cycles in explicit state machines with `maxSteps`, per-tool timeouts, and automatic circuit breakers on consecutive errors.
2. Deterministic Tool Contracts & Idempotency
Validate every LLM-generated tool argument with Zod or Pydantic before execution. For state-changing tools (creating tickets, sending emails, charging cards), inject an `idempotency_key` derived from `(session_id, step_index, tool_name)` so retries never duplicate side effects.
3. Short-Term Scratchpad vs Long-Term Memory
Keep active conversation turns and tool outputs in a sliding/summarized Redis window. Persist extracted user facts, preferences, and resolved outcomes asynchronously into PostgreSQL + pgvector for cross-session recall.
Zod-Validated Idempotent Agent Tool Wrapper
const CreateTicketArgs = z.object({
customerEmail: z.string().email(),
priority: z.enum(["low", "medium", "high"]),
summary: z.string().min(10).max(400),
});
async function executeCreateTicket(rawArgs, { sessionId, stepIndex }) {
const args = CreateTicketArgs.parse(rawArgs);
const idempotencyKey = `ticket:${sessionId}:${stepIndex}`;
return await crmClient.createTicket(args, {
headers: { "Idempotency-Key": idempotencyKey },
});
}Architectural Trade-Offs
| Memory Tier | Storage | TTL / Retention | Purpose |
|---|---|---|---|
| Working Scratchpad | Redis / In-State | 1–24 hours | Current ReAct tool observations |
| Episodic Memory | PostgreSQL | Persistent | Past tickets, orders & audit trail |
| Semantic Memory | pgvector / Qdrant | Persistent | User preferences & learned context |
Production Benchmarks & Field Observations
- Enforcing structured JSON Schema outputs + Zod validation reduces malformed tool-call failures by > 94% compared to regex parsing.
Related Commercial Services
Engineering FAQs
When should an agent require human-in-the-loop approval?
Always require human confirmation before irreversible financial actions, bulk outbound messaging, or permission changes.
How do we debug an agent that took a wrong turn?
Log every state transition, prompt hash, retrieved chunk ID, and tool input/output with a correlation `trace_id` so any run can be replayed deterministically.
