}
class ReflexAgent {
private llm: LLMClient;
private tools: Map<string, Tool>;
constructor(llm: LLMClient, tools: Tool[]) {
this.llm = llm;
this.tools = new Map(tools.map(t => [t.name, t]));
}
async run(input: string): Promise<string> {
// Structured output forces the LLM to return valid JSON
const response = await this.llm.chat({
messages: [
{ role: 'system', content: 'Select the appropriate tool and arguments.' },
{ role: 'user', content: input }
],
response_format: { type: 'json_schema', schema: this.getToolSchema() }
});
const action = JSON.parse(response.content);
const tool = this.tools.get(action.tool);
if (!tool) throw new Error(`Unknown tool: ${action.tool}`);
// Validate arguments against schema
tool.schema.parse(action.args);
return await tool.execute(action.args);
}
}
### 2. The ReAct Pattern (Reasoning + Acting)
ReAct interleaves reasoning traces with actions. The agent thinks, acts, observes the result, and repeats. This pattern handles dynamic environments where the next step depends on previous observations.
**Architecture:** Thought β Action β Observation β Loop until answer.
**TypeScript Implementation:**
```typescript
interface ReActState {
thought: string;
action?: { tool: string; args: any };
observation?: string;
finalAnswer?: string;
}
class ReActAgent {
private maxSteps: number;
async run(input: string): Promise<string> {
let history: Message[] = [{ role: 'user', content: input }];
let step = 0;
while (step < this.maxSteps) {
// 1. Reasoning Phase
const response = await this.llm.chat({
messages: history,
stop: ['Observation:']
});
const output = response.content;
history.push({ role: 'assistant', content: output });
// 2. Parsing Phase
if (output.includes('Final Answer:')) {
return output.split('Final Answer:')[1].trim();
}
const actionMatch = output.match(/Action: (.+)\nAction Input: (.+)/);
if (!actionMatch) {
// Force correction if format is invalid
history.push({ role: 'user', content: 'Invalid format. Use Action: and Action Input:' });
continue;
}
// 3. Acting Phase
const toolName = actionMatch[1];
const toolArgs = JSON.parse(actionMatch[2]);
const tool = this.tools.get(toolName);
let observation: string;
try {
observation = await tool.execute(toolArgs);
} catch (err) {
observation = `Error: ${err.message}`;
}
// 4. Observation Phase
history.push({ role: 'user', content: `Observation: ${observation}` });
step++;
}
throw new Error('Agent exceeded maximum steps without solution.');
}
}
3. Planner-Executor Pattern
This pattern decouples planning from execution. A Planner agent generates a step-by-step plan, and an Executor agent carries out steps, potentially asking the Planner for re-planning if failures occur.
Architecture: Planner generates plan β Executor runs steps β Feedback to Planner β Adjust plan.
Rationale: This reduces context window pollution. The Executor only needs the current step and relevant context, not the entire history of the plan generation. It improves reliability for long-horizon tasks.
interface PlanStep {
id: string;
description: string;
status: 'pending' | 'completed' | 'failed';
result?: string;
}
class PlannerExecutorAgent {
async run(input: string): Promise<string> {
// 1. Generate Initial Plan
let plan = await this.planner.generatePlan(input);
for (const step of plan.steps) {
try {
// 2. Execute Step
step.result = await this.executor.runStep(step);
step.status = 'completed';
} catch (err) {
step.status = 'failed';
// 3. Re-planning on failure
plan = await this.planner.replan(input, plan, err.message);
// Restart loop with updated plan or handle critical failure
break;
}
}
return this.synthesizeResult(plan);
}
}
Architecture Decisions
- Structured Outputs: Always enforce JSON schema validation on LLM outputs. Unstructured text parsing is a primary source of brittle agent code.
- Tool Definition: Tools must include strict input schemas and clear descriptions. The description is the primary signal for tool selection; vague descriptions lead to routing errors.
- State Management: Agent state should be externalized (e.g., Redis or database) to support persistence, recovery, and multi-session continuity.
- Guardrails: Implement input/output filters to prevent prompt injection and ensure compliance with safety policies.
Pitfall Guide
1. Infinite Reasoning Loops
Mistake: The agent fails to reach a conclusion and continues generating thoughts/actions until hitting rate limits or cost caps.
Remediation: Always implement a hard max_steps counter. Add a termination condition check in the loop. If the agent repeats the same action, force termination or switch to a fallback strategy.
Mistake: The LLM generates a tool name that doesn't exist or arguments that violate the schema.
Remediation: Use function calling APIs with strict schemas. Implement a retry mechanism where the error message is fed back to the LLM to correct the arguments. Never trust raw LLM output for tool invocation.
3. Context Window Overflow
Mistake: As the agent runs, the conversation history grows, consuming the context window and causing the LLM to forget instructions or truncate critical information.
Remediation: Implement sliding windows or summarization strategies. For long-running agents, compress older observations into summaries. Use retrieval-augmented generation (RAG) to fetch relevant context rather than dumping everything into the prompt.
4. Cost Spirals in Recursive Patterns
Mistake: Multi-agent or recursive patterns trigger exponential token usage due to redundant processing.
Remediation: Monitor token usage per step. Implement caching for identical tool calls. Use cheaper models for planning/reflection and expensive models only for final execution or complex reasoning.
5. Lack of Observability
Mistake: Treating the agent as a black box makes debugging impossible when it fails.
Remediation: Instrument every step. Log thoughts, actions, observations, and tool responses. Use tracing tools (e.g., LangSmith, Phoenix) to visualize the agent's decision tree. Store traces for post-mortem analysis.
6. Over-Engineering Simple Tasks
Mistake: Applying a Multi-Agent Swarm to a task that requires a single tool call.
Remediation: Evaluate task complexity before choosing a pattern. If the task is deterministic or requires <2 steps, use a Reflex Agent. Reserve complex patterns for tasks requiring dynamic adaptation or domain specialization.
7. State Leakage
Mistake: Agent state from one user session bleeds into another, causing data privacy violations or incorrect context.
Remediation: Isolate state by session ID. Clear memory buffers between requests. Use namespacing in vector stores and databases. Validate user permissions before accessing state.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Single tool call with validation | Reflex Agent | Minimal latency; deterministic routing. | Low |
| Web research or data lookup | ReAct Pattern | Dynamic interaction with external sources. | Medium |
| Complex workflow (>5 steps) | Planner-Executor | Separation of concerns; better error recovery. | High |
| Multi-domain collaboration | Multi-Agent Swarm | Specialized expertise; parallel execution. | Very High |
| Real-time chatbot | Reflex + RAG | Low latency; context-aware responses. | Low-Medium |
Configuration Template
{
"agent": {
"name": "production-support-agent",
"pattern": "react",
"model": {
"provider": "openai",
"name": "gpt-4o",
"temperature": 0.1,
"max_tokens": 2000
},
"constraints": {
"max_steps": 10,
"max_context_tokens": 8000,
"timeout_ms": 30000
},
"tools": [
{
"name": "search_knowledge_base",
"schema": {
"type": "object",
"properties": {
"query": { "type": "string", "description": "Search query" },
"limit": { "type": "integer", "default": 5 }
},
"required": ["query"]
}
},
{
"name": "create_ticket",
"schema": {
"type": "object",
"properties": {
"title": { "type": "string" },
"description": { "type": "string" },
"priority": { "type": "string", "enum": ["low", "medium", "high"] }
},
"required": ["title", "description"]
}
}
],
"observability": {
"enabled": true,
"trace_level": "verbose",
"exporter": "otlp"
}
}
}
Quick Start Guide
-
Initialize Project:
npm init -y
npm install zod openai @langchain/core
-
Define Tools:
Create a tools.ts file defining your tools with Zod schemas and execution logic. Ensure schemas are strict.
-
Implement Agent Loop:
Copy the ReAct agent structure from the Core Solution. Configure the LLM client and inject tools. Set max_steps to 5 for testing.
-
Run and Observe:
Execute the agent with a test input. Check the console logs for thoughts and actions. Verify tool calls match schemas. Introduce an error case to test error handling.
-
Deploy with Guardrails:
Wrap the agent in a service layer that enforces rate limits, input sanitization, and cost monitoring. Enable tracing before production rollout.