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

Bash is all you need:50行代码复现Claude Code内核
各位后端老铁,有没有过这种体验:对着LangChain的RunnableWithFallbacks源码翻了三遍,还是搞不清invoke和stream的context传播路径?或者在写一个“读取文件→分析内容→生成PR描述”的小工具时,光是搭ToolNode+StateGraph就写了200行,结果模型连ls -la都执行错了?
这不是你菜——是当前Agent框架把“调用bash”这件事,包装得太厚了。
直到我点开 shareAI-lab/learn-claude-code 的 v0_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_use或stop_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_resultmessage 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,只有importlib和yaml.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共享同一
messageslist引用。修复只需一行: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.
真正的智能,永远诞生于清晰的边界与克制的代码。