AI & Machine Learning18 min read1,954 words

Building AI Agents in 2026: Complete Guide to Autonomous Systems

Learn how to build AI agents that can autonomously complete tasks. Covers agent architectures, tool use, planning, memory systems, and production deployment patterns.

SJ

Sarah Johnson

AI agents represent the next evolution in AI applications - systems that can autonomously plan, execute, and adapt to complete complex tasks. Unlike simple chatbots, agents use tools, maintain memory, and reason about multi-step problems. This guide covers practical patterns for building production AI agents in 2026.

Agent Architecture Overview

A complete AI agent consists of four core components: a reasoning engine (LLM), tools for taking actions, memory for context, and a planning system for complex tasks.

typescript
// Core Agent Architecture
import Anthropic from '@anthropic-ai/sdk';

interface Tool {
  name: string;
  description: string;
  parameters: Record<string, any>;
  execute: (params: any) => Promise<any>;
}

interface AgentMemory {
  shortTerm: Message[];        // Conversation history
  longTerm: VectorStore;       // Persistent knowledge
  working: Map<string, any>;   // Current task context
}

interface AgentConfig {
  model: string;
  systemPrompt: string;
  tools: Tool[];
  maxIterations: number;
  temperature: number;
}

class Agent {
  private client: Anthropic;
  private config: AgentConfig;
  private memory: AgentMemory;

  constructor(config: AgentConfig) {
    this.client = new Anthropic();
    this.config = config;
    this.memory = {
      shortTerm: [],
      longTerm: new VectorStore(),
      working: new Map(),
    };
  }

  async run(task: string): Promise<string> {
    // Add task to memory
    this.memory.shortTerm.push({ role: 'user', content: task });

    let iteration = 0;
    while (iteration < this.config.maxIterations) {
      // Get relevant context from long-term memory
      const context = await this.memory.longTerm.search(task, 5);

      // Call LLM with tools
      const response = await this.client.messages.create({
        model: this.config.model,
        max_tokens: 4096,
        system: this.buildSystemPrompt(context),
        tools: this.formatTools(),
        messages: this.memory.shortTerm,
      });

      // Process response
      const { content, stopReason } = this.processResponse(response);

      // Check if agent wants to use a tool
      if (response.stop_reason === 'tool_use') {
        const toolResults = await this.executeTools(response.content);
        this.memory.shortTerm.push(
          { role: 'assistant', content: response.content },
          { role: 'user', content: toolResults }
        );
        iteration++;
        continue;
      }

      // Agent is done
      if (response.stop_reason === 'end_turn') {
        // Store interaction in long-term memory
        await this.memory.longTerm.add({
          task,
          response: content,
          timestamp: new Date(),
        });
        return content;
      }
    }

    return 'Max iterations reached without completion';
  }

  private async executeTools(content: any[]): Promise<any[]> {
    const results = [];
    
    for (const block of content) {
      if (block.type === 'tool_use') {
        const tool = this.config.tools.find(t => t.name === block.name);
        if (!tool) {
          results.push({
            type: 'tool_result',
            tool_use_id: block.id,
            content: `Error: Tool ${block.name} not found`,
          });
          continue;
        }

        try {
          const result = await tool.execute(block.input);
          results.push({
            type: 'tool_result',
            tool_use_id: block.id,
            content: JSON.stringify(result),
          });
        } catch (error) {
          results.push({
            type: 'tool_result',
            tool_use_id: block.id,
            content: `Error: ${error.message}`,
          });
        }
      }
    }

    return results;
  }

  private formatTools() {
    return this.config.tools.map(tool => ({
      name: tool.name,
      description: tool.description,
      input_schema: {
        type: 'object',
        properties: tool.parameters,
        required: Object.keys(tool.parameters),
      },
    }));
  }

  private buildSystemPrompt(context: any[]): string {
    let prompt = this.config.systemPrompt;
    
    if (context.length > 0) {
      prompt += '\n\nRelevant context from memory:\n';
      context.forEach((c, i) => {
        prompt += `${i + 1}. ${c.content}\n`;
      });
    }

    return prompt;
  }
}

Tool Implementation

Tools extend agent capabilities beyond text generation. Well-designed tools are atomic, composable, and handle errors gracefully.

typescript
// Tool Implementations

// Web Search Tool
const webSearchTool: Tool = {
  name: 'web_search',
  description: 'Search the web for current information. Use for recent events, documentation, or facts.',
  parameters: {
    query: { type: 'string', description: 'Search query' },
    num_results: { type: 'number', description: 'Number of results (1-10)' },
  },
  async execute({ query, num_results = 5 }) {
    const response = await fetch(
      `https://api.search.example/search?q=${encodeURIComponent(query)}&n=${num_results}`
    );
    const data = await response.json();
    
    return data.results.map((r: any) => ({
      title: r.title,
      snippet: r.snippet,
      url: r.url,
    }));
  },
};

// Code Execution Tool (sandboxed)
const codeExecutionTool: Tool = {
  name: 'execute_code',
  description: 'Execute Python code in a sandboxed environment. Returns stdout, stderr, and return value.',
  parameters: {
    code: { type: 'string', description: 'Python code to execute' },
    timeout: { type: 'number', description: 'Timeout in seconds (max 30)' },
  },
  async execute({ code, timeout = 10 }) {
    const response = await fetch('https://sandbox.example/execute', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code, timeout: Math.min(timeout, 30) }),
    });
    
    return response.json();
  },
};

// Database Query Tool
const databaseTool: Tool = {
  name: 'query_database',
  description: 'Query the database. Only SELECT queries are allowed.',
  parameters: {
    query: { type: 'string', description: 'SQL SELECT query' },
  },
  async execute({ query }) {
    // Validate query is SELECT only
    const normalized = query.trim().toLowerCase();
    if (!normalized.startsWith('select')) {
      throw new Error('Only SELECT queries are allowed');
    }
    
    // Check for dangerous patterns
    const dangerous = ['drop', 'delete', 'update', 'insert', 'alter', '--'];
    if (dangerous.some(d => normalized.includes(d))) {
      throw new Error('Query contains forbidden keywords');
    }

    const result = await db.query(query);
    return {
      rows: result.rows.slice(0, 100), // Limit results
      rowCount: result.rowCount,
    };
  },
};

// File Operations Tool
const fileOperationsTool: Tool = {
  name: 'file_operations',
  description: 'Read, write, or list files in the workspace.',
  parameters: {
    operation: { type: 'string', enum: ['read', 'write', 'list'] },
    path: { type: 'string', description: 'File path relative to workspace' },
    content: { type: 'string', description: 'Content for write operation' },
  },
  async execute({ operation, path, content }) {
    const workspacePath = '/workspace';
    const fullPath = `${workspacePath}/${path}`;
    
    // Security: prevent path traversal
    if (fullPath.includes('..') || !fullPath.startsWith(workspacePath)) {
      throw new Error('Invalid path');
    }

    switch (operation) {
      case 'read':
        return await fs.readFile(fullPath, 'utf-8');
      
      case 'write':
        await fs.writeFile(fullPath, content);
        return { success: true, path };
      
      case 'list':
        const files = await fs.readdir(fullPath);
        return files;
      
      default:
        throw new Error(`Unknown operation: ${operation}`);
    }
  },
};

Planning and Reasoning

Complex tasks require planning. The ReAct (Reasoning + Acting) pattern helps agents break down problems and track progress.

typescript
// ReAct Agent with Planning

interface Plan {
  goal: string;
  steps: PlanStep[];
  currentStep: number;
  status: 'planning' | 'executing' | 'completed' | 'failed';
}

interface PlanStep {
  id: number;
  description: string;
  status: 'pending' | 'in_progress' | 'completed' | 'failed';
  result?: string;
}

class ReActAgent extends Agent {
  private plan: Plan | null = null;

  async run(task: string): Promise<string> {
    // Phase 1: Planning
    this.plan = await this.createPlan(task);
    console.log('Plan created:', this.plan);

    // Phase 2: Execution
    for (let i = 0; i < this.plan.steps.length; i++) {
      this.plan.currentStep = i;
      this.plan.steps[i].status = 'in_progress';

      try {
        const result = await this.executeStep(this.plan.steps[i]);
        this.plan.steps[i].result = result;
        this.plan.steps[i].status = 'completed';

        // Check if we should replan based on results
        if (await this.shouldReplan(result)) {
          this.plan = await this.replan(this.plan, result);
          i = this.plan.currentStep - 1; // Reset to new current step
        }
      } catch (error) {
        this.plan.steps[i].status = 'failed';
        
        // Try to recover
        const recovery = await this.attemptRecovery(error, this.plan.steps[i]);
        if (!recovery.success) {
          this.plan.status = 'failed';
          return `Failed at step ${i + 1}: ${error.message}`;
        }
      }
    }

    // Phase 3: Synthesize results
    return this.synthesizeResults(this.plan);
  }

  private async createPlan(task: string): Promise<Plan> {
    const response = await this.client.messages.create({
      model: this.config.model,
      max_tokens: 2048,
      system: `You are a planning agent. Break down the task into concrete steps.
        
Output your plan as JSON:
{
  "goal": "the overall goal",
  "steps": [
    { "id": 1, "description": "step description" },
    ...
  ]
}

Keep steps atomic and actionable. Usually 3-7 steps is appropriate.`,
      messages: [{ role: 'user', content: task }],
    });

    const planText = response.content[0].text;
    const planJson = JSON.parse(planText);

    return {
      ...planJson,
      currentStep: 0,
      status: 'executing',
      steps: planJson.steps.map((s: any) => ({
        ...s,
        status: 'pending',
      })),
    };
  }

  private async executeStep(step: PlanStep): Promise<string> {
    const response = await this.client.messages.create({
      model: this.config.model,
      max_tokens: 4096,
      system: `You are executing step ${step.id} of a plan.
        
Goal: ${this.plan!.goal}
Current step: ${step.description}

Previous steps completed:
${this.plan!.steps
  .filter(s => s.status === 'completed')
  .map(s => `- ${s.description}: ${s.result}`)
  .join('\n')}

Use the available tools to complete this step. Be thorough but efficient.`,
      tools: this.formatTools(),
      messages: [{ role: 'user', content: `Execute: ${step.description}` }],
    });

    // Execute any tools and get final result
    return this.processAgentResponse(response);
  }

  private async shouldReplan(result: string): Promise<boolean> {
    const response = await this.client.messages.create({
      model: this.config.model,
      max_tokens: 256,
      messages: [{
        role: 'user',
        content: `Given this step result: "${result}"
          
And the remaining plan:
${this.plan!.steps
  .filter(s => s.status === 'pending')
  .map(s => `- ${s.description}`)
  .join('\n')}

Should we adjust the plan? Reply with just YES or NO.`,
      }],
    });

    return response.content[0].text.trim().toUpperCase() === 'YES';
  }

  private async replan(currentPlan: Plan, newInfo: string): Promise<Plan> {
    const response = await this.client.messages.create({
      model: this.config.model,
      max_tokens: 2048,
      system: 'You are adjusting a plan based on new information.',
      messages: [{
        role: 'user',
        content: `Current plan goal: ${currentPlan.goal}
          
Completed steps:
${currentPlan.steps
  .filter(s => s.status === 'completed')
  .map(s => `- ${s.description}: ${s.result}`)
  .join('\n')}

New information: ${newInfo}

Provide updated remaining steps as JSON array.`,
      }],
    });

    const newSteps = JSON.parse(response.content[0].text);
    
    return {
      ...currentPlan,
      steps: [
        ...currentPlan.steps.filter(s => s.status === 'completed'),
        ...newSteps.map((s: any, i: number) => ({
          ...s,
          id: currentPlan.currentStep + i + 1,
          status: 'pending',
        })),
      ],
    };
  }

  private synthesizeResults(plan: Plan): string {
    const completedSteps = plan.steps
      .filter(s => s.status === 'completed')
      .map(s => `${s.description}: ${s.result}`)
      .join('\n\n');

    return `Completed goal: ${plan.goal}\n\nResults:\n${completedSteps}`;
  }
}

Memory Systems

typescript
// Advanced Memory System
import { Pinecone } from '@pinecone-database/pinecone';
import { OpenAIEmbeddings } from '@langchain/openai';

interface MemoryEntry {
  id: string;
  content: string;
  type: 'conversation' | 'fact' | 'procedure' | 'preference';
  metadata: {
    timestamp: Date;
    importance: number;
    accessCount: number;
    lastAccessed: Date;
    source: string;
  };
  embedding?: number[];
}

class AgentMemorySystem {
  private shortTermMemory: MemoryEntry[] = [];
  private pinecone: Pinecone;
  private embeddings: OpenAIEmbeddings;
  private maxShortTermSize = 20;

  constructor() {
    this.pinecone = new Pinecone();
    this.embeddings = new OpenAIEmbeddings();
  }

  // Add to short-term memory
  async addShortTerm(content: string, type: MemoryEntry['type']) {
    const entry: MemoryEntry = {
      id: crypto.randomUUID(),
      content,
      type,
      metadata: {
        timestamp: new Date(),
        importance: 0.5,
        accessCount: 0,
        lastAccessed: new Date(),
        source: 'conversation',
      },
    };

    this.shortTermMemory.push(entry);

    // Consolidate if too large
    if (this.shortTermMemory.length > this.maxShortTermSize) {
      await this.consolidateMemory();
    }
  }

  // Consolidate short-term to long-term memory
  private async consolidateMemory() {
    // Keep recent and important entries
    const toKeep = this.shortTermMemory
      .sort((a, b) => {
        const recencyA = Date.now() - a.metadata.timestamp.getTime();
        const recencyB = Date.now() - b.metadata.timestamp.getTime();
        const scoreA = a.metadata.importance - recencyA / 1000000;
        const scoreB = b.metadata.importance - recencyB / 1000000;
        return scoreB - scoreA;
      })
      .slice(0, this.maxShortTermSize / 2);

    const toConsolidate = this.shortTermMemory.filter(
      e => !toKeep.includes(e)
    );

    // Store in long-term memory
    await this.addToLongTerm(toConsolidate);

    this.shortTermMemory = toKeep;
  }

  // Add to long-term vector memory
  private async addToLongTerm(entries: MemoryEntry[]) {
    const index = this.pinecone.index('agent-memory');

    for (const entry of entries) {
      const embedding = await this.embeddings.embedQuery(entry.content);

      await index.upsert([{
        id: entry.id,
        values: embedding,
        metadata: {
          content: entry.content,
          type: entry.type,
          timestamp: entry.metadata.timestamp.toISOString(),
          importance: entry.metadata.importance,
        },
      }]);
    }
  }

  // Retrieve relevant memories
  async recall(query: string, limit = 5): Promise<MemoryEntry[]> {
    // Search short-term first (recency bias)
    const shortTermResults = this.searchShortTerm(query);

    // Search long-term
    const queryEmbedding = await this.embeddings.embedQuery(query);
    const index = this.pinecone.index('agent-memory');
    
    const longTermResults = await index.query({
      vector: queryEmbedding,
      topK: limit,
      includeMetadata: true,
    });

    // Combine and deduplicate
    const combined = [
      ...shortTermResults,
      ...longTermResults.matches.map(m => ({
        id: m.id,
        content: m.metadata?.content as string,
        type: m.metadata?.type as MemoryEntry['type'],
        metadata: {
          timestamp: new Date(m.metadata?.timestamp as string),
          importance: m.metadata?.importance as number,
          accessCount: 0,
          lastAccessed: new Date(),
          source: 'long-term',
        },
      })),
    ];

    // Update access counts
    combined.forEach(e => e.metadata.accessCount++);

    return combined.slice(0, limit);
  }

  private searchShortTerm(query: string): MemoryEntry[] {
    // Simple keyword matching for short-term
    const queryWords = query.toLowerCase().split(' ');
    
    return this.shortTermMemory
      .filter(e => {
        const contentWords = e.content.toLowerCase().split(' ');
        return queryWords.some(qw => contentWords.some(cw => cw.includes(qw)));
      })
      .slice(0, 3);
  }

  // Forget irrelevant memories
  async forget(criteria: { olderThan?: Date; type?: string }) {
    const index = this.pinecone.index('agent-memory');
    
    // Delete from Pinecone based on criteria
    // (Implementation depends on your indexing strategy)
  }
}

Best Practices

AI Agent Best Practices

Design:

- Keep tools atomic and composable

- Implement robust error handling

- Add rate limiting and timeouts

- Log all actions for debugging

Safety:

- Sandbox code execution

- Validate all tool inputs

- Implement human-in-the-loop for critical actions

- Set spending/usage limits

Performance:

- Cache tool results where appropriate

- Use streaming for long-running tasks

- Implement parallel tool execution

- Monitor and optimize token usage

Conclusion

AI agents are transforming automation by combining LLM reasoning with real-world actions. Success requires careful attention to tool design, planning systems, and memory management. Start simple with a few well-designed tools and add complexity as needed.

Ready to build AI agents for your business? Contact Jishu Labs for expert guidance on designing and implementing autonomous AI systems.

SJ

About Sarah Johnson

Sarah Johnson is the CTO at Jishu Labs with deep expertise in AI systems. She has built production AI agents for enterprise automation and developer tools.

Related Articles

Ready to Build Your Next Project?

Let's discuss how our expert team can help bring your vision to life.

Top-Rated
Software Development
Company

Ready to Get Started?

Get consistent results. Collaborate in real-time.
Build Intelligent Apps. Work with Jishu Labs.

SCHEDULE MY CALL