Trong bối cảnh AI Agent đang bùng nổ năm 2026, có một nghịch lý mà hầu hết đội ngũ phát triển đều gặp phải: prototype hoạt động hoàn hảo trên máy tính của bạn, nhưng khi lên production thì thất bại thảm khốc. Nguyên nhân gốc rễ nằm ở cách thiết kế Agent orchestration — đặc biệt là mô hình ReAct (Reasoning + Acting). Bài viết này sẽ phân tích chuyên sâu từ lý thuyết đến thực chiến, kèm theo so sánh chi phí với HolySheep AI để bạn có thể triển khai Agent production-ready ngay hôm nay.

Chi phí thực tế: So sánh 10M Token/Tháng

Trước khi đi sâu vào kỹ thuật, hãy cùng xem bảng so sánh chi phí thực tế của các model phổ biến nhất cho Agent reasoning năm 2026:

Model Output Price ($/MTok) 10M Token/Tháng ($) HolySheep Price (¥/MTok) Tương đương ($/MTok)
GPT-4.1 $8.00 $80 ¥8 $8.00
Claude Sonnet 4.5 $15.00 $150 ¥15 $15.00
Gemini 2.5 Flash $2.50 $25 ¥2.50 $2.50
DeepSeek V3.2 $0.42 $4.20 ¥0.42 $0.42
Tiết kiệm khi dùng DeepSeek V3.2 vs GPT-4.1 ~95% ($75/10M tokens)

Bảng 1: So sánh chi phí các model AI phổ biến cho Agent orchestration — cập nhật tháng 1/2026

Với mô hình ReAct, mỗi lần reasoning + action tạo ra nhiều round-trip, nghĩa là chi phí token nhân lên theo số bước. Một Agent ReAct điển hình có thể tiêu tốn 50-200 tokens/step × 10-30 steps = 500-6000 tokens/request. Nếu bạn chạy 10,000 requests/ngày với GPT-4.1, chi phí hàng tháng có thể lên đến $2,400-4,800. Với DeepSeek V3.2 trên HolySheep, con số này chỉ còn $126-252.

ReAct Pattern là gì? Tại sao nó quan trọng với AI Agent

1.1. Kiến trúc ReAct (Reasoning + Acting)

ReAct được giới thiệu bởi Yao et al. (2022) là mô hình kết hợp hai quá trình:

Vòng lặp ReAct tạo ra chuỗi: Observation → Thought → Action → Observation → ... → Final Answer

1.2. Tại sao ReAct thất bại khi đi từ Demo lên Production

Qua kinh nghiệm triển khai hàng chục Agent cho doanh nghiệp, tôi nhận thấy 3 vấn đề phổ biến nhất:

  1. Token explosion: Mỗi step đều bao gồm conversation history, dẫn đến context window overflow
  2. Loop vô hạn: Agent không có termination condition rõ ràng
  3. Error propagation: Một action thất bại khiến toàn bộ chain sai

HolySheep Agent Orchestration: Kiến trúc Production-Ready

3.1. Streaming Response Architecture

Điểm mấu chốt đầu tiên của HolySheep là hỗ trợ Server-Sent Events (SSE) cho streaming response, giảm perceived latency từ 3-5s xuống còn 200-500ms cho mỗi token chunk. Dưới đây là implementation hoàn chỉnh:

const https = require('https');

/**
 * HolySheep AI - Streaming ReAct Agent với real-time token consumption tracking
 * Base URL: https://api.holysheep.ai/v1
 * Author: HolySheep AI Technical Team
 * License: MIT
 */

const HOLYSHEEP_BASE_URL = 'api.holysheep.ai';
const HOLYSHEEP_API_KEY = process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY';

class StreamingReActAgent {
  constructor(config = {}) {
    this.model = config.model || 'deepseek-v3.2';
    this.maxSteps = config.maxSteps || 15;
    this.tools = config.tools || [];
    this.conversationHistory = [];
    this.stepCount = 0;
    this.totalTokens = 0;
    this.totalCostUSD = 0;
    
    // Pricing lookup (updated Jan 2026)
    this.pricing = {
      'deepseek-v3.2': { pricePerMTok: 0.42 },
      'gpt-4.1': { pricePerMTok: 8.00 },
      'claude-sonnet-4.5': { pricePerMTok: 15.00 },
      'gemini-2.5-flash': { pricePerMTok: 2.50 }
    };
  }

  /**
   * Streaming completion với SSE support
   */
  async streamComplete(messages) {
    return new Promise((resolve, reject) => {
      const postData = JSON.stringify({
        model: this.model,
        messages: messages,
        stream: true,
        temperature: 0.7,
        max_tokens: 4096
      });

      const options = {
        hostname: HOLYSHEEP_BASE_URL,
        path: '/v1/chat/completions',
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': Bearer ${HOLYSHEEP_API_KEY},
          'Content-Length': Buffer.byteLength(postData)
        }
      };

      const req = https.request(options, (res) => {
        let responseBody = '';
        
        res.on('data', (chunk) => {
          responseBody += chunk.toString();
          
          // Parse SSE chunks
          const lines = responseBody.split('\n');
          responseBody = lines.pop();
          
          for (const line of lines) {
            if (line.startsWith('data: ')) {
              const data = line.slice(6);
              if (data === '[DONE]') {
                resolve({ done: true });
                return;
              }
              
              try {
                const parsed = JSON.parse(data);
                if (parsed.choices && parsed.choices[0].delta && parsed.choices[0].delta.content) {
                  process.stdout.write(parsed.choices[0].delta.content);
                  
                  // Track usage if available
                  if (parsed.usage) {
                    this.totalTokens += parsed.usage.total_tokens;
                    this.totalCostUSD = (this.totalTokens / 1_000_000) * 
                      this.pricing[this.model].pricePerMTok;
                  }
                }
              } catch (e) {
                // Ignore parse errors for incomplete chunks
              }
            }
          }
        });

        res.on('end', () => {
          resolve({ done: true, totalTokens: this.totalTokens, totalCostUSD: this.totalCostUSD });
        });

        res.on('error', (err) => {
          reject(new Error(HTTP Error: ${err.message}));
        });
      });

      req.on('error', (err) => {
        reject(new Error(Request Error: ${err.message}));
      });

      req.write(postData);
      req.end();
    });
  }

  /**
   * Execute ReAct loop với tool calling support
   */
  async run(initialTask) {
    console.log(\n🚀 Starting ReAct Agent (Model: ${this.model}));
    console.log(📊 Pricing: $${this.pricing[this.model].pricePerMTok}/MTok\n);
    
    const startTime = Date.now();
    this.conversationHistory = [
      { role: 'system', content: this._buildSystemPrompt() },
      { role: 'user', content: initialTask }
    ];

    for (this.stepCount = 1; this.stepCount <= this.maxSteps; this.stepCount++) {
      console.log(\n--- Step ${this.stepCount}/${this.maxSteps} ---);
      
      const response = await this.streamComplete(this.conversationHistory);
      
      // Parse assistant message for tool calls
      const lastMessage = this.conversationHistory[this.conversationHistory.length - 1];
      
      if (lastMessage.tool_calls) {
        for (const toolCall of lastMessage.tool_calls) {
          const result = await this._executeTool(toolCall.function);
          this.conversationHistory.push({
            role: 'tool',
            tool_call_id: toolCall.id,
            content: JSON.stringify(result)
          });
        }
      } else if (lastMessage.content.includes('[FINAL]')) {
        // Termination condition
        console.log('\n✅ Agent completed successfully');
        break;
      } else {
        // Add assistant response to history
        this.conversationHistory.push(lastMessage);
      }
    }

    const elapsed = Date.now() - startTime;
    console.log(\n📈 Summary:);
    console.log(   Steps: ${this.stepCount}/${this.maxSteps});
    console.log(   Total Tokens: ${this.totalTokens});
    console.log(   Total Cost: $${this.totalCostUSD.toFixed(4)});
    console.log(   Latency: ${elapsed}ms);
    console.log(   Cost per Request: $${(this.totalCostUSD / Math.max(1, this.stepCount)).toFixed(4)});
    
    return {
      success: true,
      steps: this.stepCount,
      totalTokens: this.totalTokens,
      costUSD: this.totalCostUSD,
      latencyMs: elapsed
    };
  }

  _buildSystemPrompt() {
    return `You are a ReAct agent. For each step:
1. THINK: Analyze the current state and determine next action
2. ACT: Execute a tool call if needed, or provide [FINAL] answer

Available tools: ${JSON.stringify(this.tools.map(t => ({ name: t.name, description: t.description })))}`;
  }

  async _executeTool(functionCall) {
    const tool = this.tools.find(t => t.name === functionCall.name);
    if (!tool) {
      return { error: Unknown tool: ${functionCall.name} };
    }
    
    try {
      const args = JSON.parse(functionCall.arguments);
      console.log(🔧 Calling tool: ${functionCall.name}, args);
      return await tool.handler(args);
    } catch (e) {
      return { error: e.message };
    }
  }
}

// Usage Example
const agent = new StreamingReActAgent({
  model: 'deepseek-v3.2',  // Most cost-effective for ReAct
  maxSteps: 15,
  tools: [
    {
      name: 'search',
      description: 'Search the web for information',
      handler: async (args) => {
        // Implement your search logic
        return { results: [], query: args.query };
      }
    },
    {
      name: 'calculate',
      description: 'Perform calculations',
      handler: async (args) => {
        return { result: eval(args.expression) };
      }
    }
  ]
});

// Run the agent
agent.run('Research the latest developments in AI Agent frameworks and summarize key trends')
  .then(result => console.log('\n🎉 Final Result:', JSON.stringify(result, null, 2)))
  .catch(err => console.error('❌ Error:', err));

3.2. Token Budget Management với Context Compression

Vấn đề lớn nhất của ReAct là context explosion. Sau 10-15 steps, conversation history có thể chiếm 50K+ tokens. Giải pháp là dynamic context compression:

/**
 * TokenBudgetManager - HolySheep AI
 * Implements hierarchical context compression for long-running ReAct agents
 */

class TokenBudgetManager {
  constructor(config = {
    maxContextTokens: 128000,  // Leave 32K for response
    compressionThreshold: 0.85,
    preserveSystemMessages: true,
    preserveLastNMessages: 3
  }) {
    this.config = config;
    this.availableTokens = config.maxContextTokens;
    this.messages = [];
    this.compressionEvents = 0;
  }

  /**
   * Calculate current token usage (approximate)
   * Simple tokenizer: ~4 chars per token for English, ~2 for Vietnamese
   */
  calculateTokens(messages) {
    let total = 0;
    for (const msg of messages) {
      const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
      // Rough estimation: 4 chars per token average
      total += Math.ceil(content.length / 4) + 10; // +10 for message overhead
    }
    return total;
  }

  /**
   * Compress context while preserving critical information
   */
  compressContext() {
    if (this.messages.length <= this.config.preserveLastNMessages + 2) {
      return; // Nothing to compress
    }

    const systemMessages = this.messages.filter(m => m.role === 'system');
    const recentMessages = this.messages.slice(-this.config.preserveLastNMessages);
    const middleMessages = this.messages.slice(1, -this.config.preserveLastNMessages);

    // Generate compression summary for middle messages
    const compressionSummary = this._generateCompressionSummary(middleMessages);

    // Rebuild messages
    this.messages = [
      ...systemMessages,
      {
        role: 'system',
        content: [COMPRESSED HISTORY - ${this.compressionEvents} compressions]\n${compressionSummary}
      },
      ...recentMessages
    ];

    this.compressionEvents++;
    console.log(📦 Context compressed. Events: ${this.compressionEvents}, Tokens saved: ~${this.calculateTokens(middleMessages)});
  }

  /**
   * Generate semantic summary of middle messages
   */
  _generateCompressionSummary(messages) {
    if (messages.length === 0) return 'No intermediate steps.';

    const summaryParts = [];
    for (const msg of messages) {
      if (msg.role === 'user') {
        summaryParts.push(Task: ${msg.content.substring(0, 100)}...);
      } else if (msg.role === 'assistant') {
        const thoughtMatch = msg.content.match(/THINK:?\s*(.+?)(?:ACT:|$)/s);
        const actionMatch = msg.content.match(/ACT:?\s*(.+?)(?:\[FINAL\]|$)/s);
        
        if (thoughtMatch) summaryParts.push(Thought: ${thoughtMatch[1].substring(0, 80)});
        if (actionMatch) summaryParts.push(Action: ${actionMatch[1].substring(0, 80)});
        if (msg.content.includes('[FINAL]')) summaryParts.push('Reached conclusion.');
      } else if (msg.role === 'tool') {
        summaryParts.push(Tool result: ${msg.content.substring(0, 60)}...);
      }
    }

    return summaryParts.join('\n');
  }

  /**
   * Add message and check if compression needed
   */
  addMessage(message) {
    this.messages.push(message);
    
    const currentTokens = this.calculateTokens(this.messages);
    const usageRatio = currentTokens / this.config.maxContextTokens;

    if (usageRatio > this.config.compressionThreshold) {
      console.log(⚠️  Token usage: ${(usageRatio * 100).toFixed(1)}% (${currentTokens}/${this.config.maxContextTokens}));
      this.compressContext();
    }

    return {
      tokens: currentTokens,
      usagePercent: (usageRatio * 100).toFixed(1),
      compressed: this.compressionEvents > 0
    };
  }

  /**
   * Get messages formatted for API call
   */
  getContext() {
    return this.messages;
  }

  /**
   * Get budget statistics
   */
  getStats() {
    return {
      totalMessages: this.messages.length,
      totalTokens: this.calculateTokens(this.messages),
      maxTokens: this.config.maxContextTokens,
      usagePercent: ((this.calculateTokens(this.messages) / this.config.maxContextTokens) * 100).toFixed(1),
      compressionEvents: this.compressionEvents
    };
  }
}

/**
 * ReAct Loop với Token Budget Management
 */
async function runReActWithBudget(task, tools, options = {}) {
  const budgetManager = new TokenBudgetManager({
    maxContextTokens: options.maxContextTokens || 128000,
    compressionThreshold: options.compressionThreshold || 0.85
  });

  const maxSteps = options.maxSteps || 15;
  const startTime = Date.now();

  // Initialize with system prompt
  budgetManager.addMessage({
    role: 'system',
    content: `You are a ReAct agent. Format your response as:
THINK: [your reasoning]
ACT: [tool call if needed, or FINAL ANSWER]`
  });

  budgetManager.addMessage({ role: 'user', content: task });

  let step = 0;
  let finalAnswer = null;

  while (step < maxSteps) {
    step++;
    console.log(\n📍 Step ${step}/${maxSteps});
    console.log(   Budget: ${budgetManager.getStats().usagePercent}%);

    const context = budgetManager.getContext();
    
    // Call HolySheep API
    const response = await callHolySheep(context);
    
    if (response.finish_reason === 'stop' && response.content.includes('[FINAL]')) {
      finalAnswer = response.content.replace('[FINAL]', '').trim();
      break;
    }

    budgetManager.addMessage({ role: 'assistant', content: response.content });

    if (response.tool_calls) {
      for (const toolCall of response.tool_calls) {
        const result = await executeTool(toolCall);
        budgetManager.addMessage({
          role: 'tool',
          tool_call_id: toolCall.id,
          content: JSON.stringify(result)
        });
      }
    }
  }

  return {
    answer: finalAnswer,
    steps: step,
    budgetStats: budgetManager.getStats(),
    latencyMs: Date.now() - startTime
  };
}

/**
 * Placeholder for HolySheep API call
 */
async function callHolySheep(messages) {
  // Implementation would call https://api.holysheep.ai/v1/chat/completions
  return { content: '', finish_reason: 'continue' };
}

async function executeTool(toolCall) {
  return { result: 'tool execution result' };
}

// Usage
const budget = new TokenBudgetManager({ maxContextTokens: 128000 });
budget.addMessage({ role: 'user', content: 'Task 1' });
budget.addMessage({ role: 'assistant', content: 'Response 1' });
budget.addMessage({ role: 'user', content: 'Task 2' });
budget.addMessage({ role: 'assistant', content: 'Response 2' });

console.log('Initial Stats:', budget.getStats());

// Simulate compression
for (let i = 0; i < 50; i++) {
  budget.addMessage({ role: 'assistant', content: 'x'.repeat(500) });
}

console.log('After Compression:', budget.getStats());

Lỗi thường gặp và cách khắc phục

Lỗi 1: Token Limit Exceeded (Context Overflow)

Mã lỗi: context_length_exceeded hoặc 400 Bad Request

Nguyên nhân: Sau 10-20 steps ReAct, conversation history vượt quá context window (thường là 128K-200K tokens cho các model mới).

Giải pháp:

/**
 * Error Handler cho Token Limit Exceeded
 */
class TokenLimitError extends Error {
  constructor(currentTokens, maxTokens, stepNumber) {
    super(Token limit exceeded at step ${stepNumber});
    this.name = 'TokenLimitError';
    this.currentTokens = currentTokens;
    this.maxTokens = maxTokens;
    this.stepNumber = stepNumber;
    this.recoveryAction = 'COMPRESS_CONTEXT';
  }
}

/**
 * Automatic recovery với context compression
 */
async function handleReActError(error, agentState) {
  if (error instanceof TokenLimitError) {
    console.log(⚠️  Caught TokenLimitError: ${error.currentTokens}/${error.maxTokens});
    
    switch (error.recoveryAction) {
      case 'COMPRESS_CONTEXT':
        // Solution 1: Compress context
        const compressed = agentState.compressContext();
        console.log(✅ Context compressed. Remaining tokens: ${compressed.remainingTokens});
        return {
          action: 'RETRY_WITH_COMPRESSED_CONTEXT',
          newContext: compressed.messages
        };
        
      case 'SWITCH_TO_SUMMARIZER':
        // Solution 2: Use specialized summarizer model
        const summary = await callSummarizerModel(
          agentState.messages,
          'Summarize key decisions and current state in 500 tokens'
        );
        return {
          action: 'REBUILD_CONTEXT_FROM_SUMMARY',
          summary: summary
        };
        
      case 'TRUNCATE_OLDEST':
        // Solution 3: Remove oldest messages (keep system + recent)
        const truncated = [
          agentState.messages[0], // System
          ...agentState.messages.slice(-20) // Last 20 messages
        ];
        return {
          action: 'RETRY_WITH_TRUNCATED_CONTEXT',
          newContext: truncated
        };
    }
  }
  
  throw error; // Re-throw unknown errors
}

/**
 * Retry wrapper với exponential backoff
 */
async function executeWithRetry(operation, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      if (error instanceof TokenLimitError) {
        const recovery = await handleReActError(error, getAgentState());
        if (recovery.action) {
          setAgentState({ messages: recovery.newContext });
          continue; // Retry with recovery
        }
      }
      
      if (attempt === maxRetries) {
        console.error(❌ Max retries (${maxRetries}) exceeded);
        throw error;
      }
      
      const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
      console.log(⏳ Retrying in ${delay}ms (attempt ${attempt}/${maxRetries}));
      await sleep(delay);
    }
  }
}

Lỗi 2: Tool Call Timeout / Failure

Mã lỗi: tool_timeout, tool_error, ECONNREFUSED

Nguyên nhân: External API timeout, database connection failed, hoặc tool handler exception không được catch.

Giải pháp:

/**
 * Resilient Tool Executor với timeout và retry
 */
class ToolExecutor {
  constructor(config = {
    defaultTimeout: 10000, // 10 seconds
    maxRetries: 2,
    retryDelay: 1000
  }) {
    this.config = config;
    this.executionLog = [];
  }

  async execute(toolCall, context = {}) {
    const startTime = Date.now();
    const toolName = toolCall.function?.name || toolCall.name;
    const args = JSON.parse(toolCall.function?.arguments || toolCall.arguments || '{}');

    this.executionLog.push({
      tool: toolName,
      args: args,
      status: 'PENDING'
    });

    for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
      try {
        const result = await this._executeWithTimeout(
          this._getToolHandler(toolName),
          args,
          this.config.defaultTimeout
        );

        this.executionLog[this.executionLog.length - 1].status = 'SUCCESS';
        this.executionLog[this.executionLog.length - 1].durationMs = Date.now() - startTime;

        return {
          success: true,
          result: result,
          attempts: attempt + 1,
          durationMs: Date.now() - startTime
        };

      } catch (error) {
        console.error(❌ Tool ${toolName} failed (attempt ${attempt + 1}/${this.config.maxRetries + 1}):, error.message);

        if (attempt < this.config.maxRetries) {
          await sleep(this.config.retryDelay * (attempt + 1));
        } else {
          // Final failure - return error but don't crash agent
          this.executionLog[this.executionLog.length - 1].status = 'FAILED';
          this.executionLog[this.executionLog.length - 1].error = error.message;
          this.executionLog[this.executionLog.length - 1].durationMs = Date.now() - startTime;

          return {
            success: false,
            error: error.message,
            errorType: this._classifyError(error),
            fallbackMessage: Tool '${toolName}' failed after ${attempt + 1} attempts. Suggest alternative approach.,
            attempts: attempt + 1,
            durationMs: Date.now() - startTime
          };
        }
      }
    }
  }

  async _executeWithTimeout(fn, args, timeoutMs) {
    return Promise.race([
      fn(args),
      new Promise((_, reject) => 
        setTimeout(() => reject(new Error(Timeout after ${timeoutMs}ms)), timeoutMs)
      )
    ]);
  }

  _getToolHandler(toolName) {
    const handlers = {
      'search': async (args) => {
        // Implement search with proper error handling
        const response = await fetch('https://api.search.example.com', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(args),
          signal: AbortSignal.timeout(8000)
        });
        
        if (!response.ok) {
          throw new Error(Search API error: ${response.status} ${response.statusText});
        }
        
        return await response.json();
      },
      
      'database': async (args) => {
        // Database operations
        const pool = getDbPool();
        const client = await pool.connect();
        
        try {
          const result = await client.query(args.sql, args.params);
          return { rows: result.rows, rowCount: result.rowCount };
        } finally {
          client.release();
        }
      },
      
      'calculate': async (args) => {
        // Safe calculation
        if (!this._isSafeExpression(args.expression)) {
          throw new Error('Unsafe expression detected');
        }
        return { result: math.evaluate(args.expression) };
      }
    };

    return handlers[toolName] || (() => { throw new Error(Unknown tool: ${toolName}); });
  }

  _isSafeExpression(expr) {
    // Prevent code injection
    const dangerous = /[;'"`$\\{}]|eval|Function|require|import/g;
    return !dangerous.test(expr);
  }

  _classifyError(error) {
    if (error.message.includes('Timeout')) return 'TIMEOUT';
    if (error.message.includes('ECONNREFUSED')) return 'CONNECTION_ERROR';
    if (error.message.includes('404')) return 'NOT_FOUND';
    return 'UNKNOWN';
  }

  getExecutionLog() {
    return this.executionLog;
  }
}

// Usage in ReAct loop
const executor = new ToolExecutor({ defaultTimeout: 10000, maxRetries: 2 });

async function executeToolSafely(toolCall) {
  const result = await executor.execute(toolCall);
  
  if (!result.success) {
    // Log for debugging but don't crash
    console.error(⚠️  Tool failed: ${result.error});
    console.log(📋 Suggestion: ${result.fallbackMessage});
  }
  
  return result;
}

Lỗi 3: Infinite Loop / Non-terminating Agent

Mã lỗi: max_steps_exceeded, loop_detection

Nguyên nhân: Agent tiếp tục suy nghĩ mà không đưa ra kết luận, thường do prompt không rõ ràng hoặc thiếu termination condition.

Giải pháp:

/**
 * Loop Detection và Prevention System
 */
class LoopDetector {
  constructor(config = {
    maxIdenticalResponses: 3,
    maxSteps: 15,
    loopWindowSize: 5
  }) {
    this.config = config;
    this.responseHistory = [];
    this.stepCount = 0;
    this.terminationConditions = [];
  }

  /**
   * Check if agent is stuck in loop
   */
  analyze(assistantMessage) {
    this.stepCount++;
    this.responseHistory.push({
      step: this.stepCount,
      message: assistantMessage,
      timestamp: Date.now()
    });

    // Keep only recent history
    if (this.responseHistory.length > this.config.loopWindowSize * 2) {
      this.responseHistory = this.responseHistory.slice(-this.config.loopWindowSize);
    }

    const analysis = {
      isLooping: false,
      isTerminating: false,
      warnings: [],
      recommendations: []
    };

    // Check 1: Identical responses
    const identicalCount = this._countIdenticalResponses();
    if (identicalCount >= this.config.maxIdenticalResponses) {
      analysis.isLooping = true;
      analysis.warnings.push(Detected ${identicalCount} identical responses);
      analysis.recommendations.push('Force termination or change approach');
    }

    // Check 2: Semantic similarity
    const similarity = this._calculateSemanticSimilarity();
    if (similarity > 0.9) {
      analysis.warnings.push(High semantic similarity (${(similarity * 100).toFixed(1)}%));
      analysis.recommendations.push('Agent may be repeating same reasoning');
    }

    // Check 3: Termination conditions
    for (const condition of this.terminationConditions) {
      if (condition.check(assistantMessage)) {
        analysis.isTerminating = true;
        analysis.terminationReason = condition.reason;
        break;
      }
    }

    // Check 4: Max steps
    if (this.stepCount >= this.config.maxSteps) {
      analysis.isLooping = true;
      analysis.warnings.push(Max steps (${this.config.maxSteps}) reached);
      analysis.recommendations.push('Implement graceful termination');
    }

    return analysis;
  }

  _countIdenticalResponses() {
    if (this.responseHistory.length < 2) return 1;
    
    const last = this.responseHistory[this.responseHistory.length - 1].message;
    let count = 1;
    
    for (let i = this.responseHistory.length - 2; i >= 0; i--) {
      if (this.responseHistory[i].message === last) {
        count++;
      } else {
        break;
      }
    }
    
    return count;
  }

  _calculateSemanticSimilarity() {
    if (this.responseHistory.length < 2) return 0;
    
    const recent = this.responseHistory.slice(-this.config.loopWindowSize);
    const texts = recent.map(r => r.message.toLowerCase());
    
    // Simple word overlap similarity
    const sets = texts.map(t => new Set(t.split(/\s+/)));
    const intersection = sets[0];