JetBrains 开源 ThinkRail 实测:spec 图谱管项目记忆,我用免费模型跑通了「改代码 + 更新文档」闭环

10 月 25 日,JetBrains 的孵化项目 ThinkRail 冲上了 Hacker News 首页。这是一个 Apache 2.0 开源的 Agent IDE,内嵌 pi 编码 Agent,主打两个概念:每个任务一个 git worktree 工作区,以及一份「活的」SPEC.md 图谱——设计意图不随会话消失,而是作为项目知识被 Agent 持续读写。我在这台 16GB 的 M4 MacBook 上把它从头到尾跑了一遍:安装、启动、RPC 驱动、免费模型的真实 Agent 回合,一直到「Agent 改代码并顺手更新 spec」的闭环。这篇把这些全记下来,连同我踩到的五个坑。

它想解决什么问题

用编码 Agent 做过连续开发的人都有体感:Agent 不记得你上周的决定。它每次都重新读一遍代码,把你明确否掉过的方案再提一遍。上下文压缩类工具(比如之前写过的 Headroom 和 Graft Trail)管的是「这一轮塞进多少有效上下文」,ThinkRail 管的是另一件事:把项目约定固化成一份 Agent 能查、能改的图谱,让意图跨会话存活。

具体形态是:项目里散落若干 SPEC.md 文件,每个文件带 frontmatter(id、type、title,以及 depends-on/references/implements 这些关系字段),Agent 通过一组专用工具(spec_get、spec_grep、spec_graph、spec_create、spec_update)来查询和维护这张图,而不是靠会话记忆。内嵌的 Agent 引擎来自开源的 pi coding agent,ThinkRail 负责工作区、编辑器和界面这层壳。

安装与启动:第一个坑在监听地址

我没走 install 脚本(【未实测,需人工确认】官方 install.sh 的完整行为),而是直接从 GitHub Releases 下载 v0.2.0 的 darwin-arm64 自包含 CLI,121,126,352 字节,SHA256 与官方 SHA256SUMS 完全一致:

Bash
shasum -a 256 thinkrail-darwin-arm64
# f107f53da541663bda51c6c0ed4136c90b37802617b25fcf4ae5a29d26f58308
./thinkrail-darwin-arm64 --version   # 0.2.0

--help 信息完整:--port(默认 24242)、--no-open(无头不弹浏览器)、--no-analytics。启动后日志打印 listening on port 24342,然后坑来了:用 curl http://127.0.0.1:24342/ 探活,连接拒绝。lsof 一查,它只绑定了 IPv6 的 [::1]:

Bash
lsof -nP -iTCP:24342 -sTCP:LISTEN
# thinkrail 7090 ... TCP [::1]:24342 (LISTEN)
curl -s -o /dev/null -w "%{http_code}" "http://[::1]:24342/"   # 200

写自动化健康检查的话,127.0.0.1 会让你误判服务挂了。顺带一提,UI 是自包含的:字体本地打包、无 CDN 依赖,断网也能跑界面。

没有 GUI 也能驱动它:WebSocket RPC

我想在无浏览器的环境里验证它的核心流程,于是扒了下前端代码:ThinkRail 的 UI 通过一条 WebSocket(/ws?client=xxx&protocol=85)与引擎通信,帧格式是 {id, method, params} 的 JSON-RPC。用 Node 原生 WebSocket 就能直接驱动:

Javascript
const ws = new WebSocket("ws://[::1]:24342/ws?client=cli&protocol=85");
ws.send(JSON.stringify({
  id: "req_1",
  method: "project.open",
  params: { path: "/path/to/your/repo" }
}));

这条路让我把整个流程验证了个遍,也踩出了剩下几个坑,对想给 ThinkRail 做自动化的人应该有参考价值:

  • 请求 id 是全局去重的。复用之前的 id(哪怕换了连接)会直接被拒:request id was reused with a different payload。每次运行用时间戳生成 id 前缀即可绕开。
  • 参数名和直觉不一致。project.setTrust 的参数是 {id, trusted: true} 而不是 {projectId: ...};session.setModel 要求传完整模型对象 {id, provider, name, contextWindow, reasoning},传字符串会报 undefined is not an object。这些在文档里都查不到,只能从打包后的前端代码里反推。
  • 未知 API 路径不返回 404,而是回落到 SPA 首页返回 200——和我在 OpenCode 2.0 实测里遇到的错误路径兜底如出一辙,脚本靠状态码判活会全绿。

免费模型跑通第一个 Agent 回合

ThinkRail 内置 41 家云 provider,没有任何本地模型选项——这一点值得单独说:独立版的 pi 支持 llama.cpp 本地后端,但 ThinkRail 0.2.0 的 provider 列表里没有它,我起的 llama-server 路由根本挂不上去。想在 IDE 里零成本试用,只剩云端免费额度一条路。

我用环境变量 OPENROUTER_API_KEY 接上了 openrouter,模型列表拿到 377 个,其中 13 个 :free。接着用 RPC 建项目、建工作区、开会话,让免费模型回答一个需要读文件的问题:

Plain text
prompt: 读取 SPEC.md,用一句话告诉我折扣上限是多少。不要修改任何文件。

模型(cohere/north-mini-code:free)先推理,然后发起 read {"path": "SPEC.md"} 工具调用,4 毫秒拿到文件内容,第二轮给出正确答案「折扣上限是 30%,由 apply_discount 强制执行」。整个回合 cost $0,会话摘要里 outcome: succeeded。这里有个附加发现:新建会话的默认模型是 Anthropic Claude Fable 5,如果你的 openrouter 账户余额不足,第一次提问就会收到 402——它默认申请 128000 max_tokens,余额只够 798 也会被拒,错误会安静地落在会话的 completion 状态里。换 :free 模型前,别以为是自己配错了。

闭环:改代码 + 更新 spec

重头戏是让 Agent 在同一个回合里改代码并更新 spec。同一个仓库(一个 pricing.py 和一份带 frontmatter 的 SPEC.md),prompt:「给 apply_discount 加参数校验,percent 小于 0 时抛 ValueError,并更新 SPEC.md 的决策记录。」

第一次用 cohere 的 free 模型,两轮都在 edit 工具调用时收到 Provider finish_reason: error——免费档位不稳定是常态,换 poolside/laguna-s-2.1:free 一次通过。Agent 连续发起两次 read、两次 edit,第一段 8.2 秒、第二段 10.7 秒,outcome: succeeded。磁盘上的 diff 干净利落:

Python
def apply_discount(cents: int, percent: int) -> int:
    """Apply a percentage discount, capped at 30%."""
    if percent < 0:
        raise ValueError("percent must be non-negative")
    cap = 30
    pct = min(percent, cap)
    return round(cents * (100 - pct) / 100)

SPEC.md 的决策记录也多了一行:「2026-10-26: apply_discount 新增参数校验,percent 小于 0 时抛出 ValueError。」代码和行为同步进了图谱,这正是「living specs」想达到的效果。

工作区隔离是真的:每调一次 workspace.create,~/.thinkrail/worktrees/<仓库名>/ 下就多一个真实 git worktree,独立分支、独立目录,主检出区纹丝不动。

spec 图谱的字段坑:写错字段名,静默无边

测 spec.graph 时我踩了最隐蔽的一个坑。我给第二个 spec 文件的 frontmatter 写了 links: [pricing],图谱能列出两个节点,但 dependsOn 数组始终为空——不报错、不警告,就是没有边。从二进制字符串里翻到 spec_update 工具的描述才确认:关系字段的名字是 depends-on、references、implements、covers、tags,写 links 会被静默忽略。改成正确字段名后,图谱立刻返回 "dependsOn": ["pricing"]。

对想迁移现有设计文档的团队,这是成本最高的坑:字段名错了不会失败,只会让你的图谱看起来「什么都关联不上」。另外,裸的 SPEC.md(没有 frontmatter)不被识别为 spec——project.hasSpecs 会返回 false,只有补上 id/type/title 之后才纳入图谱。

值不值得用

说结论。ThinkRail 的差异化不在「又一个 Agent 终端」,而在它对项目知识的组织方式:spec 图谱 + worktree 工作区 + 每回合可评审的 receipt,这套组合在开源 IDE 里我还是第一次见。它和 task-view 那种「事后对账」思路互补——一个管事前约定,一个管事后审计。

不建议现在把主力工作流迁过去的理由也充分:0.2.0 还很早期,本地模型后端缺失(断网或在意隐私的场景直接出局)、RPC 无文档、spec 字段名靠逆向才能确定。但如果你已经在用 openrouter 免费额度或者有现成 API key,花十分钟装上 CLI、把项目约定写成带 frontmatter 的 SPEC.md,让 Agent 在真实任务里维护它,是值得做的一次实验——尤其当你的 Agent 总是「忘记」当初为什么这么设计的时候。

最后交代没测的部分:桌面 App(我只测了 CLI 的浏览器形态)、JetBrains AI 登录、Agent Skills 加载、MCP 审批流、子代理与终端面板,以及 0.3.0 nightly 线——这些留给你自己上手验证。

推荐阅读

  • task-view 实测:让编码 Agent 交一份能被 Git 核对的自述,我故意写错三处看它抓不抓得住

    task-view 把编码 Agent 的改动变成一页可核对的评审地图:agent 写自述,工具拿 Git 逐条对账,行号、代码文本、commit 全都要对得上。我用两个文件的小改动跑完整链路,又故意…

  • OpenCode 2.0 实测:一个后台服务管所有窗口,免费模型 7 秒修完一个 bug——升级前必看的三个坑

    开源终端编码 Agent OpenCode 2.0 重写架构:终端、网页、桌面共享一个常驻后台服务,MCP 工具改为 Code Mode 批量调用。我在 M4 Mac 上实测:官方安装脚本默认装 1.…

  • 讯飞开源 Spark-X2.6-4B 实测:3.3GB 权重跑进 16GB MacBook,三档思考的坑我先替你踩了

    讯飞 10 月 22 日开源 Spark-X2.6-4B 端侧模型,Apache 2.0、Q4_K_M 权重仅 3.3GB。我在 16GB MacBook 上用 llama.cpp 实测:3.4 秒加…

  • 小米开源 Xiaomi-Lens-0 实测:4B 模型边说边「指」,我用官方 Space 跑通了第一例,也踩了两个额度坑

    小米 SeerRay-Lab 开源的 Xiaomi-Lens-0 基于 Qwen3.5-4B,边生成回答边输出像素级分割掩码,Apache 2.0 共 9.2GB 权重。本文用官方 Space API…