跪拜 Guibai
← Back to the summary

Cursor's Agent Loop Is Just 30 Lines of Code

Stop being superstitious about black boxes! A hands-on guide to building an AI programming assistant that can automatically create React projects.

Introduction: Can AI Programming Assistants Really "Think"?

When you tell Cursor, "Help me create a TodoList application," and it swiftly creates files, installs dependencies, and starts a service — is this magic or code?

The answer is: a carefully designed Agent loop + a few core tool functions.

Today, we're not playing games. We'll directly hand-write a mini-cursor. Using fewer than 300 lines of code, we'll replicate the core capabilities of an AI programming assistant. After reading this, you will thoroughly see through:


1. Architecture Panorama: How the AI's "Brain" and "Limbs" Cooperate

┌──────────────────────────────────────────────────────┐
│              mini-cursor Core Architecture           │
│                                                    │
│  ┌────────────────────────────────────────────┐    │
│  │         Agent Main Loop (mini-cursor.mjs)  │    │
│  │   ┌─────────────────────────────────┐     │    │
│  │   │  ReAct Loop:                     │     │    │
│  │   │  Think → Act → Observe          │     │    │
│  │   │  (Max 30 iterations)            │     │    │
│  │   └─────────────────────────────────┘     │    │
│  └────────────────────────────────────────────┘    │
│                      │                             │
│                      ▼                             │
│  ┌────────────────────────────────────────────┐    │
│  │        Tool Layer (all-tools.mjs)          │    │
│  │  ┌──────┐ ┌──────┐ ┌──────┐ ┌────────┐  │    │
│  │  │Read  │ │Write │ │List  │ │Execute │  │    │
│  │  │File  │ │File  │ │Dir   │ │Command │  │    │
│  │  └──────┘ └──────┘ └──────┘ └────────┘  │    │
│  └────────────────────────────────────────────┘    │
│                      │                             │
│                      ▼                             │
│  ┌────────────────────────────────────────────┐    │
│  │       Node.js Child Process (node-exec.mjs)│    │
│  │    Isolated execution of time-consuming    │    │
│  │    commands like pnpm / vite               │    │
│  └────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────┘

One-sentence summary: The large model is responsible for "thinking," the tools for "doing," and the child process for "running."


2. Deep Dive into the Tool Layer: How the AI's "Limbs" Are Forged

All tools are created using LangChain's tool() function. This function does three things:

  1. Receives an asynchronous execution function
  2. Receives tool metadata (name, description, parameter Schema)
  3. Returns a standard tool object callable by the AI

🔧 Read File Tool — The AI's "Eyes"

const readFileTool = tool(
    async({ filePath }) => {
        const content = await fs.readFile(filePath, 'utf-8');
        console.log(`[Tool Call] Read ${filePath}, ${content.length} characters total`);
        return content;
    },
    {
        name: 'read_file',
        description: 'Read file content, used for viewing code, configuration files, etc.',
        schema: z.object({
            filePath: z.string().describe('File path, supports relative or absolute paths')
        })
    }
);

Design Points:

✍️ Write File Tool — The AI's "Hand"

const writeFileTool = tool(
    async({ filePath, content }) => {
        try {
            const dir = path.dirname(filePath);
            await fs.mkdir(dir, { recursive: true });  // Auto-create parent directories
            await fs.writeFile(filePath, content, 'utf-8');
            return `✅ Successfully wrote to ${filePath}`;
        } catch(err) {
            return `❌ Write failed: ${err.message}`;
        }
    },
    {
        name: 'write_file',
        description: 'Write file content. Creates the file if it does not exist, auto-creates parent directories if they do not exist.',
        schema: z.object({
            filePath: z.string().describe('Target file path'),
            content: z.string().describe('Content to write')
        })
    }
);

Engineering Wisdom:

📂 List Directory Tool — The AI's "Navigation"

const listDirectoryTool = tool(
    async ({ directoryPath }) => {
        const files = await fs.readdir(directoryPath);
        return files.map(f => `📄 ${f}`).join('\n');
    },
    {
        name: 'list_directory',
        description: 'List directory contents to help the AI understand the project structure.',
        schema: z.object({
            directoryPath: z.string().describe('Directory path')
        })
    }
);

⚡ Execute Command Tool — The AI's "Superpower"

This is the most complex tool and the key to the AI actually "doing work":

const executeCommandTool = tool(
    async ({ command, workingDirectory }) => {
        const cwd = workingDirectory || process.cwd();
        return new Promise((resolve) => {
            const [cmd, ...args] = command.split(' ');
            const child = spawn(cmd, args, {
                cwd,
                stdio: 'inherit',  // Real-time output to terminal
                shell: true,
            });

            let errorMsg = '';
            child.on('error', (err) => { errorMsg = err.message });
            child.on('close', (code) => {
                if (code === 0) {
                    resolve(`✅ Command executed successfully: ${command}`);
                } else {
                    resolve(`❌ Command failed (exit code: ${code}): ${errorMsg}`);
                }
            });
        });
    },
    {
        name: 'execute_command',
        description: 'Execute a system command, supports specifying a working directory.',
        schema: z.object({
            command: z.string().describe('The command to execute'),
            workingDirectory: z.string().describe('Working directory')
        })
    }
);

Key Design Decisions:


3. Agent Main Loop: How the AI's "Brain" Makes Decisions

3.1 Binding Tools to the Model

const model = new ChatOpenAI({
    modelName: 'deepseek-v4-pro',
    temperature: 0,
});

const modelWithTools = model.bindTools([
    readFileTool,
    writeFileTool,
    listDirectoryTool,
    executeCommandTool
]);

bindTools() is the core magic of LangChain:

3.2 System Prompt — Setting Rules for the AI

const messages = [
    new SystemMessage(`
        You are a project management assistant. Available tools:
        1. read_file: Read files
        2. write_file: Write files
        3. execute_command: Execute commands (supports workingDirectory parameter)
        4. list_directory: List directories
        
        ⚠️ Important Rules:
        - When using execute_command, after specifying workingDirectory,
          do not use cd to switch directories again in the command
        - Correct: { command: "pnpm install", workingDirectory: "react-todo-app" }
        - Incorrect: { command: "cd react-todo-app && pnpm install", workingDirectory: "react-todo-app" }
    `),
    new HumanMessage(query)
];

The Art of Prompt Engineering:

3.3 The ReAct Loop — The Soul of the Agent

async function runAgentWithTools(query, maxIterations = 30) {
    const messages = [systemMessage, humanMessage];

    for (let i = 0; i < maxIterations; i++) {
        console.log(chalk.bgGreen(`🧠 Thinking iteration ${i}...`));

        // 1. THINK: AI reasoning
        const response = await modelWithTools.invoke(messages);
        messages.push(response);

        // 2. No tool calls → Task complete
        if (!response.tool_calls?.length) {
            console.log(chalk.green(`✅ ${response.content}`));
            return response.content;
        }

        // 3. ACT: Execute tools
        for (const toolCall of response.tool_calls) {
            const tool = tools.find(t => t.name === toolCall.name);
            if (tool) {
                // 4. OBSERVE: Record results
                const result = await tool.invoke(toolCall.args);
                messages.push(new ToolMessage({
                    content: result,
                    tool_call_id: toolCall.id
                }));
            }
        }
        // Return to step 1, continue the loop
    }
}

The Essence of the ReAct Loop:

User Query → 🤔 Think (AI decides which tool to use)
           → ⚡ Act  (Execute the tool)
           → 👀 Observe (Add the result to context)
           → 🤔 Think (Decide the next step based on the result)
           → ⚡ Act  ...
           → ✅ Task complete, output final answer

This process simulates the human problem-solving chain of thought: see a problem → think of a solution → take action → observe the result → adjust the plan → continue acting → finish.


4. Practical Task Execution: The Full Flow from Query to Completion

When we give the Agent this task:

const task = `
Create a React TodoList application:
1. Create the project using Vite
2. Implement full TodoList functionality
3. Add beautiful styles and animations
4. Install dependencies and start the service
`;

The Agent's decision trajectory looks like this:

🧠 Thinking iteration 0...
   🤔 "I need to create the project first, using the create-vite command"
   ⚡ execute_command({
        command: "pnpm create vite react-todo-app --template react-ts",
        workingDirectory: process.cwd()
      })
   👀 "✅ Project created successfully"

🧠 Thinking iteration 1...
   🤔 "Now I need to write the complete App.tsx code"
   ⚡ write_file({
        filePath: "react-todo-app/src/App.tsx",
        content: "Complete TodoList component code..."
      })
   👀 "✅ Successfully wrote 2847 bytes"

🧠 Thinking iteration 2...
   🤔 "Need to install dependencies"
   ⚡ execute_command({
        command: "pnpm install",
        workingDirectory: "react-todo-app"
      })
   👀 "✅ Dependencies installed, 342 packages added"

🧠 Thinking iteration 3...
   🤔 "Start the dev server"
   ⚡ execute_command({
        command: "pnpm run dev",
        workingDirectory: "react-todo-app"
      })
   👀 "✅ Service started at http://localhost:5173"

🧠 Thinking iteration 4...
   🤔 "All tasks complete, ready to report"
   💬 "🎉 TodoList application created! Visit http://localhost:5173 to view"

5. Child Process Isolation: Why Independent Execution is Needed

The Node.js main process is single-threaded. If pnpm install is executed in the main process, the entire Agent will be blocked. The solution:

// node-exec.mjs
const [cmd, ...args] = command.split(' ');
const child = spawn(cmd, args, {
    cwd,
    stdio: 'inherit',  // Display output in real-time
    shell: true,       // Execute using Shell
});

child.on('close', (code) => {
    if (code === 0) {
        console.log('✅ Child process executed successfully');
        process.exit(0);
    } else {
        console.log(`❌ Child process failed, exit code: ${code}`);
        process.exit(code);
    }
});

Why this design:


6. Complete Execution Log Display

$ node mini-cursor.mjs

🧠 Thinking iteration 0...
[Tool Call] execute_command(pnpm create vite react-todo-app --template react-ts)
Working directory: /Users/xxx/projects
✔ Project created successfully

🧠 Thinking iteration 1...
[Tool Call] write_file(react-todo-app/src/App.tsx)
✅ Successfully wrote 2847 bytes

🧠 Thinking iteration 2...
[Tool Call] execute_command(pnpm install)
Working directory: react-todo-app
✔ Dependencies installed (342 packages)

🧠 Thinking iteration 3...
[Tool Call] execute_command(pnpm run dev)
Working directory: react-todo-app
  ➜  Local:   http://localhost:5173/

🧠 Thinking iteration 4...
✅ AI Final Reply:
🎉 TodoList application created successfully!
- Project directory: react-todo-app
- Access URL: http://localhost:5173
- Features: Add/Delete/Mark Complete/Category Filter/Data Persistence
- Styles: Gradient background + Card design + Transition animations

7. Summary of Core Design Patterns

Design Pattern Implementation Method Problem Solved
Tool Abstraction tool() + Zod Schema Allows AI to call any function
Message History Maintain a messages array Gives AI "memory" capability
ReAct Loop Think → Act → Observe Allows AI to complete tasks autonomously
Child Process Isolation child_process.spawn Prevents time-consuming commands from blocking the main thread
Error Feedback ToolMessage carries error information Allows AI to adjust strategy based on errors

8. Expansion and Optimization Directions

🚀 New Tools That Can Be Added Immediately

🧠 Decision Mechanisms That Can Be Optimized


Final Words: The Essence of AI Programming

By handwriting this mini-cursor, we discovered:

AI programming assistants are not mysterious; they are just a carefully designed tool-calling system.

The large model is responsible for "thinking" about what to do next, while the actual "execution" falls on the tool functions we write. This is why:

Once you grasp this core principle, you can:

  1. Customize your own AI programming assistant
  2. Add new capabilities to existing tools
  3. Understand the underlying logic of products like Claude Code and Copilot

Now, pick up your keyboard and create your own AI assistant! 🚀


💬 Discussion Topic: If you were to design an AI programming assistant, what "superpower" would you most want to give it? Feel free to share your ideas in the comments!