← back to /home

// posted June 2026 · ~10 min read

function buildAIAgent()

A practical guide to building AI agents from scratch — the deterministic loop, tool-calling patterns, and the lessons I picked up shipping AI Business Assistant and prototyping for CafeHop.

// TL;DR

An AI agent is a loop: think → call a tool → observe → repeat until the model decides it's done. You don't need a heavy framework. You need a model with tool calling, a small set of well-described tools, and a deterministic harness that you control.

## What is an AI agent, actually?

An AI agent is a program that uses an LLM as its reasoning engine and a set of tools as its hands. The model doesn't execute anything itself — it decides what to do next, and your code decides whether and how to do it. That separation is what makes agents debuggable, testable, and safe.

Three things turn a chatbot into an agent: (1) tools the model can call, (2) a loop that keeps running until a stop condition, and (3) memory of what's happened so far.

## The deterministic agent loop

Forget framework magic. Every agent — LangChain, AutoGPT, Cursor, the one you'll build — is the same loop:

while (!done) {
  const reply = await llm.chat({ messages, tools });

  if (reply.tool_calls?.length) {
    for (const call of reply.tool_calls) {
      const result = await runTool(call.name, call.args);
      messages.push({ role: "tool", tool_call_id: call.id, content: result });
    }
    continue;
  }

  // No tool calls = final answer
  done = true;
  return reply.content;
}

That's the whole pattern. Add a max-step guard (e.g. steps > 10 → bail), a timeout, and structured logs for every iteration — and you have a production-shaped agent.

## Designing tools the model will actually use

Tools are the agent's API to the world: search_web, get_order,send_email, query_db. The model picks tools based on names and descriptions — so write them like you're writing docs for a junior engineer who has never seen your codebase.

{
  name: "get_customer_orders",
  description:
    "Return the last 10 orders for a customer. " +
    "Use when the user asks about their order history, refunds, or shipping status.",
  parameters: {
    type: "object",
    properties: {
      customer_id: { type: "string", description: "UUID of the customer" }
    },
    required: ["customer_id"]
  }
}

Rules of thumb from shipping agents in production:

## Memory: short-term vs long-term

Short-term memory is just the message array you pass to the LLM each turn. Trim it: keep the system prompt, the user's original ask, and the last N tool calls. Everything older gets summarized.

Long-term memory is a vector store (pgvector, Pinecone, Qdrant). On every turn, embed the user query, fetch the top-k relevant chunks, inject them into the system prompt. That's the entire "RAG" pattern — there's nothing more to it.

## Stop conditions and guardrails

An agent without a stop condition is a bill, not a product. Every loop should enforce:

## Ship it: the smallest useful agent

The fastest way to learn this is to build a single-purpose agent first — not a general assistant. Pick one workflow you do manually every week and wrap it. For me, the first real one was a CafeHop ops agent: "given this venue email, draft a reply, check the booking calendar, and propose three slots." Three tools, one loop, two hours.

The "AI agent builder" market is loud right now, but most production agents are still a few hundred lines of code around the loop above. The leverage isn't in the framework — it's in the taste you bring to choosing tools and writing prompts.

## What to read next

// liked this?

I'm Aradhya Bhosale — founder of CafeHop and an AI product builder. Find more of my work on the home page.