How to Build an AI Agent

An AI agent is a loop. A language model reads the goal and everything that has happened so far, then either calls one of the tools you defined or gives a final answer. Your code runs the tool, appends the result and asks again. Step limits, permissions, human approval and tests keep that loop safe.

The five parts of an agent
PartWhat you buildWhat goes wrong without it
LoopCode that calls the model, runs tools and repeatsNothing happens after the first reply
ToolsNamed functions with a description and an input schemaThe model can only talk, not act
MemoryThe running transcript, plus any notes saved between runsThe model repeats calls it already made
GuardrailsStep and cost limits, least privilege, approval for risky actionsRunaway loops, surprise bills, damaging actions
EvaluationFixed test tasks with checks you rerun after every changeYou find regressions from users

Updated

Run the simulator to watch one task move through the loop. Turn approval off and on to see where a guardrail interrupts a risky step. The trace is scripted so you can study it; a real run would ask a model at each decide step.

Agent loop simulator

Goal: find the cheapest of three supplier quotes and email the summary to the office manager.

  1. Decide Model picks read_quotes to see what arrived.
  2. Act Code runs read_quotes() and returns three files.
  3. Observe Result added to memory: A $1,240, B $1,180, C $1,315.
  4. Decide Model has what it needs and picks send_email with a draft summary.
  5. Guardrail send_email is marked high impact, so the run pauses for a person to approve the draft.
  6. Act Approved. Code runs send_email() with the approved text.
  7. Observe Result added to memory: message sent.
  8. Done Model returns a final answer: Quote B is cheapest at $1,180; summary sent. 3 model calls, under the 10-step limit.

The loop in plain JavaScript

No framework and no vendor SDK. callModel is a stub that plays back fixed decisions so the file runs as-is; replace it with a request to the model service you use.

agent.mjs

Tools live in one object, risky ones need approval, and the loop stops at a step limit. Run it with node agent.mjs.

const tools = {
  read_quotes: {
    risky: false,
    run: async () => ({ A: 1240, B: 1180, C: 1315 }),
  },
  send_email: {
    risky: true,
    run: async ({ to, text }) => `sent to ${to}: ${text}`,
  },
};

// Stub: replace with a real model request that receives the goal,
// the tool list and the transcript, and returns a tool call or an answer.
const script = [
  { tool: "read_quotes", args: {} },
  { tool: "send_email", args: { to: "[email protected]", text: "Quote B is cheapest at $1,180." } },
  { answer: "Quote B is cheapest; summary sent." },
];
async function callModel(transcript) {
  return script[transcript.filter((m) => m.role === "tool").length];
}

async function approve(tool, args) {
  console.log(`Approval needed for ${tool}:`, args);
  return true; // ask a person here
}

async function runAgent(goal, maxSteps = 10) {
  const transcript = [{ role: "user", content: goal }];
  for (let step = 1; step <= maxSteps; step++) {
    const move = await callModel(transcript);
    if (move.answer) return move.answer;
    const tool = tools[move.tool];
    if (!tool) {
      transcript.push({ role: "tool", content: `Unknown tool ${move.tool}` });
      continue;
    }
    if (tool.risky && !(await approve(move.tool, move.args))) {
      transcript.push({ role: "tool", content: "A person declined this action." });
      continue;
    }
    const result = await tool.run(move.args);
    transcript.push({ role: "tool", content: JSON.stringify(result) });
  }
  return "Stopped: step limit reached.";
}

runAgent("Find the cheapest quote and email the office.").then(console.log);

Risks to design against

OWASP Top 10 for LLM Applications 2025 entries that matter most for agents
RiskHow it shows up in an agentFix in code
LLM01:2025 Prompt InjectionA fetched page tells the model to email your data elsewhereSeparate untrusted content, approve privileged actions
LLM06:2025 Excessive AgencyThe agent can delete records it only needed to readFewer tools, narrow credentials, approval for high impact
LLM10:2025 Unbounded ConsumptionA loop retries a failing tool all nightStep limit, spend limit, timeouts, rate limits
LLM05:2025 Improper Output HandlingModel output is pasted into SQL or HTML uncheckedValidate and escape before any downstream use

Guardrails that actually hold

OWASP's 2025 list names Excessive Agency as a core risk and traces it to three causes: excessive functionality, excessive permissions and excessive autonomy. Each maps to a fix you control in code.

Give the agent only the tools the task needs, give each tool the narrowest credentials that work, and require a person to approve high-impact actions such as sending, paying or deleting. Check authorization in the system the tool calls rather than trusting the model to behave.

Untrusted text is the main attack surface

Anything a tool fetches, such as a web page, an email or a file, can contain instructions aimed at your model. OWASP calls this indirect prompt injection.

Mark fetched content clearly as data, keep it apart from your own instructions and never let text from a tool result unlock a more powerful tool on its own. Human approval for privileged steps is the backstop when the other defences miss.

How it works

  1. Press Next step to advance the loop one move at a time.
  2. Read each line: decide is the model choosing, act is your code running a tool, observe is the result being added to memory.
  3. Toggle approval for send_email and run it again to compare the two traces.
  4. Press Reset to start over.

Tips

Frequently asked questions

Do I need a framework to build an agent?

No. The core is a while loop around one model call and a switch over tool names, as the example on this page shows. Frameworks add retries, tracing and integrations, which help later but hide the loop while you are learning.

How does the model call a tool?

You send tool definitions with each request. When the model wants one, it replies with the tool name and arguments instead of plain text. Your code runs the function and sends the result back as the next message.

What is the difference between memory and context?

Context is what the model sees on this call: instructions, the goal and the transcript so far. Memory is anything you store and choose to put back into context later, such as notes from a previous run.

How do I stop an agent running forever?

Cap the number of steps and the total spend, and end the run with a clear message when either is reached. OWASP lists Unbounded Consumption among its 2025 risks for applications built on language models.

How do I test an agent?

Write a set of fixed tasks with checkable outcomes: the right tool was called, a risky action waited for approval, the final answer contains a figure. Run the set after every prompt or tool change and compare pass counts.

Related: AI agent vs agentic AI, What is an MCP server?, How to build an MCP server, AI agent test checklist, AI API cost calculator.

Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.

More in Guides

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap