The term "AI agent" gets thrown around loosely. But real AI agents are autonomous systems that can reason about tasks, use tools to accomplish goals, and adapt when things don't go as planned.
In this tutorial, we'll build a genuine AI agent from scratch—an autonomous system that can browse the web, execute code, read files, and complete multi-step tasks with minimal human intervention.
AI Agent Architecture: Tools, Memory, and Planning
The core architecture consists of three components:
- The Foundation Model: The LLM serves as the "brain"—reasoning about what to do next, interpreting results, and deciding when tasks are complete.
- Tools: Extend what the agent can do—web search, code execution, file operations, API calls.
- Memory: Short-term (task context), long-term (persistent knowledge), and working memory (intermediate results).
The Agent Loop
async function agentLoop(task: string): Promise<string> {
const context = initializeContext(task)
while (!context.isComplete) {
const observation = await observe(context)
const thought = await reason(observation, context)
const result = await act(thought.action, thought.parameters)
context.addMemory(thought, result)
context.isComplete = await checkCompletion(context)
}
return synthesizeResult(context)
}Implementing Tool Use with Function Calling
import { z } from 'zod'
const WebSearchSchema = z.object({
query: z.string(),
maxResults: z.number().default(5)
})
const CodeExecutionSchema = z.object({
language: z.enum(['javascript', 'python', 'bash']),
code: z.string(),
timeout: z.number().default(30000)
})
const tools = [
{
name: 'web_search',
description: 'Search the web for information',
schema: WebSearchSchema,
execute: async ({ query, maxResults }) => {
const results = await searchWeb(query, maxResults)
return JSON.stringify(results)
}
},
{
name: 'execute_code',
description: 'Execute code in a sandboxed environment',
schema: CodeExecutionSchema,
execute: async ({ language, code, timeout }) => {
return await runInSandbox(language, code, timeout)
}
}
]Connecting Tools to the LLM
async function runAgent(task: string): Promise<string> {
const messages = [{ role: 'user', content: task }]
const systemPrompt = `You are an autonomous agent that completes tasks using tools.
Think carefully about what tool to use at each step.`
while (true) {
const response = await client.messages.create({
model: 'claude-4-sonnet-20260215',
max_tokens: 4096,
system: systemPrompt,
tools: toolsToAnthropicFormat(tools),
messages
})
messages.push({ role: 'assistant', content: response.content })
const toolUses = response.content.filter(b => b.type === 'tool_use')
if (toolUses.length === 0) {
return response.content.find(b => b.type === 'text')?.text
}
const toolResults = await Promise.all(
toolUses.map(async tu => ({
type: 'tool_result',
tool_use_id: tu.id,
content: await tools.find(t => t.name === tu.name)?.execute(tu.input)
}))
)
messages.push({ role: 'user', content: toolResults })
}
}Safety Rails and Human-in-the-Loop
type RiskLevel = 'low' | 'medium' | 'high' | 'critical'
const riskAssessment: Record<string, RiskLevel> = {
'web_search': 'low',
'file_read': 'low',
'file_write': 'medium',
'execute_code': 'high',
'send_email': 'critical'
}
async function executeWithApproval(action: string, params: unknown, executor: () => Promise<string>) {
const risk = riskAssessment[action] || 'high'
if (risk === 'critical') {
const approved = await requestHumanApproval({ action, params })
if (!approved) return 'Action denied by human operator'
}
return executor()
}Budget and Rate Limiting
class BudgetTracker {
private tokensUsed = 0
private toolCalls = 0
constructor(private limits: { maxTokens: number; maxToolCalls: number }) {}
checkLimits(): { ok: boolean; reason?: string } {
if (this.tokensUsed > this.limits.maxTokens)
return { ok: false, reason: 'Token limit exceeded' }
if (this.toolCalls > this.limits.maxToolCalls)
return { ok: false, reason: 'Tool call limit exceeded' }
return { ok: true }
}
}Conclusion
Building an AI agent is more than connecting an LLM to tools—it's creating a system that reasons, acts, and adapts. Key takeaways: start simple with well-designed tools, memory matters, safety first, and iterate on prompts. The age of AI agents is here—now you know how to build them.



