← All posts
ai agents typescript

What is an Agent?

April 10, 2026

If you’ve spent any time around AI tooling lately, you’ve probably heard the word agent used for almost everything. A chatbot gets called an agent. A script that calls two APIs gets called an agent. A workflow with a few prompts gets called an agent.

That makes the term feel fuzzy, but under the hood the core idea is straightforward. An agent is a loop where a model can decide to call tools, get results back, and decide what to do next until it can produce a final answer. If you can build that loop yourself once, every framework starts to make a lot more sense.

A Chat Completion Is Not an Agent

A normal chat completion is one request and one response.

  1. You send messages.
  2. The model sends text back.

That works fine for summarization or general Q&A, but it cannot act on your system. It cannot query your database or call your internal API unless you provide that data upfront in the prompt.

An agent adds one capability: the model can request that your code execute a tool.

Your code then:

  1. Executes that tool
  2. Sends the result back to the model
  3. Gives the model another turn

If the model needs more information, it can call another tool. If it has enough information, it returns a final response.

That repeated cycle is the Agentic Loop.

Building a Working Agent from Scratch

We’ll build a complete TypeScript example that runs against the Anthropic SDK.

Setup

Terminal window
mkdir my-agent
cd my-agent
npm init -y
npm install @anthropic-ai/sdk
npm install -D tsx typescript @types/node

Set your API key:

Terminal window
export ANTHROPIC_API_KEY="your-key-here"

Create agent.ts and work through each section below.

The Client and Tool Types

For this example, we’ll expose two tools to the model: search_docs to search a knowledge base by query, and create_ticket to create a support ticket with a subject, description, and priority.

Each tool definition includes a name, a description, and a JSON input schema. That’s all the model sees. Your application is responsible for actually executing each tool.

import Anthropic from "@anthropic-ai/sdk";
type SearchDocsInput = {
query: string;
limit?: number;
};
type CreateTicketInput = {
subject: string;
description: string;
priority: "low" | "medium" | "high";
};
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const tools = [
{
name: "search_docs",
description: "Search the knowledge base for articles matching a query.",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
limit: { type: "number", description: "Maximum number of results to return (default 3)" },
},
required: ["query"],
},
},
{
name: "create_ticket",
description: "Create a support ticket.",
input_schema: {
type: "object",
properties: {
subject: { type: "string" },
description: { type: "string" },
priority: { type: "string", enum: ["low", "medium", "high"] },
},
required: ["subject", "description", "priority"],
},
},
] as const;

Handling Tool Calls

runTool is the dispatcher. The model decides which tool to call and what to pass it, but your code is responsible for actually executing it and returning a result the model can read. Whatever the model requests, this function handles it.

function runTool(name: string, input: unknown): string {
if (name === "search_docs") {
const { query, limit = 3 } = input as SearchDocsInput;
// Stubbed results so this example stays self-contained.
const results = [
{ title: "How to reset your password", excerpt: "Visit the login page and click 'Forgot password' to receive a reset link by email." },
{ title: "Account lockout policy", excerpt: "Accounts are locked after 5 failed login attempts. Contact support to unlock your account early." },
{ title: "Two-factor authentication setup", excerpt: "Enable 2FA under Account Settings > Security to add an extra layer of protection." },
];
return JSON.stringify(results.slice(0, limit));
}
if (name === "create_ticket") {
const { subject, description, priority } = input as CreateTicketInput;
const ticketId = `TK-${Date.now()}`;
return JSON.stringify({ ticketId, subject, description, priority, status: "open" });
}
return `Unknown tool: ${name}`;
}

The Agent Loop

This is the core of the agent. Each iteration sends the current message history to the model and checks stop_reason. If the model wants to use a tool, your code executes it and pushes the results back as a new user message before the next iteration. If stop_reason is end_turn, the model is done and you extract the final text response.

async function runAgent(userPrompt: string): Promise<string> {
const messages: Anthropic.Messages.MessageParam[] = [
{ role: "user", content: userPrompt },
];
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages,
});
messages.push({ role: "assistant", content: response.content });
// Check if the model has completed its turn
if (response.stop_reason === "end_turn") {
const finalText = response.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("\n")
.trim();
return finalText;
}
if (response.stop_reason !== "tool_use") {
throw new Error(`Unexpected stop reason: ${response.stop_reason}`);
}
// Handle tool calls
const toolResults = response.content
.filter((block) => block.type === "tool_use")
.map((toolUse) => {
const result = runTool(toolUse.name, toolUse.input);
return {
type: "tool_result" as const,
tool_use_id: toolUse.id,
content: result,
};
});
// Push tool results back as user messages for the next turn
messages.push({ role: "user", content: toolResults });
}
}

There is no fixed number of turns. Sometimes the model finishes in one tool round, sometimes it needs several. The loop keeps running until stop_reason changes from tool_use to end_turn.

Running It

Pass a prompt to the agent and print the result. The loop handles everything in between.

const finalAnswer = await runAgent(`
Search our docs for how to reset a password, then open a high
priority ticket for a customer who is locked out of their account.
`);
console.log(`Final answer: ${finalAnswer}`);

Now with everything in place, we can run the agent:

Terminal window
npx tsx agent.ts

What about Agent Frameworks?

If you only need one small internal agent, writing the loop yourself is reasonable. You get full control, and you learn exactly how tool calling works.

But once you move toward production, you’ll quickly need more than this basic loop:

That’s where Agent Frameworks come in. They handle the agentic loop for you and layer on the features above.

Some of the most popular agent frameworks include:

Each of these frameworks abstract over the same foundation you just built by hand: a model turn, a tool call, a tool result, then another model turn.

Once you understand that foundation, frameworks become the logical next step. You can tell what they simplify, what tradeoffs they introduce, and where you still need custom code.

If you remember one thing, make it this: an agent is a loop with tools. Everything else is implementation detail.