Agent 核心就几十行代码,那剩下的几万行到底在解决什么问题?
作为一个开源 AI 编程助手 Blade Code 的作者,我来回答这个问题。
先说结论:核心循环确实只有几十行,但让它在真实场景下可靠运行,需要几万行代码来处理「现实世界的复杂性」。
Agent 的核心循环有多简单?
你说得对,Agent 的核心逻辑确实很简单。用伪代码表示大概是这样:
while True:
response = llm.chat(messages, tools)
if no_tool_calls(response):
return response.content # 任务完成
for tool_call in response.tool_calls:
result = execute_tool(tool_call)
messages.append(tool_result)
Agent 核心循环示意图
sequenceDiagram
participant Agent
participant LLM
participant Tools
loop Core Loop
Agent->>LLM: Chat(messages, tools)
LLM-->>Agent: Response
alt has tool_calls
Agent->>Tools: Execute Tool
Tools-->>Agent: Tool Result
Agent->>Agent: Append Result to Messages
else no tool_calls
Agent->>Agent: Return Content
end
end
就这么几行。调用 LLM,如果 LLM 想用工具就执行,把结果塞回去继续调用,直到 LLM 觉得任务完成了。
那问题来了:为什么 Claude Code、Cursor、还有我做的 Blade Code,代码量都是几万行起步?
剩下的几万行在解决什么?
我以 Blade Code 为例(7.5 万行 TypeScript),拆解一下这些代码都在干什么。
1. 上下文管理(Context Management)
问题:LLM 有上下文窗口限制,对话长了就会超。
解决方案:
- 对话历史压缩(Compaction)
- Token 计数和预警
- 智能截断策略
- 会话持久化(JSONL 存储)
Blade Code 里有一个 ContextManager,负责管理多层上下文:系统层、会话层、对话层、工具层、工作空间层。每一层都有自己的生命周期和压缩策略。
src/context/
├── ContextManager.ts # 统一管理所有上下文
├── CompactionService.ts # 压缩服务
├── TokenCounter.ts # Token 计数
└── storage/
├── JSONLStore.ts # JSONL 持久化
├── MemoryStore.ts # 内存存储
└── PersistentStore.ts # 持久化存储
上下文管理架构
graph TD
CM[ContextManager] --> Layers
CM --> Storage
subgraph Layers [Context Layers]
L1[System Layer]
L2[Session Layer]
L3[Conversation Layer]
L4[Tool Layer]
L5[Workspace Layer]
end
subgraph Storage [Storage & Processing]
S1[MemoryStore]
S2[PersistentStore JSONL]
S3[CacheStore]
P1[ContextCompressor]
P2[ContextFilter]
end
你提到的 uuid/parentUuid 线程机制,就是用来追踪对话链的。当需要压缩时,可以根据这个链条决定哪些消息可以合并、哪些必须保留。
2. 文件修改的历史快照(Snapshot)
问题:AI 改错文件怎么办?用户想回滚怎么办?
解决方案:每次文件修改前自动创建快照。
// src/tools/builtin/file/SnapshotManager.ts
export class SnapshotManager {
async createSnapshot(filePath: string, messageId: string): Promise<SnapshotMetadata> {
// 生成文件哈希
const fileHash = this.generateFileHash(filePath, version)
// 保存到 ~/.blade/file-history/{sessionId}/{fileHash}@v{version}
await fs.writeFile(snapshotPath, content)
// ...
}
async restoreSnapshot(filePath: string, messageId: string): Promise<void> {
// 恢复到指定版本
}
}
这不是什么高深的技术,但没有它,用户就没有「后悔药」。
3. 工具执行管道(Execution Pipeline)
问题:工具执行不是简单的「调用函数」,需要权限检查、用户确认、错误处理、结果格式化……
解决方案:7 阶段执行管道。
Discovery → Permission → Hook(Pre) → Confirmation → Execution → PostHook → Formatting
每个阶段都有自己的职责:
- Discovery:找到对应的工具定义
- Permission:检查权限(allow/ask/deny 列表)
- Hook(Pre):执行前置钩子(用户自定义逻辑)
- Confirmation:危险操作需要用户确认
- Execution:实际执行工具
- PostHook:执行后置钩子
- Formatting:格式化结果给 LLM 和用户
工具执行管道流程
flowchart LR
A[Discovery] --> B[Permission]
B --> C[Hook Pre]
C --> D{Confirmation}
D -- Approved --> E[Execution]
D -- Denied --> End[Abort]
E --> F[PostHook]
F --> G[Formatting]
G --> Result[Result]
为什么要这么复杂?因为你不能让 AI 随便 rm -rf /。
4. 子代理(Subagent)
问题:复杂任务需要分解,主 Agent 不能什么都干。
解决方案:支持子代理,每个子代理有自己的工具集和上下文。
src/agent/subagents/
├── SubagentExecutor.ts # 子代理执行器
├── SubagentRegistry.ts # 子代理注册表
├── AgentSessionStore.ts # 会话隔离
└── BackgroundAgentManager.ts # 后台任务管理
比如「搜索代码库」这种任务,可以交给专门的搜索子代理,它只有只读工具,不会误改文件。
5. 权限和安全
问题:AI 能执行 Shell 命令,能读写文件,能访问网络。这太危险了。
解决方案:多层安全机制。
- 权限模式:default / autoEdit / plan / yolo
- 工具白名单/黑名单
- 敏感文件检测(.env、私钥等)
- 路径安全检查(防止路径穿越)
- 文件锁(防止并发写入冲突)
// src/tools/validation/SensitiveFileDetector.ts
const SENSITIVE_PATTERNS = [
/\.env$/,
/\.pem$/,
/id_rsa$/,
/\.ssh\/config$/,
// ...
]
6. 多模型适配
问题:不同 LLM 的 API 格式不一样,流式响应处理方式不一样,Tool Calling 格式也不一样。
解决方案:统一的 ChatService 接口 + 多个实现。
src/services/
├── ChatServiceInterface.ts # 统一接口
├── AnthropicChatService.ts # Anthropic 实现
├── OpenAIChatService.ts # OpenAI 实现
├── GeminiChatService.ts # Google 实现
├── AzureOpenAIChatService.ts # Azure 实现
└── ...
每个实现都要处理:
- 流式响应的增量解析
- Tool Calling 的参数累积(流式时参数是分块的)
- 错误重试和降级
- Token 使用量统计
7. 用户界面
问题:终端里怎么显示 Markdown?怎么显示代码高亮?怎么显示 diff?
解决方案:React + Ink 的终端 UI。
src/ui/
├── components/
│ ├── MessageRenderer.tsx # 消息渲染
│ ├── CodeHighlighter.tsx # 代码高亮
│ ├── DiffRenderer.tsx # Diff 显示
│ ├── ThinkingBlock.tsx # 思考过程显示
│ └── ...
└── utils/
├── markdown.ts # Markdown 解析
└── markdownIncremental.ts # 增量 Markdown 渲染
流式响应时,Markdown 是一点一点来的,你不能等全部接收完再渲染,要增量渲染。这比想象中复杂得多。
8. MCP 和插件系统
问题:内置工具不够用,用户想扩展怎么办?
解决方案:支持 MCP(Model Context Protocol)和插件系统。
src/mcp/
├── McpClient.ts # MCP 客户端
├── McpRegistry.ts # MCP 服务注册
├── HealthMonitor.ts # 健康检查
└── auth/ # OAuth 认证
MCP 让 Agent 可以连接外部工具服务器,比如数据库、API、甚至其他 AI 服务。
9. 还有一堆「看不见」的东西
- 日志系统:调试时需要知道每一步发生了什么
- 配置管理:全局配置、项目配置、运行时配置的合并
- 版本检查:提醒用户更新
- 错误边界:某个组件崩了不能影响整体
- 优雅退出:Ctrl+C 时要清理资源
- 测试:单元测试、集成测试、E2E 测试
一个类比
Agent 核心循环就像汽车的发动机原理:「吸气-压缩-点火-排气」,四个步骤,小学生都能理解。
但真正造一辆能上路的汽车,你需要:
- 燃油系统(上下文管理)
- 刹车系统(权限控制)
- 安全气囊(错误处理)
- 仪表盘(用户界面)
- 变速箱(多模型适配)
- 导航系统(任务规划)
- 保养记录(日志和快照)
发动机原理是核心,但只有发动机,你哪儿也去不了。
最后
如果你对这些实现细节感兴趣,可以看看 Blade Code 的源码。它是开源的,代码结构还算清晰。
或者直接用起来:
npm install -g blade-code
blade
你会发现,当你真正用一个 Agent 来完成实际工作时,那些「额外的几万行代码」带来的体验差异是巨大的。
参考资料:
- Blade Code 源码:https://github.com/echoVic/blade-code
- Claude Code 系统提示逆向分析(多个社区讨论)
- MCP 协议规范:https://modelcontextprotocol.io/