Bash is all you need:50行代码复现Claude Code内核

1 次阅读 0 点赞 0 评论 12 分钟原创开源项目

learn-claude-code项目用纯Bash+Python实现极简AI编码代理,v0仅50行代码完成Agent Loop;v4引入插件化Skill系统,支持PDF解析、代码审查等能力动态加载,架构堪比OS内核设计。

#ai-agent #claude #python #bash #skills-system #agent-architecture
Bash is all you need:50行代码复现Claude Code内核

Bash is all you need:50行代码复现Claude Code内核

各位后端老铁,有没有过这种体验:对着LangChain的RunnableWithFallbacks源码翻了三遍,还是搞不清invokestream的context传播路径?或者在写一个“读取文件→分析内容→生成PR描述”的小工具时,光是搭ToolNode+StateGraph就写了200行,结果模型连ls -la都执行错了?

这不是你菜——是当前Agent框架把“调用bash”这件事,包装得太厚了。

直到我点开 shareAI-lab/learn-claude-codev0_bash_agent.py,看到那50行代码,手抖着倒了杯咖啡。


架构设计:没有调度器的Agent,才是真·Agent

这个项目不叫 claude-agent-sdk,也不叫 anthropic-toolkit,它直白地叫 learn-claude-code——学的是Claude Code的行为范式,不是API调用姿势。

它的核心洞察只有一句:Model as Agent. That's the whole secret.

模型即代理,其余皆冗余。于是整个架构被压成三层:

  • 顶层model(messages, tools) —— 封装Anthropic SDK调用,返回带tool_usestop_reason="end_turn"的响应;
  • 中层execute(tool_calls) —— 仅做一件事:subprocess.run(cmd, shell=True, capture_output=True, text=True)
  • 底层/bin/bash —— 所有IO、FS、进程、管道、重定向,全部交给它。

没有中间件、没有事件总线、没有AgentExecutor抽象类。它甚至没用asyncio——因为Claude的tool-use响应延迟远大于subprocess.run()的开销。真正的低延迟,来自不做无谓抽象。


核心模块深挖:从v0到v4的进化图谱

v0:呼吸级Agent Loop(v0_bash_agent.py

这才是全文最硬核的50行。我们逐行拆解(已精简注释版):

python 复制代码
import os
import subprocess
from anthropic import Anthropic

client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

def execute(tool_calls):
    # 只处理bash命令,其他tool直接raise NotImplementedError
    for call in tool_calls:
        if call.name == "bash":
            result = subprocess.run(
                call.input["command"],  # 注意:这里直接取command字符串
                shell=True,
                capture_output=True,
                text=True,
                timeout=30
            )
            return {"type": "tool_result", "tool_use_id": call.id, "content": result.stdout[:2048] + ("...[truncated]" if len(result.stdout) > 2048 else "")}

messages = [{"role": "user", "content": "你是Claude Code,能执行bash命令。请列出当前目录所有.py文件"}]

tools = [{"name": "bash", "description": "Execute a bash command", "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}}}]

while True:
    response = client.messages.create(
        model=os.getenv("MODEL_ID", "claude-3-5-sonnet-20241022"),
        max_tokens=2048,
        messages=messages,
        tools=tools
    )
    
    # 关键分支:模型说“我干完了”,就终止循环
    if response.stop_reason != "tool_use":
        print("✅ Final answer:", response.content[0].text)
        break
    
    # 否则,执行工具并追加结果消息
    tool_result = execute(response.content[-1].input)  # 注意:Claude v3.5响应结构
    messages.append({"role": "user", "content": [tool_result]})

⚠️ 注意三个魔鬼细节:

  • subprocess.run(..., timeout=30) 显式设超时,防止tail -f /var/log/syslog卡死整个Agent;
  • stdout截断至2048字符,避免token爆炸——这是对LLM上下文窗口的真实敬畏;
  • messages.append(...) 直接追加结构化tool_result,而非拼接字符串,完全契合Claude的tool_result message schema。

v4:Skill系统——Agent界的OSGi Bundle

v4不再满足于“能跑bash”,而是要“懂领域”。它把PDF解析、代码审查、Git操作封装为独立skill

bash 复制代码
skills/
├── pdf-parser/
│   ├── spec.yaml          # OpenAPI-like接口定义
│   ├── impl.py           # class PDFParserSkill(Skill): def invoke(self, file_path: str) -> str:
│   └── examples/
├── code-review/
│   ├── spec.yaml
│   ├── impl.py
│   └── examples/
└── skill_loader.py       # 动态import + validate + register

spec.yaml示例(pdf-parser/spec.yaml):

yaml 复制代码
name: pdf-parser
version: 0.1.0
input_schema:
  type: object
  properties:
    file_path:
      type: string
      description: Absolute path to PDF file
output_schema:
  type: string
  description: Extracted plain text

SkillLoader加载逻辑极简:

python 复制代码
## skills/skill_loader.py
def load_skill(skill_dir: str) -> Skill:
    spec = yaml.safe_load((Path(skill_dir) / "spec.yaml").read_text())
    impl_module = importlib.import_module(f"{skill_dir.replace('/', '.')}.impl")
    skill_class = getattr(impl_module, f"{spec['name'].replace('-', '_').title()}Skill")
    return skill_class(**spec)  # 传入spec做runtime校验

这比Spring Boot的@ConditionalOnClass还轻量——没有classloader隔离,没有bean scope,只有importlibyaml.load两个原语。


实战演示:初始化一个带Git技能的Agent

先装环境:

bash 复制代码
## 安装依赖与配置密钥
git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env
## 编辑 .env 填入 ANTHROPIC_API_KEY

再初始化一个Level 1 Agent(含read_file, write_file, edit_file, bash四工具):

bash 复制代码
## 初始化新Agent项目(生成 my-agent/ 目录)
python skills/agent-builder/scripts/init_agent.py my-agent --level 1

## 查看生成结构
ls my-agent/
## → agent.py  config.yaml  prompts/  skills/

## 启动(自动加载skills/下的所有skill)
python my-agent/agent.py

此时Agent已具备:

  • 读写任意文件(read_file/write_file
  • 行级编辑(edit_file基于sed -i实现)
  • Git状态感知(skills/git-status/impl.py调用git status --porcelain

你可以直接问它:“把当前目录下所有.py文件的print(替换成logging.info(,然后提交git”——它会分步执行find, sed, git add, git commit,全程不碰一行prompt engineering。


踩坑指南:坦诚比炫技更珍贵

  • v0递归子Agent栈溢出:当模型反复调用bash执行python v0_bash_agent.py时,Python默认递归限制(1000)会被击穿。解法:在v0_bash_agent.py顶部加sys.setrecursionlimit(5000),或改用threading.Thread隔离子调用。
  • v3 Subagent context污染:多个Subagent共享同一messages list引用。修复只需一行:messages = copy.deepcopy(messages) before spawning new thread.
  • Claude v3.5 tool_use响应格式变更:v4已适配,但若你fork v0需注意:response.content[-1].input在v3.5中是response.content[-1].delta.tool_use.input,必须按response.model动态解析。

个人评价:一张留白的架构画布

这不是又一个“教你调API”的玩具项目。它是少有的、把Agent架构当作系统工程来设计的开源实践:

  • subprocess代替langchain.tools,是把信任交还给POSIX;
  • yaml+importlib代替pydantic.BaseModel动态注册,是拒绝过度工程化;
  • TodoManager显式管理待办(非隐式messages追加),是向K8s Job API学来的确定性。

如果我是技术负责人,我会把它作为团队Agent能力建设的基座:

  • TodoManager对接Argo Workflows,每个todo是一个K8s Job;
  • SkillLoader接入Nacos,实现技能热更新(watch /skills/**/spec.yaml);
  • execute()里注入OpenTelemetry trace,让每次bash调用都有span_id可追踪。

最后再念一遍那句让我锁屏壁纸换了三次的话:

Model as Agent. That's the whole secret.

真正的智能,永远诞生于清晰的边界与克制的代码。

最后更新:2026-02-04T10:01:23

评论 (0)

发表评论

blog.comments.form.loading
0/500
加载评论中...