Thinkrail 实测:worktree 隔离了改动,没隔离 stash
JetBrains 把 Thinkrail 开源了(Apache-2.0,写这篇时 1914 星):它是套在 pi 编码 agent 外面的一个轻量 IDE,主打「一个任务切一个 git worktree,多个 agent 并行干活,互不碰你的主目录」。Hacker News 上那帖我看的时候是 190 分、141 条评论,很大一部分在吵 README 是不是 AI 写的,我翻到的评论里几乎没人贴自己跑出来的结果。
我在 Windows 10 上下了 10 月 25 日的 0.3.0-nightly.6,接上 DeepSeek,开了两个任务同时改同一个文件,再开一个项目目录模式的对话,中间重启一次,最后翻 agent 的会话记录。这篇讲三件事:隔离到底隔住了什么、哪几处没隔住(有一处我用纯 git 复现了),以及面板上的 token 和费用能不能直接当账单看。
先说结论
| 想知道的 | 实测结果 |
|---|---|
| 两个任务同时改同一文件 | ✅ 各自建 worktree + 分支,分别提交,main 没动,合并无冲突 |
| 每个对话都自动隔离吗 | ❌ 不是。「Work in project folder」模式直接改主目录,界面写明了 no isolation |
| spec 存在哪 | 任务 spec 在 worktree 的 .thinkrail/context/,被 gitignore;项目 spec 是仓库里的 SPEC.md,要你自己提交 |
| 重启后还在吗 | 工作区、对话、token 读数都在;但项目目录里没提交的 SPEC.md,新 worktree 看不到 |
| 会互相踩吗 | 文件不会;git stash 会——所有 worktree 共用一个 stash 栈,agent 自己就用了裸 git stash |
| token / 费用面板 | 和 pi 会话记录逐项一致;但单价是写死的美元价,DeepSeek 空闲时段半价它不知道 |
三步上手建议:
- 想要隔离就走「Start building」,别在「Work in project folder」里干并行活;
- 想让后续任务读到的 spec,先 commit 到
main; - 在项目规则里写一句「禁止裸
git stash」,要暂存就做临时 WIP 提交。

背景(一句话版)
按 README 的说法,Thinkrail 是「a thin host that runs pi in-process」:pi 管模型、技能、上下文压缩和费用,Thinkrail 管工作区、编辑器和前后端通信;每个任务可以「cut workspaces from it as git worktrees, each with its own branch and working directory」,也可以「explicitly work in the project folder itself」。发布形式有两种:桌面安装包和一个自带完整引擎的 thinkrail 命令行,两者内嵌的是同一个引擎。
实测
1. 下载、校验、启动
Releases 页面上 nightly.6 一共 19 个文件,Windows 相关的是桌面包 thinkrail-desktop-windows-x64.zip(55.9 MB)和命令行 thinkrail-windows-x64.exe(231 MB)。先对哈希、看签名:
$ sha256sum --ignore-missing -c SHA256SUMS
thinkrail-desktop-windows-x64.zip: OK
thinkrail-windows-x64.exe: OK
PS> Get-AuthenticodeSignature .\thinkrail-windows-x64.exe
Status : Valid
Signer : CN=JetBrains s.r.o., O=JetBrains s.r.o., L=Prague, C=CZ, ...
桌面 zip 解开只有一个 1.3 MB 的 ThinkRail-Setup-canary.exe,加上 .installer\ 目录里 55 MB 的 .tar.zst 载荷。README 特意说要「run its setup executable in place, next to its payload」,所以别把 setup.exe 单独拖出来运行。另外这个 zip 的路径分隔符是反斜杠,Git Bash 的 unzip 会报一句 appears to use backslashes as path separators,好在能正常解开。
为了能脚本化地观察,这次我用的是命令行版(它会起一个本地服务,用浏览器打开界面)。启动时把 HOME 指到一个空目录,这样它写的所有文件都在眼皮底下:
$ thinkrail-windows-x64.exe --version
0.3.0-nightly.6
$ thinkrail-windows-x64.exe --no-open --no-analytics --port 24242 ./repo
[2026-10-26T03:30:33.388Z] INFO: [log] logging initialized (thinkrail 0.3.0-nightly.6, pid 28640, level info)
[2026-10-26T03:30:33.456Z] INFO: [host] listening on port 24242
thinkrail → http://localhost:24242
从初始化日志到开始监听只用了 68 ms。启动时踩到两个小坑:
$ netstat -ano | grep 24242
TCP [::1]:24242 [::]:0 LISTENING 28640
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:24242/
502
$ curl -s --noproxy '*' -o /dev/null -w "%{http_code}\n" http://localhost:24242/
200
$ curl -s --noproxy '*' -o /dev/null -w "%{http_code}\n" http://127.0.0.1:24242/
000
- 默认只监听 IPv6 的
[::1],用127.0.0.1访问连不上; - 本机设了
HTTP_PROXY(开着代理软件)的话,curl 或脚本访问localhost会被代理转走,返回 502,要加--noproxy。
还有一点值得知道:我加了 --no-analytics,首次启动弹出的「Share additional usage data」开关仍然是默认打开的,~/.thinkrail/config.json 里写的是 "analyticsEnabled": true,手动关掉之后才变成 false。README 也说了,基础使用事件和错误报告是「always sent」的,这个开关只管附加数据。
模型这块我什么都没配:它直接读到了环境变量里的 DEEPSEEK_API_KEY,默认选中「DeepSeek V4.1 Flash」,实际请求的模型名是 deepseek-flash。
2. 两个任务同时改同一个文件
测试仓库只有一个 calc.js,里面放了两个互不相关的函数 add 和 formatPrice。两个任务前后差 17 秒提交:
- 任务 A(11:32:31):给
add加参数校验,非数字就抛TypeError,只改add; - 任务 B(11:32:48):把
formatPrice改成¥3.00这种格式,只改formatPrice。
刚提交 A,worktree 就有了;B 一提交,又多出一个:
$ git worktree list
…\src\repo 257f6d5 [main]
…\src\home\.thinkrail\worktrees\repo\workspace-1 257f6d5 [add-typeparam-validation]
…\src\home\.thinkrail\worktrees\repo\workspace-2 257f6d5 [format-price-rmb]
注意 worktree 不在仓库旁边,而是统一放在 ~/.thinkrail/worktrees/<项目>/workspace-N。分支名是 agent 读完任务后自己起的(它调用了一个 set_title 工具,顺手把工作区也重命名了)。
两个任务都在 30 秒左右提交完:
$ git log --all --oneline --graph --format='%h %ad %d %s' --date=format:%T
* 8ed0628 11:33:13 (format-price-rmb) Format prices with a ¥ prefix and two decimals
| * c13270a 11:33:03 (add-typeparam-validation) Throw TypeError in add when arguments are not numbers
|/
* 257f6d5 11:30:22 (HEAD -> main) init
main 上的 calc.js 一个字没变。我另外 clone 了一份,把两个分支先后合并进来,没有冲突:
$ git merge --no-edit origin/add-typeparam-validation
$ git merge --no-edit origin/format-price-rmb
calc.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
到这一步,「文件层面互不相踩」这个说法成立。
3. agent 在 worktree 里实际做了什么
pi 把每个对话存成一份 jsonl,在 ~/.pi/agent/sessions/<worktree 路径>/ 下面。我写了个小脚本把工具调用一条条列出来,有三个发现。
第一,每个任务开工前都会先读两份内置技能(choosing-a-workflow、brainstorming 的 SKILL.md),再写一份任务 spec,最后才改代码。改一行代码的任务也是这个流程,所以 token 主要花在了读上下文,后面讲费用时会看到。
第二,它发现了一个跟 worktree 位置有关的问题。 任务 A 跑 node test.js 时报错了:
ReferenceError: require is not defined in ES module scope, you can use import instead
This file is being treated as an ES module because it has a '.js' file extension and
'F:\desk_code\articles\package.json' contains "type": "module".
原因是我把测试用的 HOME 放在了一个上级目录有 package.json("type": "module")的地方,Node 往上找配置时找到了它。agent 自己看出了原因,把文件复制到临时目录里重新验证,然后在 spec 里写明「改动前同样报错,与本变更无关」。这次主目录和 worktree 都在这个目录下面,所以两边结果一样。但这暴露了一个更普遍的问题:worktree 的上级目录是 ~/.thinkrail/...,不是你仓库原来的上级目录。凡是「从当前目录往上找」的配置(package.json 的 type、.npmrc、.editorconfig、某些 lint/格式化配置),在 worktree 里都可能跟主目录不一样。
第三,agent 用了裸 git stash。 任务 A 为了确认「改动前也报错」,跑了这样一条命令:
git stash -q && node test.js; echo "exit=$?"; git stash pop -q && git status --porcelain
这次没出事,因为任务 B 那段时间没碰 stash。但 stash 是存在 .git 里、所有 worktree 共用的,隔离对它不起作用。我用纯 git 把「运气不好」的情况复现了一遍:
# wt1、wt2 是同一仓库的两个 worktree
cd wt1 && echo '// A 的改动' >> calc.js && git stash -q # A:stash 一下,想跑原版
cd wt2 && echo '// B 的改动' >> calc.js && git stash -q # B:恰好这时也 stash
cd wt1 && git stash pop -q # A:pop 回来
== wt1 的 calc.js 末行:
// B 的改动
== stash list:
stash@{0}: WIP on t1: 257f6d5 init
A pop 回来的是 B 的改动,A 自己的改动还压在栈里。两个 agent 并行跑的时候,这种事只要时间点碰上就会发生,而且很难看出来。
4. spec 存在哪,重启后还能不能读到
任务 A 写的任务 spec 长这样(节选):
.thinkrail/context/.gitignore 内容只有一个 *
.thinkrail/context/TASK-add-arg-validation.md 2518 字节,带 id / type: task-spec / status: done
$ git status --short --ignored
!! .thinkrail/
内容写得挺扎实:需求、设计取舍(「用 typeof 而非 Number.isFinite」)、明确不做的事、验证结果和 commit 号都有。但它被 * 整体 gitignore 了,只活在这个 worktree 里,删掉工作区就没了,也不会跟着分支走。
项目级 spec 是另一回事。我切到「Work in project folder」开了一个对话,让它「用 spec 工具建立项目级 SPEC.md」。它在主目录写了一份 SPEC.md,右侧 Specs 面板也显示出了「calc 计算模块」:
$ git -C repo status --short
?? SPEC.md
两个现象:
- 这个对话直接改的是主目录,没有建 worktree,跟界面上写的「no isolation」一致;
- 这份 spec 是照
main上的旧代码写的:「formatPrice不输出货币符号」「add不做类型校验」。可此时另外两个分支已经把这两条都改掉了。spec 只能反映它所在那份 checkout 的状态。
然后我按 PID 结束进程、重新启动。三个工作区、各自的对话、token 读数都还在。接着从 main 新开了一个只读任务,问它「这个项目目前有哪些 spec,formatPrice 的契约是什么」:
TOOL spec_grep {"pattern":"formatPrice"}
-> No matches.
TOOL spec_validate {}
-> Spec-graph is valid: no issues found.
## 结论
**这个项目目前没有任何 spec 文件。**
原因很简单:新 worktree 是从 main 的提交切出来的,主目录里没提交的 SPEC.md 不会带过去。「spec 是事实依据」这句话的前提,是 spec 已经提交了。
5. token 和费用面板准不准
面板读数和 pi 会话 jsonl 里 usage 的累加值逐项对得上,重启后也没变:
| 对话 | 面板(↑输入 · ↓输出 · R 缓存读 · 费用) | 会话记录累加 |
|---|---|---|
| 任务 A | ↑18k · ↓5.5k · R236k · $0.014 | 18473 / 5502 / 235520 / $0.01356 |
| 任务 B | ↑14k · ↓3.7k · R153k · $0.009 | 13805 / 3672 / 153344 / $0.00947 |
问题出在单价上。我在程序里找到了 pi 内置的 deepseek-flash 价格表:
"deepseek-flash", name: "DeepSeek V4.1 Flash", ... cost: { input: 0.3, output: 1.2, cacheRead: 0.006, cacheWrite: 0
单位是美元/百万 token。DeepSeek 官方价格页上的 Flash 用人民币计价,还分时段:
| 每百万 token | 高峰(工作日 9–12、14–18 点) | 空闲时段 |
|---|---|---|
| 输入(缓存命中) | 0.04 元 | 0.02 元 |
| 输入(缓存未命中) | 2 元 | 1 元 |
| 输出 | 8 元 | 4 元 |
按 1 美元约 7 元粗算,pi 的写死价大致等于高峰价。我这次是周一 11 点多跑的,正好在高峰,所以面板偏差不大。按官方高峰价算,任务 A 约 0.09 元。但如果在晚上或周末跑,实际扣费只有一半,面板会高估一倍左右。另外有两点要注意:
- 缓存读 token 是输入的十几倍(任务 A:236k 对 18k),只看「↑」会低估上下文规模;
- 我本想用 DeepSeek 的余额接口对一下账,但这个 key 同时在别处用着:11:34:35 到 11:35:21 之间 Thinkrail 没有任务在跑,余额却从 11.34 元变成了 11.24 元,扣费没法单独归到这次测试。所以「面板和账单一致」这个结论我没能验证。要对账,最好给它单独开一个 key。
深入:哪些情况会互相踩
把上面几项合起来,「一任务一 worktree」能隔住的是工作目录里的文件,隔不住的是所有 worktree 共用的 .git,以及路径变了带来的环境差异:
- stash:共用一个栈,agent 自己就会用;
- hooks、config:
.git/hooks和仓库 config 是同一份,一个任务改了 hook,所有任务都受影响; - 未提交文件:主目录里的草稿、spec、
.env都不会进新 worktree,而.env这类通常本来就不提交,新 worktree 里跑测试可能缺配置; - 上级目录配置:worktree 在
~/.thinkrail/worktrees/下面,往上找配置的工具看到的是另一套上级目录。
也有人觉得这类新 IDE 没必要。HN 上一条评论说它们「mostly just worktrees+sandbox+tabs+prompts, which is just git+docker+tmux」。从实测看,Thinkrail 确实没做 sandbox,它的隔离就是 git worktree 本身,git worktree 有的边界它都有。
适用边界
- 值得用:同一仓库同时跑 2–4 个改动面不重叠的小任务,想要「每个任务一个分支、一键开 PR」,而且习惯先 spec 后编码的人;
- 先别用:依赖上级目录配置的仓库(monorepo 里的子目录、放在带
package.json的大目录里的项目);需要强隔离(进程、网络、数据库)的任务,worktree 管不了这些; - 版本提示:这次测的是 nightly,10 月 19 日到 25 日一共发了 6 个 nightly,表现可能很快就会变;macOS 只有 Apple Silicon 版。
清单
这次的整个流程,是在 SpectrAI 里让一个 Agent 通过内置浏览器操作 Thinkrail 界面、在终端里逐条核对 git 状态完成的,命令和输出都留在会话记录里。
参考
- Thinkrail 仓库与 README:https://github.com/JetBrains/thinkrail
- Releases(v0.3.0-nightly.6,2026-10-25):https://github.com/JetBrains/thinkrail/releases
- Hacker News 讨论:https://news.ycombinator.com/item?id=50233294
- DeepSeek 模型与价格:https://api-docs.deepseek.com/zh-cn/quick_start/pricing
浙公网安备 33010602011771号