Vibe Coding Codex Β· CH 23

Chapter 23: Building AI Agents That Take Real Actions

⏱️ 5 min read·Updated Jul 2026

From Responder to Actor

Every chapter up to this point has covered AI as a responder: you give it a prompt, it gives you output. But the next generation of AI applications is built on agents β€” systems that can plan a course of action, execute steps, observe the results, and adjust their approach.

This chapter covers the architecture and code to build agents that do real work.

---

What Makes Something an Agent?

An agent has four properties that a simple LLM call does not:

    • Goal: A higher-level objective, not just a specific prompt
    • Tools: The ability to take actions in the world (call APIs, search, read/write files)
    • Memory: Context that persists across the reasoning steps
    • Iteration: The ability to observe results and try again if something fails
The simplest agent architecture is the ReAct loop:
code
REASON β†’ ACT β†’ OBSERVE β†’ REASON β†’ ACT β†’ OBSERVE β†’ ... β†’ DONE

The model reasons about what to do, takes an action using a tool, observes the result, reasons about the next step, and continues until the goal is achieved.

---

Building a Web Research Agent

Let's build a practical agent that can research a topic by searching the web, reading pages, and synthesising the findings.

Define the Tools

javascript
// agent/tools.js
import OpenAI from 'openai';

export const tools = [
  {
    type: 'function',
    function: {
      name: 'search_web',
      description: 'Search the web for information on a topic. Returns a list of relevant URLs and snippets.',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'The search query' },
          num_results: { type: 'number', description: 'Number of results (1-10)', default: 5 },
        },
        required: ['query'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'read_webpage',
      description: 'Fetch and read the content of a webpage. Returns the main text content.',
      parameters: {
        type: 'object',
        properties: {
          url: { type: 'string', description: 'The URL to read' },
        },
        required: ['url'],
      },
    },
  },
  {
    type: 'function',
    function: {
      name: 'save_finding',
      description: 'Save an important finding to memory for inclusion in the final report.',
      parameters: {
        type: 'object',
        properties: {
          finding: { type: 'string', description: 'The key finding to save' },
          source: { type: 'string', description: 'The URL source of this finding' },
        },
        required: ['finding', 'source'],
      },
    },
  },
];

Implement the Tool Handlers

javascript
// agent/tool-handlers.js

// Serper API for web search (or use your preferred search API)
export async function search_web({ query, num_results = 5 }) {
  const response = await fetch('https://google.serper.dev/search', {
    method: 'POST',
    headers: {
      'X-API-KEY': process.env.SERPER_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ q: query, num: num_results }),
  });
  const data = await response.json();
  return data.organic?.map(r => ({ title: r.title, url: r.link, snippet: r.snippet })) || [];
}

export async function read_webpage({ url }) {
  try {
    const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
    const html = await response.text();
    // Strip HTML tags and return plain text (simplified)
    return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 5000);
  } catch (err) {
    return `Error reading page: ${err.message}`;
  }
}

const findings = []; // In-memory for this session
export async function save_finding({ finding, source }) {
  findings.push({ finding, source });
  return `Finding saved. Total findings: ${findings.length}`;
}

export function getFindings() { return findings; }

The Agent Loop

javascript
// agent/research-agent.js
import OpenAI from 'openai';
import { tools } from './tools';
import * as handlers from './tool-handlers';

const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

export async function runResearchAgent(topic, maxIterations = 10) {
  const messages = [
    {
      role: 'system',
      content: `You are a thorough research agent. Your goal is to research the given topic comprehensively.

Process:
1. Start by searching for the topic to get an overview
2. Read the most relevant pages in full
3. Search for specific sub-topics that need more depth
4. Save your key findings as you go
5. When you have gathered sufficient information, synthesise a final comprehensive report

Be thorough. Read at least 3-5 sources before concluding.`
    },
    { role: 'user', content: `Research this topic thoroughly: ${topic}` }
  ];

  for (let i = 0; i < maxIterations; i++) {
    const response = await client.chat.completions.create({
      model: 'gpt-4o',
      messages,
      tools,
      tool_choice: 'auto',
    });

    const choice = response.choices[0];
    messages.push(choice.message);

    // If model is done reasoning, get final answer
    if (choice.finish_reason === 'stop') {
      return {
        report: choice.message.content,
        findings: handlers.getFindings(),
        iterations: i + 1,
      };
    }

    // Execute all requested tool calls
    if (choice.finish_reason === 'tool_calls') {
      for (const toolCall of choice.message.tool_calls) {
        const args = JSON.parse(toolCall.function.arguments);
        const handler = handlers[toolCall.function.name];
        
        let result;
        try {
          result = await handler(args);
        } catch (err) {
          result = { error: err.message };
        }

        messages.push({
          role: 'tool',
          tool_call_id: toolCall.id,
          content: JSON.stringify(result),
        });
      }
    }
  }

  return { report: 'Max iterations reached', findings: handlers.getFindings(), iterations: maxIterations };
}

---

Agent Design Principles

Give agents a way to signal completion. Without a stop condition (a "done" tool or a natural stopping point), agents loop unnecessarily.

Cap iterations. Always set a maxIterations limit to prevent runaway agents consuming infinite tokens.

Make tools deterministic. Tools should return consistent, predictable results. Ambiguous tool outputs confuse the agent's reasoning.

Log everything. Agents are hard to debug without full logs. Log every tool call, every result, and every model response.

Design for partial failure. A tool might fail mid-run. Good agents notice this, try an alternative approach, and do not just crash.

Finished reading this chapter?

Mark it completed to update your AI Builder skill profile score on your dashboard.

πŸ“‹

Download Printable Chapter Checklist

Get a print-friendly, formatted checklist of workspace actions to apply this chapter offline.