Back to blog

ECHO_loop: Engineering React Agent Loop for Minecraft AI

Deep dive into engineering React Agent Loop for Minecraft AI companion, including Electron desktop architecture, LangChain Agent orchestration, Mineflayer game interaction, and local memory system design.

#AI#React Agent#Minecraft#Electron#LangChain#TypeScript

Introduction

In the exploration path of AI companions, ECHO_loop represents a purely engineering-driven approach—adopting the React Agent Loop architecture to integrate LLM orchestration, game interaction, and memory management into an Electron desktop application.

Unlike ECHO_feeling's cognitive science architecture (seven-layer cognition, emotional system) and ECHO_CLAW's context-driven design, ECHO_loop's core is engineering practice first: using mature tech stacks and clear processes to achieve intelligent behavior.

Why React Agent Loop

Limitations of Traditional Approaches

In early Minecraft AI implementations, we tried two paths:

  1. Hard-coded Rule System: Define numerous conditional branches like "attack when seeing enemies", "run away when health is low". The problem is rigid behavior that cannot handle unexpected situations.
  2. Pure LLM Decision: Let LLM decide every step. Problems include slow response, high cost, and tendency to loop.

The Balance Point of React Agent

React Agent Loop (from the ReAct paper) provides a compromise:

Observe → Think → Act → Observe...

The key aspects of this loop:

  • Visible Thinking Steps: LLM's reasoning process is explicit, easy to debug and intervene
  • Interruptible Actions: Each action is an independent atomic operation, can adjust after failure
  • Accumulating Context: Results of each loop feed into the next iteration's input

System Architecture

Overall Architecture

┌─────────────────────────────────────────────────────────────┐
│                     Electron Desktop App                     │
├─────────────────────────────────────────────────────────────┤
│  Vue 3 Frontend                                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  Chat Panel                                          │   │
│  │  • User input                                        │   │
│  │  • AI response display                               │   │
│  │  • Action logs                                       │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  Memory Panel                                        │   │
│  │  • Episodic memory browser                           │   │
│  │  • Semantic memory retrieval                         │   │
│  │  • Status statistics                                 │   │
│  ├─────────────────────────────────────────────────────┤   │
│  │  Settings                                            │   │
│  │  • LLM config                                        │   │
│  │  • Game server config                                │   │
│  │  • Behavior parameters                               │   │
│  └─────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────┤
│                    IPC Communication Layer                   │
│  • Renderer ↔ Main process communication                    │
│  • Event-driven: onMessage, onAction, onMemoryUpdate         │
├─────────────────────────────────────────────────────────────┤
│                  Node.js Main Process                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │           LangChain Agent (Tool-Calling)             │   │
│  │  • ReAct loop implementation                         │   │
│  │  • Tool registration & invocation                    │   │
│  │  • Streaming output handling                         │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              Mineflayer Bot Controller               │   │
│  │  • Connect to Minecraft server                       │   │
│  │  • Listen to game events                             │   │
│  │  • Execute game operations                           │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │               Local Vector DB (sql.js)               │   │
│  │  • Episodic memory storage                           │   │
│  │  • Vector embedding & retrieval                      │   │
│  │  • Encrypted persistence                             │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Tech Stack Selection

ComponentTechnologyReason for Selection
RuntimeElectronCross-platform desktop app, local storage support
FrontendVue 3Reactive UI, component-based development
Agent FrameworkLangChainMature Tool-Calling Agent implementation
Game InteractionMineflayerNode.js implementation of Minecraft protocol
Local Databasesql.jsPure JavaScript SQLite, no external dependencies

Core Flow Implementation

Engineering ReAct Loop

LangChain's Tool-Calling Agent is essentially an engineering implementation of ReAct. We extended it:

// Core loop structure
interface ReActLoop {
  // 1. Observe: Collect game state
  observe(): GameState
 
  // 2. Think: LLM reasons next step
  think(state: GameState, context: ConversationHistory): Promise<Action[]>
 
  // 3. Act: Execute specific operations
  act(actions: Action[]): Promise<ActionResult[]>
 
  // 4. Reflect: Evaluate results, update memory
  reflect(results: ActionResult[]): void
}

Code Agent Mode

Traditional ReAct actions are single-step operations, but in complex environments like Minecraft, single-step operations are too slow. We introduced Code Agent Mode:

// AI-generated JavaScript code block
const generatedCode = `
// 1. Check for nearby wood
const nearbyTrees = bot.findBlocks({
  matching: mcData.blocksByName.log.id,
  maxDistance: 32
});
 
// 2. Move to nearest tree
if (nearbyTrees.length > 0) {
  await bot.pathfinder.goto(
    new GoalBlock(nearbyTrees[0].x, nearbyTrees[0].y, nearbyTrees[0].z)
  );
}
 
// 3. Dig wood
await bot.dig(bot.blockAt(nearbyTrees[0]));
`;
 
// Execute in sandbox
await sandbox.execute(generatedCode);

Advantages of this mode:

  • Batch Operations: Generate multiple steps at once, reduce LLM calls
  • Context Coherence: Complete logic within code block
  • Debuggable: Generated code can be viewed and modified

node:vm Sandbox Isolation

Security is key for Code Agent. We use node:vm to create isolated sandbox:

import { VM } from 'vm2';
 
const sandbox = new VM({
  timeout: 5000,          // 5 second timeout
  sandbox: {
    bot: botProxy,        // Proxy object, limit dangerous operations
    mcData: mcData,
    console: safeConsole  // Safe console, output to log
  }
});
 
// Pre-execution validation
function validateCode(code: string): boolean {
  // Disallow fs, process, require
  const forbidden = ['require', 'process', 'fs', 'child_process'];
  for (const keyword of forbidden) {
    if (code.includes(keyword)) return false;
  }
  return true;
}

Memory System Design

Three-Layer Memory Architecture

┌─────────────────────────────────────────┐
│          Working Memory                  │
│  Current task context, part of LLM input │
│  • Recent conversation (compressed)      │
│  • Current task state                    │
│  • Temporary variables                   │
├─────────────────────────────────────────┤
│          Episodic Memory                 │
│  Specific event records, persisted       │
│  • Game event logs                       │
│  • Conversation history                  │
│  • Task completion records               │
├─────────────────────────────────────────┤
│          Semantic Memory                 │
│  Abstract knowledge, vector retrieval    │
│  • Game knowledge (recipes, item props)  │
│  • Learned patterns                      │
│  • Player preferences                    │
└─────────────────────────────────────────┘

sql.js Local Storage

Why sql.js instead of SQLite native binding?

  1. No External Dependencies: Pure JavaScript implementation, easy to package
  2. Cross-platform Consistency: No dependency on system SQLite version
  3. Memory + Persistence: Supports pure memory operation and file persistence
import { Database } from 'sql.js';
 
// Initialize database
const db = new Database();
 
// Create table
db.run(`
  CREATE TABLE IF NOT EXISTS episodic_memory (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp INTEGER NOT NULL,
    event_type TEXT NOT NULL,
    content TEXT NOT NULL,
    embedding BLOB
  )
`);
 
// Encrypted persistence
function saveToDisk(db: Database, password: string): void {
  const data = db.export();
  const encrypted = encrypt(data, password);
  fs.writeFileSync('memory.db.enc', encrypted);
}

Mineflayer Game Interaction

Event Listening Mechanism

// Listen to game events
bot.on('chat', (username, message) => {
  if (username === bot.username) return;
 
  // Trigger Agent response
  agent.handleChatMessage(username, message);
});
 
bot.on('spawn', () => {
  agent.handleSpawn();
});
 
bot.on('health', () => {
  agent.handleHealthChange(bot.health, bot.food);
});
 
bot.on('death', () => {
  agent.handleDeath();
});

Operation Encapsulation

Wrap Mineflayer operations as Tools for LangChain Agent:

const tools = [
  new Tool({
    name: 'move_to',
    description: 'Move to specified position',
    parameters: {
      type: 'object',
      properties: {
        x: { type: 'number' },
        y: { type: 'number' },
        z: { type: 'number' }
      }
    },
    execute: async ({ x, y, z }) => {
      await bot.pathfinder.goto(new GoalBlock(x, y, z));
      return { success: true, position: bot.position };
    }
  }),
 
  new Tool({
    name: 'dig_block',
    description: 'Dig specified block',
    parameters: {
      type: 'object',
      properties: {
        position: { type: 'object' }
      }
    },
    execute: async ({ position }) => {
      const block = bot.blockAt(position);
      await bot.dig(block);
      return { success: true, blockType: block.name };
    }
  })
];

Comparison with Other ECHO Paths

FeatureECHO_feelingECHO_loopECHO_CLAW
Core ApproachCognitive Science ArchitectureReact Agent EngineeringContext + Skill Invocation
Decision SourceIntrinsic EmergenceLLM Reasoning + RulesContext-Driven
Knowledge ManagementCognitive Layer ManagementMemory SystemMemory Query
Emotional SystemPAD/OCC Dual EngineNone (extensible)None
Action MethodIntention Strength DrivenTool-CallingSkill Orchestration
Best ForDeep Immersive ExperienceEngineering PracticeUniversal Game Framework

Development Experience

Electron Packaging Pitfalls

  1. sql.js Loading Issue: sql.js needs to load wasm file, must handle correctly during packaging

    // In webpack config
    new CopyWebpackPlugin({
      patterns: [{ from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'sql-wasm.wasm' }]
    })
  2. Mineflayer Connection Issue: Network permissions differ between dev and production mode

LangChain Streaming Output

Streaming output is critical for user experience:

// Use LangChain's streaming callback
const agent = new ToolCallingAgent({
  llm,
  tools,
  stream: true
});
 
for await (const chunk of agent.stream(input)) {
  // Real-time send to frontend
  ipcRenderer.send('agent-stream', chunk);
}

Current Status & Outlook

Completed

  • ✅ Electron + Vue 3 framework setup
  • ✅ LangChain Agent basic implementation
  • ✅ Mineflayer connection and operation encapsulation
  • ✅ sql.js local memory storage
  • ✅ Code Agent mode and sandbox

In Progress

  • 🔄 Memory compression algorithm optimization
  • 🔄 Long-term memory vector retrieval
  • 🔄 Action log visualization

Future Plans

  • 🔜 Multi-Agent collaboration (multiple bots running simultaneously)
  • 🔜 Learning module (auto-learn from game Wiki)
  • 🔜 Architecture comparison experiments with ECHO_feeling/ECHO_CLAW

Related Links


Last updated: 2026-05-13