文章总结: 本文通过56行代码复现了ClaudeCode的核心工作机制,揭示其本质是由两层while循环构成:外层处理用户输入交互,内层循环调用LLM并执行文件操作工具(读文件、列目录、写文件)。文章指出这是大多数代码助手Agent的基础架构,虽简化但完整展示了AI代理通过工具调用自主完成编码任务的原理。 综合评分: 85 文章分类: 安全开发,AI安全,安全工具,实战经验,技术标准
56 行代码,两个 Loop 的 Claude Code 工作原理的详细分析
原创
王建硕 王建硕
王建硕
2026年6月10日 11:19 上海
在小说阅读器读本章
去阅读
Claude Code 虽然很复杂(50 万行),但是却非常精巧。从本质上来说,就是两层 while(true) {} 的循环,组织起来的读写查文件三个工具调用。我用了 56 行,复刻了一下 Claude Code 的最核心代码,虽然不能真的做到平替,但是写些简单的代码是可以的,最主要是理解 Claude Code(或者大多数 Agent)的工作原理。以下是全部代码,Github 源代码放在「查看原文」里面。
import OpenAI from "openai";import fs from "fs";import path from "path";import readline from "readline";const client = new OpenAI({ apiKey: process.env.MOONSHOT_API_KEY, baseURL: "https://api.moonshot.cn/v1" });const fn = (name, desc, props, req = []) => ({ type: "function", function: { name, description: desc, parameters: { type: "object", properties: props, required: req } },});const TOOLS = [ fn("read_file", "Read a file.", { path: { type: "string" } }, ["path"]), fn("list_files", "List files in a directory.", { path: { type: "string" } }), fn("edit_file", "Write content to a file.", { path: { type: "string" }, content: { type: "string" } }, ["path", "content"]),];function runTool(name, input) { try { if (name === "read_file") return fs.readFileSync(input.path, "utf8"); if (name === "list_files") return fs.readdirSync(input.path ?? ".").sort().join("\n"); if (name === "edit_file") { fs.mkdirSync(path.dirname(path.resolve(input.path)), { recursive: true }); fs.writeFileSync(input.path, input.content); return `Wrote ${input.content.length} bytes to ${input.path}`; } } catch (e) { return `Error: ${e.message}`; }}async function agentLoop(userMessage, history) { history.push({ role: "user", content: userMessage }); while (true) { const { choices } = await client.chat.completions.create({ model: "moonshot-v1-8k", tools: TOOLS, tool_choice: "auto", messages: history }); const msg = choices[0].message; history.push(msg); if (msg.content) console.log(`\nAssistant: ${msg.content}`); if (!msg.tool_calls?.length) return; for (const tc of msg.tool_calls) { const input = JSON.parse(tc.function.arguments); console.log(` → ${tc.function.name}(${tc.function.arguments})`); const out = runTool(tc.function.name, input); console.log(` ← ${String(out).slice(0, 120)}`); history.push({ role: "tool", tool_call_id: tc.id, content: out }); } }}const rl = readline.createInterface({ input: process.stdin, output: process.stdout });const ask = q => new Promise(r => rl.question(q, r));const history = [{ role: "system", content: "You are a coding assistant. Use tools to read, list, and edit files — never guess file contents. Always call a tool when the task involves the filesystem. Always output code into filesystem" }];console.log("Mini Code Assistant (Kimi) — press Ctrl+C to exit\n");while (true) { const msg = (await ask("You: ")).trim(); if (msg) await agentLoop(msg, history);}
两层循环
最外层,就是最后四句,收到一个用户输入,把它和历史一起开启一个内部的小循环。
while (true) { const msg = (await ask("You: ")).trim(); if (msg) await agentLoop(msg, history);}
这是大循环。
这是那个内部的小循环:
while (true) { const { choices } = await client.chat.completions.create({ model: "moonshot-v1-8k", tools: TOOLS, tool_choice: "auto", messages: history }); const msg = choices[0].message; history.push(msg); if (msg.content) console.log(`\nAssistant: ${msg.content}`); if (!msg.tool_calls?.length) return; for (const tc of msg.tool_calls) { const input = JSON.parse(tc.function.arguments); console.log(` → ${tc.function.name}(${tc.function.arguments})`); const out = runTool(tc.function.name, input); console.log(` ← ${String(out).slice(0, 120)}`); history.push({ role: "tool", tool_call_id: tc.id, content: out }); }}
两个都是死循环。
前一个大循环(User Loop),只有当用户输入 Ctrl+C 强行中断的时候跳出,第二个小循环,只有当 tool_calls.length == 0 的时候跳出。也就是说,只有 LLM 回复要求的工具调用都调用完毕以后,这一轮交互才算真正结束。
最里面有一个很小的循环,就是如果 LLM 同时要调用多个工具,就一个一个按顺序调用本地的工具完成。
没了。就这么简单。主要代码完毕。
工具定义
上面就是两个函数定义了一下工具。一个是真正的定义,里面只定义了三个代码 Agent 至少要用的工具:一个是读文件,一个是读文件夹内容,一个是写文件。都是最简单的文件操作。当然 Claude Code 内置了四十几个工具,大家可以从泄漏的代码的 Tools 文件夹里面看到,每一个工具的定义不比我下面定义的复杂多少。
const fn = (name, desc, props, req = []) => ({ type: "function", function: { name, description: desc, parameters: { type: "object", properties: props, required: req } },});const TOOLS = [ fn("read_file", "Read a file.", { path: { type: "string" } }, ["path"]), fn("list_files", "List files in a directory.", { path: { type: "string" } }), fn("edit_file", "Write content to a file.", { path: { type: "string" }, content: { type: "string" } }, ["path", "content"]),];function runTool(name, input) { try { if (name === "read_file") return fs.readFileSync(input.path, "utf8"); if (name === "list_files") return fs.readdirSync(input.path ?? ".").sort().join("\n"); if (name === "edit_file") { fs.mkdirSync(path.dirname(path.resolve(input.path)), { recursive: true }); fs.writeFileSync(input.path, input.content); return `Wrote ${input.content.length} bytes to ${input.path}`; } } catch (e) { return `Error: ${e.message}`; }}
在后面就是把这几个工具变成一个 JSON 数据传给 LLM 就好了。
最终生成的 JSON 格式就是这样,原封不动的给大模型就行:
[ { type: 'function', function: { name: 'read_file', description: 'Read a file.', parameters: [Object] } }, ...]
LLM 会根据上下文和你提供的工具,找到合适的工具,把最终的项目完成。
就是这简单的 56 行,是大多数 Coding Agent 的骨架。在这之上可以加很多更多的功能,比如 Skills 的支持等。
Claude Code 也好,Codex 也好,核心框架都是这两个 Loop。而「读」,「写」,「查目录」三个工具,也分别了普通聊天和代码工具。
Boris 很厉害,但也真的没有那么厉害。不厉害的是,这个底层真的很简单。厉害的是,他在 1 年前想明白了就用这两个简单的循环,和大语言模型可以在没有人工参与的情况下完成工作这个信念,从这里开始写出了 Claude Code。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:王建硕 王建硕 王建硕《56 行代码,两个 Loop 的 Claude Code 工作原理的详细分析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论