How to Implement an AI Agent: A Developer's Guide
Implementing an AI agent means building a loop where a language model can reason about a task, decide which tools to call, execute those tools, and use the results to keep working until the task is done. It's not a single API call — it's a control flow around an LLM that gives it the ability to act, not just answer.
This guide walks through the actual pieces you need: the agent loop, tool definitions, state management, and error handling, with working code. It applies whether you're calling Claude, GPT, or another model, and whether you're hitting the provider's API directly or through a proxy like SubToAPI.
The Core Agent Loop
Every AI agent, no matter how sophisticated, is built around the same basic loop:
- Send the user's request plus available tools to the model
- The model responds with either a final answer or a tool call request
- If it's a tool call, execute the tool and send the result back
- Repeat until the model returns a final answer
That's it. Frameworks like LangChain, CrewAI, or AutoGen wrap this loop in abstractions, but you can build a functional agent in under 100 lines without any of them. Understanding the raw loop first makes debugging agent behavior far easier later.
async function runAgent(userMessage, tools, maxSteps = 8) {
let messages = [{ role: "user", content: userMessage }];
for (let step = 0; step < maxSteps; step++) {
const response = await callModel(messages, tools);
if (response.stop_reason !== "tool_use") {
return response.content; // final answer
}
messages.push({ role: "assistant", content: response.content });
const toolResults = await Promise.all(
response.tool_calls.map(async (call) => ({
tool_use_id: call.id,
content: await executeTool(call.name, call.input),
}))
);
messages.push({ role: "user", content: toolResults });
}
throw new Error("Agent exceeded max steps without finishing");
}
The maxSteps cap matters more than it looks — without it, a model stuck in a bad reasoning pattern will keep calling tools indefinitely and burn through your API budget.
Step 1: Define Tools Precisely
Tools are just functions the model can request, described with a name, a description, and a JSON schema for arguments. The quality of your tool descriptions directly affects how reliably the agent picks the right tool at the right time.
const tools = [
{
name: "search_orders",
description: "Search customer orders by email or order ID. Returns order status, items, and total.",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "Email address or order ID" },
},
required: ["query"],
},
},
];
Two practical rules: keep tool names and descriptions specific (not "search" — "search_orders"), and never give the model two tools that do nearly the same thing. Overlapping tools cause it to pick the wrong one or alternate between them unpredictably. See /docs/tools for schema details if you're integrating against SubToAPI's Messages endpoint.
Step 2: Execute Tools Safely
The executeTool function is where most agent bugs actually live, not in the model's reasoning. It needs to validate inputs, catch failures, and return something the model can act on — including failures.
async function executeTool(name, input) {
try {
switch (name) {
case "search_orders":
return JSON.stringify(await db.orders.search(input.query));
default:
return JSON.stringify({ error: `Unknown tool: ${name}` });
}
} catch (err) {
return JSON.stringify({ error: err.message });
}
}
Never let a tool exception crash the loop. If the model gets a clear error message back, it can often self-correct — retry with different arguments, or fall back to telling the user what went wrong. A silent crash just kills the whole agent run.
Step 3: Manage State and Memory
For single-turn agent tasks, the message array in the loop above is your entire memory. For anything that spans multiple user sessions, you need to persist state outside the loop:
- Conversation history: store the full message array per session, trimmed or summarized once it gets long
- Task state: if the agent is mid-workflow (e.g., "step 3 of a multi-step form"), track that separately from chat history
- Tool results cache: avoid re-calling expensive tools (search, database queries) for identical inputs within a session
A common mistake is treating the LLM's context window as your only source of state. It works for a demo, but it's expensive and unreliable at scale — long histories increase token cost and can cause the model to lose track of earlier instructions.
Step 4: Add Guardrails
Production agents need limits beyond the max-step counter:
- Timeouts on individual tool calls, not just the overall loop
- Cost ceilings — track tokens used per run and abort if a single task blows past a threshold
- Human-in-the-loop checkpoints for irreversible actions (sending money, deleting data, emailing a customer)
- Input sanitization before passing tool arguments to anything that touches a database or shell
None of this is optional if the agent has access to real systems. An agent that can only read data is low risk; one that can write or delete needs explicit confirmation steps before executing.
Choosing How to Call the Model
You can call the model provider's API directly, but if you're already paying for a Claude subscription, you may not have API access separately — Claude.ai plans and the Anthropic API are billed independently. SubToAPI turns an existing Claude subscription into a standard HTTPS API with a sub_live_... key, so the agent loop above works unchanged against a familiar Messages-style endpoint:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Find order #4471"}],
"tools": [...]
}'
This gives you streaming, tool use, and usage metadata without managing separate API billing. Start with /docs/quickstart, check the request/response shape in /docs/messages, and see /docs/streaming if your agent needs to stream partial responses to a UI while tool calls happen in the background. Plans start at €9/month with a free trial at /signup, and team pricing is on /pricing.
Testing Your Agent
Before shipping, run the agent against a fixed set of test prompts covering: a happy path, a case where the required tool fails, a case where the user asks something no tool can handle, and a case designed to trigger a loop (ambiguous instructions). If it handles all four without crashing or looping forever, your core implementation is solid — everything after that is tuning prompts and tool descriptions.
questions
Do I need a framework like LangChain to implement an AI agent? No. The agent loop is straightforward enough to write directly, as shown above. Frameworks help with complex multi-agent orchestration, but for a single agent with a handful of tools, raw code is easier to debug and has fewer moving parts.
How many tools should an agent have access to? Keep it under 10–15 well-described tools per agent. Beyond that, models start confusing similar tools and calling the wrong one. If you need more capabilities, split into specialized sub-agents rather than one agent with dozens of tools.
What's the biggest cause of agent failures in production? Unbounded loops and unhandled tool errors. A missing max-step limit or a tool that throws instead of returning a structured error will cause runs that hang, cost far more than expected, or fail silently with no useful debugging trail.