关于 orcareplay
OrcaReplay 是一个命令行工具,用于记录、离线重放和分叉编码 Agent 的运行过程。它通过本地代理捕获模型请求与工具调用,无需修改 Agent 本身,适合排查 Agent 出错原因的开发者。
主要功能
- 01记录编码 Agent 的完整运行过程
- 02离线精确重放,不调用模型、不消耗 token
- 03从指定步骤切换到其他模型继续运行
- 04对比不同模型在同一检查点上的结果
- 05生成运行过程的时间线与因果关系图
适用场景
- 01排查 Agent 误删文件等意外操作的原因
- 02在相同上下文下比较多个模型的表现与成本
- 03导出运行过程的图像用于问题报告
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
6收录的工具列表 · 更新于 2 周前
此工具无需参数。
详细介绍
OrcaReplay
English · 简体中文 · 日本語 · 한국어 · Deutsch · Français · Español · العربية
你的 Agent 凌晨 2 点弄坏了东西。早上 9 点重放——精确、离线,想重放多少次都行。
记录任意编码 Agent。逐字节重现运行过程,无需调用模型。从任意步骤分叉到另一个模型,看看谁能做对。
由 OrcaRouter 背后的团队打造——用一个 API 密钥和一个端点接入 Claude、GPT、Gemini、Grok、DeepSeek、Qwen 等模型。orca setup 默认指向它,orca compare 也因此只需一条命令,而不是准备四个服务商账户。
找到我们:OrcaRouter 全部模型 API
GitHub 仓库:OrcaCode Review · OrcaRouter Lite
联系:X · Discord · Hugging Face · Ollama

来自一次真实会话的输出——录制一次 Claude Code 运行,与录制内容进行重放,然后在检查点 4 分叉到两个模型,并通过 npx tsc --noEmit 进行评估。这里没有任何模拟内容。
三条命令即可试用
一个名为 orcareplay 的 npm 包,会在 PATH 上提供一个名为 orca 的命令。没有单独名为“orca”的工具,也不会向你的 Agent 安装任何东西。
npm i -g orcareplay # the package is orcareplay; the command it installs is orca
orca record claude # your agent, unmodified, doing whatever it does
orca replay last # the same run again — no network, no tokens, no charge
orca replay last --from 4 --model claude-haiku-4-5 --ui
第三条命令是大家愿意继续用下去的原因:文件相同、对话前缀相同,从第 4 步开始使用不同模型。模型是唯一变量,因此结果才有意义。
上面的三条命令需要已安装的 Agent、密钥、网络连接和实际 Token。如果这些你都还没有,也可以用一条命令自带所需内容:
orca quickstart
它会生成一个带有真实 Bug 的小型项目,以及一段 Agent 修复该 Bug 的录制内容,然后在不调用模型的情况下对该项目重放录制内容:修复前有两个测试失败,修复后四个测试通过,三个轮次由追踪记录提供,全程零开销。--full 会打印完整时间线和重放过程。
查看 Agent 自己的系统提示词
能看到完整交互循环的代理,也能看到运行框架在发送任何内容之前组装好的提示词。一条命令即可捕获提示词、清除其中的机器信息,并按模型归档:
node capture/capture.mjs claude --model claude-opus-5
交互式提示词和 -p 提示词并不相同,而且不同模型使用的提示词也各不相同。请参阅 capture/README.md,了解实测差异、注意事项和脱敏规则。
为何要做这个项目
如今,调试 Agent 就像考古。你在终端里翻来翻去,重新运行却遇到不同的失败,还要往别人的测试框架里加打印语句。现有工具都是可观测性工具:它们告诉你一次运行花了 4.12 美元、用了 61k 个 token,但这不是你真正想问的问题。你想知道的是,它为什么删掉了我的迁移文件?
OrcaReplay 让你重现这次运行,从而回答这个问题。
| 可观测性工具 | OrcaReplay | |
|---|---|---|
| 告诉你一次运行花了多少钱 | ✅ | ✅ |
| 告诉你是哪个工具调用删除了文件 | 有时 | ✅ |
| 再次运行 Agent 并得到相同答案 | ❌ | ✅ 从录制内容重现,逐字节一致 |
| 让你更改模型并从第 4 步重新运行 | ❌ | ✅ |
| 需要你修改 Agent | 通常需要 SDK 封装 | ❌ 两个环境变量 |
| 关闭终端后仍能使用 | ❌ | ✅ 它是一个文件 |
| 能看到模型 API 之外的内容——shell 退出代码、文件写入 | ❌ | ✅ 每一轮都能 |
| 能录制没有可重定向 API 端点的 Agent | ❌ | ✅ 可选启用 --tls-intercept |
最后两行涉及 SDK 封装在结构上无法触及的内容。捕获发生在 Agent 下方——位于进程和套接字边界——因此 Agent 是否属于你、你能否编辑它,甚至它是否持有 API 密钥,都无关紧要:使用 ChatGPT 订阅登录的 Codex CLI 会通过 TLS 连接自己的后端,并使用只有该后端接受的凭据,因此无法将其指向别处并保持为同一会话——而 orca 仍然可以录制它。请参阅测试框架无法重定向时。
工作原理
模型 API 是无状态的,因此 Agent 每一轮都会重新发送整个对话——包括上一轮的工具结果。因此,位于模型前面的代理可以看到整个循环:每个请求、每个流式响应、模型发出的每次工具调用,以及测试框架产生的每个工具结果。这个特性是该工具的基础,也正因如此,OrcaReplay 不会修补你的 Agent——它会启动本地代理、设置两个环境变量,然后不再干预。
“阻止外发”究竟是什么意思。 重放时,代理不会转发任何无法从跟踪记录中提供的内容,因此不会调用模型,也不会花费 token——运行结果会显示 egress=blocked。--loose 会有意解除这一限制:未匹配的请求会交由提供商响应,并记录为重大偏差,同一行会显示 egress=live-on-unmatched。重放仍会实际执行录制的工具调用;自行打开套接字的工具——例如运行 curl 的 shell 命令或获取内容的 MCP 服务——无论如何都不在保证范围内。默认情况下,这类请求不会经过代理,而是照常访问网络。使用 --tls-intercept 时则会经过代理,因为该选项会为整个子进程设置 HTTPS_PROXY:不在拦截列表中的主机会通过隧道原样转发,但其主机名、端口和字节数仍会记录到跟踪中;在列表中的主机则会像其他未匹配的调用一样被拒绝。重放不是沙箱;如果你需要沙箱,请在沙箱中运行它。
另外三层会捕获协议无法看到的内容:退出代码、实际耗时、字节来自哪个流,以及未经告知便写入的文件。对于完全不读取基础 URL 变量的 Agent,还有第五层——请参阅哪些 Agent。第六层会读取 Agent 自身对结构的描述,适用于具有这类描述的测试框架:哪个子 Agent 运行了、哪个子 Agent 向哪个移交了任务、是否触发了防护措施、哪个图节点产生了什么,以及其中哪些节点完全没有调用模型——这些信息都不会传到线上。
%%{init: {'theme':'neutral'}}%%
flowchart LR
A["<b>your agent</b><br/><i>unmodified</i>"]
subgraph orca["orca · six capture layers"]
direction TB
P["<b>proxy</b><br/>base-URL env var"]
SH["<b>PATH shim</b><br/>exit code · timing · streams"]
MC["<b>JSON-RPC tee</b><br/>MCP config rewrite"]
FS["<b>shadow git index</b><br/>workspace per turn"]
FH["<b>fetch hook</b><br/>for a hardcoded origin"]
end
A --> P & SH & MC & FS & FH
P -->|"forwarded, auth intact"| U["<b>the model API</b><br/><i>or OrcaRouter · any gateway</i>"]
orca ==> T[("<b>one trace</b><br/>.orca/runs/run_a1b2c3")]
所有内容都会进入同一条时间线,并按照实际发生的时间排序,而不是按照 orca 读取它们的时间排序。
精确重放、分叉和比较其实是一回事
它们不是三个子系统,而是同一个代理,只是带有一个游标——记录流中的一个位置,代理从这里开始停止读取磁盘上的内容,并转而从网络获取响应。
%%{init: {'theme':'neutral'}}%%
flowchart LR
subgraph disk["from disk · byte-for-byte · network blocked"]
direction LR
T1["turn 1"] --> T2["turn 2"] --> T3["turn 3"] --> T4["turn 4"]
end
T4 ==> CUR{{"<b>cursor</b>"}}
CUR ==> T5
subgraph net["from the network · any model you name"]
direction LR
T5["turn 5"] --> T6["turn 6"] --> T7["…"]
end
| 命令 | 游标位置 | 你会得到什么 |
|---|---|---|
orca replay last | 末尾 | 整次运行重放一遍,阻止网络访问——不消耗令牌、不产生费用、没有差异 |
orca replay last --from 4 --model X | 检查点 4 | 截止第 4 个检查点的轮次保持一致,之后由另一个模型接管 |
orca compare last --from 4 --models a,b | 多次位于检查点 4 | 一张表格,一个变量——模型 |
检查点不是录制出来的,而是推导出来的——也就是对话前缀完整且工作区已创建快照的任意位置。因此,每次分叉都从一个可以证明曾经存在的状态开始。
一次 Bug 排查的真实过程
你的 Agent 本该修复一个失败的身份验证测试。它以 0 退出,但测试仍然失败。先看看它实际做了什么:
$ orca show last
run_6473f858b59e [email protected] 14 events exit 0
SEQ KIND WHAT DETAIL
0 RUN run started generic-openai
1 SNAP tree 919d32ba037537b43814c83779963b2cc3023db7 0 changed
2 MODEL claude-opus-5 1 messages
3 MODEL claude-opus-5 stop: tool_use · 100 in · 20 out
4 TOOL edit_file {"path":"auth.ts",…}
5 SNAP tree c6af62b75c0c8b8938bd6087328b5148f3dcd534 1 changed
6 FILE auth.ts modified +1 −3
7 TOOL edit_file ok
8 MODEL claude-opus-5 3 messages
9 MODEL claude-opus-5 stop: end_turn · 101 in · 5 out
10 SNAP tree c6af62b75c0c8b8938bd6087328b5148f3dcd534 0 changed
11 SHELL ["sh","-c","node --check nonexistent-file.ts"] /tmp/hunt
12 SHELL shell result exit 1 · 43ms
13 RUN run ended exit 0
info usage input=201 output=25 cost=$0.004890
模型自身的记录无法告诉你的三个事实,却被运行的退出代码掩盖了:文件确实发生了变化(seq 6,+1 −3),Agent 运行的检查失败了(seq 12,exit 1),但它还是结束了。运行以 0 退出,是因为 Agent 以 0 退出了。
最后这个事实值得用一条独立命令查看。orca show 会列出事件发生的顺序;orca graph 则会展示事件之间的因果关系:
$ orca graph last
FROM TO KIND WHY
3 model.response 4 tool.call recorded tool_use block in the response
4 tool.call 6 fs.change inferred changed path appears in tool input, same or previous turn
4 tool.call 7 tool.result recorded tool result answers its call
7 tool.result 8 model.request recorded tool_result block in the request
11 shell.exec 12 shell.result recorded shell result answers its exec
1 inferred — derived from this trace, not recorded in it
两种边,区别很重要。已记录边在运行时写入,因为 tool_use 块实际包含在发出它的响应中。推断边则是刚刚根据其命名的规则推导出来的——文件系统快照是每轮采集一次,而不是每次工具调用都采集,因此把文件变更归因于某个特定调用只是一个合理猜测,并非事实。推断边永远不会写回追踪记录,就像检查点是派生出来的、从不记录一样,因此第三方读取者信任的字段里永远不会包含 orca 编造的内容。
--graph-card 会以这种方式绘制整个运行——时间从左到右,事件类型从上到下,并将导致失败的因果链从其他事件中突出显示:

这种结构才是重点。一次运行就是一个反复出现的模式——请求、响应、调用、效果、结果——因此任何打破这一模式的地方都值得一看;而一个没有任何边从它出发的事件,是列表完全无法展示的缺失。
orca export last --card bug.svg 只绘制这条因果链,适合放进 Issue 或消息中:

主体并非手动指定——没有传入 --to。卡片会附带图例,因为如果虚线脱离追踪记录单独传播,就会把猜测包装成事实;卡片还会打印可复现问题的命令。
SVG 可以在 GitHub Issue 中渲染,但在其他重要场景几乎都不行——X 不接受它作为上传文件,Slack 和 Discord 也不会提供预览——因此将文件命名为 .png 就能生成 PNG,命名为 .gif 则会让因果链逐步呈现。这条路径需要浏览器,而 orca 并不依赖浏览器:docs/media/README.md 将渲染工具链排除在 package.json 之外,这样运行 npm ci 的人就不必下载 Chromium;绘图命令也不是重新引入它的理由。如果没有所需依赖却要求生成栅格图,orca 会提示解决方法;无论如何,orca doctor 都会报告依赖情况,而 .svg 永远不需要任何依赖。
orca export last --card bug.png # the chain, ready to post
orca export last --card bug.gif # the same chain, one hop per frame
npm i --no-save playwright-core pngjs gifenc # only needed for the two above
现在,你可以随心所欲地免费复现它:
$ orca replay last
info replay.done reused=2/2 exact=2 divergences=0 unmatched=0 exit=0
无需网络、令牌,也没有波动。然后问问你真正想知道的问题——换一个模型能处理好吗?
$ orca compare last --from 5 --models claude-opus-5,claude-haiku-4-5 --verify "npm test"
MODEL VERDICT TOKENS COST WALL RUN
claude-opus-5 pass 201/25 $0.004890 0.3s run_1457b35062ba
claude-haiku-4-5 pass 201/25 $0.000326 0.3s run_b8ee08479fb6
两者都通过了。其中一个成本低 15 倍。文件相同,对话前缀相同,检查点相同——唯一改变的是模型,也只有这样,这个数字才有意义。
时间线
orca replay last --ui(或 orca ui)会将此次运行作为一个独立的 HTML 文件打开——无需持续运行服务器,无需联网,也无需安装任何东西。你可以筛选、逐步查看,或按空格键,按照运行实际发生的节奏回放。

每一层都会显示在同一条时间线上,因此你可以将此次运行视为一个完整的故事来阅读,而不是分成四部分:模型轮次及其令牌数量、每次工具调用及其参数和结果、带有退出代码和耗时的 shell 命令,以及产生的目录树和文件系统变更。
orca export last -o bug.html 会将该页面原样写入一个文件,你可以将它附加到问题中。它不包含任何外部引用——CI 会对此进行断言——因此即使从下载文件夹中打开、在飞机上或五年后,它也能正常显示。
相同任务,不同模型
orca compare 会从同一个检查点将一次记录的运行分叉到多个模型,并为它们提供相同的文件和对话前缀,再用你选择的命令为每个模型评分。模型是唯一的变量,这样比较结果才有意义。

orca compare last --from 4 \
--models claude-sonnet-5,claude-haiku-4-5 \
--verify "npm test" \
--share verdict.svg # the card above, ready to paste into an issue
指定多个模型
比较模型意味着要连接多个提供商,而手动操作就得知道 --upstream-anthropic 和 --upstream-openai 的存在、一个网关可以同时提供这两种线路格式,以及密钥应该填在哪里。这些都是真实存在的配置,却无从得知,因此我们提供了一个命令来询问:
$ orca setup
Gateway URL (serves the model APIs) [https://api.orcarouter.ai]:
get a key at https://www.orcarouter.ai/console/token — OrcaRouter keys start sk-orca-
API key (stored 0600; leave blank for none):
info config.saved path=~/.config/orca/config.json mode=0600 gateway=https://api.orcarouter.ai auth=stored
6 models available:
anthropic/claude-opus-5
anthropic/claude-haiku-4-5
openai/gpt-5.2
...
$ orca models
MODEL $/MTOK IN $/MTOK OUT
anthropic/claude-opus-5 15 75
anthropic/claude-haiku-4-5 1 5
openai/gpt-5.2 1.25 10
some-local-model — —
orca setup 会询问网关实际提供哪些服务,而不仅仅是写入配置文件。因此,错误的 URL 或失效的密钥会立即得到反馈,而不是等到比较过程中才收到 401。它还会保存你选择的模型,因此之后运行 orca compare last --verify "npm test" 时,无需提供模型列表,也无需任何上游标志。orca models 会为它能识别的模型标出价格,对无法识别的模型则显示短横线,因为为未知模型捏造价格,会导致比较表列出一个根本不存在的费用。
OrcaRouter 是第一个问题的默认答案——按 Enter 键即可使用一个源站和一个密钥来调用 Claude、GPT、Gemini、Grok、DeepSeek、Qwen 等模型,这正是 orca compare 所需的配置形式。它的模型 ID 按提供商命名空间区分(anthropic/claude-sonnet-4.6、openai/gpt-4o-mini),orca 会处理这一格式:命名空间用于选择线路格式,并会在定价前去除。
它只是一个默认选项,而非唯一选择:你可以直接替换它,或传入 --gateway <url>;任何支持 OpenAI 兼容的 /v1/models 和 chat 端点的服务都同样适用,无论是其他托管网关,还是你自行运行的服务。
它也始终只会作为你主动要求发送流量时的默认选项。如果未配置网关,orca record 会代理 Agent 自己的调用,直接发往它原本使用的提供商,并使用 Agent 自己的密钥。Orca 不会擅自改变你未配置过的录制流向:否则按下录制按钮,就会作为副作用将你的源代码发送给第三方。
非交互模式:orca setup --key <k> 使用默认网关,orca setup --gateway <url> --key <k> 指定其他网关,而 --key-env <VAR> 则从环境变量读取密钥,而不是将凭据保存在磁盘上。
密钥绝不会进入追踪记录。它只会附加到出站请求中,而录制内容则由剥离了身份验证信息的传入请求生成——因此密钥在机制上就不会出现在录制内容中,无需依赖任何人记住某条规则。如果某个标志会将流量发送到签发密钥的网关以外的位置,则不会发送该密钥。
运行的另一半:orca push 和 orca pull
本地录制和网关录制看到的内容各不相同,而且都不能呈现完整情况。
orca record 在你的机器上运行,因此能看到 Shell、文件系统和 MCP——也就是副作用。
OrcaRouter 网关运行在线路上,因此能看到任何笔记本电脑都看不到的内容:每个密钥、每位同事、
每个 CI 任务,以及每次调用背后的路由决策。两者写入的都是同一种 orca-trace v0,因此
真正值得关注的方向从来不是二选一,而是在它们之间移动一次运行。
此前,这种往返操作需要通过浏览器下载和表单上传。现在只需三个命令:
$ orca list --remote # what the gateway is holding
$ orca push [run] # this machine -> the gateway
$ orca pull <run> # the gateway -> this machine

真实输出。此卡片由 scripts/render-sync-card.mjs 根据
docs/media/sync-transcript.txt 生成,该文件记录了一次实际往返操作——orca record,然后 push,再 pull 到
另一个 工作区,最后由 orca show 读取拉取的副本。没有任何一行是手动编写的。
这里的网关是一个本地存根,它提供相同的两个端点,因此地址是一个回环地址。
和其他所有接收运行作为参数的命令一样,push 默认处理最近一次运行。pull 则不会:对于
尚未见过该运行的机器来说,“最近一次”毫无意义,因此它需要一个 ID。
这就是 orca list --remote 的用途。你没有的 ID 不能作为默认值提供,而过去唯一能读取 ID 的地方
是浏览器中的控制台——因此需要 ID 的命令被困在终端无法打开的标签页里。--remote 会向网关提出
与 orca list 向当前目录提出的相同问题,通过 push 已经 POST 到的路径执行 GET:
$ orca list --remote --limit 3
RUN STARTED SOURCE APP TURNS TOOLS LAYERS MODELS OUTCOME
run_3d9e61a07b52 2026-09-21 09:36 upload generic-openai 1 0 model,fs m exit 0
run_8c14f2e9a07d3b6150e2c49a 2026-09-20 04:21 gateway Unknown 1 0 model,route deepseek/deepseek-v4-flash-free end_turn
run_f07a5c2e91d4468bb3e0a17c 2026-09-20 02:02 gateway curl 1 0 model,route deepseek/deepseek-v4.1-flash error
orca pull <run> # fetch one into this machine’s store
应看 LAYERS 列。 这正是本页两部分要讲的内容:model 和
route 来自网关,它能看到笔记本电脑看不到的路由决策;fs、shell、mcp
和 net 来自这里录制的运行。它会在你花费下载资源之前,告诉你运行中包含什么。SOURCE
列与它并列,因为运行的来源和运行包含的内容是两个不同的问题。
STARTED 是运行的第一个事件,并非网关存储该运行的时刻——对于 push 的运行,这两个时间相差
你等待后才执行 push 的时长。OUTCOME 是网关对其录制的运行给出的结果(end_turn、error),
对于从这里 push 的运行,则表示退出状态。网关未报告的信息会显示为 —,而非留空;较旧的部署
完全不发送时间戳,因此 STARTED 会保持未设置,而不是显示为 1970 年。--source gateway 将结果
限定为网关录制的内容;--source upload 则限定为 push 到网关的内容——这两个选项都可用,
如果拼错则会报错,而不会返回空列表;不加此选项时,两者都会显示。--limit 设置数量上限
(默认为 20)。--gateway 对 push 和 pull 仍然是原来的用途——可选地覆盖目标主机——而不是兼作
开关,因此已指定目标的命令无需再输入 URL。这三个选项都属于 --remote:不带它们时会报错,
而非忽略,因为如果 orca list --gateway <url> 返回当前目录中的运行,看起来就像是该主机返回的结果。
--json 也能提出相同的问题:orca list --remote --json 会将网关上的运行作为一个
JSON 文档输出到 stdout;当没人查看表格时,orca pull <run> 所需的 ID 就可以从这里获取。
所需条件
需要一个具有 replay 范围的密钥,可在 Console → API Keys → replay 中获取。该范围可以读取和推送 Sessions 运行记录,但不能中继,因此它不是换了个名目的模型流量凭证——replay 密钥会被中继路由直接拒绝。上传功能也默认按工作区关闭,可在 Settings → request logs 下启用;网关只记录、不接受上传是正常状态。
$ export ORCA_GATEWAY_URL=https://api.orcarouter.ai # or your own gateway
$ read -rs ORCA_GATEWAY_KEY && export ORCA_GATEWAY_KEY # not typed on the command line
$ orca push last
这两个导出变量都会在当前 shell 会话中持续有效,这正是上述规则的用意:在同一 shell 中稍后运行 orca push --gateway <somewhere-else> 会被拒绝,而不会悄悄使用你为自己的网关导出的密钥。
它不会做什么
- 绝不会使用默认目标。
orca setup可能会将 OrcaRouter 设为 模型 网关,因为代理 Agent 已经发起的调用并不构成信息披露。但运行记录不同:其中包含源代码、shell 输出和工作区快照,这些内容会以内容寻址 blob 的形式随记录一起传输。因此,push 没有默认主机。只有当你明确指定了网关时,它才算目标——通过--gateway、ORCA_GATEWAY_URL,或在orca setup中输入的 URL;orca setup在你按 Enter 时为你填写的那个地址不算,无论它处理你的模型流量有多出色。直接运行orca setup,然后运行orca push last会被拒绝,并说明原因。push.packed也会在发出请求之前报告文件数量和字节大小,而不是事后报告。 - 绝不会发送工作区快照,除非你主动要求。
orca scrub会重写events.jsonl和 blob,但无法重写fs/下的影子 git 存储——其中的对象经过 deflate 压缩,并以其内容的哈希寻址,因此它会搜索这些对象,告知你其中仍有哪些内容,并提供--drop-fs选项。因此,push 默认会将fs/留在本地:如果 scrub 报告“对象中仍有这些内容”,之后就不应再 push 并把它们一并发送。需要这些内容时,可使用--fs发送——例如队友需要基于该树复现问题的情况;发送完成时会明确告知,因为这是两端扫描都无法读取的唯一部分载荷(对象经过压缩,因此明文扫描看不到任何内容)。 - 绝不会把密钥交给未为其配置的主机。 每个凭证都有归属主机——存储的密钥归
orca setup设置的网关所有,导出的密钥归ORCA_GATEWAY_URL所指向的主机所有,而旁边没有 URL 的导出密钥则没有归属主机——只有当目标与归属主机匹配时,才会附带该密钥。--gateway会更改运行记录的去向,而不会更改密钥的签发用途,因此将它指向其他地方会导致 push 被拒绝,而不是使用别人的凭证进行身份验证。唯一能通过的情况是:某个密钥的唯一目标就是本次调用所指定的目标;此前没有任何操作将它关联到某台主机,这正是 CI 的使用方式。 - 绝不会匿名发送。 没有密钥时,不会尝试这三种操作中的任何一种。未经过身份验证的请求,恰恰是配置错误的公共端点会响应的内容;用一个
200来得知你的运行记录去了无人负责的地方,或某个列表显示给你的内容其实从未限定为你所有,都不是好办法。 - 绝不会进行不完整替换。
pull --force会在旧副本旁写入新副本,再通过重命名进行切换,因此即使发生崩溃,任一时刻都至少保留一份完整运行记录;下次 pull 会完成切换,或回滚上次遗留的操作。--force表示请求替换一份记录,并不意味着可以销毁它后再操作失败。
网关收到数据后会做什么
它会重新计算 events.jsonl 的完整性根,并拒绝根值与清单不匹配的推送——这就是为什么推送会发送磁盘上已有的字节,而不会进行任何转换。它还会扫描归档中的明文机密信息,发现后便拒绝接收。这正是 --force 的作用,也是 --force 采用可选启用的原因:默认行为可以防止密钥泄露到共享服务。
这里不会重新执行脱敏。TraceWriter 会让每个事件在写入磁盘前依次经过 溢写、脱敏、校验、追加,正是为了确保没有调用方能够绕过这一流程,因此推送读取的字节已经过脱敏;再次清理会改写完整性根所覆盖的字节,而这恰恰会导致归档无法推送。网关的扫描是第二道独立防线,并非重复第一道检查。
支持哪些 Agent
有两件事决定了能否记录某个测试框架:一是能否将其指向代理,二是数据到达后,orca 是否理解它所使用的线路格式。
| Agent | 捕获方式 | 状态 |
|---|---|---|
| Claude Code | ANTHROPIC_BASE_URL | 可用 — 已通过实际 Bug 修复验证,详细说明 |
| Codex CLI(API 密钥) | OPENAI_BASE_URL → Responses API | 可用 |
| Codex CLI(ChatGPT 登录) | --tls-intercept → Responses API | 可用,但需要做出选择 |
| OpenAI Agents SDK | OPENAI_BASE_URL → Responses API | 可用 — SDK 本身使用其默认的 Responses API,在 exact=1 下记录和回放,CI 中的测试;其自身的追踪是第二个出站请求,详见 |
| Vercel AI SDK | 通过 @ai-sdk/openai 使用 OPENAI_BASE_URL(从 2.0.41 开始)— orca record generic-openai --;当源代码中编译了目标源站时,使用 fetch hook(orca record node --) | 可用 — 通过 generic-openai 使用可读取环境变量的 SDK 客户端,在 exact=1 下记录和回放,CI 中的测试;向编译在 Agent 源代码中的目标源站发送请求的 Agent,可在 exact=1 下记录和回放 |
| grok-cli(及其 Telegram 机器人) | orca record grok — GROK_BASE_URL,以及用于其子 Agent 的 hook | 可用 |
| OpenClaw | orca record openclaw — 用于网关的 hook,以及供其启动的 Agent 继承的变量 | 可用 |
| opencode | orca record opencode | 适配器已发布,两个源站均已重定向 |
| goose(Block) | orca record goose — OPENAI_HOST 和 OPENAI_BASE_URL,ANTHROPIC_HOST → Responses API | 可用 — 已针对 goose 1.49.0 进行端到端测试,了解它有何不同 |
| LangGraph / LangChain | OPENAI_BASE_URL、ANTHROPIC_BASE_URL | 可用 — 一个包含两个节点、使用流式传输和工具的图,在 exact=2 下记录和回放,并可实时分叉,CI 中的测试;pip install orcareplay-langgraph 还会记录图 — 包括运行了哪个节点、在哪个 superstep 中运行,以及哪些节点没有调用模型 |
| Haystack | orca record generic-openai -- python your_pipeline.py — 其 OpenAIChatGenerator 会将 base_url 留给 OpenAI SDK 处理,而后者会读取 OPENAI_BASE_URL | 可用 — 包含 ChatPromptBuilder 和 OpenAIChatGenerator 的 Pipeline 在 exact=1 下记录和回放,CI 中的测试 |
| OpenHands | orca record generic-openai -- python your_agent.py — 其 SDK 封装了 LiteLLM,并会读取 OPENAI_API_BASE | 可用 — SDK 自身的 LLM 层在 exact=1 下记录和回放,CI 中的测试 |
| CrewAI | orca record generic-openai -- python your_crew.py — 从 1.x 开始使用其自有 provider,并读取 OPENAI_API_BASE 和 OPENAI_BASE_URL | 可用 — 一个真实的 Agent、Task 和 Crew 在 exact=1 下记录和回放,CI 中的测试;了解 1.x 有何变化 |
| Aider | orca record generic-openai -- python your_agent.py — 通过 LiteLLM 路由,后者会读取 OPENAI_API_BASE | 可用 — LiteLLM 层在 exact=1 下记录和回放,CI 中的测试 |
| browser-use | orca record generic-openai -- python your_task.py — 其 ChatOpenAI 会将未设置的 base_url 直接传递下去 | 可用 — 在 exact=1 下记录和回放,CI 中的测试;仅限 LLM 层,不会驱动浏览器 |
| Hermes(Nous Research) | ORCA_BASE_URL_VARS=… orca record generic-openai -- hermes … | 应该可用 — 它会按 provider 覆盖设置;指定变量名 |
| IndexRAG | orca record indexrag -- --data-dir dataset/<name>/documents — 将整个流水线作为一次运行:聊天使用 OPENAI_BASE_URL,第二个源站使用 INDEXRAG_EMBEDDING_BASE_URL | 可用 — 已针对真实流水线进行端到端测试:索引、嵌入和回答均可离线回放,结果为 exact=11/11 retrieval=3/3;分叉时会保留记录的索引,了解流水线有何不同 |
| RAG 流水线(LlamaIndex、GraphRAG、LightRAG) | orca record generic-openai -- <cmd> — 嵌入和重排回放无需方言 | 可用 — 并发构建索引并批量生成嵌入,在 exact=5 retrieval=2 下记录和回放,CI 中的测试 |
| IDE 中的 Codex | orca record exec --tls-intercept -- code . | 可用 — 扩展会启动 Agent,且 Agent 会继承捕获配置 |
| 源站硬编码的机器人 | orca record exec --tls-intercept -- <cmd> | 可用 — 一个向其自身源代码中 URL 发送请求的 Grok 机器人,详细说明 |
| 沙盒中或另一台机器上的 Agent | orca attach | 可用 — orca 可访问,并会打印需要导出的内容,详细说明 |
| 其他任何情况 | orca record generic-openai -- <cmd> | 如果它读取 base URL 变量,则可用;如果不读取,则使用 orca record node -- <cmd> |
| Claude Code、Hermes 和 goose 都已使用真实运行器进行端到端测试,而且它们各自都导致了一些问题。Claude Code 导致了四个问题;Hermes 发现代理完全丢弃了一种流式交互;goose 又导致了两个问题,而且都不在适配器中——一个出在回放匹配器,另一个是运行器面对全是错误的跟踪记录,却报告成功。其余客户端均受适配器契约和测试夹具约束,夹具会记录每个客户端设置的确切变量,因此,如果运行器把它读取的变量改了,检查就会报红,而不是生成空跟踪记录。 |
读取不同变量的运行器
此表中其他符合 OpenAI 格式的客户端都会读取 OPENAI_BASE_URL。goose 也会读取它,但会优先读取 OPENAI_HOST;至于 Anthropic,它只读取 ANTHROPIC_HOST。
这种组合造成的问题比听起来更严重,因为两种情况都会静默失败:
- 只设置
OPENAI_BASE_URL,在用户已经为其他用途导出OPENAI_HOST之前都能正常工作。此时用户自己的值会优先生效,运行会发往他们的目标地址,而 orca 则会在空跟踪记录上生成一份看似正常的录制。 ANTHROPIC_BASE_URL——generic-openai设置的变量,也是生态系统中其他客户端读取的变量——在这里完全不起作用。仅通过它指定的接收端不会收到任何请求。
因此,适配器会将 OPENAI_HOST、OPENAI_BASE_URL 和 ANTHROPIC_HOST 全部设为同一个代理地址,测试夹具也会固定这三个变量。到达代理的流量使用 Responses API——启动时会发送 POST /v1/responses 和一个 GET /v1/models 请求——代理已有的 openai-responses 方言已覆盖此协议。
GOOSE_PROVIDER=openai GOOSE_MODEL=<model> orca record goose -- run -t "fix the failing test"
GOOSE_PROVIDER 和 GOOSE_MODEL 会原样传递,不会凭空设定:goose 对自定义端点没有默认值,替你选择会启动一个与直接运行 goose 不同的 Agent。
使用前有两项限制值得了解。goose 运行 shell 时不会经过 orca 的 PATH 垫片,因此,shell 工具调用会连同其输出一起记录,但不会记录真实退出代码、耗时或 stdout/stderr 分流——使用 --no-shell 录制,宁可不声明这些信息,也不要给出不准确的信息。此外,goose 会在对话进行过程中调用第二个模型来生成会话标题,因此,该调用会与第一个真实轮次发生竞争;回放匹配器可以处理两者以任意顺序到达的情况,而在 goose 成为第一个这样做的运行器之前,它还不支持这一点。
启动编码 Agent 的网关
OpenClaw 本身不负责编码:它会将 Claude Code、Codex 或 opencode 作为子进程运行,并通过聊天应用驱动它们,因此,一次运行会产生两类模型流量。网关自身的调用由 fetch 钩子捕获。编码 Agent 的调用则由常规变量捕获——并不是因为 OpenClaw 会读取这些变量,而是因为子进程会继承父进程的环境,所以它启动的 Claude Code 会看到 ANTHROPIC_BASE_URL,就像你亲自运行它时一样。
orca record openclaw
这种继承是操作系统的特性,而不是 orca 的特性。这类机制看起来显而易见,直到中间某一层清理了环境变量。因此,我们为此设置了一项测试:一个网关测试夹具,它自身不调用模型,而是启动一个会调用模型的 Agent,然后通过其孙进程的流量进行离线录制和回放。
什么也不读取的 Agent
如果 Bot 的源码里写死了 https://api.x.ai/v1,它就不会读取任何变量,因此也无从重定向。这也是人们编写的 Agent 中最常见的形式。可以在 Agent 下方、套接字处捕获它:
orca record exec --tls-intercept -- python bot.py
orca record exec --tls-intercept -- ./my-agent --task "fix the test"
exec(或 any)会启动你的命令,但不设置任何内容——既不设置源地址,也不设置凭据——因此 Bot 仍会与原本通信的对象通信,而 orca 会在途中读取对话。api.x.ai 在默认解密列表中,因此 Grok Bot 无需指定主机名。
只要 orca 启动的某个进程是它的祖先进程,同一条命令也能捕获一个并非由 orca 启动的 Agent。这就是 IDE 中的 Codex 这种形式:扩展会将 Agent 作为子进程启动,因此录制编辑器时,也会捕获其下方的 Agent。
orca record exec --tls-intercept -- code .
orca record exec --tls-intercept -- cursor .
重放时无需重复指定标志。通过拦截录制的运行会记录解密过的主机,orca replay 会读取这些记录并重新建立相同的策略——如果你不希望拦截 TLS,可使用 --no-tls-intercept。
若要解密默认列表未涵盖的端点,可将其添加进去,而不是替换整个列表:
orca record exec --tls-intercept --tls-hosts '+my-gateway.internal' -- ./my-agent
单独使用 --tls-hosts a,b 仍表示“只解密这些主机”,与以往一样。混用这两种形式时,程序会拒绝执行,而不是自行猜测。
不在这台机器上的 Agent
开发容器、VPS 或他人的 CI 中的 Agent 无法由 orca 启动,因此也无法为其构建环境。orca attach 会反过来操作:orca 保持代理运行,打印出可粘贴到远端的代码块,并持续记录收到的内容,直到你停止它。
orca attach --for claude
orca attach --for claude --bind 0.0.0.0 --advertise host.docker.internal
orca attach --tls-intercept # for an agent over there that reads no variable either
info attached run=run_e3d64e15ee8f proxy=http://127.0.0.1:33437 for=claude-code
# in the sandbox, before starting your agent:
export ANTHROPIC_BASE_URL='http://127.0.0.1:33437'
Recording. Press ctrl-C when the agent is done.
这些变量来自与 orca record 相同的适配器,因此沙箱录制不会与本地录制产生偏差。绑定到通配地址时必须指定 --advertise:0.0.0.0 表示监听地址,而不是任何设备都能连接的地址;orca 不会打印无法使用的 URL。
重放此类运行时也会遇到相反的问题——Agent 可能不存在于这台机器上——因此重放也支持 attach:
orca attach --replay <run>
它会在阻止出站连接的情况下提供录制内容,并直接从录制中获取测试框架。让同一个远程 Agent 指向它,即可离线再次运行,且不会调用任何模型。
orca 从未听说过的基础 URL 变量
要一一列举它们是不可能的。Hermes 会按提供商分别覆盖设置,其 .env.example 文件中就包含 NOVITA_BASE_URL、GLM_BASE_URL、KIMI_BASE_URL、MINIMAX_BASE_URL、HF_BASE_URL、NEBIUS_BASE_URL 等十多个变量。把变量列表写死在 orca 中,发布不到一周就会过时,因此只需指定变量名:
ORCA_BASE_URL_VARS='OPENROUTER_BASE_URL' orca record generic-openai -- hermes
ORCA_BASE_URL_VARS='GLM_BASE_URL,KIMI_BASE_URL' orca record generic-openai -- my-agent
ORCA_BASE_URL_VARS='SOMETHING_BASE_URL=/' orca record node -- node agent.mjs
每个变量都会指向追加了 /v1 的代理地址,这是 OpenAI 兼容覆盖设置所需的格式;=<path> 可覆盖该路径,而 =/ 则会使用不带路径的基础地址。
如果以这种方式录制 Agent 后运行正常,编写一个适配器只需大约 20 行代码——docs/plugins.md。如果运行不正常,最有用的内容就是跟踪记录:orca export last -o run.html。
当运行框架无法重定向时
基础 URL 注入可以捕获所有读取基础 URL 变量的运行框架,而 fetch 钩子则覆盖不读取该变量的 Node 运行框架。不过,使用 ChatGPT 订阅登录的 Codex CLI 两者都不适用——但原因并非本节先前所述。
它确实会读取提供方基础 URL,而显式指定的 URL 会覆盖 ChatGPT 后端;两条路径甚至使用相同的线协议。与该后端绑定的是凭据:已登录的订阅会携带 ChatGPT 后端接受、但 api.openai.com 不接受的套餐令牌。把 URL 指向别处,Codex 就会改为要求 API 密钥——你记录的不再是订阅会话,而是另一个会话。Codex 使用 Rust 编写,因此我们没有 fetch 可以触及其中任何一条路径。
--tls-intercept 可以解决这个问题;它被刻意设计为一项单独的选择,因为它会签发一个证书颁发机构。
orca record codex --tls-intercept
orca record codex --tls-intercept --tls-hosts 'api.openai.com,*.chatgpt.com'
该 CA 为每次运行单独生成,只通过子进程自身的环境信任它启动的 Agent,而不会加入系统或浏览器信任库;运行结束后即会删除。Orca 不会提出在任何地方安装它。允许列表以外的主机将通过隧道传输,不会被读取;记录内容仅包括地址和字节数,不含路径或正文,因为 orca 从未持有明文。若请求拦截所有内容,系统会拒绝,而不会照做。
经由它返回的内容并非日志行。被拦截的请求会像其他请求一样,按照相同的线协议方言进行解析,因此会作为普通交互写入跟踪记录——可离线重放,也可分叉到不同的模型;整个运行从未使用过你的 API 密钥。
orca replay --model、orca fork 和 orca compare 也适用,因为它们出于相同原因会启动实时 Agent。
面向 Agent、脚本或 CI
跟踪记录是一个文件,而这是可观测性仪表板无法做到的——因此,针对运行失败,最有用的问题是Agent可以提出的:*重放我上一次运行,并告诉我哪里出现了差异。*每条命令都以数据形式作答,而 orca 通过 MCP 提供自身服务。
$ orca replay last --json
{"runId":"run_a278eea7b535","mode":"exact","traceRunId":"run_687e3f84b208","matchedExact":2,"divergences":0,"unmatched":0,"liveCalls":0,"exitCode":0}
$ orca show last --json | jq '.events[] | select(.kind == "TOOL")'
$ orca checkpoints last --json | jq '.[-1].seq'
stdout 上只有一份 JSON 文档,诊断信息写入 stderr——包括记录中的 Agent 自身输出,因此即使运行正在输出内容,文档仍可解析。失败时也会以 JSON 作答,并返回非零退出码。--json 适用于 list、show、events、checkpoints、graph、record、replay、compare 和 doctor。
作为工具。orca mcp 会通过 stdio 向 Agent 提供跟踪记录存储:
{ "mcpServers": { "orca": { "command": "orca", "args": ["mcp"] } } }
orca_list_runs、orca_show_run、orca_checkpoints、orca_graph、orca_replay 和 orca_compare。重放免费且离线;orca_compare 会在自身描述中说明它会消耗实际令牌,因为选择工具的模型只会读取这段文字——同理,orca_graph 也会用描述说明 recorded 和 inferred 的含义。
在代码中,如果你更愿意不通过 Shell 调用:
import { Orca } from 'orcareplay';
const orca = new Orca({ cwd: process.cwd() });
const { unmatched, divergences } = await orca.replay('last');
const timeline = await orca.show('last');
它绝不会写入你的 stdout,也绝不会调用 process.exit——这两点都有断言保障,因为任何一项不满足的库都无法作为构建基础。
状态
早期阶段。v0 是上述三个命令的基础骨架。以下所有内容均通过了 1,393 项测试、跟踪格式一致性检查和插件 API 中立性检查,并在 Node 20 和 22 上完成验证。
| 能力 | 状态 |
|---|---|
| Trace 格式 v0 + JSON Schema | 可用 |
| Anthropic / OpenAI 兼容模型捕获 | 可用 |
| OpenAI Responses API 捕获 | 可用 — OpenAI Agents SDK 和 Codex CLI 默认使用的格式。可记录、离线重放和分叉;分叉会保留 Agent 使用的线上格式 |
| 不读取 base-URL 变量的 Agent | 可用 — orca record node -- <cmd> 会将 preload 写入运行目录,并为允许列表中的提供商主机重定向 globalThis.fetch。Node 和 Bun 均适用,因为 Bun 会忽略 NODE_OPTIONS 中的 --require。当源代码中编译了源站地址时,请使用此方式;遵循 OPENAI_BASE_URL 的常规 @ai-sdk/openai 客户端则使用 generic-openai |
| orca 无法读取的调用 | 可用 — 会转发而非拒绝,并记录为 net.request / net.response:这是证据,而非可重放的轮次。若录制未捕获任何内容,会发出警告,而不是正常退出 |
机器可读输出(--json) | 可用 — stdout 输出一份 JSON 文档,stderr 输出诊断信息,失败时输出 JSON |
因果图(orca graph) | 可用 — 以表格或 JSON 展示因果关系。每条边都会说明是 trace 记录的,还是 orca 此刻推导出的,并在两种情况下都注明规则。--to N 可缩小范围,仅显示产生某个事件的链 |
| 可分享卡片 | 可用 — orca export --card 绘制一条因果链,--graph-card 绘制整个运行并突出显示该链,compare --share 绘制判定表。始终支持 .svg;安装可选渲染工具链后也支持 .png 和 .gif。orca doctor 会报告该工具链,且 npm ci 绝不会安装它 |
首次运行,无需 Agent(orca quickstart) | 可用 — 包中附带真实录制内容及其对应的项目,并离线重放该录制内容,因此初次体验无需密钥、网络或令牌 |
MCP 服务(orca mcp) | 可用 — 通过 stdio 提供六种工具,让 Agent 能读取、解释和重放自己的运行记录 |
程序化 API(Orca) | 可用 — 命令会呈现其返回的内容,因此终端只是单一事实来源的一个视图 |
| 重放手动输入的会话 | 可用,但只是近似重放 — 具体含义 |
| 带偏差报告的精确重放 | 可用 — 将录制时的文件系统还原到工作树,然后恢复原状;使用 --worktree 可创建临时副本,使用 --in-place 则不进行还原。会单独生成一份运行记录,记录重放发现的内容 — 偏差、未匹配的请求;对于仅仅重复的内容,则指向父运行记录;使用 --no-trace 可跳过 |
| 从检查点分叉重放 | 可用 — 分叉会记录自己的文件系统快照,因此分叉后的运行也可以再次分叉 |
| 跨模型比较 | 可用 — orca setup 会保存网关(默认使用 OrcaRouter,也可以指定任意 URL)、密钥和模型列表,因此 orca compare 无需任何标志 |
| 文件系统快照和差异 | 可用 |
| 单文件 HTML 导出 | 可用 |
| MCP 调用录制 | 可用 — 使用 --mcp-config <path> 启用。重放和分叉会根据录制时使用的配置重新插桩,因此该层不会在分叉点终止 |
事后脱敏(orca scrub) | 可用 |
Shell 捕获(PATH shim) | 可用 — 捕获退出码、持续时间以及 stdout/stderr 的拆分。使用 --no-shell 可跳过 |
| 非模型网络捕获 | 可用 — 使用 --tls-intercept 启用;为每次运行签发仅由被启动的 Agent 信任的 CA,解密允许列表中的主机,对其余流量进行不可读隧道转发,并在运行结束时删除密钥 |
| Codex 订阅模型捕获/重放 | 可用 — 可识别 /backend-api/codex/responses HTTPS 回退接口,解码 zstd 请求体以进行匹配,并在重放期间提供录制的 SSE 响应,而不连接源站 |
| 已通过真实 Agent 验证 | 使用 Claude Code 录制了针对真实错误的真实修复:完成录制、端到端离线重放、从检查点分叉并导出。它暴露出四个测试夹具无法触发的问题,这些问题现已全部修复 — 真实 Agent 发现的问题 |
| 订阅认证测试环境 | Claude Code 可用。使用 ChatGPT 订阅登录的 Codex CLI 会携带只有其自身后端接受的套餐凭据,因此重定向 URL 会录制另一段会话,而非原会话:需要使用 --tls-intercept。使用 API 密钥则无需特殊处理 |
回放手动输入的会话
以 orca record claude 启动并手动操作的运行,其提示词不会出现在传输内容中。Orca 会从宿主程序自身的转录记录中恢复提示词,并通过在没有终端的情况下重新传入这些提示词来回放会话——这会重现对话,但不会逐字节重现运行。两处差异是固有的,并非缺陷;Orca 会明确说明这两处差异,而不是掩盖它们:
- 宿主程序会自行发起调用。 首轮之前的配额探测、为会话命名的请求、为子 Agent 新编写的委派提示词。回放不会重复这些调用。匹配器会跳过它们,继续寻找下一个真正的匹配项,并报告数量。因此,交互式录制中显示
reused=3/5,表示你要求的内容已完整回放,而不是只回放了一部分。 - 终端会话可以使用回放无法提供的工具。
AskUserQuestion、EnterPlanMode、ExitPlanMode、EndConversation都需要有人在场,因此在没有人参与的情况下驱动同一个 Agent 时,这些工具不会出现。它们的 schema 很大,因此携带这些工具的请求可能与录制的请求相差甚远,以至于回放中止;中止信息会说明缺少哪些工具,以及原因。
通过 argv 传入提示词进行录制——orca record claude -- -p "…"——不会遇到这两个问题,因为回放启动方式与录制时完全相同。若要精确回放某次运行,请使用这种方式。无论采用哪种方式,查看运行记录都不受影响:orca show 和查看器都能完整呈现两种运行。
安装
npm i -g orcareplay
orca doctor # checks node, git, and which agents it can find
需要 Node 20 或更高版本。没有原生依赖,因此无需编译,也不会在安装时下载除 tarball 本身之外的内容。
从 0.1.1 开始发布的每个版本,均由 RELEASING.md 中带标签的工作流发布,并附有注明构建版本所用提交和运行的来源证明——npm 会在软件包页面上显示该证明,npm audit signatures 可对其进行验证。
从源代码安装,用于开发或运行尚未发布的提交:
git clone https://github.com/Continuum-AI-Corp/OrcaReplay && cd OrcaReplay
npm ci && npm run build
npm install -g ./packages/cli # puts `orca` (and `orcareplay`) on PATH
在仓库根目录运行 npm install -g . 不会安装任何内容:根目录是一个工作区,本身没有可执行文件,而 orca 位于 packages/cli 中。
运行需要 Node 20+(CLI 自身的 engines 声明为 >=20.0.0)。贡献代码需要 ^20.19.0 || >=22.12.0,因为测试工具链需要此版本;根目录的 package.json 会单独声明这一要求,因此 npm ci 会提前告知你。无需账户、注册或更改 API 密钥。
在 Windows 上,shell 捕获会在 sh.exe 和 bash.exe 前写入 .cmd 垫片(Git for Windows 会提供这两个程序),而 Windows 只能通过 shell 运行 .cmd。因此,它能捕获通过 cmd.exe 或 PowerShell 启动的 shell,但无法捕获 Agent 自行启动的 shell:Node 宿主程序的 child_process.spawn('bash') 会直接调用真正的 bash.exe,而 Claude Code 无需查询 PATH 就能找到自己的 shell。orca doctor 会说明此机器上属于哪种情况;如果某次运行的工具调用执行了垫片未捕获到的命令,orca record 就会发出 shell.ineffective 警告——请使用 --no-shell 录制这些运行。如果 PATH 中没有 POSIX shell,doctor 会发出警告,此时应使用 --no-shell 进行录制。
运行记录的保存位置
所有内容都会写入录制时所在项目中的 .orca/runs/——按项目分别保存,绝不会存入全局目录,因此运行记录会与所属的检出副本一起移动。每个运行目录都是一个可自我说明的整体:
.orca/
.gitignore # just `*` — the store excludes itself, so a trace cannot be committed by accident
runs/run_d0a2ee7ce615/
manifest.json # who, when, which adapter, the git commit, counts, integrity digest
events.jsonl # the timeline, one JSON object per line, append-only
blobs/ # content-addressed payloads over 4 KB, deduplicated
fs/ # shadow git index: the workspace at every turn
shims/ # the PATH shims this run used; written by orca, read by nothing
py/ # bootstrap for the agent-structure layer, when the agent is Python
redactions.json # what was removed, by rule and count — never by value
查找旧会话:
orca list # every run here, newest first, with what it was forked from
orca list --remote # and what the gateway is holding, without opening a browser
orca show run_d0a2ee7ce615 # the timeline in the terminal
orca replay last # `last` = newest recording (it skips replay traces)
orca replay run_d0a2ee7ce615 # or name one outright
orca gc --older-than 7d --dry-run # what would be reclaimed, before anything is
orca list 会直接读取运行目录,因此也适用于别人发给你的跟踪记录:将其放入 .orca/runs/,所有命令都能看到它。没有任何索引,也没有数据库可供损坏。--remote 是唯一例外,但它也不是索引:它会请求网关并打印响应,不会保存任何内容。
隐私
追踪记录保存在本地,权限为 0600,记录器本身不会建立网络连接。写入时会对机密信息进行脱敏:环境变量捕获默认采用拒绝策略,不会写入身份验证标头,已知的密钥格式和高熵字符串会替换为稳定的占位符。
脱敏只能尽力降低风险,并非保证。请将追踪记录视为敏感信息——其敏感程度大致相当于 Shell 历史记录加堆转储。
orca export last -o bug.html # prints exactly what it is about to write
orca scrub last --match my-hostname # remove something after the fact
orca scrub 会重写 events.jsonl、清单和每个文本数据块,重新运行标准检测器,刷新完整性摘要,并保持二进制数据块的字节内容不变。
它无法重写文件系统快照。Git 对象以自身内容的哈希寻址,因此编辑对象会改变其 ID,进而迫使所有引用它的树都重写,以及之后所有引用这些树的事件也都重写——这相当于改写历史,一旦失败,运行记录将无法再恢复。因此,scrub 会搜索快照存储,并在其中仍包含你的字符串时告知你,而不是谎称它已清理干净。--drop-fs 会直接删除存储,但代价是无法再分叉此运行记录。
哪些内容开源,哪些不开放
以下内容始终以 Apache-2.0 许可证开源:追踪格式、核心、CLI、查看器、适配器和提供方接口。
OrcaReplay 由构建 OrcaRouter 的团队开发,这一点体现在两处,都是你要求的内容。未指定网关时,orca setup 会推荐它——这是一个你可以查看并覆盖的默认选项,只有在你选择回答的问题中才会出现,不会自行决定任何请求的路由。你明确生成的内容——例如导出文件或 --share 卡片——也会标注“由 OrcaRouter.ai 团队构建”,就像图表会注明其来源一样。
每条模型路径都是一个普通 URL,你可以将其指向任何位置;代码中没有任何路径会区别对待该来源;署名也不会将任何内容路由到任何地方。
供应商不会获得特权。插件——包括 OrcaRouter 的插件——只能使用 @orcareplay/plugin-api 中公开的 Provider 接口,不能使用任何私有 API。目前还没有供应商插件,因此负责执行此规则的 CI 任务(scripts/check-neutrality.mjs)会如实报告这一点,并以空操作通过;一旦有插件加入,它就会开始基于已发布的程序包构建,而不是基于工作区源码。如果某个插件需要某项能力,该能力必须先加入公开接口,并提供第二种实现,以证明接口并非围绕某一家供应商设计。
文档
如果你现在就遇到了问题,请从这里开始:
参考资料:
spec/orca-trace-v0.md— 规范性追踪格式docs/integrations.md— 记录框架,每个框架只需一条命令,CI 中会断言每个数字docs/architecture.md— 捕获、重放和分叉的实际工作方式docs/validation.md— 首次用于真实 Agent 时遇到的问题docs/launch-path.md— 已构建的内容、尚未构建的内容,以及后续计划docs/plugins.md— 编写适配器或提供方CONTRIBUTING.md— 五分钟开发循环- 适合新手的优先事项 — 共十二项,并附有入手文件
欢迎贡献
格式处于 v0 阶段,最小可运行骨架已经完成——这是项目发展过程中很有意思的阶段: 此时调整决策的成本仍然很低,而且已经列出了许多显而易见、可以着手处理的文件任务。
- 十二个适合新手的优先事项,每项都注明了文件和测试。
- 编写一个适配器。 一个文件,一个 fixture。如果你的测试框架读取 base-URL 变量,大约只需二十行——docs/plugins.md。如果不读取,
node可能已经能满足需求;如果某个未列在上文的测试框架生成的录制内容为空,无论哪种情况,都值得提交一个 issue。 - 在集成检查中添加一个框架。
test/integrations/会针对 stub origin 运行一个真实框架,关闭 origin 后再重放。当前覆盖了八个;AutoGen、LlamaIndex、Pydantic AI 和 smolagents 尚未覆盖。新增一个框架只需编写一个脚本并添加一行记录。 - 重新实现读取器。 规范采用 CC BY 4.0 许可是有意为之。Python 读取器已经存在;Go 和 Rust 版本欢迎贡献。
- 找出重放的问题。 匹配阶梯是此项目的核心;改进它最快的方法,就是提供一份能让它出错的真实录制内容。提交 issue 时附上
orca export last -o bug.html的结果——这是一个独立文件,你可以先用orca scrub处理其中需要清理的内容。
如果它为你省下了一个下午,点个 ⭐ 能帮助更多人找到它。
许可证
代码采用 Apache-2.0 许可。轨迹规范采用 CC BY 4.0 许可,因此任何人都可以重新实现。
由 OrcaRouter 团队打造 ·
orcarouter.ai ·
所有模型 ·
OrcaCode Review ·
X ·
Hugging Face
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。