当你看到 Claude Code 在终端里行云流水地读代码、改文件、跑命令,是不是也想拥有一个属于自己的版本?本文从架构拆解到核心代码实现,带你一步步构建自己的终端 AI 编程助手。

在这里插入图片描述

一、为什么想自己造一个?

2025 年以来,"终端 AI 编程助手"这个品类迎来爆发。Anthropic 的 Claude Code、OpenAI 的 Codex CLI、Google 的 Gemini CLI 相继登场,开源社区的 OpenCode、Aider、Goose 等项目也各自收获了数万 Star。这类工具有一个共同特征:运行在终端里,通过 Agent 循环自主调用工具,完成从读代码到写代码再到执行命令的完整工作流
对于开发者来说,使用现成工具当然方便,但自己造一个的动机同样充分:

  • 深度理解 Agent 架构。纸上得来终觉浅,只有自己实现一遍 ReAct 循环、工具调度、上下文管理,才能真正理解 AI Agent 的工程本质。
  • 定制能力。你可以接入私有模型、自定义工具链、适配公司内部的代码规范和发布流程。
  • 数据安全。所有代码和交互都在本地处理,不经过第三方服务器,这对企业场景至关重要。
  • 成本可控。使用自有 API Key 甚至本地部署开源模型,避免订阅费用。
    好消息是:这件事的门槛比你想象的低。Claude Code 的核心并不是什么黑科技,而是一套精心设计的工程实践。下面我们就来拆解它。

二、Claude Code 的架构到底长什么样?

通过对 Claude Code 开源代码的深入分析(参考 Dive-into-Claude-Code 项目的逆向工程),可以将其架构归纳为七层分离设计

┌─────────────────────────────────────────────┐
│ 1. UI 层(React + Ink 终端渲染)             │
├─────────────────────────────────────────────┤
│ 2. Hook 层(27 个生命周期钩子)              │
├─────────────────────────────────────────────┤
│ 3. 核心引擎(Agent Loop + 状态机)           │
├─────────────────────────────────────────────┤
│ 4. 工具层(54 个标准化工具)                 │
├─────────────────────────────────────────────┤
│ 5. 服务层(LLM API / MCP 协议)              │
├─────────────────────────────────────────────┤
│ 6. 基础设施(日志 / 缓存 / 存储)            │
├─────────────────────────────────────────────┤
│ 7. 运行时(Node.js / Bun)                   │
└─────────────────────────────────────────────┘

其中最关键的三个部分是:
Agent Loop(智能体循环):本质是一个 while 循环。每轮循环中,系统收集上下文、调用 LLM、解析返回的工具调用指令、执行工具、将结果追加到上下文,然后进入下一轮,直到 LLM 返回纯文本(不再调用工具)为止。用一句话概括就是:

Agent = While Loop + Tools
工具系统(Tool System):所有能力都被抽象为"工具"——读文件是工具、写文件是工具、执行 Shell 命令是工具、甚至搜索代码也是工具。每个工具有统一的接口定义,包含名称、描述、输入 Schema 和执行函数。Claude Code 最多支持 54 个工具,通过 Builder 模式声明式定义。
权限系统(Permission System):采用"拒绝优先"策略,设计了六层纵深防御。以执行 Shell 命令为例,一条命令需要经过语法树解析、语义检测、沙箱隔离、规则匹配、LLM 意图分类、最终人工确认这六道关卡。这确保了 AI 不会在未经授权的情况下执行危险操作。

三、自己实现一个需要哪些核心模块?

理解了 Claude Code 的架构之后,我们自己动手实现时需要关注以下五个核心模块:

3.1 Agent Loop:一切的发动机

Agent Loop 是整个系统的心脏。它的伪代码异常简单:

def agent_loop(user_message: str, context: ConversationContext):
    context.add_user_message(user_message)
    
    while True:
        # 1. 构建完整的消息列表(含系统提示词、历史、工具定义)
        messages = context.build_messages()
        
        # 2. 调用 LLM,流式获取响应
        response = llm.chat(messages=messages, tools=tool_registry.get_schemas())
        
        # 3. 如果 LLM 返回纯文本,退出循环
        if response.type == "text":
            print(response.content)
            break
            
        # 4. 如果 LLM 请求调用工具,逐个执行
        for tool_call in response.tool_calls:
            result = tool_registry.execute(tool_call.name, tool_call.arguments)
            context.add_tool_result(tool_call.id, result)
            
        # 循环继续,将工具结果送回 LLM

这个循环看似简单,但有几个关键设计决策需要注意:

  • 异步生成器模式:实际实现中应使用异步生成器(async generator),实现流式输出,让用户不必等待整个响应完成就能看到中间结果。
  • 最大循环次数:必须设置上限(如 50 轮),防止 LLM 陷入无限工具调用死循环。
  • 错误恢复:工具执行失败时,应将错误信息作为工具结果返回给 LLM,让它自行决定如何修复,而非直接中断循环。

3.2 工具系统:能力的基石

工具系统的设计原则是"一切皆工具,接口统一"。以下是一个最小化的工具注册与执行框架:

import json
import subprocess
from typing import Any, Callable
from dataclasses import dataclass, field
@dataclass
class Tool:
    """工具定义"""
    name: str
    description: str
    parameters: dict  # JSON Schema 格式
    handler: Callable[..., Any]
class ToolRegistry:
    """工具注册中心"""
    def __init__(self):
        self._tools: dict[str, Tool] = {}
    def register(self, tool: Tool):
        self._tools[tool.name] = tool
    def get_schemas(self) -> list[dict]:
        """生成 OpenAI / Anthropic 兼容的工具 Schema"""
        return [
            {
                "type": "function",
                "function": {
                    "name": t.name,
                    "description": t.description,
                    "parameters": t.parameters,
                },
            }
            for t in self._tools.values()
        ]
    def execute(self, name: str, arguments: dict) -> str:
        """执行工具并返回结果字符串"""
        tool = self._tools.get(name)
        if not tool:
            return f"Error: unknown tool '{name}'"
        try:
            result = tool.handler(**arguments)
            return str(result) if not isinstance(result, str) else result
        except Exception as e:
            return f"Error executing {name}: {e}"

有了注册中心之后,定义具体工具就变得非常直观。下面是四个最核心的内置工具实现(注意:我们在 run_command 中加入了基础安全防护机制):

import os
import re
# 基础黑名单:防止 LLM 幻觉产生破坏性操作(实际工程中需更严格的语法树解析与沙箱隔离)
DANGEROUS_PATTERNS = [
    r"rm\s+-rf\s+/",     # 递归删除根目录
    r"mkfs\.",           # 格式化磁盘
    r"dd\s+if=.*of=/dev/", # 覆盖块设备
    r":\(\)\{\s*:\|:&\s*\};:", # Fork 炸弹
    r">\s*/dev/sda",     # 清空磁盘设备
    r"shutdown",         # 关机
    r"reboot"            # 重启
]
def read_file(path: str, offset: int = 0, limit: int = 500) -> str:
    """读取文件内容,支持分页"""
    abs_path = os.path.abspath(path)
    if not os.path.isfile(abs_path):
        return f"File not found: {abs_path}"
    with open(abs_path, "r", encoding="utf-8", errors="replace") as f:
        lines = f.readlines()
    total = len(lines)
    selected = lines[offset : offset + limit]
    numbered = [f"{i+1:6d} {line}" for i, line in enumerate(selected, start=offset)]
    header = f"File: {abs_path} ({total} lines total, showing {offset+1}-{offset+len(selected)})\n"
    return header + "".join(numbered)
def write_file(path: str, content: str) -> str:
    """写入文件(整体覆盖)"""
    abs_path = os.path.abspath(path)
    os.makedirs(os.path.dirname(abs_path), exist_ok=True)
    with open(abs_path, "w", encoding="utf-8") as f:
        f.write(content)
    return f"Successfully wrote {len(content)} chars to {abs_path}"
def edit_file(path: str, old_text: str, new_text: str) -> str:
    """精确替换文件中的文本片段"""
    abs_path = os.path.abspath(path)
    with open(abs_path, "r", encoding="utf-8") as f:
        content = f.read()
    if old_text not in content:
        return f"Error: old_text not found in {abs_path}"
    new_content = content.replace(old_text, new_text, 1)
    with open(abs_path, "w", encoding="utf-8") as f:
        f.write(new_content)
    return f"Successfully edited {abs_path}"
def run_command(command: str, timeout: int = 30) -> str:
    """执行 Shell 命令并返回输出"""
    # 安全防线:拦截高危命令模式
    for pattern in DANGEROUS_PATTERNS:
        if re.search(pattern, command):
            return f"Error: Blocked dangerous command pattern '{pattern}'. Operation aborted for safety."
            
    try:
        result = subprocess.run(
            command, shell=True, capture_output=True, text=True, timeout=timeout,
        )
        output = ""
        if result.stdout:
            output += f"STDOUT:\n{result.stdout[-5000:]}"  # 截断过长输出
        if result.stderr:
            output += f"\nSTDERR:\n{result.stderr[-2000:]}"
        output += f"\nExit code: {result.returncode}"
        return output
    except subprocess.TimeoutExpired:
        return f"Command timed out after {timeout}s"

将这四个工具注册到 ToolRegistry 中,你就拥有了一个能读、能写、能改、能(相对安全地)执行命令的最小编程助手。

3.3 上下文管理:有限窗口的无限智慧

上下文管理是 Agent 系统中最容易被低估、却又最影响体验的部分。LLM 的上下文窗口是有限的(即便是 200K token 的模型,面对大型代码库也远远不够),因此必须设计分级的上下文压缩策略。
Claude Code 采用的是三级压缩策略

  • Level 1:逐轮微压缩。每轮自动执行,用占位符替换冗长的工具输出(如大文件的中间部分),保留首尾关键信息。
  • Level 2:阈值触发摘要。当 token 使用量达到窗口的 80% 时,自动调用 LLM 对早期对话生成摘要,保留核心决策和文件引用。
  • Level 3:手动干预兜底。用户主动输入 /compact 命令,触发强制压缩。
    下面是一个简化版的上下文管理器实现(已补全 add_assistant_message 方法,并优化了 LLM 摘要逻辑):
import tiktoken
class ConversationContext:
    """对话上下文管理器"""
    def __init__(self, system_prompt: str, max_tokens: int = 128_000):
        self.system_prompt = system_prompt
        self.max_tokens = max_tokens
        self.messages: list[dict] = []
        self._encoder = tiktoken.encoding_for_model("gpt-4o")
    def add_user_message(self, content: str):
        self.messages.append({"role": "user", "content": content})
    def add_assistant_message(self, content: str, tool_calls: list | None = None):
        """记录助手的回复及工具调用意图"""
        msg = {"role": "assistant", "content": content}
        if tool_calls:
            msg["tool_calls"] = tool_calls
        self.messages.append(msg)
    def add_tool_result(self, tool_call_id: str, result: str):
        # Level 1 微压缩:截断超长工具输出
        max_result_tokens = 4000
        if len(self._encoder.encode(result)) > max_result_tokens:
            result = self._truncate_middle(result, max_result_tokens)
        self.messages.append({
            "role": "tool",
            "tool_call_id": tool_call_id,
            "content": result,
        })
    def build_messages(self, llm_provider=None) -> list[dict]:
        # Level 2 检查:token 使用率超 80% 时触发摘要压缩
        current_tokens = self._count_tokens()
        if current_tokens > self.max_tokens * 0.8:
            self._compact_early_messages(llm_provider)
        return [
            {"role": "system", "content": self.system_prompt},
            *self.messages,
        ]
    def _count_tokens(self) -> int:
        all_text = self.system_prompt + "".join(
            m.get("content", "") for m in self.messages
        )
        return len(self._encoder.encode(all_text))
    def _truncate_middle(self, text: str, max_tokens: int) -> str:
        """保留首尾,压缩中间部分"""
        lines = text.split("\n")
        if len(lines) <= 20:
            return text
        keep_head = lines[:10]
        keep_tail = lines[-10:]
        omitted = len(lines) - 20
        return "\n".join(keep_head) + f"\n... [{omitted} lines omitted] ...\n" + "\n".join(keep_tail)
    def _compact_early_messages(self, llm_provider=None):
        """压缩早期消息为摘要"""
        if len(self.messages) < 10:
            return
        # 保留最近 5 条消息,将前面的工具交互提取出来
        early = self.messages[:-5]
        recent = self.messages[-5:]
        
        summary_parts = []
        for msg in early:
            if msg["role"] == "user":
                summary_parts.append(f"User asked: {msg['content'][:200]}")
            elif msg["role"] == "assistant":
                summary_parts.append(f"Assistant replied: {msg.get('content', '')[:200]}")
            elif msg["role"] == "tool":
                summary_parts.append(f"Tool result: {msg['content'][:100]}")
        # 如果传入了 llm_provider,则调用 LLM 生成更高质量的自然语言摘要
        # 实际工程中必须这么做以保留语义逻辑,为保持示例独立性,此处做了兼容处理
        if llm_provider:
            prompt = "请将以下历史对话精简提炼为一段摘要,保留关键决策和文件路径:\n" + "\n".join(summary_parts)
            summary = llm_provider.chat([{"role": "user", "content": prompt}]).content
        else:
            # 退化方案:简单拼接截断
            summary = "\n".join(summary_parts)
        self.messages = [
            {"role": "system", "content": f"[Earlier conversation summary]\n{summary}"},
            *recent,
        ]

3.4 系统提示词:Agent 的灵魂

系统提示词决定了 AI 助手的行为方式。它不是简单的"你是一个编程助手",而是一份详尽的行为规范。Claude Code 的系统提示词据分析超过 1 万 token,涵盖了角色定义、工具使用规范、安全约束、输出格式等多个维度。
以下是一个经过实战验证的系统提示词框架:

SYSTEM_PROMPT = """你是一个专业的终端 AI 编程助手,运行在用户的工作目录中。
## 核心能力
你可以读取文件、写入文件、编辑文件、执行 Shell 命令,帮助用户完成编程任务。
## 工作流程
1. 收到任务后,先通过 read_file 和 run_command 了解项目结构和代码现状
2. 制定修改方案,必要时分步骤执行
3. 使用 edit_file 进行精确修改(优先于 write_file 的整体覆盖)
4. 修改完成后,运行测试或 lint 命令验证结果
## 工具使用规范
- 每次只调用 1-2 个工具,避免一次性发起大量并行调用
- 优先使用 edit_file 而非 write_file,减少对文件的破坏性修改
- 执行命令前先确认当前工作目录
- 如果命令执行失败,分析错误原因后再重试
## 安全约束
- 不得执行 rm -rf、format、del 等破坏性命令
- 不得修改 .env、credentials 等敏感文件
- 不得向外部网络发送请求(除非用户明确要求)
- 遇到不确定的操作,先询问用户
## 输出格式
- 直接给出可执行的操作,减少冗余解释
- 代码修改时说明改了什么、为什么改
- 出错时先给出诊断,再给出修复方案
"""

3.5 多模型适配层:不绑死一家 API

一个实用的终端编程助手应该支持多种 LLM 后端。无论是 OpenAI、Anthropic、Google,还是国内的大模型 API,甚至是本地部署的 Ollama,都应该能通过统一接口切换。

from abc import ABC, abstractmethod
from dataclasses import dataclass
@dataclass
class LLMResponse:
    """统一的 LLM 响应格式"""
    content: str
    tool_calls: list | None = None
    finish_reason: str = "stop"  # "stop" | "tool_calls"
class LLMProvider(ABC):
    """LLM 提供者抽象基类"""
    @abstractmethod
    def chat(self, messages: list[dict], tools: list[dict] | None = None, stream: bool = False) -> LLMResponse:
        ...
class OpenAIProvider(LLMProvider):
    """OpenAI API 适配器"""
    def __init__(self, api_key: str, model: str = "gpt-4o"):
        from openai import OpenAI
        self.client = OpenAI(api_key=api_key)
        self.model = model
    def chat(self, messages, tools=None, stream=False) -> LLMResponse:
        kwargs = {"model": self.model, "messages": messages}
        if tools:
            kwargs["tools"] = tools
            kwargs["tool_choice"] = "auto"
        response = self.client.chat.completions.create(**kwargs)
        choice = response.choices[0]
        if choice.message.tool_calls:
            return LLMResponse(
                content=choice.message.content or "",
                tool_calls=[
                    {
                        "id": tc.id,
                        "name": tc.function.name,
                        "arguments": tc.function.arguments,
                    }
                    for tc in choice.message.tool_calls
                ],
                finish_reason="tool_calls",
            )
        return LLMResponse(content=choice.message.content or "")
class AnthropicProvider(LLMProvider):
    """Anthropic Claude API 适配器"""
    def __init__(self, api_key: str, model: str = "claude-sonnet-4-20250514"):
        from anthropic import Anthropic
        self.client = Anthropic(api_key=api_key)
        self.model = model
    def chat(self, messages, tools=None, stream=False) -> LLMResponse:
        # Anthropic API 的消息格式与 OpenAI 不同,需要适配
        system_msg = next((m["content"] for m in messages if m["role"] == "system"), "")
        user_msgs = [m for m in messages if m["role"] != "system"]
        
        kwargs = {
            "model": self.model,
            "max_tokens": 8192,
            "system": system_msg,
            "messages": user_msgs,
        }
        if tools:
            # 转换 tool schema 为 Anthropic 格式
            kwargs["tools"] = [
                {
                    "name": t["function"]["name"],
                    "description": t["function"]["description"],
                    "input_schema": t["function"]["parameters"],
                }
                for t in tools
            ]
        response = self.client.messages.create(**kwargs)
        tool_uses = [b for b in response.content if b.type == "tool_use"]
        text_blocks = [b for b in response.content if b.type == "text"]
        if tool_uses:
            return LLMResponse(
                content=" ".join(b.text for b in text_blocks),
                tool_calls=[
                    {"id": t.id, "name": t.name, "arguments": t.input}
                    for t in tool_uses
                ],
                finish_reason="tool_calls",
            )
        return LLMResponse(content=" ".join(b.text for b in text_blocks))

通过这种策略模式,用户可以在配置文件中一行切换模型后端,而上层的 Agent Loop 和工具系统完全不受影响。

四、把它们组装起来:一个完整的最小实现

将上面的所有模块整合到一起,就得到一个可运行的最小终端编程助手:

import json
import os
import sys
from rich.console import Console
from rich.markdown import Markdown
console = Console()
def create_agent(work_dir: str = "."):
    """创建并配置一个编程助手实例"""
    registry = ToolRegistry()
    # 注册四个核心工具,附带 JSON Schema 描述
    registry.register(Tool(
        name="read_file",
        description="读取指定路径的文件内容,支持分页",
        parameters={
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "文件路径"},
                "offset": {"type": "integer", "description": "起始行号", "default": 0},
                "limit": {"type": "integer", "description": "读取行数", "default": 500},
            },
            "required": ["path"],
        },
        handler=read_file,
    ))
    registry.register(Tool(
        name="write_file",
        description="将内容写入指定文件(覆盖写入)",
        parameters={
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "文件路径"},
                "content": {"type": "string", "description": "文件内容"},
            },
            "required": ["path", "content"],
        },
        handler=write_file,
    ))
    registry.register(Tool(
        name="edit_file",
        description="精确替换文件中的文本片段",
        parameters={
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "文件路径"},
                "old_text": {"type": "string", "description": "要被替换的原文"},
                "new_text": {"type": "string", "description": "替换后的新文本"},
            },
            "required": ["path", "old_text", "new_text"],
        },
        handler=edit_file,
    ))
    registry.register(Tool(
        name="run_command",
        description="执行 Shell 命令并返回输出",
        parameters={
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "Shell 命令"},
                "timeout": {"type": "integer", "description": "超时秒数", "default": 30},
            },
            "required": ["command"],
        },
        handler=run_command,
    ))
    # 根据环境变量选择 LLM 后端
    provider = OpenAIProvider(api_key=os.environ.get("OPENAI_API_KEY", ""))
    context = ConversationContext(system_prompt=SYSTEM_PROMPT)
    return provider, context, registry
def main():
    """主交互循环"""
    provider, context, registry = create_agent()
    max_turns = 50  # 防止无限循环
    console.print("[bold green]MyCodeAgent[/] ready. Type your task (Ctrl+C to exit):")
    while True:
        try:
            user_input = console.input("\n[bold cyan]> [/]").strip()
            if not user_input:
                continue
        except (KeyboardInterrupt, EOFError):
            console.print("\n[dim]Goodbye![/]")
            break
        context.add_user_message(user_input)
        turn = 0
        while turn < max_turns:
            turn += 1
            # 构建消息时传入 provider 用于上下文压缩的 LLM 摘要
            messages = context.build_messages(llm_provider=provider)
            response = provider.chat(messages, tools=registry.get_schemas())
            # 显示 AI 的文本输出
            if response.content:
                console.print(Markdown(response.content))
            # 如果没有工具调用,本轮结束
            if not response.tool_calls:
                break
            # 执行所有工具调用
            context.add_assistant_message(
                response.content, tool_calls=response.tool_calls
            )
            for tc in response.tool_calls:
                name = tc["name"]
                args = tc["arguments"]
                if isinstance(args, str):
                    args = json.loads(args)
                    
                console.print(f"  [dim]⚙ {name}({json.dumps(args, ensure_ascii=False)})[/]")
                result = registry.execute(name, args)
                context.add_tool_result(tc["id"], result)
                
                # 显示工具输出的前 500 字符
                preview = result[:500] + "..." if len(result) > 500 else result
                console.print(f"  [dim]{preview}[/]")
if __name__ == "__main__":
    main()

运行效果大致如下:

MyCodeAgent ready. Type your task (Ctrl+C to exit):
> 帮我看看当前目录下有哪些 Python 文件,然后给每个文件加一个 UTF-8 编码声明
  ⚙ run_command({"command": "find . -name '*.py' -type f"})
  STDOUT:
  ./main.py
  ./utils.py
  Exit code: 0
  ⚙ read_file({"path": "./main.py"})
  ...
  ⚙ edit_file({"path": "./main.py", "old_text": "...", "new_text": "# -*- coding: utf-8 -*-\n..."})
  Successfully edited ./main.py
  ⚙ edit_file({"path": "./utils.py", ...})
  Successfully edited ./utils.py
已完成,为 main.py 和 utils.py 添加了 UTF-8 编码声明。

五、与开源方案的横向对比

自己从零实现固然能学到很多,但生产环境下建议结合成熟开源项目。下表对比了当前主流开源终端编程助手(数据截至 2025 年初):

项目 语言 Star (近似) 核心特色 适用场景
Aider Python 25K+ Git 深度集成,自动 commit,AST 级别代码理解 注重版本管理的团队
OpenHands Python 40K+ 浏览器操作 + 文件编辑 + CI 集成 端到端自动化开发
Goose Rust 10K+ 通用 Agent,支持 70+ MCP 扩展 不限于编程的通用自动化
OpenCode TypeScript 5K+ 双 Agent 模式(Plan + Build),LSP 集成 大型项目开发
Cline TypeScript 20K+ VS Code 深度集成,可中断的工具审批流 IDE 内重度依赖开发者
如果你希望快速获得一个可用的工具,直接使用 Aider 或 OpenHands 是最高效的选择。如果你希望深度定制或学习原理,自己实现一遍 Agent Loop 非常有价值。两者并不矛盾——你完全可以在理解原理的基础上,fork 一个开源项目做二次开发。

六、进阶优化方向

完成最小实现之后,以下方向可以进一步提升系统的实用性:

6.1 Diff 编辑替代整文件替换

write_file 会覆盖整个文件,对大型项目风险较高。更安全的做法是采用 diff-based 编辑——让 LLM 输出类似 git diff 的补丁格式,逐块应用修改。Aider 在这方面做得非常好,它使用 “search/replace block” 格式,兼顾了准确性和效率。

6.2 子 Agent 与并行执行

当任务复杂时,主 Agent 可以将子任务委派给独立的子 Agent 执行。子 Agent 拥有独立的上下文窗口,避免主上下文被无关信息污染。Claude Code 的 SubAgent 机制正是这样设计的——子 Agent 在隔离环境中运行,完成后将精简结果返回给主 Agent。

6.3 MCP 协议支持

Model Context Protocol(MCP) 是 Anthropic 提出的工具扩展标准。支持 MCP 后,你的 Agent 可以动态加载外部工具服务器,获得数据库查询、API 调用、浏览器操作等无限扩展能力,而无需修改核心代码。

6.4 持久化与记忆

当前实现每次启动都是"无记忆"状态。可以通过以下方式增加记忆能力:将项目结构摘要保存到本地文件,启动时自动加载;对话历史持久化到 SQLite,支持搜索和恢复;使用 CLAUDE.md 或 MEMORY.md 风格的 Markdown 文件作为项目级记忆。

6.5 安全沙箱

虽然我们在 run_command 中加入了正则黑名单,但对于执行 Shell 命令这类高风险操作,这仍显不足。更稳妥的方案是在 Docker 容器或系统级沙箱(如 Firejail 或 macOS Seatbelt)中运行。OpenAI 的 Codex CLI 默认在隔离沙箱中执行命令,这是一个值得借鉴的安全实践。

七、总结

回到标题的问题:我可以自己开发一个自己的"Claude Code"吗?
答案是肯定的,而且核心工作量远比你想象的小。一个能用的最小实现大约 500 行 Python 代码,核心就是三件事:

  1. Agent Loop:一个 while 循环,不断在"调用 LLM → 解析工具调用 → 执行工具 → 返回结果"之间往复。
  2. Tool System:统一的工具注册与执行接口,让 LLM 通过标准化的 Schema 调用外部能力。
  3. Context Management:智能管理有限的上下文窗口,确保关键信息不被丢弃。
    从这个最小实现出发,你可以逐步加入权限控制、多模型适配、子 Agent、MCP 扩展等高级特性。更重要的是,在这个过程中你会深刻理解 AI Agent 的工程本质——它不是魔法,而是精心设计的工程实践。

项目依赖清单(最小实现所需):
openai(或 anthropic)、tiktokenrich。安装命令:pip install openai tiktoken rich
如果这篇文章对你有帮助,欢迎点赞、收藏、关注。有问题欢迎评论区交流!


参考资源

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐