[收录于 · 自学游戏开发](https://www.zhihu.com/column/studygame)

窦月汐 等 120 人赞同了该文章

> “任何足够复杂的 C 或 Fortran 程序，都包含了一个临时拼凑的、非正式规定的、有 bug 的、慢速的 Common Lisp 一半功能的实现。” —— [Greenspun 第十定律](https://zhida.zhihu.com/search?content_id=273177273&content_type=Article&match_order=1&q=Greenspun+%E7%AC%AC%E5%8D%81%E5%AE%9A%E5%BE%8B&zhida_source=entity)

### 一、Tool Calling 已经走到了一个十字路口

过去两年里，让大模型”使用工具”的范式经历了三次主要形态：

1. **Function Calling** ：模型输出固定 schema 的 JSON，应用层路由到具体函数。简单、可验证，但每一个能力都需要人类工程师提前注册。
2. **[MCP](https://zhida.zhihu.com/search?content_id=273177273&content_type=Article&match_order=1&q=MCP&zhida_source=entity) (Model Context Protocol)** ：把 function calling 抽离成跨进程协议。任何服务实现 MCP server 就能把能力暴露给 agent。工具的供给侧被解耦了，但抽象层次没有提升 —— 它依然是”预定义、再调用”的 RPC 模式。
3. **[CLI](https://zhida.zhihu.com/search?content_id=273177273&content_type=Article&match_order=1&q=CLI&zhida_source=entity) Agent** ：agent 直接操作 shell，所有 Unix 工具瞬间成为可用能力。这是真正的范式转变：工具注册表被换成了一个通用执行面。

。当执行单位从”注册过的函数”升级到”让一个图灵完备的解释器求值”时，能力上限就从”工程师预想过的那些任务”变成了”这门语言能表达的任何任务”。

问题是：CLI 止步得太早。它把解释器暴露给了 agent，但那是一个能力受限的解释器 —— bash 的类型系统是字符串，组合是 `|` 和 `&&` ，状态是全局环境变量。CLI 指对了方向，但还不是终点。

终点是 REPL：Read-Eval-Print Loop，一个针对表达式的“读取-求值-输出”循环

### 二、MCP 正在重新发明一个半成品的解释器

Greenspun 第十定律的现代变体应该这么写：

> 任何足够成熟的 tool-calling 基础设施，最终都会包含一个临时拼凑的、欠规范的、有 bug 的 REPL 的一半实现。

这不是修辞，而是可观察的事实。看一个稍微复杂点的 MCP server 实际在做什么：

- 它需要 **变量绑定** （”把上一个查询的结果传给下一个工具”）→ 在重新实现赋值与作用域。
- 它需要 **条件分发** （”如果场景里有 X 就做 A，否则做 B”）→ 在重新实现 `if` 。
- 它需要 **批处理与循环** （”对列表里每一项调用这个工具”）→ 在重新实现 `for` 。
- 它需要 **工具组合** （”先查询、再修改、再验证”）→ 在重新实现函数组合。
- 它需要处理 **异步** （等待加载、等待动画、等待网络）→ 在重新实现 [coroutine](https://zhida.zhihu.com/search?content_id=273177273&content_type=Article&match_order=1&q=+coroutine&zhida_source=entity) 或 Promise。

每一条都是在 JSON schema 上打补丁，模拟一门编程语言本来就自带的能力。而 agent 拿到这些拼凑品之后，还要用自然语言去驾驭它们 —— 相当于用一层翻译层操作另一层翻译层。

MCP 在历史上并不错。在模型还没学会稳定生成代码的时候，结构化 schema 是避免幻觉的护栏。但当模型生成代码的能力已经逼近甚至超过它们调用工具的能力时，schema 就从”护栏”变成了”天花板”。

### 三、Token is language. Language is evaluation. Evaluation is the universal tool.

REPL 的核心论断很简单： **模型已经在生成一门真实编程语言的 token 了，那就直接 eval 它。**

Agent 的输出是 `AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/Enemies/Blender.prefab")` ，它本身就是一段合法的 C#。不需要包装成 `{"tool": "load_asset", "params": {"path": "..."}}` ，不需要服务端再把 JSON 反序列化回 C# 去反射调用 —— 直接把源码送进解释器求值就结束了。

这个观察带来三个结构性优势。

### 1\. Schema-free：工具面不再需要被枚举

传统 tool-calling 的工具面是 **封闭的** ：注册了 100 个工具，agent 就只会用这 100 个。在 REPL 范式里，工具面等于整个宿主语言生态 —— 所有库、所有类型、甚至反射可达的一切。这是一个开放、无穷的表面。

这不是”功能多一点”的量变，而是质变。面对一个从未见过的系统状态，agent 可以即席组合标准库、用户代码、LINQ、反射，做出一次 **此前没有人想到过** 的查询。没有人需要提前为它注册一个 endpoint。

### 2\. 组合性：语言天然自带的能力

把能力塞进语言、而不是塞进协议，意味着立刻继承了语言的一切 —— 变量、闭包、泛型、管道、async/await。语言做一件事，协议要做五件事，这就是 Greenspun 定律的直接后果。

### 3\. 异步的语言级解决

这是被严重低估的一点。传统 RPC 架构里处理”等加载完”或”两秒后再检查”这类异步场景，要么让 agent 轮询（浪费 token、破坏控制流），要么在 server 侧自己搭一个状态机。

REPL 走了第三条路： **让 agent 返回一个 coroutine / Promise / generator，server 自己把它 drive 到完成** ，然后把最终值作为响应写回。这在 MCP 协议层是做不到的 —— MCP 的请求/响应语义就是一次性的，没有”我暂时不能回你，你帮我 tick 一下”这种 idiom。但在一个 REPL 里，这就是语言本身的控制流。异步从”上层要解决的分布式问题”降维成了”语言原生支持的一种表达”。

### 四、Exploration → Crystallization：Agent 自己编织工具链

传统工具架构有一个根深蒂固的假设： **工具的作者和工具的使用者是两个不同的角色** 。工程师负责把 API 包装成 tool，agent 负责按说明书使用。

REPL 取消了这个分工。

一个典型的链路是这样的：

1. Agent 第一次面对一个陌生项目，打开 REPL 像一个新来的工程师一样交互式探索：列类型、dump 状态、跑小片段验证假设。
2. 在某次成功探索之后，它发现某套动作序列是有价值的。
3. Agent 把这套序列 **固化** 成一个脚本文件，写进工程里。下次一行调用就能复用。

**探索与固化都由 agent 独立完成** 工具不是被预先安装的，而是在飞行中被锻造的。这改变了 tool-calling 的本质：工具不再是 agent 的”输入”，而是 agent 工作产物的一部分。

这是 MCP 触达不到的形态。MCP 的工具生命周期在协议之外 —— 需要重启 server、重新注册、人类审阅。REPL 的工具生命周期在代码里 —— 提交一个文件就完成了。

### 五、终局

工具调用的演化，是”抽象层次逐步上移”的过程：

| 阶段 | 抽象单位 | 谁决定能力边界 |
| --- | --- | --- |
| Function Calling | 单个 endpoint | 工程师 |
| MCP | 一组 endpoint | 工程师 + 协议维护者 |
| CLI | shell 表达式 | 操作系统 |
| REPL | 宿主语言表达式 | agent 自己 |

当抽象单位等同于宿主语言本身时，再往上就没有更高的抽象层了 —— 你已经直接面对图灵完备。任何进一步的”优化”只能发生在语言内部（更好的库、更好的 sandbox、更好的错误消息），而不是协议层的重新设计。

这就是 REPL 作为”最终形态”的含义：不是因为它最新，而是因为 **再往前一步就会撞上计算的理论上限** 。在 MCP 协议上再加十年的 spec 演进，它仍然是在逼近同一个目标 —— 一个能让模型自由地表达意图并求值的环境。而那个环境，有一个古老、成熟、久经考验的名字：

**REPL.**

### 六、把论证落到一个真实的引擎上

论点归论点，架构的价值只有在被”做成”之后才真正显现。我们选了一个最适合检验这套思路的场景 —— Unity，然后开源了 **[Unity REPL](https://link.zhihu.com/?target=https%3A//github.com/LambdaLabsHQ/unity-repl)** 。

Unity 是一个被传统 tool 架构彻底打败的领域。游戏状态是深度嵌套的 scene graph、几百种 MonoBehaviour、序列化资源、异步加载、Editor 与 PlayMode 两套语义。想用 MCP 暴露哪怕”合理的 10%“，就要维护几十个 endpoint，而任何一个稍有新意的调试需求都会立即溢出这个表面。

更关键的是： **Unity 的能力已经被 C# 完美地 API 化了** 。Unity 的设计者几十年前就做完了”设计一门语言面向所有引擎能力”的工作。再在它之上罩一层 MCP schema，相当于把一座精心设计的图书馆重新包装成一本目录，然后要求读者只能按目录借书。

Unity REPL 把这层多余的目录直接撕掉：

```
AI Agent  ──(Raw C# Tokens)──►  File IPC (/Temp/UnityReplIpc/)  ──►  Unity Editor Main Thread
```

Agent 发 C# 源码， `Mono.CSharp` 在 Unity Editor Main Thread 上直接求值，整个 Editor / Runtime API 都是可用工具面。Session 里声明的类型和变量跨调用保留，直到 domain reload。

第三节讲的”语言级异步”在这里是这样落的 —— REPL 表达式返回 `IEnumerator` 时，server 会跨帧 drive 它，把最后一个 yield 值作为响应写回：

```
public static IEnumerator ComplexSetup() {
    EditorSceneManager.OpenScene("Assets/Scenes/TestScene.unity");
    yield return null;                       // 等一帧
    var go = new GameObject("TestEnemy");
    yield return new WaitForSeconds(2.0f);   // 真实等待两秒
    go.GetComponent<Health>().Damage(10);
    yield return "done";                     // 最后一个 yield 值 → 响应
}
```

Agent 写的是地道的 Unity coroutine，而不是把异步拆成五次 MCP round-trip。

第四节讲的”exploration → crystallization”，在这里是一段真实的 live session 到一个提交文件的过程：

```
UnityREPL ready. Type C# expressions:
> var prefab = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Prefabs/Enemies/Blender.prefab");
> var obj = PrefabUtility.InstantiatePrefab(prefab) as GameObject;
> obj.transform.position = new Vector3(10, 0, 5);
> var turrets = GameObject.FindObjectsOfType<Turret>();
> string.Join("\n", turrets.Select(t => $"{t.name}: {Vector3.Distance(t.transform.position, obj.transform.position)}m"))
LaserTurret_1: 12.5m
GrenadeTurret_2: 8.2m
```

没有人给这个 agent 预先注册过 `SpawnEnemy()` 或 `GetTurretDistances()` 。它写了 C#，C# 就执行了。而当它发现这套动作值得复用，它会自己把它固化成一个编辑器脚本，放进 `Assets/Editor/` ：

```
// 由 agent 自己生成并提交到版本控制
public static class TurretRangeProbe {
    public static string RunAt(Vector3 pos) =>
        string.Join("\n", GameObject.FindObjectsOfType<Turret>()
            .Select(t => $"{t.name}: {Vector3.Distance(t.transform.position, pos)}m"));
}
```

下次一行 `TurretRangeProbe.RunAt(new Vector3(10, 0, 5))` 就能复用。工具链不是 Unity 团队维护的，不是插件作者维护的，而是 **这个 agent 自己在这个项目里锻造出来的** ，像任何工程师写的代码一样可以被 review、被复用、被演进。

### 开始使用

在你的 Unity 工程里打开任意支持 skill 的 agent（Claude Code / Cursor / Codex CLI 等），贴入下面这段提示，它会自行完成安装、注册、握手验证：

```
Add \`"com.lambda-labs.unity-repl": "https://github.com/LambdaLabsHQ/unity-repl.git"\` to the \`dependencies\` in \`Packages/manifest.json\`.

Then register the Unity REPL skill: try running \`npx skills add ./Packages/com.lambda-labs.unity-repl\`.

If that fails, the skill definition is at \`./Packages/com.lambda-labs.unity-repl/.agents/skills/unity-repl/SKILL.md\` — use your agent runtime's skill installer to register it.

Finally, verify the REPL server is working by evaluating \`Application.unityVersion\` through the skill.
```

全程不需要手动配 MCP server，不需要设计任何 schema。要求 Unity 2021.3+，仅 Editor，Windows / macOS / Linux 全平台。

- 主仓库： [github.com/LambdaLabsHQ](https://link.zhihu.com/?target=https%3A//github.com/LambdaLabsHQ/unity-repl)
- 键鼠输入注入（可选）： [github.com/LambdaLabsHQ](https://link.zhihu.com/?target=https%3A//github.com/LambdaLabsHQ/unity-agent-input)
- Game View 视觉（可选）： [github.com/LambdaLabsHQ](https://link.zhihu.com/?target=https%3A//github.com/LambdaLabsHQ/unity-agent-vision)

三者合起来，就是一个完整的、不依赖任何预定义工具的 Unity AI 工位。

---

> *The language is your only tool.*

编辑于 2026-04-15 21:42・上海
