【免费下载链接】whip
A fast coding-agent harness in Go. Tool-use loop, bubbletea TUI, provider-routable models with live catalog discovery, MCP support, background subagents. One binary, no runtime, built for open-source models.
WhipCode 是一款用 Go 编写的快速编程智能体(coding agent),它的核心是 RLM 运行时——模型通过唯一工具rlm_exec执行 Starlark 或 JavaScript(QuickJS)单元(cell),从而调用文件、Shell、消息、MCP 等宿主模块,实现递归委派与工具调用。本文将带你完整理解这套执行模型的工作原理、两种语言的选择方式,以及默认限额等关键细节。
一、什么是 WhipCode RLM 运行时:编程智能体的执行模型
大多数编程智能体靠"不断增长的对话记录 + 一堆零散工具"来工作,上下文越长越慢、越贵。WhipCode 的 RLM(Recursive Language Model,递归语言模型)运行时换了一种思路,它有四个设计目标:
- 大上下文始终可用,但不必反复塞进提示词;
- 递归委派使用统一的会话抽象(根节点和子节点是同一种
AgentSession); - 协调、权限、预算与恢复全部持久化;
- 所有副作用都收拢在 daemon 拥有的单一策略边界之后。
在这套模型里,会话没有"模式"字段,也没有"直接工具"模式——每个模型能看到的工具只有rlm_exec一个。它由 daemon 托管、独立于 UI 运行,关闭窗口不会中断会话。
💡 如果你在终端里使用它,启动时会看到这样的 TUI 界面(bubbletea 构建):
二、rlm_exec 深度解析:模型看到的唯一工具
rlm_exec的定义在 internal/rlm/tool.go。它接收一个参数:code(源代码字符串),然后在受限内核(kernel)中执行一个"有界单元"。可以把它理解为一个带超时的 REPL:
# 一次 rlm_exec 调用(Starlark 单元) content = files.read(path="README.md")["output"] print("项目名:", content.split("\n")[0])模型每写一小段代码,运行时就执行一小段。执行结果是一个 JSON 对象,包含value(返回值)、output(打印输出)、steps(执行步数)以及检查点通知等字段;超过 16 KiB 的值会被截断为value_preview并标记truncated: true,保证返回给模型的上下文永远有界。
几个关键约束(新手最常踩的坑):
- 宿主模块操作只接受关键字参数,如
files.read(path="README.md"); - Starlark 不是 Python:没有
try/except、import、open; - QuickJS 不是 Node.js:没有
require、npm、定时器、文件系统 API; files/shell的结果是 JSON 对象而非字符串:小结果文本在output字段下,超过 8 KiB 则返回handle、size和 4 KiB 预览,需用context.read(handle=..., offset=0, length=8192)分片读取。
三、Starlark vs JavaScript:两种执行语言怎么选?
新会话可以在创建时选择执行语言,默认是 Starlark;CLI 上用--rlm-engine指定,例如:
whipcode --rlm-engine quickjs运行中的会话不能中途切换语言;子会话继承根会话的选择,恢复会话时用错误的语言参数会直接报错。引擎描述符定义在 internal/rlm/engines.go,QuickJS 引擎运行在一个内嵌的 WASM 构建(wazero 执行)中,源码见 internal/rlm/engine/quickjs/。
两种语言的核心差异:
| 维度 | Starlark(默认) | JavaScript(QuickJS) |
|---|---|---|
| 调用宿主模块 | 关键字参数:files.read(path=...) | await files.read({path: "..."}),支持顶层await |
| 并发宿主调用 | 串行(1 个) | 最多 16 个并发 |
| 检查点保真度 | 部分保真:保存受支持的顶层数据与助手函数 | 整堆保真:闭包、循环引用、类实例、BigInt 全量保存 |
| 状态恢复 | 恢复受支持的数据子集与无状态依赖的顶层def | 完整恢复整个 JS 堆(词法绑定、闭包、对象同一性) |
| 典型适用 | 简单、稳态逻辑 | 复杂状态、异步并发扇出、Promise.all批量调用 |
JavaScript 单元的典型写法:
const [a, b] = await Promise.all([ files.read({path: "go.mod"}), files.read({path: "package.json"}), ]); console.log(a["output"].split("\n")[0]);⚠️ 注意:QuickJS 的宿主负载是"被动 JSON 树",超大整数用BigInt,例如BigInt("9007199254740993");不安全的整数 Number 和访问器(accessor)会在宿主边界被拒绝。
四、宿主模块全览:单元里能调用哪些能力
所有宿主模块注册在 internal/rlm/modules.go,内核只能看到其定义所选模块的子集——选择是强制执行的:宿主会拒绝调用清单之外的任何模块。常用模块一览:
| 模块 | 能力 | 常用操作 |
|---|---|---|
files | 文件读写 | list/search/read/write/patch |
shell | 命令执行 | run(阻塞,120s 上限)、start/poll/tail/wait/kill(后台任务) |
context | 自身历史检索 | inspect/search/read/history |
agents | 递归委派 | spawn/submit/wait/inspect/stop |
messages | 节点间邮箱 | send/list/read/complete/defer |
mcp | MCP 服务器工具 | search/describe/list_tools/call |
models | 无状态模型调用 | call/batch |
state | 持久化状态 | private_set/blackboard_get/subscribe |
browser | 桌面浏览器控制 | open/attach/run/detach |
artifacts/schedules/permissions/user | 大对象、定时任务、权限请求、询问用户 | 略 |
递归委派是 RLM 的招牌能力:agents.spawn(prompt=..., name=...)会立即返回子代理元数据,父代理结束本轮,等子代理完成或来信后由邮箱"唤醒"新回合。长任务(构建、测试、服务器)应通过shell.start` 放到后台,每个代理最多 8 个并发任务。
五、检查点与状态持久化:重启后工作不丢
每个单元执行结束后,内核都会保存一个检查点。这意味着 worker 进程被回收、甚至 daemon 重启后,状态都能恢复:
- Starlark:顶层全局变量持久化在存活 worker 中;支持的数据子集(None、布尔、整数、浮点、字符串、字节、列表、元组、字典)直接重建。闭包、循环引用、容器里的函数会被跳过并在
scratch通知中报告; - QuickJS:快照捕获整个 JS 堆——词法绑定、闭包、类、BigInt 都跨重启存活。忘记
await的调用会被排干,未处理的 rejection 会显式失败; - 检查点带 SHA-256 完整性校验,绑定引擎 build/ABI/profile,损坏或不兼容的镜像会显式失败,而不是被静默覆盖。
📌 官方建议:重要的持久信息应写入state、artifacts、messages或子代理记录,而不是只留在单元变量里。
上下文管理同样是 RLM 的一部分:原始转录保持只增不改,模型视图聚焦到最近少量对话加一个有界摘要;历史、工具参数和分片内容在压缩、重启后依然可通过context.search/context.read检索。
六、rlm_exec 执行限额:默认值一览
每个单元都是"有界"的,默认限额定义在 internal/rlm/kernel.go(可用rlm.*配置项调整):
| 配置项 | 默认值 | 含义 |
|---|---|---|
rlm.steps | 1,000,000 | Starlark 单元步数上限 |
rlm.hostRequests | 1,024 | 每单元宿主调用次数 |
rlm.wallMillis | 30,000 | 单元计算时间上限(宿主调用耗时不计入) |
rlm.memoryMiB | 256 | worker 内存上限 |
rlm.outputBytes | 65,536 | 单元捕获输出上限 |
rlm.maxWorkers | 16 | 全 daemon 存活内核数 |
QuickJS 另有:32 MiB 访客堆、16 个并发宿主调用、10 分钟整单元看门狗。这些是执行边界;token、成本、时长等"持久预算"由会话层单独核算。
七、可观测性:实时追踪 rlm_exec 执行过程
单元在运行时是"可观看"的:打印输出会节流推送到stream.tool.output,每个宿主调用都会发出stream.cell.host事件(模块.操作、参数摘要、耗时、错误)。客户端因此可以为任意节点渲染一个实时 REPL,而不占用模型的上下文——桌面端右侧的"执行追踪"面板就是这一机制的体现(见本文首图)。
八、延伸阅读与关键路径
- RLM 运行时完整设计:docs/rlm-runtime.md
rlm_exec工具定义:internal/rlm/tool.go- 宿主模块注册表:internal/rlm/modules.go
- 引擎描述符(Starlark/QuickJS):internal/rlm/engines.go
- QuickJS 内核泵:internal/rlm/quickjs_kernel.go
- 模型侧运行指南(Starlark 版):internal/rlm/testdata/TestDefinitionPromptGolden/coding/starlark/guide.golden
- 语言选择与恢复冲突:docs/setup.md
- 确定性 RLM 评估:evals/rlm/
常见问题速答
- 为什么模型只有一个工具?统一接口让根节点和子节点行为一致,副作用全部走 daemon 策略边界,权限与预算才真正可控。
- Starlark 单元失败会回滚吗?不会。单元完成的外部副作用保留;检查点失败不回滚已完成的效果,也不要为"修复检查点"重复执行有副作用的操作。
- 能否让模型自己挑语言?不能在运行中切换;由创建时元数据或
--rlm-engine决定,子节点继承。
WhipCode 的 RLM 运行时,本质是把"智能体循环"变成了"带检查点的代码执行":模型写短小的 Starlark 或 JavaScript 单元,宿主模块负责一切副作用,预算、权限和恢复交给 daemon。理解了rlm_exec这一个工具,就理解了 WhipCode 的全部工作方式。🚀
【免费下载链接】whip
A fast coding-agent harness in Go. Tool-use loop, bubbletea TUI, provider-routable models with live catalog discovery, MCP support, background subagents. One binary, no runtime, built for open-source models.
相关推荐
开源可见光光谱仪DIY实战指南:从硬件构建到光谱分析
开源可见光光谱仪DIY实战指南:从硬件构建到光谱分析 想要探索光的世界却苦于专业设备价格昂贵?开源可见光光谱仪项目为你提供了一个完整的解决方案。这款基于TCD1
sast-skills 三步工作流揭秘:LLM 驱动的 SAST 评估到底如何运行
sast skills 三步工作流揭秘:LLM 驱动的 SAST 评估到底如何运行 sast skills 是一个 LLM 驱动的 SAST(静态应用安全测试)
RLM轨迹metadata深挖:如何从一个dict完整重建整次运行?完整入门指南
RLM轨迹metadata深挖:如何从一个dict完整重建整次运行?完整入门指南 RLM(Recursive Language Models,递归语言模型)是一
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考