Back to Blog
Tutorials 16 min read February 16, 2025

AI Agent Architecture: From Chatbots to Autonomous Systems

Deep dive into building AI agents that can plan, take actions, and complete multi-step tasks autonomously. Covers ReAct, tool use, memory, and multi-agent patterns.

DevForge Team

DevForge Team

AI Development Educators

Futuristic AI robot representing autonomous agent systems

What is an AI Agent?

An AI agent is a system that uses an LLM as its reasoning engine to take actions toward a goal, rather than just answer a single question. The key difference:

  • Chatbot: User asks question → AI answers → done
  • Agent: User gives goal → AI plans steps → AI takes actions → AI observes results → AI adapts → goal achieved (or explained why not)

Agents can browse the web, run code, call APIs, read/write files, send emails, query databases, and more — all autonomously, with the LLM deciding what to do based on results.

This is genuinely powerful and genuinely different from previous automation approaches. The LLM's reasoning ability means agents can handle situations that rigid rule-based automation cannot.

The Core Loop: ReAct

The most common agent architecture is ReAct (Reason + Act):

  1. Reason: LLM analyzes the current state and decides what to do
  2. Act: LLM calls a tool/function
  3. Observe: LLM receives the tool result
  4. Repeat: Until the goal is achieved
text
Goal: "Find the top 3 trending JavaScript repositories on GitHub today,
      summarize what each does, and format as a markdown table."

Thought: I need to search GitHub for trending JavaScript repos.
Action: search_github(query="trending javascript", period="today")
Observation: [{name: "shadcn/ui", stars: 1234, description: "..."}, ...]

Thought: I have the data. Now I need to summarize each repo.
Action: summarize_text(text="shadcn/ui is a collection of re-usable...")
Observation: "A customizable component library for React using Tailwind CSS"

Thought: I have summaries for all 3 repos. I'll format as markdown.
Action: format_table(data=[{name, summary, stars}, ...])
Observation: "| Repository | Description | Stars |..."

Final Answer: [markdown table with 3 repos]

Building a Simple Agent with Claude

typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// Define your tools
const tools: Anthropic.Tool[] = [
  {
    name: "search_web",
    description: "Search the web for current information",
    input_schema: {
      type: "object" as const,
      properties: {
        query: { type: "string", description: "Search query" },
      },
      required: ["query"],
    },
  },
  {
    name: "run_code",
    description: "Execute Python code and return the output",
    input_schema: {
      type: "object" as const,
      properties: {
        code: { type: "string", description: "Python code to execute" },
      },
      required: ["code"],
    },
  },
  {
    name: "read_file",
    description: "Read the contents of a file",
    input_schema: {
      type: "object" as const,
      properties: {
        path: { type: "string", description: "File path to read" },
      },
      required: ["path"],
    },
  },
];

// Tool execution
async function executeTool(
  name: string,
  input: Record<string, unknown>
): Promise<string> {
  switch (name) {
    case "search_web":
      return await searchWeb(input.query as string);
    case "run_code":
      return await runPythonCode(input.code as string);
    case "read_file":
      return await readFile(input.path as string);
    default:
      return `Unknown tool: ${name}`;
  }
}

// The agent loop
async function runAgent(
  goal: string,
  maxSteps = 10
): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: goal }
  ];

  let steps = 0;

  while (steps < maxSteps) {
    steps++;
    console.log(`\nStep ${steps}:`);

    const response = await client.messages.create({
      model: "claude-opus-4-5",
      max_tokens: 4096,
      tools,
      messages,
      system: `You are an autonomous agent that completes tasks step by step.
Use the available tools to gather information and take actions.
Think carefully before each action. Be efficient — don't use tools
unless necessary. When you have enough information, provide your
final answer directly without using tools.`,
    });

    console.log(`Stop reason: ${response.stop_reason}`);

    // Agent is done
    if (response.stop_reason === "end_turn") {
      const finalText = response.content
        .filter(b => b.type === "text")
        .map(b => b.type === "text" ? b.text : "")
        .join("");
      return finalText;
    }

    // Agent wants to use tools
    if (response.stop_reason === "tool_use") {
      messages.push({ role: "assistant", content: response.content });

      const toolResults: Anthropic.ToolResultBlockParam[] = [];

      for (const block of response.content) {
        if (block.type === "tool_use") {
          console.log(`Tool: ${block.name}(${JSON.stringify(block.input)})`);

          const result = await executeTool(
            block.name,
            block.input as Record<string, unknown>
          );

          console.log(`Result: ${result.slice(0, 100)}...`);

          toolResults.push({
            type: "tool_result",
            tool_use_id: block.id,
            content: result,
          });
        }
      }

      messages.push({ role: "user", content: toolResults });
    }
  }

  return "Maximum steps reached without completing the task.";
}

Agent Memory

One of the biggest challenges with agents is memory. LLMs have a context window — they can only "remember" what's in the current prompt.

Short-term Memory (In-context)

Simply include relevant history in the messages array. Works for single sessions.

Long-term Memory (External)

For memory that persists across sessions:

typescript
interface Memory {
  type: 'fact' | 'preference' | 'interaction';
  content: string;
  embedding: number[];
  timestamp: Date;
}

class AgentMemory {
  async store(content: string, type: Memory['type']) {
    const embedding = await generateEmbedding(content);
    await db.memories.insert({ content, type, embedding });
  }

  async retrieve(query: string, k = 5): Promise<Memory[]> {
    const queryEmbedding = await generateEmbedding(query);
    return await vectorSearch(queryEmbedding, k);
  }

  async buildContext(currentTask: string): Promise<string> {
    const relevant = await this.retrieve(currentTask);
    return relevant.map(m => m.content).join('\n');
  }
}

// Inject memory into system prompt
const memory = new AgentMemory();
const context = await memory.buildContext(userGoal);
const systemPrompt = `${baseSystemPrompt}

Relevant context from memory:
${context}`;

Multi-Agent Systems

For complex tasks, a single agent often isn't sufficient. Multi-agent architectures use specialized agents that collaborate:

text
User Goal: "Research quantum computing and write a technical blog post"

Orchestrator Agent
├── Research Agent
│   ├── Tool: search_arxiv
│   ├── Tool: search_web
│   └── Returns: research findings
├── Writing Agent
│   ├── Tool: draft_content
│   ├── Input: research findings
│   └── Returns: draft article
└── Review Agent
    ├── Tool: fact_check
    ├── Tool: improve_writing
    └── Returns: final article
typescript
class Orchestrator {
  async execute(task: string): Promise<string> {
    // Break task into subtasks
    const plan = await this.planTask(task);

    const results: Record<string, string> = {};

    for (const subtask of plan.subtasks) {
      const agent = this.getAgent(subtask.type);
      results[subtask.id] = await agent.run(
        subtask.instruction,
        subtask.inputs.map(id => results[id])  // pass results from dependencies
      );
    }

    return await this.synthesize(task, results);
  }

  private getAgent(type: string): Agent {
    const agents = {
      research: new ResearchAgent(),
      writing: new WritingAgent(),
      review: new ReviewAgent(),
      code: new CodeAgent(),
    };
    return agents[type as keyof typeof agents] || new GeneralAgent();
  }
}

Guardrails and Safety

Agents can cause real-world harm if not properly constrained:

typescript
const safetyConfig = {
  // Actions that require human confirmation
  requireConfirmation: [
    'send_email',
    'delete_file',
    'post_to_social_media',
    'make_payment',
  ],

  // Actions that are completely blocked
  blocked: [
    'access_user_passwords',
    'read_private_keys',
    'modify_auth_config',
  ],

  // Rate limits
  rateLimits: {
    api_calls_per_minute: 60,
    files_written_per_session: 100,
    emails_sent_per_session: 10,
  }
};

async function safeExecuteTool(
  name: string,
  input: unknown,
  config: typeof safetyConfig
): Promise<string> {
  if (config.blocked.includes(name)) {
    return `Error: Tool '${name}' is blocked for safety reasons.`;
  }

  if (config.requireConfirmation.includes(name)) {
    const confirmed = await requestHumanApproval(name, input);
    if (!confirmed) {
      return `User declined to execute '${name}'.`;
    }
  }

  return await executeTool(name, input as Record<string, unknown>);
}

When to Use Agents (and When Not To)

Good use cases for agents:

  • Multi-step research and synthesis
  • Automated testing and bug fixing
  • Data pipeline orchestration
  • Content generation with research
  • Customer support with system access

Where agents struggle:

  • Tasks requiring real-time responses
  • Very long-running tasks (token costs)
  • Tasks requiring perfect accuracy (agents make mistakes)
  • Tasks where you can't verify the output

The pragmatic approach: Start with simple tool use (one round of retrieval or code execution) before building full agents. Full agentic loops are expensive and prone to failure. Use them when simpler approaches fail.

The Future of AI Agents

We're still in early days. The most significant near-term improvements:

  • Better planning: Models that can break down complex goals more reliably
  • Longer context: Bigger context windows mean more history, better coherence
  • Faster inference: Speed improvements making real-time agents feasible
  • Multimodal: Agents that can see, hear, and interact with UIs visually

Build your agent expertise now. This skill will be extraordinarily valuable over the next 3-5 years as these systems become more capable and move into production at scale.

#AI Agents#LangChain#Claude API#Autonomous AI#Architecture