C# 实现简版 Claude Code | Bash 就是一切(1)
❝该系列文章基于
github.com/shareAI-lab/learn-claude-code写就,该仓库以大道至简的风格剖析了Claude Code的核心原理,值得大家学习。由于该仓库是基于Python语言,为方便.NET开发者学习,我已经将代码基于.NET 10的dotnet file重写,源码已上传至github,源码地址见文末。

v0: Bash 就是一切 - 最简 Agent 的哲学
❝本文是 Learn Claude Code (C# 版) 系列的第一篇,对应代码文件
v0_bash_agent.cs。
核心问题
构建了各种复杂的 Agent 系统后,我们追问一个根本问题:
Agent 的本质是什么?
答案令人惊讶地简单:一个工具 + 一个循环 = 完整的 Agent 能力。
为什么 Bash 就够了?
Unix 哲学说"一切皆文件,一切皆可管道"。Bash 是通往这个世界的大门:
|
你需要 |
Bash 命令 |
|---|---|
|
读取文件 |
cat, |
|
写入文件 |
echo '...' > file
, |
|
搜索 |
find, |
|
执行 |
python, |
| 子代理 | dotnet run v0_bash_agent.cs "task" |
最后一行是关键洞察:通过 bash 调用自身实现子代理!
代码解析
唯一的工具定义
var bashTool = new Tool
{
Name = "bash",
Description = """
执行 shell 命令。常用模式:
- 读取: cat/head/tail, grep/find/rg/ls, wc -l
- 写入: echo 'content' > file, sed -i 's/old/new/g' file
- 子代理: dotnet run v0_bash_agent.cs '任务描述'
""",
InputSchema = new InputSchema
{
Type = "object",
Properties = new Dictionary<string, JsonElement>
{
["command"] = JsonDocument.Parse("""{"type": "string"}""").RootElement
},
Required = ["command"]
}
};
工具定义就是告诉模型"你可以做什么"。注意 Description 中包含了使用模式——这是在教模型如何使用工具。
核心 Agent 循环
async Task<string> ChatAsync(string prompt, List<Message>? history = null)
{
history ??= [];
history.Add(prompt.AsUserMessage());
while (true)
{
// 1. 调用模型
var response = await client.CreateMessageAsync(modelId, history, tools);
// 2. 添加助手消息到历史
history.Add(response.AsRequestMessage());
// 3. 如果没有工具调用,完成
if (response.StopReason != StopReason.ToolUse)
return ExtractText(response);
// 4. 执行工具并添加结果
var results = await ExecuteToolsAsync(response);
history.Add(results);
}
}
这就是所有 Agent 的核心模式:
-
调用模型 - 带上历史消息和可用工具
-
检查是否需要工具 -
StopReason.ToolUse表示模型想调用工具 -
执行工具 - 运行模型请求的命令
-
继续循环 - 把结果反馈给模型,直到它认为任务完成
子代理机制
v0 最精妙的设计是通过 bash 实现子代理:
主 Agent
|-- bash: dotnet run v0_bash_agent.cs "分析架构"
|-- 子 Agent (隔离进程,干净历史)
|-- bash: find . -name "*.cs"
|-- bash: cat src/Program.cs
|-- 通过 stdout 返回摘要
进程隔离 = 上下文隔离
子进程有自己的消息历史,不会污染父 Agent 的上下文。这是一个简单但强大的设计。
系统提示的艺术
var systemPrompt = $"""
你是一个位于 {workDir} 的 CLI agent。使用 bash 命令解决问题。
规则:
- 行动优先,简短解释。
- 读取文件: cat, grep, find, rg, ls, head, tail
- 写入文件: echo '...' > file, sed -i, 或 cat << 'EOF' > file
- 子代理: 对于复杂子任务,生成子代理:
dotnet run v0_bash_agent.cs "探索 src/ 并总结架构"
何时使用子代理:
- 任务需要读取很多文件(隔离探索过程)
- 任务独立且自包含
- 你想避免用中间细节污染当前对话
""";
系统提示做了几件事:
-
定义身份 - "你是一个 CLI agent"
-
提供上下文 - 当前工作目录
-
教授模式 - 怎么读文件、写文件
-
引导策略 - 何时使用子代理
关键洞察
1. 极简主义的力量
v0 证明了一个反直觉的观点:一个工具可以做一切。
不需要专门的 read_file、write_file 工具。cat、echo 就够了。但这种极简主义有代价——模型需要更多 tokens 来"思考"怎么用 bash 完成任务。
2. 模型即决策者
代码只做两件事:
-
提供工具(bash)
-
运行循环
所有决策都由模型做:调用什么命令、以什么顺序、何时停止。
3. 递归的优雅
子代理不是新概念,只是递归。v0_bash_agent.cs 调用自己,就像函数调用自己一样自然。
运行示例
# 交互式模式
dotnet run v0_bash_agent.cs
>> 列出当前目录的 cs 文件
$ ls *.cs
v0_bash_agent.cs
v1_basic_agent.cs
...
>> 分析 v0_bash_agent.cs 的结构
$ head -50 v0_bash_agent.cs
...
# 子代理模式
dotnet run v0_bash_agent.cs "探索 src/ 并总结架构"
从 v0 到 v1
v0 证明了最简可行性,但在实际使用中有局限:
-
效率 - 用 bash 读文件比专用工具慢(需要更多 tokens)
-
安全 - 没有路径检查,可能逃逸工作目录
-
可读性 -
echo '...' > file不如write_file直观
v1 将引入专用工具(read_file、write_file、edit_file),在保持核心循环不变的前提下,优化这些方面。
总结
v0 的哲学:
❝Bash 就是一切。一个工具 + 一个循环 = 完整的 Agent。
这不是最实用的设计,但它揭示了 Agent 的本质: 模型是 80%,代码是 20% 。
更多推荐


所有评论(0)