Building an AI agent requires two things: an LLM that supports tool calling, and a loop that runs until the goal is achieved. Everything else — frameworks, orchestration layers, memory systems — is built on top of this foundation. Start with the minimal implementation, verify it works, and add complexity only when you have a concrete reason.
This guide walks through the five steps to build a working agent from scratch using the Anthropic SDK. The same concepts apply to the OpenAI SDK or any other provider that supports tool calling.
What You Need Before You Start
- An API key for a model that supports tool calling (Claude, GPT-4o, Gemini Pro)
- A clear, narrow goal for the agent
- Defined tools (functions the agent can call)
- A stopping condition
The last two are where most first agents fail. Unclear goals produce agents that loop without making progress. Missing stopping conditions produce agents that run indefinitely or get stuck.
Step 1: Define the Agent's Goal and Scope
Narrow goals produce better agents. "Help with software development" is too broad — the agent will never know when it is done. "Fix the failing tests in the auth module and report what you changed" is narrow enough to be completable.
For your first agent, pick a goal that:
- Has a clear success condition ("tests pass," "file created," "answer found")
- Requires 3 to 10 steps (not 1 to 2, which does not need an agent; not 20+, which is too complex to debug)
- Has a bounded action space (a small number of well-defined tools)
Step 2: Define Tools as Functions
Tools are functions that the agent can call. Each tool needs a name, a description, an input schema, and an implementation.
const tools = [
{
name: "search_web",
description: "Search the web for current information. Use when you need facts, recent events, or data not in your training knowledge.",
input_schema: {
type: "object",
properties: {
query: {
type: "string",
description: "The search query. Be specific for better results."
}
},
required: ["query"]
}
},
{
name: "read_file",
description: "Read the contents of a file. Use when you need to inspect code or data.",
input_schema: {
type: "object",
properties: {
path: {
type: "string",
description: "The file path relative to the project root."
}
},
required: ["path"]
}
},
{
name: "write_file",
description: "Write content to a file, creating it if it does not exist.",
input_schema: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string" }
},
required: ["path", "content"]
}
}
];
async function executeTool(name: string, input: Record<string, string>): Promise<string> {
switch (name) {
case "search_web":
return await searchWeb(input.query);
case "read_file":
return await fs.readFile(input.path, "utf-8");
case "write_file":
await fs.writeFile(input.path, input.content, "utf-8");
return "File written successfully.";
default:
return `Unknown tool: ${name}`;
}
}
Step 3: Write the System Prompt
The system prompt defines the agent's role, available tools, and how it should behave:
You are a research assistant with access to web search and file tools.
Your goal is to complete the user's research request by:
1. Breaking the request into specific search queries
2. Searching for information
3. Synthesizing results into a structured answer
4. Writing the answer to a file if the user requests it
Use tools when you need current information or need to read/write files.
Think carefully about what to search for before calling the search tool — specific queries return better results than broad ones.
When you have enough information to answer fully, say TASK_COMPLETE before your final answer.
Step 4: Implement the Agent Loop
This is the core of any agent: call the model, check if it wants to use a tool, execute the tool, add the result to context, repeat.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function runAgent(goal: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: goal }
];
let finalAnswer = "";
let iterations = 0;
const MAX_ITERATIONS = 15;
while (iterations < MAX_ITERATIONS) {
iterations++;
const response = await client.messages.create({
model: "claude-opus-4-5",
max_tokens: 4096,
system: systemPrompt,
tools: tools,
messages: messages
});
messages.push({ role: "assistant", content: response.content });
const toolUses = response.content.filter(
(block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);
if (toolUses.length === 0) {
const textBlocks = response.content.filter(
(block): block is Anthropic.TextBlock => block.type === "text"
);
finalAnswer = textBlocks.map(b => b.text).join("
");
break;
}
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const toolUse of toolUses) {
const result = await executeTool(
toolUse.name,
toolUse.input as Record<string, string>
);
toolResults.push({
type: "tool_result",
tool_use_id: toolUse.id,
content: result
});
}
messages.push({ role: "user", content: toolResults });
if (response.stop_reason === "end_turn") {
const lastText = response.content
.filter((b): b is Anthropic.TextBlock => b.type === "text")
.map(b => b.text)
.join("
");
if (lastText.includes("TASK_COMPLETE")) {
finalAnswer = lastText.replace("TASK_COMPLETE", "").trim();
break;
}
}
}
if (iterations >= MAX_ITERATIONS) {
finalAnswer = "Agent reached maximum iteration limit without completing the task.";
}
return finalAnswer;
}
Step 5: Add Stopping Conditions
Stopping conditions prevent infinite loops and contain runaway agents.
Hard limit: A maximum number of iterations. Always include this.
Completion signal: A specific phrase the model outputs when it believes the task is done ("TASK_COMPLETE" in the example above).
Error budget: If the same tool call fails more than N times, stop and report rather than retrying indefinitely.
let consecutiveFailures = 0;
try {
const result = await executeTool(toolUse.name, toolUse.input as Record<string, string>);
consecutiveFailures = 0;
toolResults.push({ type: "tool_result", tool_use_id: toolUse.id, content: result });
} catch (error) {
consecutiveFailures++;
if (consecutiveFailures >= 3) {
return "Agent failed: repeated tool execution errors. Check tool implementations.";
}
toolResults.push({
type: "tool_result",
tool_use_id: toolUse.id,
content: `Error: ${error instanceof Error ? error.message : "Unknown error"}`,
is_error: true
});
}
Common Failure Patterns
Infinite loops. The agent calls the same tool repeatedly without making progress. Cause: the goal is unclear, the tool is not providing useful results, or the model is confused about what progress means. Fix: add iteration limits and log each step so you can see where the loop starts.
Tool hallucinations. The model invents tool names or parameters that do not exist. Fix: list available tools explicitly in the system prompt.
Context overflow. On long tasks, the message history grows until it exceeds the context window. Fix: summarize older context periodically for long-running agents.
Goal drift. The agent starts pursuing a sub-goal and forgets the original goal. Fix: include the goal in the system prompt, not just the first user message.
When to Use a Framework vs. Build From Scratch
Build from scratch when:
- Your agent has 2 to 5 tools and a single well-defined task type
- You want full visibility into what the agent is doing at each step
- Debugging is more important than feature richness
Use LangChain, LangGraph, or LlamaIndex when:
- You need complex multi-agent coordination (supervisor/worker patterns)
- You need built-in memory backends
- You need streaming, checkpointing, or human-in-the-loop patterns
The raw API approach above is roughly 80 lines of TypeScript. For simple agents, framework abstraction is overhead. For complex multi-agent pipelines, it pays for itself.
Keep Reading
- AI Agents Explained: What They Are and How They Actually Work — The conceptual foundation before you start building
- Prompt Injection Security Guide — Agents that process external data are the primary target for injection attacks
- Multi-Agent Systems: When You Need More Than One AI Agent — After your first agent works, here is when and how to compose multiple agents
Pristren builds AI-powered software for teams. Zlyqor is our all-in-one workspace — chat, projects, time tracking, AI meeting summaries, and invoicing — in one tool. Try it free.