TL;DR — 我们逆向了 Claude Code 的 TypeScript 源码,搞清楚了它的 Agent 架构如何处理安全、复杂任务和工具权限。然后把这些模式用到了一个开源项目上——让玩家在泰拉瑞亚游戏里跟 AI 聊天,AI 还能给道具、改天气、传送玩家。以下是我们的发现、实现过程和踩坑总结。
为什么要拆 Claude Code 的源码
Claude Code 不只是个编程助手。底层它是一个 Agent 运行时——会 spawn 子 Agent、管理文件权限、跑 bash 命令、判断什么时候该问用户什么时候该直接做。我们想搞清楚它的内部机制,然后把这些想法用到一个完全不同的场景:泰拉瑞亚游戏服务器。
我们的项目 terra_llm_bridge 把泰拉瑞亚 TShock 服务器接到了一个 LLM 上。玩家在聊天框打 @ai 就能跟 AI 对话——但 AI 不止能聊天,还能做事:给道具、改天气、传送玩家,甚至能切换困难模式。最后那条就是我们翻车的地方。
第一次有玩家让 AI 设成雨天,LLM 自作主张调了 terra_world_hardmode(confirm=True)——把整个服务器的世界不可逆地切成了困难模式。没人要求它这么做。模型自己觉得该做就做了。
我们需要一个真正的权限系统。于是去翻 Claude Code 的源码。
Claude Code 的 7 层权限架构
通读 src/utils/permissions/permissions.ts 的约 1500 行代码,加上 Agent 工具的基础设施(约 3800 行),一套清晰的架构浮现出来。Claude Code 不是靠单点检查做安全——它有七层:
Layer 1a: 拒绝规则 → "永远不允许 Bash(git push --force)"
Layer 1b: 询问规则 → "Bash(curl *) 总是弹窗确认"
Layer 1c: 工具自检 → 每个工具 checkPermissions() 自己的逻辑
Layer 1d: 工具自拒 → Read 工具白名单特定路径
Layer 1f: 内容规则 → "就算 bypass 模式,npm publish 也要弹窗"
Layer 1g: 安全检查 → ".git/、.claude/ 永远不能绕过用户确认"
Layer 2: 模式旁路 → bypassPermissions / auto / acceptEdits / dontAsk
Layer 3: YOLO 分类器 → AI 读全文 transcript,判断是否安全
最有意思的是 YOLO 分类器——一个独立的小模型,读取完整对话记录,把每次工具调用分类为安全或危险。两阶段系统:快速分类器处理明显 case,深度思考分类器处理边界情况。
但对我们最有用的不是 AI 分类器。而是 Claude Code 如何在结构上防止某些工具在错误的上下文中被调用——通过工具白名单、黑名单和子 Agent 特化。
Agent 模式:不是多 Agent 协作,而是专项 Worker
Claude Code 用的不是"Agent 之间协商谈判"的多 Agent 协作。它是一个主协调器 + 专项 Worker:
主 Agent(Tool Calling,全部工具)
│
├─ 简单: "读文件 X" → Read 工具
│
└─ 复杂: "审计这个分支" → Agent("Explore")
│
├─ 工具: [Read, Grep, Glob] ← 白名单
├─ 禁止: [Edit, Write] ← 黑名单
├─ 系统提示: "你是文件搜索专家"
└─ 返回结果 → 主 Agent 行动
每个子 Agent 类型由三要素定义:
- 工具权限(白名单 + 黑名单)——能碰什么
- 系统提示——角色专属指令
- 模型——Explore Agent 用 Haiku(便宜),Plan Agent 用 Sonnet(推理强)
核心洞察:主 Agent 不会变更复杂。它保持简单,只有一个 Agent 工具让它把复杂任务委派出去。子 Agent 就是另一个 Tool Calling 循环,只是工具受限 + 提示词不同。
这套架构的可组合性是关键:每个零件简单,但组合起来能处理单个 prompt 消化不了的复杂度。
我们怎么把这个模式用到 terra_llm_bridge
我们的泰拉瑞亚桥接比 Claude Code 简单——46 个工具而非几百个,“安全问题"是"别在玩家问天气时切 hardmode"而不是"别让 AI rm -rf /"。但模式是直接可以搬的。
问题
改之前:LLM 同时看到所有 46 个工具。当玩家问"给我最强套装”,LLM 会并行调 wiki_search 查资料 + give_item 给东西——一边查 wiki 一边已经预判了 Solar Flare 套装。有时候猜对,有时候给召唤师玩家塞了一套战士装备。
解决方案:两阶段工具开放
我们没有加子 Agent——46 个工具不需要。但我们在 graph 层面用了工具限制模式:
route → llm(研究) ⇄ tool → escalate → llm(行动) ⇄ authorize ⇄ tool → output
17 个只读工具 46 全工具 关键词 gate
wiki、lookup、状态 give、kick、spawn
图有两个阶段:
研究阶段——LLM 只拿到 17 个只读工具(wiki_search、item_lookup、player_list、world_info 等)。它不能调 give_item、kick、spawn 或任何破坏性工具。先查资料。
升级(escalate)——当 LLM 输出文本(没有更多 tool_call),图自动切到行动模式,注入提示:“你现在可以访问全部工具了。”
行动阶段——LLM 拿到全部 46 个工具,可以对研究发现做出行动。
这是结构层面强制执行的,不是 prompt 建议。LLM 在研究阶段根本调不了 give_item,因为这个工具没绑定。
权限 Gate
在两阶段拆分之前,我们还加了 authorize_node——LLM 和 ToolNode 之间的硬拦截,检查玩家聊天最近的消息是否包含该工具领域的关键词:
|
|
如果玩家说"设个雨天试试"而 LLM 想调 world_hardmode,authorize_node 检查:玩家最近的消息里有 hardmode 相关的关键词吗?没有?拦截。 这个工具调用被替换成 BLOCKED 消息,ToolNode 根本看不到。
这是粗过滤器——它检查的是玩家提到了什么,而不是玩家请求了什么。“上次打肉山的时候"会通过关键词检查,尽管玩家没要求开 hardmode。但粗够了:目标是拦截灾难性的跨界调用(天气 → hardmode),不是完美理解意图。
我们选择不做的
没有 YOLO 分类器
Claude Code 的 AI 分类器读完整 transcript,用另一个模型判断工具调用是否安全。我们没做,因为:
- 增加延迟——每次 gated 工具调用前多一次 LLM 请求
- 泰拉瑞亚聊天风险低——给错套装可以补救
- 关键词匹配已经能拦住灾难性 case
没有子 Agent 派生
Claude Code 为复杂任务 spawn 子进程。我们不需要:
- 泰拉瑞亚工具面小(46 个)
- 多轮工具调用已经能处理我们实际面对的场景
- 给游戏聊天机器人 spawn 子进程是过度工程
没有 ReAct 模式
经典的 Thought → Action → Observation 循环会增加 token 消耗,但不改变我们的核心能力。DeepSeek 的 thinking tokens 已经承担了推理,而两阶段工具访问比基于 prompt 的 ReAct 更可靠地强制了"先研究再行动”。
一张图看清架构
┌──────────────────────────────────────────────────────────┐
│ 泰拉瑞亚服务器(TShock + C# 插件,24 个游戏 Hook) │
│ 玩家输入 "@ai 给我最好的套装" │
└──────────────────────┬───────────────────────────────────┘
│ JSON webhook
┌──────────────────────▼───────────────────────────────────┐
│ Python aiohttp 监听器 (:9876) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────┐
│ LangGraph StateGraph │
│ │
│ route → llm(研究) ⇄ tool 17 只读工具 │
│ │ │
│ escalate → llm(行动) ⇄ authorize ⇄ tool │
│ 46 全工具 关键词拦截 │
│ │ │
│ output → 广播到游戏聊天 │
│ │
│ 记忆: AsyncSqliteSaver 按玩家(thread_id)持久化 │
└──────────────────────────────────────────────────────────┘
│
┌─────────────┴──────────────┐
▼ ▼
TShock REST API Terraria Wiki API
(give / kick / spawn) (terraria.wiki.gg)
源码分析的启示
读 Claude Code 源码教会我们三件事,适用于任何 Agent 项目:
1. 安全是分层的,不是二元的。 一个 confirm 参数对 LLM 来说只是软建议。真正的安全需要结构性约束——LLM 不该能调用它无权使用的工具,就像 Web 服务器不该让你访问没有权限的端点,不管你怎么礼貌地请求。
2. 工具限制是最便宜也最可靠的安全形式。 Claude Code 的 Explore Agent 之所以"只读",不是因为 prompt 写了——是因为 Edit 和 Write 不在它的工具列表里。我们的研究阶段之所以"先查资料",不是 prompt 建议——是因为 give_item 根本没绑定。你不能通过 prompt injection 绕过不存在的工具。
3. 特化胜过复杂化。 Claude Code 的子 Agent 不比主 Agent 更聪明——只是更受约束。更少的工具 + 聚焦的 prompt = 更可靠的行为。我们的两阶段系统同理:先限制,准备就绪再扩展。
关于这个项目
terra_llm_bridge 是一个连接泰拉瑞亚游戏服务器与 LLM 的开源项目。功能包括:
- 24 个游戏 Hook——自研 C# TShock 插件捕获聊天、Boss 击杀、死亡、登录等 24 种事件
- 46 个管理工具——给道具、管玩家、控天气、召 NPC、管区域和权限
- 两阶段 Agent——研究(17 工具)→ 行动(46 工具)
- 硬权限 Gate——基于关键词的 authorize_node 拦截未授权工具调用
- MCP 服务端——同 46 工具暴露给 Claude Code 做服务器管理
- 持久化记忆——通过 LangGraph AsyncSqliteSaver 按玩家保持对话历史
项目目前处于活跃测试阶段,尚未发布到 GitHub。我们在私有泰拉瑞亚服务器上运行,迭代 Agent 架构后再开源。如果对代码感兴趣或想提前体验,欢迎联系。
技术栈:Python 3.14, LangGraph 1.x, DeepSeek(Anthropic 兼容 API), C# .NET 9, TShock v6.1.0, aiohttp, httpx.