If you want to build AI agents in JavaScript, the good news is that the JavaScript ecosystem has matured quickly: you can now build an agent loop, connect it to tools, and ship it inside a Node.js or browser application without leaving your existing stack. This guide walks through what an AI agent actually is at the code level, how to structure the core loop in TypeScript, which frameworks are worth considering, and the mistakes that most commonly break agents in production.
An AI agent is a language-model-driven system that can decide which actions to take — calling functions, querying databases, browsing APIs — and iterate on the results until it completes a goal. Unlike a simple chat completion, where the model responds once and stops, an agent runs a loop: the model reasons, invokes a tool, observes the output, and continues until it decides the task is done. Understanding that loop is the foundation for everything else in this article.
What an Agent Actually Is Under the Hood
Strip away the marketing language and an agent is surprisingly simple: it is a while-loop around a language model with tool access. The model receives a system prompt describing its role and available tools, a conversation history, and a user goal. On each iteration it either produces a final answer or emits a tool call — a structured request to execute a function your code controls. Your code executes that function, appends the result to the conversation, and calls the model again.
This matters for web developers because it reframes the problem. You are not "doing AI"; you are writing an orchestration loop, a set of well-typed tool functions, and a state machine for conversation history. Those are things JavaScript developers are already good at. The model is just a decision-making component in the middle.
The main building blocks are:
- The model: an LLM that supports function or tool calling, such as those available through the OpenAI API or Anthropic Claude.
- Tools: plain functions with descriptions and typed parameters that the model can request.
- Memory: the conversation history, which you must manage explicitly — context windows are finite.
- The loop: your code that alternates between model calls and tool execution until a stop condition is met.
A Minimal Agent Loop in TypeScript
You do not need a framework to build your first agent. Here is a compact but realistic loop using the OpenAI SDK, which supports tool calling natively:
import OpenAI from "openai";
const client = new OpenAI(); // uses OPENAI_API_KEY
const tools = [
{
type: "function" as const,
function: {
name: "get_weather",
description: "Get the current weather for a city",
parameters: {
type: "object",
properties: {
city: { type: "string" },
},
required: ["city"],
},
},
},
];
async function executeTool(name: string, args: Record<string, unknown>) {
if (name === "get_weather") {
// Replace with a real weather API call
return { city: args.city, temperatureC: 21, condition: "cloudy" };
}
throw new Error(`Unknown tool: ${name}`);
}
export async function runAgent(userGoal: string, maxSteps = 10) {
const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: "system", content: "You are a helpful assistant. Use tools when needed." },
{ role: "user", content: userGoal },
];
for (let step = 0; step < maxSteps; step++) {
const response = await client.chat.completions.create({
model: "gpt-4o",
messages,
tools,
});
const message = response.choices[0].message;
messages.push(message);
if (!message.tool_calls?.length) {
return message.content; // Final answer
}
for (const call of message.tool_calls) {
const result = await executeTool(
call.function.name,
JSON.parse(call.function.arguments)
);
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
}
throw new Error("Agent exceeded maximum steps without finishing");
}
Three details in this example are worth internalizing. First, the maxSteps limit is not optional — an agent without a step budget can loop indefinitely, burning tokens and money. Second, tool results go back into the conversation as messages with a tool role and the original tool_call_id; models depend on this pairing to reason about what happened. Third, everything the model "knows" about your tools comes from the names, descriptions, and JSON schemas you provide — vague descriptions produce wrong tool selections.
The same pattern works with Anthropic's SDK, which uses a similar tool-use flow, and both have official JavaScript libraries, so the concepts transfer directly regardless of provider.
Choosing Between Frameworks and Raw SDKs
Once you outgrow a single loop, you face a choice. The minimalist path — raw SDK calls like the example above — gives you full control, no dependencies, and no abstraction leaks. It is the right choice when your agent has three to five tools and a predictable workflow.
For more complex systems, several JavaScript-native options exist:
- LangChain.js and LangGraph.js offer abstractions for chains, agents, and graph-based orchestration. LangGraph is particularly useful when your agent needs explicit state transitions, branching, or human-in-the-loop checkpoints.
- The Vercel AI SDK is popular among web developers because it integrates cleanly with React and Next.js, provides streaming out of the box, and has a
generateText/streamTextAPI with tool support that keeps boilerplate low. - Model Context Protocol (MCP), an open standard introduced by Anthropic, lets you expose tools as reusable servers that any compatible client can consume — increasingly the pragmatic way to share tooling across projects.
The tradeoff is control versus convenience. Frameworks save you from writing retry logic, history management, and streaming plumbing, but they also bury the loop behind abstractions, which makes debugging strange model behavior harder. A common mistake is adopting a heavyweight framework for what is genuinely a two-tool agent; the framework's indirection buys you nothing and costs you debuggability. Conversely, hand-rolling a multi-agent workflow with parallel branches is a recipe for subtle race conditions. Match the tooling to the complexity you actually have.
Where Agents Belong in Web Applications
For web developers, the most natural deployment points are server-side API routes and background jobs. A typical architecture in a Next.js application: the client sends a goal to an API route, the route runs the agent loop server-side (keeping API keys out of the browser), and responses stream back to the UI as the agent works.
Realistic use cases include:
- Support and internal-tooling assistants that query your database or ticketing system through read-only tools.
- Workflow automation where the agent chains API calls — for example, triaging an inbound email, looking up the customer, and drafting a response for human approval.
- Developer tools such as agents that read a codebase and propose changes, or that operate on your CI system.
Be honest about where agents are overkill. A task with a fixed, known sequence of steps should be a plain function, not an agent — deterministic code is cheaper, faster, and testable. Agents earn their cost only when the path to the goal is unpredictable and the model's judgment about which tool to call next adds real value.
Production Considerations and Common Mistakes
The difference between a demo and a production agent is almost entirely in the failure handling. Several issues come up repeatedly:
Prompt injection is the headline security risk. If your tools fetch external content — web pages, emails, user submissions — that content can contain instructions the model may follow, potentially triggering dangerous tool calls. Mitigations include restricting tools to read-only or low-risk operations, validating tool arguments against strict schemas, and treating all tool output as untrusted input.
Cost and latency scale with loop iterations. Every step is a full model call with the entire history attached, so a five-step agent is at least five billable calls, each more expensive than the last. Keep tool results compact — return the fields the model needs, not raw JSON dumps — and consider smaller, faster models for simple routing decisions.
Determinism and testing require deliberate effort. Nondeterministic model output means traditional unit tests cover only your tool functions. For the loop itself, use recorded model responses (many SDKs and frameworks support mocked or replayed responses) and evaluate agents with test suites that score outcomes rather than exact wording.
Observability is non-negotiable. Log every model call, tool invocation, arguments, and result with a correlation ID per run. When an agent does something inexplicable in production — and eventually one will — the ability to replay the exact message sequence is the difference between a five-minute fix and a week of guessing. Specialized tracing tools exist for this, but structured logging is a fine start.
Finally, add a human approval gate for any tool with side effects: sending email, writing to a database, spending money. Let the agent prepare the action, then require explicit confirmation before executing it. This single pattern prevents most catastrophic agent failures.
Frequently Asked Questions
Technically you can call LLM APIs from the browser, but you should not for agents. Your API keys would be exposed, tool functions often need server-side access (databases, secrets, internal APIs), and long-running loops are fragile over flaky connections. Run the agent loop on a server or edge function and stream results to the client instead.
No. A framework’s core value is managing the loop, history, and streaming for you. For simple agents with a handful of tools, raw SDK calls are clearer and easier to debug. Consider LangChain.js, LangGraph.js, or the Vercel AI SDK when you need streaming UI integration, complex state graphs, or many interchangeable providers.
Always set a maximum step or iteration count and treat exceeding it as an error. You can also add a stop condition that checks whether the agent is repeating identical tool calls, and include instructions in the system prompt telling the model to conclude when it has enough information. Cost caps — a maximum token budget per run — are a sensible second line of defense.
Both support tool calling well in their JavaScript SDKs, and the core pattern — schema-defined functions the model can invoke — is essentially the same. Choose based on the rest of your stack, pricing, and model preferences rather than tool-calling capability alone. Designing your loop to be provider-agnostic is more valuable than picking a “winner.”
For short-lived sessions, keeping the message array in memory is fine. For anything persistent, store the message list in your database keyed by session or run ID, so you can resume interrupted runs and audit behavior. Be aware of context-window limits: as history grows, summarize older turns or drop stale tool results rather than sending an ever-growing payload on every call.
Conclusion
Building AI agents in JavaScript is fundamentally an exercise in good engineering: a bounded loop, carefully described tools, disciplined state management, and defensive failure handling. The barrier to entry is lower than ever — a working agent is fifty lines of TypeScript — but production quality comes from the unglamorous parts: step limits, approval gates, logging, and honest decisions about where agentic behavior actually helps. Start small: pick one real workflow in your application where the next step genuinely depends on judgment, implement the minimal loop above with two tools, and add a human approval gate before any side effect. From there, frameworks like LangGraph or the Vercel AI SDK can grow with you when complexity demands it.
Transparency Note: Links in this article are not affiliate links. I recommend these tools and platforms based on their relevance to the topic, and no compensation is received for including them.