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 完全一致:
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]:
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 就能直接驱动:
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 建项目、建工作区、开会话,让免费模型回答一个需要读文件的问题:
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 干净利落:
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 线——这些留给你自己上手验证。