agenticreplay 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +36 -912
  2. package/node_modules/@agenticreplay/adapters/dist/claude-code.d.ts.map +1 -1
  3. package/node_modules/@agenticreplay/adapters/dist/claude-code.js +7 -1
  4. package/node_modules/@agenticreplay/adapters/dist/claude-code.js.map +1 -1
  5. package/node_modules/@agenticreplay/adapters/dist/session.d.ts +2 -0
  6. package/node_modules/@agenticreplay/adapters/dist/session.d.ts.map +1 -1
  7. package/node_modules/@agenticreplay/adapters/dist/session.js +7 -1
  8. package/node_modules/@agenticreplay/adapters/dist/session.js.map +1 -1
  9. package/node_modules/@agenticreplay/adapters/package.json +6 -6
  10. package/node_modules/@agenticreplay/core/dist/index.d.ts +1 -0
  11. package/node_modules/@agenticreplay/core/dist/index.d.ts.map +1 -1
  12. package/node_modules/@agenticreplay/core/dist/index.js +1 -0
  13. package/node_modules/@agenticreplay/core/dist/index.js.map +1 -1
  14. package/node_modules/@agenticreplay/core/dist/paths.d.ts.map +1 -1
  15. package/node_modules/@agenticreplay/core/dist/paths.js +8 -4
  16. package/node_modules/@agenticreplay/core/dist/paths.js.map +1 -1
  17. package/node_modules/@agenticreplay/core/dist/run-names.d.ts +7 -0
  18. package/node_modules/@agenticreplay/core/dist/run-names.d.ts.map +1 -0
  19. package/node_modules/@agenticreplay/core/dist/run-names.js +79 -0
  20. package/node_modules/@agenticreplay/core/dist/run-names.js.map +1 -0
  21. package/node_modules/@agenticreplay/core/dist/writer.d.ts +5 -0
  22. package/node_modules/@agenticreplay/core/dist/writer.d.ts.map +1 -1
  23. package/node_modules/@agenticreplay/core/dist/writer.js +38 -6
  24. package/node_modules/@agenticreplay/core/dist/writer.js.map +1 -1
  25. package/node_modules/@agenticreplay/core/package.json +4 -3
  26. package/node_modules/@agenticreplay/fs-capture/package.json +2 -2
  27. package/node_modules/@agenticreplay/mcp-shim/package.json +1 -1
  28. package/node_modules/@agenticreplay/node-instrument/package.json +1 -1
  29. package/node_modules/@agenticreplay/plugin-api/dist/plugins.d.ts +1 -0
  30. package/node_modules/@agenticreplay/plugin-api/dist/plugins.d.ts.map +1 -1
  31. package/node_modules/@agenticreplay/plugin-api/package.json +1 -1
  32. package/node_modules/@agenticreplay/providers/package.json +3 -3
  33. package/node_modules/@agenticreplay/proxy/package.json +5 -5
  34. package/node_modules/@agenticreplay/schema/package.json +1 -1
  35. package/node_modules/@agenticreplay/shell-shim/dist/install.d.ts.map +1 -1
  36. package/node_modules/@agenticreplay/shell-shim/dist/install.js +5 -2
  37. package/node_modules/@agenticreplay/shell-shim/dist/install.js.map +1 -1
  38. package/node_modules/@agenticreplay/shell-shim/package.json +3 -3
  39. package/node_modules/@agenticreplay/viewer/package.json +2 -2
  40. package/node_modules/any-ascii/LICENSE +15 -0
  41. package/node_modules/any-ascii/README.md +20 -0
  42. package/node_modules/any-ascii/any-ascii.d.ts +4 -0
  43. package/node_modules/any-ascii/any-ascii.js +30 -0
  44. package/node_modules/any-ascii/block.js +645 -0
  45. package/node_modules/any-ascii/package.json +32 -0
  46. package/node_modules/any-ascii/test.js +83 -0
  47. package/package.json +12 -12
  48. package/packages/cli/dist/commands/record.d.ts.map +1 -1
  49. package/packages/cli/dist/commands/record.js +1 -0
  50. package/packages/cli/dist/commands/record.js.map +1 -1
  51. package/packages/cli/dist/commands/sync.d.ts.map +1 -1
  52. package/packages/cli/dist/commands/sync.js +17 -3
  53. package/packages/cli/dist/commands/sync.js.map +1 -1
  54. package/packages/cli/package.json +11 -11
package/README.md CHANGED
@@ -1,941 +1,65 @@
1
1
  # AgenticReplay
2
- <sub>**English** · [简体中文](docs/i18n/README.zh-CN.md) · [日本語](docs/i18n/README.ja.md) · [한국어](docs/i18n/README.ko.md) · [Deutsch](docs/i18n/README.de.md) · [Français](docs/i18n/README.fr.md) · [Español](docs/i18n/README.es.md) · [العربية](docs/i18n/README.ar.md)</sub>
3
-
4
- ### Record model API traffic, inspect the evidence, and replay recorded responses offline.
5
-
6
- AgenticReplay records supported clients at the HTTP proxy or process-local fetch boundary.
7
- It preserves serialized request and response bodies after documented redaction, including
8
- unknown provider fields and streaming content. A run with missing, interrupted, or over-limit
9
- bodies is incomplete. Capture status, upstream outcome, and the agent's exit status are separate.
10
-
11
- The default limit is **64 MiB per body**, configurable with `--max-body-bytes`; the buffering
12
- budget is configurable with `--max-buffered-bytes`. Whole-body redaction runs before persistence.
13
- The viewer exposes transformation details and complete redacted body downloads. Replay's
14
- `canonical_exact`/`canonicalExact` score measures canonical request matching, not transport-byte equality.
15
-
16
- Wrap the **host client** using Kiokuko (for example `agenticreplay record claude -- …`), not only
17
- its `kiokuko mcp` child. Native Kiokuko DSH needs the companion recorder changes. See
18
- [the acceptance status](docs/agenticreplay-acceptance.md) for verified paths and remaining gates.
19
- Provider-hidden reasoning and SDK changes before the capture boundary are not observable.
20
-
21
- [![License](https://img.shields.io/badge/code-Apache--2.0-blue)](LICENSE)
22
-
23
- <sup>Real output from one session — a Claude Code run recorded, replayed against the recording, then
24
- forked at checkpoint 4 onto two models and graded by `npx tsc --noEmit`. Nothing here is mocked up.</sup>
25
-
26
- ## Try it in three commands
27
-
28
- The npm package `agenticreplay` installs the `agenticreplay` command. It wraps the client process
29
- with temporary capture settings; it does not edit personal client configuration.
30
-
31
- ```console
32
- npm i -g agenticreplay # the package is agenticreplay; the command it installs is agenticreplay
33
-
34
- agenticreplay record claude # your agent, unmodified, doing whatever it does
35
- agenticreplay replay last # the same run again — no network, no tokens, no charge
36
- agenticreplay replay last --from 4 --model claude-haiku-4-5 --ui
37
- ```
38
-
39
- The third line is the one people stay for: same files, same conversation prefix, different model
40
- from step 4 onward. The model is the only variable, which is what makes the answer mean anything.
41
-
42
- The three commands at the top need an agent installed, a key, a network and real tokens. If you
43
- have none of those yet, one command brings its own:
44
-
45
- ```console
46
- agenticreplay quickstart
47
- ```
48
-
49
- It writes a small project with a genuine bug in it and a recording of an agent fixing that bug,
50
- then replays the recording against the project with no model called: two failing tests before,
51
- four passing after, three turns served from the trace and nothing spent. `--full` prints the whole
52
- timeline and the replay as it happened.
53
-
54
- ## Read your agent's own system prompt
55
-
56
- A proxy that sees the whole loop also sees the prompt the harness assembled before it sent
57
- anything. One command captures it, scrubs the machine out of it, and files it by model:
58
-
59
- ```console
60
- node capture/capture.mjs claude --model claude-opus-5
61
- ```
62
-
63
- Interactive prompts and `-p` prompts are not the same prompt, and neither is the same across
64
- models. See [`capture/README.md`](capture/README.md) for the measured differences, the pitfalls,
65
- and the sanitising rules.
66
-
67
- ## Why this exists
68
-
69
- Agent debugging today is archaeology. You scroll a terminal, you re-run and get a different
70
- failure, you add print statements to someone else's harness. The tools that exist are
71
- observability tools: they tell you a run cost $4.12 and used 61k tokens, which is not the question
72
- you have. The question you have is *why did it delete my migration file.*
73
-
74
- AgenticReplay answers that by giving you the run back.
75
-
76
- | | Observability tools | AgenticReplay |
77
- |---|---|---|
78
- | Tells you what a run cost | ✅ | ✅ |
79
- | Tells you which tool call deleted the file | sometimes | ✅ |
80
- | Runs the agent again and gets the same answer | ❌ | ✅ from the recording, byte-for-byte |
81
- | Lets you change the model and re-run from step 4 | ❌ | ✅ |
82
- | Needs you to modify your agent | usually an SDK wrapper | ❌ two env vars |
83
- | Works after you close the terminal | ❌ | ✅ it is a file |
84
- | Sees past the model API — shell exit codes, file writes | ❌ | ✅ every turn |
85
- | Records an agent with no API endpoint to redirect | ❌ | ✅ opt-in `--tls-intercept` |
86
-
87
- The last two rows are the ones an SDK wrapper structurally cannot reach. Capture happens *below*
88
- the agent — at the process and socket boundary — so it does not matter whether the agent is
89
- yours, whether you can edit it, or whether it even holds an API key: a Codex CLI signed in with
90
- a ChatGPT subscription talks to its own backend over TLS, with a credential that only that backend
91
- accepts, so it cannot be pointed elsewhere and stay the same session — and agenticreplay can still record
92
- it. See
93
- [when the harness will not be redirected](#when-the-harness-will-not-be-redirected).
94
-
95
- ## How it works
96
-
97
- Model APIs are stateless, so on every turn an agent resends the entire conversation — including the
98
- previous turn's tool results. **A proxy in front of the model therefore sees the whole loop**: each
99
- request, each streamed response, every tool call the model emitted, and every tool result the
100
- harness produced. That one property is what the tool is built on, and it is why **AgenticReplay does
101
- not patch your agent** — it stands up a local proxy, sets two environment variables, and gets out of
102
- the way.
103
-
104
- **What "egress blocked" means, exactly.** On replay the proxy refuses to forward anything it cannot serve from the trace, so no model is called and no tokens are spent — that run prints `egress=blocked`. `--loose` lifts it deliberately: an unmatched request is then answered by the provider and recorded as a major divergence, and the same line reads `egress=live-on-unmatched`. A replay still *executes the recorded tool calls for real*, and a tool that opens its own socket — a shell command running `curl`, an MCP server fetching something — is outside the guarantee either way. By default it never reaches the proxy and goes to the network as usual. Under `--tls-intercept` it does reach the proxy, because that sets `HTTPS_PROXY` for the whole child: a host off the intercept list is tunnelled through untouched, though its hostname, port and byte counts still land in the trace, and a host on the list is refused there like any other unmatched call. Replay is not a sandbox; if you need one, run it inside one.
105
-
106
- Three more layers catch what the protocol cannot see: an exit code, a real duration, which stream a
107
- byte came out of, a file written without telling anyone. A fifth exists for the agents that read no
108
- base-URL variable at all — see [which agents](#which-agents). A sixth reads the agent's own account
109
- of its structure, for a harness that has one: which sub-agent ran, which handed off to which,
110
- whether a guardrail tripped, which graph node produced what and which of them called no model at
111
- all — none of which reaches the wire.
112
-
113
- ```mermaid
114
- %%{init: {'theme':'neutral'}}%%
115
- flowchart LR
116
- A["<b>your agent</b><br/><i>unmodified</i>"]
117
-
118
- subgraph agenticreplay["agenticreplay · six capture layers"]
119
- direction TB
120
- P["<b>proxy</b><br/>base-URL env var"]
121
- SH["<b>PATH shim</b><br/>exit code · timing · streams"]
122
- MC["<b>JSON-RPC tee</b><br/>MCP config rewrite"]
123
- FS["<b>shadow git index</b><br/>workspace per turn"]
124
- FH["<b>fetch hook</b><br/>for a hardcoded origin"]
125
- end
126
-
127
- A --> P & SH & MC & FS & FH
128
- P -->|"forwarded, auth intact"| U["<b>the model API</b><br/><i>or your configured gateway</i>"]
129
- agenticreplay ==> T[("<b>one trace</b><br/>.agenticreplay/runs/run_a1b2c3")]
130
- ```
131
-
132
- They all land in the same timeline, ordered by when they actually happened rather than when agenticreplay
133
- got around to reading them.
134
-
135
- ### Exact, fork and compare are one thing
136
-
137
- They are not three subsystems. They are the same proxy with a **cursor** — the position in the
138
- recorded stream where it stops answering from disk and starts answering from the network.
139
-
140
- ```mermaid
141
- %%{init: {'theme':'neutral'}}%%
142
- flowchart LR
143
- subgraph disk["from disk · byte-for-byte · network blocked"]
144
- direction LR
145
- T1["turn 1"] --> T2["turn 2"] --> T3["turn 3"] --> T4["turn 4"]
146
- end
147
- T4 ==> CUR{{"<b>cursor</b>"}}
148
- CUR ==> T5
149
- subgraph net["from the network · any model you name"]
150
- direction LR
151
- T5["turn 5"] --> T6["turn 6"] --> T7["…"]
152
- end
153
- ```
154
-
155
- | command | where the cursor sits | what you get |
156
- |---|---|---|
157
- | `agenticreplay replay last` | at the end | the whole run again, **network blocked** — no tokens, no charge, no variance |
158
- | `agenticreplay replay last --from 4 --model X` | at checkpoint 4 | turns up to 4 identical, then a different model takes over |
159
- | `agenticreplay compare last --from 4 --models a,b` | at checkpoint 4, several times | one table, one variable — the model |
160
-
161
- A **checkpoint** is not recorded; it is *derived* — any point where the conversation prefix is
162
- complete and the workspace was snapshotted. Every fork therefore starts from a state that provably
163
- existed.
164
-
165
- ## What a bug hunt actually looks like
166
-
167
- Your agent was supposed to fix a failing auth test. It exited 0 and the test still fails. Start with
168
- what it actually did:
169
-
170
- ```console
171
- $ agenticreplay show last
172
- run_6473f858b59e generic-openai@0.1.0 14 events exit 0
173
-
174
- SEQ KIND WHAT DETAIL
175
- 0 RUN run started generic-openai
176
- 1 SNAP tree 919d32ba037537b43814c83779963b2cc3023db7 0 changed
177
- 2 MODEL claude-opus-5 1 messages
178
- 3 MODEL claude-opus-5 stop: tool_use · 100 in · 20 out
179
- 4 TOOL edit_file {"path":"auth.ts",…}
180
- 5 SNAP tree c6af62b75c0c8b8938bd6087328b5148f3dcd534 1 changed
181
- 6 FILE auth.ts modified +1 −3
182
- 7 TOOL edit_file ok
183
- 8 MODEL claude-opus-5 3 messages
184
- 9 MODEL claude-opus-5 stop: end_turn · 101 in · 5 out
185
- 10 SNAP tree c6af62b75c0c8b8938bd6087328b5148f3dcd534 0 changed
186
- 11 SHELL ["sh","-c","node --check nonexistent-file.ts"] /tmp/hunt
187
- 12 SHELL shell result exit 1 · 43ms
188
- 13 RUN run ended exit 0
189
-
190
- info usage input=201 output=25 cost=$0.004890
191
- ```
192
-
193
- Three facts the model's own transcript could not have told you, and the run's exit code hid: the
194
- file really changed (seq 6, `+1 −3`), the check the agent ran **failed** (seq 12, `exit 1`), and it
195
- finished anyway. The run exited 0 because the *agent* exited 0.
196
-
197
- That last fact is the one worth a command of its own. `agenticreplay show` gives you the order things
198
- happened in; `agenticreplay graph` gives you what produced what:
199
-
200
- ```console
201
- $ agenticreplay graph last
202
- FROM TO KIND WHY
203
- 3 model.response 4 tool.call recorded tool_use block in the response
204
- 4 tool.call 6 fs.change inferred changed path appears in tool input, same or previous turn
205
- 4 tool.call 7 tool.result recorded tool result answers its call
206
- 7 tool.result 8 model.request recorded tool_result block in the request
207
- 11 shell.exec 12 shell.result recorded shell result answers its exec
208
-
209
- 1 inferred — derived from this trace, not recorded in it
210
- ```
211
-
212
- Two kinds of edge, and the difference matters. A **recorded** edge was written when the run
213
- happened, because a `tool_use` block is physically inside the response that emitted it. An
214
- **inferred** edge was worked out just now by the rule it names — a filesystem snapshot is taken
215
- once per turn rather than once per tool call, so attributing a file change to a *particular* call
216
- is a good guess and not a fact. Inferred edges are never written back into the trace, the same way
217
- checkpoints are derived and never recorded, so a field a third-party reader trusts never contains
218
- something agenticreplay made up.
219
-
220
- `--graph-card` draws the whole run that way — time left to right, kind of thing top to bottom, with
221
- the chain that produced the failure lit against everything else:
222
-
223
- ![The same run as a causal graph: model, tool and effect lanes across three turns, with the chain to the failing check lit and the inferred hops dashed](docs/graph-card.png)
224
-
225
- The shape is the point. A run is one motif repeated — request, response, call, effect, result — so
226
- anything that breaks it is worth a look, and an event with **no edge leaving it** is an absence a
227
- list cannot show at all.
228
-
229
- `agenticreplay export last --card bug.svg` draws just that chain, which is the version that fits in an issue
230
- or a message:
231
-
232
- ![One causal chain: a model response, the bash tool call it emitted, the shell command, and its exit 1](docs/chain-card.png)
233
-
234
- Nothing picked the subject by hand — `--to` was not passed. The card carries its own legend because
235
- a dashed line travelling without its trace would otherwise launder a guess into a fact, and it
236
- prints the command that reproduces it.
237
-
238
- SVG renders in a GitHub issue and almost nowhere else that matters — X will not take it as an
239
- upload, and Slack and Discord give it no preview — so name the file `.png` and you get one, or
240
- `.gif` and the chain builds a hop at a time. That path needs a browser, and agenticreplay does not depend on
241
- one: `docs/media/README.md` keeps the render toolchain out of `package.json` so nobody running
242
- `npm ci` pays for a Chromium download, and a picture command is not a reason to reverse that. Ask
243
- for a raster without it and agenticreplay says the one line that fixes it; `agenticreplay doctor` reports it either
244
- way, and `.svg` never needs anything.
245
-
246
- ```console
247
- agenticreplay export last --card bug.png # the chain, ready to post
248
- agenticreplay export last --card bug.gif # the same chain, one hop per frame
249
- npm i --no-save playwright-core pngjs gifenc # only needed for the two above
250
- ```
251
-
252
- Now reproduce it as often as you like, for nothing:
253
-
254
- ```console
255
- $ agenticreplay replay last
256
- info replay.done reused=2/2 canonical_exact=2 divergences=0 unmatched=0 exit=0
257
- ```
258
-
259
- No network, no tokens, no variance. Then ask the question you actually have — *would a different
260
- model have got this right?*
261
-
262
- ```console
263
- $ agenticreplay compare last --from 5 --models claude-opus-5,claude-haiku-4-5 --verify "npm test"
264
- MODEL VERDICT TOKENS COST WALL RUN
265
- claude-opus-5 pass 201/25 $0.004890 0.3s run_1457b35062ba
266
- claude-haiku-4-5 pass 201/25 $0.000326 0.3s run_b8ee08479fb6
267
- ```
268
-
269
- Both pass. One costs **15× less**. Same files, same conversation prefix, same checkpoint — the model
270
- is the only thing that changed, which is the only reason that number means anything.
271
-
272
- ## The timeline
273
-
274
- `agenticreplay replay last --ui` (or `agenticreplay ui`) opens the run as one self-contained HTML file — no server
275
- to keep running, no network, nothing to install. Filter it, step it, or press space and watch the
276
- run play back at the pace it actually happened.
277
-
278
- ![The AgenticReplay timeline: filtering a 42-event run down to its tool loop](docs/demo-viewer.gif)
279
-
280
- Every layer lands in the same timeline, so you can read the run as one story rather than four:
281
- the model turns and their token counts, each tool call with its arguments and result, the shell
282
- commands with their exit codes and timing, and the filesystem changes with the tree they produced.
283
-
284
- `agenticreplay export last -o bug.html` writes exactly that page to a single file you can attach to an
285
- issue. It carries no external reference of any kind — CI asserts that — so it renders from a
286
- download folder, on a plane, in five years.
287
-
288
- ## Same task, different model
289
-
290
- `agenticreplay compare` forks one recorded run onto several models from the same checkpoint, with the same
291
- files and the same conversation prefix, and grades each one with a command you choose. The model
292
- is the only variable, which is what makes the answer mean anything.
293
-
294
- ![A comparison table: two models forked from the same checkpoint, both passing, with real token counts and costs](docs/compare-card.png)
295
-
296
- ```console
297
- agenticreplay compare last --from 4 \
298
- --models claude-sonnet-5,claude-haiku-4-5 \
299
- --verify "npm test" \
300
- --share verdict.svg # the card above, ready to paste into an issue
301
- ```
302
-
303
- ### Pointing it at several models
304
-
305
- Comparing models means reaching several providers, and doing that by hand means knowing that
306
- `--upstream-anthropic` and `--upstream-openai` exist, that one gateway can serve both wire formats,
307
- and where the key goes. All of that is real and none of it is discoverable, so there is a command
308
- that asks instead:
309
-
310
- ```console
311
- $ agenticreplay setup
312
- Gateway URL (required): https://gateway.example
313
- API key (stored 0600; leave blank for none):
314
- info config.saved path=~/.config/agenticreplay/config.json mode=0600 gateway=https://gateway.example auth=stored
315
-
316
- 6 models available:
317
- anthropic/claude-opus-5
318
- anthropic/claude-haiku-4-5
319
- openai/gpt-5.2
320
- ...
321
-
322
- $ agenticreplay models
323
- MODEL $/MTOK IN $/MTOK OUT
324
- anthropic/claude-opus-5 15 75
325
- anthropic/claude-haiku-4-5 1 5
326
- openai/gpt-5.2 1.25 10
327
- some-local-model — —
328
- ```
329
-
330
- `agenticreplay setup` asks the gateway what it actually serves rather than just writing the file, so a wrong
331
- URL or a dead key is an answer now instead of a 401 in the middle of a comparison. It also stores the
332
- models you picked, so after that `agenticreplay compare last --verify "npm test"` needs no model list and no
333
- upstream flags at all. `agenticreplay models` prices what it recognises and shows a
334
- dash for what it does not, because inventing a number for an unknown model is how a comparison
335
- table ends up quoting a cost that was never real.
336
-
337
- A gateway must be configured explicitly. Use an endpoint you control or a provider you choose
338
- that supports the required API dialects. There is no default gateway.
339
-
340
- Without a configured gateway, `agenticreplay record` retains the agent's selected provider and
341
- credentials. Non-interactive setup requires `agenticreplay setup --gateway <url> --key-env <VAR>`;
342
- `--key <key>` stores the credential in the private configuration file instead.
343
-
344
- The key never reaches a trace. It is attached to the outbound request only, while what gets
345
- recorded is built from the *incoming* request with auth stripped — so it is invisible to the
346
- recording by construction, not by a rule someone has to remember. It is withheld entirely if a flag
347
- sends that traffic somewhere other than the gateway that issued it.
348
-
349
- ## The other half of the run: `agenticreplay push` and `agenticreplay pull`
350
-
351
- A compatible gateway can store and exchange AgenticReplay archives. Configure its URL explicitly;
352
- legacy OrcaReplay archives are rejected by this clean-break release.
353
-
354
- ```console
355
- $ agenticreplay list --remote # what the gateway is holding
356
- $ agenticreplay push [run] # this machine -> the gateway
357
- $ agenticreplay pull <run> # the gateway -> this machine
358
- ```
359
-
360
- ![agenticreplay push sending a locally recorded run to a gateway, then agenticreplay pull fetching it into a different workspace and agenticreplay show reading the pulled copy](docs/sync-card.png)
361
-
362
- <sub>Real output. The card is generated by `scripts/render-sync-card.mjs` from
363
- `docs/media/sync-transcript.txt`, a captured round trip — `agenticreplay record`, then push, then pull into a
364
- *different* workspace, then `agenticreplay show` reading the pulled copy. No line is written by hand. The
365
- gateway there is a local stub speaking the same two endpoints, which is why its address is a
366
- loopback one.</sub>
367
-
368
- `push` defaults to the last run, like every other run-taking command. `pull` will not: "last" means
369
- nothing on a machine that has not seen the run yet, so it takes an id.
370
-
371
- Which is what `agenticreplay list --remote` is for. An id you do not have is not a default you can supply,
372
- and the only place to read one used to be the console in a browser — so the command that needs an
373
- id lived on a tab the terminal could not open. `--remote` asks the gateway the same question
374
- `agenticreplay list` asks this directory, over the GET on the path push already POSTs to:
375
-
376
- ```console
377
- $ agenticreplay list --remote --limit 3
378
- RUN STARTED SOURCE APP TURNS TOOLS LAYERS MODELS OUTCOME
379
- run_3d9e61a07b52 2026-09-21 09:36 upload generic-openai 1 0 model,fs m exit 0
380
- run_8c14f2e9a07d3b6150e2c49a 2026-09-20 04:21 gateway Unknown 1 0 model,route deepseek/deepseek-v4-flash-free end_turn
381
- run_f07a5c2e91d4468bb3e0a17c 2026-09-20 02:02 gateway curl 1 0 model,route deepseek/deepseek-v4.1-flash error
382
-
383
- agenticreplay pull <run> # fetch one into this machine’s store
384
- ```
385
-
386
- **LAYERS is the column to read.** It is what the two halves of this page are about: `model` and
387
- `route` come from the gateway, which sees the routing decision no laptop can; `fs`, `shell`, `mcp`
388
- and `net` come from a run recorded here. It says what is in a run before you spend a download on
389
- it. SOURCE sits beside it because where a run came from is a different question from what it holds.
390
-
391
- STARTED is the run's first event, not the moment the gateway stored it — for a pushed run those
392
- differ by however long you waited before pushing. OUTCOME is the gateway's word for a run it
393
- recorded (`end_turn`, `error`) and the exit for one pushed from here. Anything the gateway does not
394
- report reads `—` rather than blank; an older deployment sends no timestamp at all, and STARTED is
395
- left unset rather than dated 1970. `--source gateway` narrows to what the gateway recorded;
396
- `--source upload` to what was pushed to it — those two, and a typo is refused rather than answered
397
- with an empty list; without the flag you get both. `--limit` sets how many (20 by default). `--gateway`
398
- stays what it is for push and pull — an optional override of *which* host — rather than doubling
399
- as the switch, so a destination you have already named needs no url typed at it. All three belong
400
- to `--remote`: given without it they are refused, not ignored, because `agenticreplay list --gateway <url>`
401
- answering with this directory's runs would read as that host's answer.
402
-
403
- `--json` asks the same question: `agenticreplay list --remote --json` emits the gateway's runs as one
404
- document on stdout, which is where the id in `agenticreplay pull <run>` wants to come from when nobody
405
- is reading a table.
406
-
407
- ### What it needs
408
-
409
- A key with the `replay` scope, from **Console → API Keys → replay**. That scope can read and push
410
- Sessions runs and cannot relay, so it is not a model-traffic credential wearing another hat — a
411
- replay key is refused on the relay routes outright. Uploads are also off per workspace by default
412
- and turned on under **Settings → request logs**; a gateway that is only recording, not accepting,
413
- is the normal state.
414
-
415
- ```console
416
- $ export AGENTICREPLAY_GATEWAY_URL=https://gateway.example # your explicitly selected gateway
417
- $ read -rs AGENTICREPLAY_GATEWAY_KEY && export AGENTICREPLAY_GATEWAY_KEY # not typed on the command line
418
- $ agenticreplay push last
419
- ```
420
-
421
- Both exports persist for the shell session, which is the point of the rule above: a later
422
- `agenticreplay push --gateway <somewhere-else>` in that same shell is refused rather than quietly handed the
423
- key you exported for your own gateway.
424
-
425
- ### What it will not do
426
-
427
- - **Explicit destination required.** Name the gateway with `--gateway`,
428
- `AGENTICREPLAY_GATEWAY_URL`, or `agenticreplay setup --gateway <url>`. A trace can contain source,
429
- shell output and filesystem snapshots. `push.packed` reports its file count and size before upload.
430
-
431
- - **Never the workspace snapshots, unless you ask.** `agenticreplay scrub` rewrites `events.jsonl` and the
432
- blobs, but it cannot rewrite the shadow git store under `fs/` — its objects are deflated and
433
- addressed by the hash of their contents, so it searches them, tells you what is still in there,
434
- and offers `--drop-fs`. Push therefore leaves `fs/` at home by default: a scrub that reports
435
- "objects still hold the material" must not be followed by a push that ships them anyway. `--fs`
436
- sends them for the case that needs them — a teammate reproducing against the tree — and says so
437
- on the way out, because that is the one part of the payload the scan on either end cannot read
438
- (the objects are compressed, so a plaintext scan sees nothing).
439
- - **Never a key to a host it was not set up for.** Every credential has a home — the stored key's
440
- is `agenticreplay setup`'s gateway, an exported key's is `AGENTICREPLAY_GATEWAY_URL`, and a key exported
441
- with no URL beside it has no home at all — and it is attached only when the destination matches
442
- that home. `--gateway` changes where the run goes, not what the key was
443
- issued for, so pointing it elsewhere refuses the push rather than authenticating it with someone
444
- else's credential. The one case left through is a key whose only destination is the one this
445
- invocation names: nothing earlier associated it with a host, which is the CI shape.
446
- - **Never anonymously.** None of the three is attempted without a key. An unauthenticated request
447
- is exactly what a misconfigured public endpoint answers, and a `200` is a poor way to learn
448
- either that your run went somewhere with no owner, or that what a listing showed you was never
449
- scoped to you.
450
- - **Never a partial replace.** `pull --force` writes the new copy beside the old one and swaps by
451
- rename, so a crash leaves a complete run either way, and the next pull finishes or rolls back
452
- whatever the last one left behind. `--force` asks to replace a recording, which is not a licence
453
- to destroy it and then fail.
454
2
 
455
- ### What the gateway does on arrival
456
-
457
- It re-computes the integrity root over `events.jsonl` and refuses a push whose root does not match
458
- its manifest — which is why push sends the bytes already on disk and converts nothing. It also
459
- scans the archive for plaintext secrets and **refuses** it on a hit. That refusal is the point of
460
- `--force`, and why `--force` is opt-in: the default is what stops a key reaching a shared server.
461
-
462
- Redaction itself is not re-run here. `TraceWriter` puts every event through *spill, redact,
463
- validate, append* on its way to disk precisely so no caller can bypass it, so the bytes push reads
464
- are already redacted; re-scrubbing them would rewrite the bytes the integrity root covers, which is
465
- the one thing that makes an archive unpushable. The gateway's scan is a second, independent line,
466
- not a repeat of the first.
467
-
468
- ## Which agents
469
-
470
- Two things decide whether a harness can be recorded: whether it can be pointed at the proxy, and
471
- whether agenticreplay understands the wire format it speaks once it arrives.
472
-
473
- | Agent | How it is captured | State |
474
- |---|---|---|
475
- | **Claude Code** | `ANTHROPIC_BASE_URL` | works — validated against a real bug fix, [in detail](docs/validation.md) |
476
- | **Codex CLI** (API key) | Invocation-scoped selected-provider URL override → Responses API | adapter implemented; current installed-client acceptance remains blocked — see [status](docs/agenticreplay-acceptance.md) |
477
- | **Codex CLI** (ChatGPT login) | `--tls-intercept` → Responses API | protocol fixtures pass; current authenticated-client acceptance remains unverified |
478
- | **OpenAI Agents SDK** | `OPENAI_BASE_URL` → Responses API | works — the SDK itself, on the Responses API it defaults to, records and replays at `canonical_exact=1`, [in CI](test/integrations/); [its own tracing is a second egress](docs/integrations.md#openai-agents-sdk) |
479
- | **Vercel AI SDK** | `OPENAI_BASE_URL` via `@ai-sdk/openai` (since 2.0.41) — `agenticreplay record generic-openai --`; fetch hook (`agenticreplay record node --`) when the origin is compiled in | works — env-aware SDK clients via `generic-openai` at `canonical_exact=1`, [in CI](test/integrations/); an agent posting to an origin compiled into its source records and replays at `canonical_exact=1` |
480
- | **grok-cli** (and its Telegram bot) | `agenticreplay record grok` — `GROK_BASE_URL`, plus the hook for its sub-agents | works |
481
- | **OpenClaw** | `agenticreplay record openclaw` — the hook for the gateway, inherited variables for the agents it spawns | works |
482
- | **opencode** | `agenticreplay record opencode` | adapter shipped, both origins redirected |
483
- | **goose** (Block) | `agenticreplay record goose` — `OPENAI_HOST` **and** `OPENAI_BASE_URL`, `ANTHROPIC_HOST` → Responses API | works — driven end to end against goose 1.49.0, [what is different about it](#the-harness-that-reads-different-variables) |
484
- | **LangGraph / LangChain** | `OPENAI_BASE_URL`, `ANTHROPIC_BASE_URL` | works — a two-node graph, streaming and with a tool, records and replays at `canonical_exact=2` and forks live, [in CI](test/integrations/); `pip install agenticreplay-langgraph` [also records the graph](python-langgraph/README.md) — which node ran, in which superstep, including the ones that call no model |
485
- | **Haystack** | `agenticreplay record generic-openai -- python your_pipeline.py` — its `OpenAIChatGenerator` leaves `base_url` to the OpenAI SDK, which reads `OPENAI_BASE_URL` | works — a `Pipeline` with `ChatPromptBuilder` and `OpenAIChatGenerator` records and replays at `canonical_exact=1`, [in CI](test/integrations/) |
486
- | **OpenHands** | `agenticreplay record generic-openai -- python your_agent.py` — its SDK wraps LiteLLM and reads `OPENAI_API_BASE` | works — the SDK's own LLM layer records and replays at `canonical_exact=1`, [in CI](test/integrations/) |
487
- | **CrewAI** | `agenticreplay record generic-openai -- python your_crew.py` — since 1.x its own provider, reading `OPENAI_API_BASE` and `OPENAI_BASE_URL` | works — a real `Agent`, `Task` and `Crew` records and replays at `canonical_exact=1`, [in CI](test/integrations/); [what 1.x changed](docs/integrations.md#crewai) |
488
- | **Aider** | `agenticreplay record generic-openai -- python your_agent.py` — routes through LiteLLM, which reads `OPENAI_API_BASE` | works — the LiteLLM layer records and replays at `canonical_exact=1`, [in CI](test/integrations/) |
489
- | **browser-use** | `agenticreplay record generic-openai -- python your_task.py` — its `ChatOpenAI` passes an unset `base_url` straight through | works — records and replays at `canonical_exact=1`, [in CI](test/integrations/); LLM layer only, [the browser is not driven](docs/integrations.md#browser-use) |
490
- | **Hermes** (Nous Research) | `agenticreplay record hermes --tls-intercept -- …` | transport adapter implemented; current installed-client acceptance remains unverified |
491
- | **IndexRAG** | `agenticreplay record indexrag -- --data-dir dataset/<name>/documents` — the whole pipeline as one run: `OPENAI_BASE_URL` for chat, `INDEXRAG_EMBEDDING_BASE_URL` for a second origin | works — driven end to end against the real pipeline: indexing, embeddings and answering replay offline at `canonical_exact=11/11 retrieval=3/3`, and a fork keeps the recorded index, [what is different about a pipeline](docs/integrations.md#rag-and-retrieval-frameworks) |
492
- | **a RAG pipeline** (LlamaIndex, GraphRAG, LightRAG) | `agenticreplay record generic-openai -- <cmd>` — embeddings and rerank replay without a dialect | works — a concurrent index build plus an embedding batch records and replays at `canonical_exact=5 retrieval=2`, [in CI](test/integrations/) |
493
- | **Codex-in-the-IDE** | `agenticreplay record exec --tls-intercept -- code .` | works — the extension spawns the agent, and it inherits the capture |
494
- | **a bot with a hardcoded origin** | `agenticreplay record exec --tls-intercept -- <cmd>` | works — a Grok bot posting to a URL in its own source, [in detail](#an-agent-that-reads-nothing-at-all) |
495
- | **an agent in a sandbox or on another machine** | `agenticreplay attach` | works — agenticreplay is reachable and prints what to export, [in detail](#an-agent-that-is-not-on-this-machine) |
496
- | **anything else** | `agenticreplay record generic-openai -- <cmd>` | works if it reads a base-URL variable; `agenticreplay record node -- <cmd>` if it does not |
497
-
498
- Claude Code, Hermes and goose have been driven end to end against the real harness, and each of
499
- them broke something. Claude Code [broke four things](docs/validation.md); Hermes found a streaming
500
- exchange the proxy was dropping entirely; goose broke two more, and neither was in the adapter —
501
- one was the replay matcher, one was a run reporting success over a trace of nothing but errors. The
502
- rest are held to the adapter contract and to fixtures that record the exact variables each one
503
- sets, so a harness that renames the variable it reads turns a check red instead of producing an
504
- empty trace.
505
-
506
- ### The harness that reads different variables
507
-
508
- Every other OpenAI-shaped client in this table reads `OPENAI_BASE_URL`. goose reads it too, but it
509
- reads `OPENAI_HOST` **first** — and for Anthropic it reads `ANTHROPIC_HOST` and nothing else.
510
-
511
- That combination is worse than it sounds, because both halves fail silently:
512
-
513
- - Setting only `OPENAI_BASE_URL` works right up until the user already has `OPENAI_HOST` exported
514
- for something else. Then their value wins, the run goes to their origin, and agenticreplay prints a clean
515
- recording over an empty trace.
516
- - `ANTHROPIC_BASE_URL` — the variable `generic-openai` sets, and the one the rest of the ecosystem
517
- reads — does nothing at all here. A sink addressed only by it receives no request.
518
-
519
- So the adapter sets `OPENAI_HOST`, `OPENAI_BASE_URL` and `ANTHROPIC_HOST`, all at the same proxy,
520
- and the fixture pins all three. The traffic that arrives is the Responses API — `POST /v1/responses`
521
- plus one `GET /v1/models` on start-up — which the proxy's `openai-responses` dialect already claims.
522
-
523
- ```console
524
- GOOSE_PROVIDER=openai GOOSE_MODEL=<model> agenticreplay record goose -- run -t "fix the failing test"
525
- ```
526
-
527
- `GOOSE_PROVIDER` and `GOOSE_MODEL` are passed through, never invented: goose has no default for a
528
- custom endpoint, and choosing one for you would launch a different agent than `goose` does.
529
-
530
- Two limits worth knowing before you rely on it. goose runs its shell without going through agenticreplay's
531
- PATH shim, so `shell` tool calls are in the trace with their output but without the real exit code,
532
- duration or stdout/stderr split — record with `--no-shell` to claim nothing rather than that. And
533
- goose asks a second model for a session title *while the conversation is already under way*, so
534
- that call and the first real turn race; the replay matcher handles them arriving in either order,
535
- which it did not before goose was the first harness to do this.
536
-
537
- ### A gateway that launches the coding agent
538
-
539
- OpenClaw does not do the coding: it runs Claude Code, Codex or opencode as child processes and
540
- drives them from a chat app, so one run carries two kinds of model traffic. The gateway's own calls
541
- are caught by the fetch hook. The coding agent's calls are caught by the ordinary variables — not
542
- because OpenClaw reads them, but because **a child process inherits its parent's environment**, so
543
- the Claude Code it spawns sees `ANTHROPIC_BASE_URL` exactly as it would if you had run it yourself.
544
-
545
- ```console
546
- agenticreplay record openclaw
547
- ```
548
-
549
- That inheritance is a property of the operating system rather than of agenticreplay, which is the kind of
550
- thing that stays obviously true right up until some layer in between sanitises the environment. So
551
- it has a test: a gateway fixture that makes no model call of its own, spawns an agent that does, and
552
- is recorded and replayed offline through the grandchild's traffic.
553
-
554
- ### An agent that reads nothing at all
555
-
556
- A bot with `https://api.x.ai/v1` typed into its source reads no variable, so nothing can be
557
- redirected. It is also, by some distance, the most common shape of agent people write. Capture it
558
- below the agent instead, at the socket:
559
-
560
- ```console
561
- agenticreplay record exec --tls-intercept -- python bot.py
562
- agenticreplay record exec --tls-intercept -- ./my-agent --task "fix the test"
563
- ```
564
-
565
- `exec` (or `any`) launches your command and sets nothing — no origin and no credential — so the bot
566
- still talks to whoever it was already talking to, and agenticreplay reads the conversation on the way past.
567
- `api.x.ai` is on the default decrypt list, so a Grok bot needs no host named.
568
-
569
- The same command reaches an agent agenticreplay did not launch, as long as something agenticreplay *did* launch is
570
- its ancestor. That is the shape of Codex-in-the-IDE: the extension spawns the agent as a child, so
571
- recording the editor captures the agent underneath it.
572
-
573
- ```console
574
- agenticreplay record exec --tls-intercept -- code .
575
- agenticreplay record exec --tls-intercept -- cursor .
576
- ```
577
-
578
- Replay needs no flags repeated. A run recorded through interception writes down which hosts it
579
- decrypted, and `agenticreplay replay` reads that back and re-establishes the same policy — `--no-tls-intercept`
580
- if you would rather it did not.
581
-
582
- To decrypt an endpoint the default list does not cover, add it rather than replacing the list:
583
-
584
- ```console
585
- agenticreplay record exec --tls-intercept --tls-hosts '+my-gateway.internal' -- ./my-agent
586
- ```
587
-
588
- A plain `--tls-hosts a,b` still means "decrypt exactly these", as it always has. Mixing the two
589
- forms is refused rather than guessed at.
590
-
591
- ### An agent that is not on this machine
592
-
593
- An agent in a dev container, on a VPS, or in someone's CI cannot be launched by agenticreplay, so there is
594
- no environment for it to build. `agenticreplay attach` turns it around: agenticreplay holds the proxy open, prints
595
- the block to paste on the far side, and records whatever arrives until you stop it.
596
-
597
- ```console
598
- agenticreplay attach --for claude
599
- agenticreplay attach --for claude --bind 0.0.0.0 --advertise host.docker.internal
600
- agenticreplay attach --tls-intercept # for an agent over there that reads no variable either
601
- ```
602
-
603
- ```
604
- info attached run=run_e3d64e15ee8f proxy=http://127.0.0.1:33437 for=claude-code
605
-
606
- # in the sandbox, before starting your agent:
607
- export ANTHROPIC_BASE_URL='http://127.0.0.1:33437'
608
-
609
- Recording. Press ctrl-C when the agent is done.
610
- ```
611
-
612
- The variables come from the same adapters `agenticreplay record` uses, so a sandbox recording cannot drift
613
- from a local one. `--advertise` is required when you bind a wildcard: `0.0.0.0` is a statement about
614
- listening, not an address anything can dial, and agenticreplay refuses to print a URL that cannot work.
615
-
616
- Replaying such a run has the same problem in reverse — the agent may not exist on this machine — so
617
- replay attaches too:
618
-
619
- ```console
620
- agenticreplay attach --replay <run>
621
- ```
622
-
623
- It serves the recording with egress blocked and takes the harness from the recording itself. Point
624
- the same remote agent at it and the run happens again, offline, with no model called.
625
-
626
- ### A base-URL variable agenticreplay has never heard of
627
-
628
- Enumerating them is hopeless. Hermes overrides per provider, and its own `.env.example` carries
629
- `NOVITA_BASE_URL`, `GLM_BASE_URL`, `KIMI_BASE_URL`, `MINIMAX_BASE_URL`, `HF_BASE_URL`,
630
- `NEBIUS_BASE_URL` and a dozen more. A list baked into agenticreplay would be stale the week after it was
631
- written, so name the variable instead:
632
-
633
- ```console
634
- AGENTICREPLAY_BASE_URL_VARS='CUSTOM_API_BASE_URL' agenticreplay record generic-openai -- my-agent
635
- AGENTICREPLAY_BASE_URL_VARS='GLM_BASE_URL,KIMI_BASE_URL' agenticreplay record generic-openai -- my-agent
636
- AGENTICREPLAY_BASE_URL_VARS='SOMETHING_BASE_URL=/' agenticreplay record node -- node agent.mjs
637
- ```
638
-
639
- Each name is pointed at the proxy with `/v1` appended, which is what an OpenAI-compatible override
640
- wants; `=<path>` overrides that, and `=/` gives the bare origin.
641
-
642
- If you record an agent this way and it works, an adapter is about twenty lines —
643
- [docs/plugins.md](docs/plugins.md). If it does not, the trace is the most useful thing you can send:
644
- `agenticreplay export last -o run.html`.
645
-
646
- ## When the harness will not be redirected
647
-
648
- Base-URL injection captures every harness that reads a base-URL variable, and the fetch hook covers
649
- the Node ones that do not. A Codex CLI signed in with a ChatGPT subscription is neither — but not
650
- for the reason this section used to give.
651
-
652
- It *does* read a provider base URL, and an explicit one overrides the ChatGPT backend; both paths
653
- even speak the same wire protocol. What is bound to that backend is the **credential**: a signed-in
654
- subscription carries a plan token the ChatGPT backend accepts and `api.openai.com` does not. Point
655
- the URL somewhere else and Codex asks for an API key instead — you are no longer recording the
656
- subscription session, you are recording a different one. Codex is Rust, so there is no `fetch` of
657
- ours to reach either.
658
-
659
- `--tls-intercept` is the answer to that, and it is deliberately a separate decision you have to
660
- make, because it mints a certificate authority.
661
-
662
- ```console
663
- agenticreplay record codex --tls-intercept
664
- agenticreplay record codex --tls-intercept --tls-hosts 'api.openai.com,*.chatgpt.com'
665
- ```
666
-
667
- The CA is unique to the run, trusted only by the agent agenticreplay launches — through that child's own
668
- environment, never a system or browser trust store — and deleted when the run ends. AgenticReplay will not
669
- offer to install it anywhere. Hosts outside the allowlist are tunnelled unread and recorded as an
670
- address and a byte count, with no path and no body, because agenticreplay never held the plaintext. Asking
671
- to intercept everything is refused rather than honoured.
672
-
673
- What comes back through it is not a log line. An intercepted request is parsed by the same wire
674
- dialects as any other, so it lands in the trace as an ordinary exchange — replayable offline and
675
- forkable to a different model, on a run that never had an API key of yours in it.
676
-
677
- It works on `agenticreplay replay --model`, `agenticreplay fork` and `agenticreplay compare` too, which launch a live agent for
678
- the same reason.
679
-
680
- ## For an agent, a script, or CI
681
-
682
- A trace is a file, which is the one thing an observability dashboard cannot be — so the most useful
683
- question about a failed run is one an *agent* can ask: *replay my last run and tell me what
684
- diverged.* Every command answers as data, and agenticreplay serves itself over MCP.
685
-
686
- ```console
687
- $ agenticreplay replay last --json
688
- {"runId":"run_a278eea7b535","mode":"exact","traceRunId":"run_687e3f84b208","matchedExact":2,"divergences":0,"unmatched":0,"liveCalls":0,"exitCode":0}
689
-
690
- $ agenticreplay show last --json | jq '.events[] | select(.kind == "TOOL")'
691
- $ agenticreplay checkpoints last --json | jq '.[-1].seq'
692
- ```
693
-
694
- One JSON document on stdout, diagnostics on stderr — including the recorded agent's own output, so
695
- the document stays parseable while a run is talking. Failures answer in JSON too, with a non-zero
696
- exit. `--json` covers `list`, `show`, `events`, `checkpoints`, `graph`, `record`, `replay`,
697
- `compare` and `doctor`.
698
-
699
- **As tools.** `agenticreplay mcp` serves the trace store to an agent over stdio:
700
-
701
- ```json
702
- { "mcpServers": { "agenticreplay": { "command": "agenticreplay", "args": ["mcp"] } } }
703
- ```
704
-
705
- `agenticreplay_list_runs`, `agenticreplay_show_run`, `agenticreplay_checkpoints`, `agenticreplay_graph`, `agenticreplay_replay` and
706
- `agenticreplay_compare`. Replay is free and offline; `agenticreplay_compare` says in its own description that it
707
- spends real tokens, because a model choosing a tool reads that string and nothing else — and
708
- `agenticreplay_graph` spends its description saying what `recorded` and `inferred` mean, for the same
709
- reason.
710
-
711
- **From code**, if you would rather not shell out:
712
-
713
- ```ts
714
- import { AgenticReplay } from 'agenticreplay';
715
-
716
- const agenticreplay = new AgenticReplay({ cwd: process.cwd() });
717
- const { unmatched, divergences } = await agenticreplay.replay('last');
718
- const timeline = await agenticreplay.show('last');
719
- ```
720
-
721
- It never writes to your stdout and never calls `process.exit` — both asserted, because a library
722
- that does either cannot be built on.
723
-
724
- ## Status
725
-
726
- Early. `v0` is the walking skeleton of the three commands above. Everything below is exercised by
727
- The test suite, the trace-format conformance check and a plugin-API neutrality check run on Node 24 and 26.
728
-
729
- | Capability | State |
730
- |---|---|
731
- | Trace format v0 + JSON Schema | working |
732
- | Anthropic / OpenAI-compatible model capture | working |
733
- | OpenAI Responses API capture | working — the format the OpenAI Agents SDK and the Codex CLI default to. Records, replays offline and forks; a fork stays on the wire format the agent speaks |
734
- | Agents that read no base-URL variable | working — `agenticreplay record node -- <cmd>` writes a preload into the run directory and redirects `globalThis.fetch` for an allowlist of provider hosts. Node and Bun both, since Bun ignores `--require` in `NODE_OPTIONS`. Use this when the origin is compiled into source; ordinary `@ai-sdk/openai` clients that honour `OPENAI_BASE_URL` go through `generic-openai` instead |
735
- | A call agenticreplay cannot read | working — forwarded rather than refused, and recorded as `net.request` / `net.response`: evidence, not a replayable turn. A recording that captured nothing warns instead of exiting clean |
736
- | Machine-readable output (`--json`) | working — one JSON document on stdout, diagnostics on stderr, failures as JSON |
737
- | Causal graph (`agenticreplay graph`) | working — what caused what, as a table or as JSON. Every edge says whether the trace recorded it or agenticreplay derived it just now, and names the rule either way. `--to N` narrows to the chain that produced one event |
738
- | Shareable cards | working — `agenticreplay export --card` draws one causal chain, `--graph-card` draws the whole run with that chain lit, and `compare --share` draws the verdict table. `.svg` always; `.png` and `.gif` when the optional render toolchain is installed, which `agenticreplay doctor` reports and `npm ci` never pulls in |
739
- | First run without an agent (`agenticreplay quickstart`) | working — the package carries a real recording and the project it was made against, and replays one over the other offline, so the first look costs no key, no network and no tokens |
740
- | MCP server (`agenticreplay mcp`) | working — six tools over stdio, so an agent can read, explain and replay its own runs |
741
- | Programmatic API (`AgenticReplay`) | working — the commands render what it returns, so the terminal is a view of one source of truth |
742
- | Replaying a session you typed into | working, and approximate — [what that means](#replaying-a-session-you-typed-into) |
743
- | Exact replay with divergence reporting | working — restores the recorded filesystem over your working tree, then puts it back; `--worktree` for a scratch copy, `--in-place` to restore nothing. Writes a run of its own recording what the replay *discovered* — divergences, unmatched requests — and points at the parent for what it merely repeated; `--no-trace` to skip |
744
- | Fork replay from a checkpoint | working — a fork records its own filesystem snapshots, so it is a run you can fork again |
745
- | Compare across models | working — `agenticreplay setup` stores an explicitly selected gateway, key and model list, so `agenticreplay compare` needs no flags |
746
- | Filesystem snapshots and diffs | working |
747
- | Single-file HTML export | working |
748
- | MCP call recording | working — opt in with `--mcp-config <path>`. Replay and fork re-instrument from the config the recording used, so the layer does not stop at the fork point |
749
- | Post-hoc scrubbing (`agenticreplay scrub`) | working |
750
- | Shell capture (`PATH` shim) | working — exit codes, duration and the stdout/stderr split. `--no-shell` to skip |
751
- | Non-model network capture | working — opt in with `--tls-intercept`; mints a per-run CA the launched agent alone trusts, decrypts an allowlist of hosts, tunnels the rest unread, and deletes the key when the run ends |
752
- | Codex subscription model capture/replay | working — recognizes the `/backend-api/codex/responses` HTTPS fallback, decodes zstd request bodies for matching, and serves the recorded SSE response without opening the origin during replay |
753
- | Validated against a real agent | Claude Code, recording a real fix to a real bug: recorded, replayed offline end to end, forked from a checkpoint and exported. It broke four things no fixture could have produced, all since fixed — [what a real agent found](docs/validation.md) |
754
- | Subscription-auth harnesses | Claude Code works. A Codex CLI signed in with a ChatGPT subscription carries a plan credential only its own backend accepts, so redirecting the URL would record a different session rather than that one: it needs `--tls-intercept`. With an API key it needs nothing special |
755
-
756
- ## Replaying a session you typed into
757
-
758
- A run started as `agenticreplay record claude` and driven by hand has its prompts nowhere on the wire. AgenticReplay
759
- recovers them from the harness's own transcript and replays the session by handing them back
760
- without a terminal — which reproduces the conversation, and does not reproduce the run byte for
761
- byte. Two differences are inherent rather than defects, and agenticreplay names both rather than papering
762
- over them:
3
+ <sub>**English** · [简体中文](docs/i18n/README.zh-CN.md) · [日本語](docs/i18n/README.ja.md) · [한국어](docs/i18n/README.ko.md) · [Deutsch](docs/i18n/README.de.md) · [Français](docs/i18n/README.fr.md) · [Español](docs/i18n/README.es.md) · [العربية](docs/i18n/README.ar.md)</sub>
763
4
 
764
- - **The harness makes calls for itself.** A quota probe before the first turn, a request to name
765
- the session, a delegation prompt written fresh for a sub-agent. A replay does not repeat them.
766
- The matcher steps over them onto the next real match and reports how many, so `reused=3/5` on an
767
- interactive recording is a complete replay of what you asked, not a partial one.
768
- - **A terminal session is offered tools a replay cannot have.** `AskUserQuestion`, `EnterPlanMode`,
769
- `ExitPlanMode`, `EndConversation` all need a person in front of them, so they are absent when the
770
- same agent is driven without one. Their schemas are large, so a request carrying them can differ
771
- from the recorded one by enough to halt; the halt says which tools are missing and why.
5
+ AgenticReplay logs AI API traffic to help debug and improve AI harnesses. It captures requests
6
+ and responses from supported clients, including reasoning/thinking content or summaries returned
7
+ by the API.
772
8
 
773
- Recording with a prompt in argv — `agenticreplay record claude -- -p "…"` — has neither problem, because the
774
- replay is started exactly the way the recording was. Use that for a run you intend to replay
775
- exactly. Reading a run is unaffected either way: `agenticreplay show` and the viewer are complete for both.
9
+ Compare the trace with tool results, captured shell commands and file changes to find where a run
10
+ went wrong.
776
11
 
777
12
  ## Install
778
13
 
779
- ```console
780
- npm i -g agenticreplay
781
- agenticreplay doctor # checks node, git, and which agents it can find
782
- ```
783
-
784
- Node 24 or newer. No native dependencies, so there is nothing to compile and nothing fetched at
785
- install time beyond the tarballs themselves.
786
-
787
- The CLI package bundles its runtime dependencies, including the internal workspace libraries.
788
-
789
- **From source**, to work on it or to run an unreleased commit:
790
-
791
- ```console
792
- git clone https://github.com/askdkc/AgenticReplay && cd AgenticReplay
793
- npm ci && npm run build
794
- npm install -g . # puts `agenticreplay` on PATH
795
- ```
796
-
797
- To release from the repository root:
14
+ Requires Node.js 24+, Git, and a configured AI harness.
798
15
 
799
16
  ```console
17
+ git clone https://github.com/askdkc/AgenticReplay.git
18
+ cd AgenticReplay
800
19
  npm ci
801
- npm publish
20
+ npm run build
21
+ npm install -g .
22
+ agenticreplay doctor
802
23
  ```
803
24
 
804
- The `prepack` lifecycle builds the CLI and validates the workspace dependency versions. npm packs
805
- the binary, quickstart assets, and runtime dependencies before uploading them. To create a local
806
- archive without uploading, run `npm pack`; it writes `agenticreplay-<version>.tgz`. Adding
807
- `--dry-run` only previews the archive. `node scripts/installed-smoke.mjs` verifies one actual root
808
- archive in a clean offline installation.
809
-
810
- **Node 24+ to run it** (the CLI's `engines` says `>=24.0.0`). Development and CI use Node 24 or
811
- 26. No account, no signup, no API key changes.
812
-
813
- On Windows, shell capture writes `.cmd` shims in front of `sh.exe` and `bash.exe` (Git for Windows
814
- provides both), and Windows runs a `.cmd` only through a shell. So it captures a shell started
815
- through cmd.exe or PowerShell, and not one an agent starts itself: a Node harness's
816
- `child_process.spawn('bash')` goes straight to the real `bash.exe`, and Claude Code finds its shell
817
- without consulting `PATH` at all. `agenticreplay doctor` says which of the two holds on the machine, and
818
- `agenticreplay record` warns `shell.ineffective` on any run whose tool calls ran commands the shim never
819
- saw — record those with `--no-shell`. If no POSIX shell is on `PATH`, doctor warns and
820
- `--no-shell` is the way to record.
821
-
822
- ## Where your runs are kept
823
-
824
- Everything lands in **`.agenticreplay/runs/` inside the project you recorded in** — per-project, never a
825
- global store, so a run travels with the checkout it belongs to. One run directory is one
826
- self-describing thing:
827
-
828
- ```
829
- .agenticreplay/
830
- .gitignore # just `*` — the store excludes itself, so a trace cannot be committed by accident
831
- runs/run_d0a2ee7ce615/
832
- manifest.json # who, when, which adapter, the git commit, counts, integrity digest
833
- events.jsonl # the timeline, one JSON object per line, append-only
834
- blobs/ # content-addressed payloads over 4 KB, deduplicated
835
- fs/ # shadow git index: the workspace at every turn
836
- shims/ # the PATH shims this run used; written by agenticreplay, read by nothing
837
- py/ # bootstrap for the agent-structure layer, when the agent is Python
838
- redactions.json # what was removed, by rule and count — never by value
839
- ```
25
+ ## Use
840
26
 
841
- Finding an old session:
27
+ Run these commands in the project you want to investigate:
842
28
 
843
29
  ```console
844
- agenticreplay list # every run here, newest first, with what it was forked from
845
- agenticreplay list --remote # and what the gateway is holding, without opening a browser
846
- agenticreplay show run_d0a2ee7ce615 # the timeline in the terminal
847
- agenticreplay replay last # `last` = newest recording (it skips replay traces)
848
- agenticreplay replay run_d0a2ee7ce615 # or name one outright
849
- agenticreplay gc --older-than 7d --dry-run # what would be reclaimed, before anything is
30
+ agenticreplay record claude -- -p "Investigate the failing test"
31
+ agenticreplay show last
32
+ agenticreplay export last -o run.html
850
33
  ```
851
34
 
852
- `agenticreplay list` reads the run directories directly, so it works on a trace someone sent you: drop it in
853
- `.agenticreplay/runs/` and every command sees it. Nothing indexes, and there is no database to corrupt.
854
- `--remote` is the one exception, and it is not an index either: it asks the gateway and prints the
855
- answer, holding nothing.
35
+ Record the harness process that sends the API requests, such as Claude Code. Logs are stored in
36
+ `.agenticreplay/runs/` in that project; `last` selects the newest recording. Open `run.html` in a
37
+ browser to inspect the trace.
856
38
 
857
- ## Privacy
39
+ Capture directories use `YYYYMMDDHHmmss-agent-title-id` (UTC). Names contain only lowercase
40
+ ASCII letters, digits and hyphens: at most 255 characters, with titles capped at 80.
41
+ Titles come from the session or, if unavailable, the first user prompt; they are redacted and
42
+ transliterated to ASCII. The final name is applied when recording closes. Existing `run_<id>`
43
+ directories and commands using a run ID remain supported.
858
44
 
859
- Traces are local, mode `0600`, and the recorder makes no network connection of its own. Secrets are
860
- redacted in the write path: environment capture is deny-by-default, auth headers are never written,
861
- and known key shapes plus high-entropy strings are replaced with stable placeholders.
862
-
863
- Redaction is best-effort mitigation, not a guarantee. **Treat a trace as sensitive** — roughly as
864
- sensitive as a shell history plus a heap dump.
45
+ For a custom harness that reads `OPENAI_BASE_URL`, replace `node agent.js` with its launch command:
865
46
 
866
47
  ```console
867
- agenticreplay export last -o bug.html # prints exactly what it is about to write
868
- agenticreplay scrub last --match my-hostname # remove something after the fact
48
+ agenticreplay record generic-openai -- node agent.js
869
49
  ```
870
50
 
871
- `agenticreplay scrub` rewrites `events.jsonl`, the manifest and every text blob, re-runs the standard
872
- detectors, refreshes the integrity digest, and leaves binary blobs byte-identical.
873
-
874
- It cannot rewrite the filesystem snapshots. Git objects are addressed by the hash of their own
875
- contents, so editing one changes its id, which forces every tree naming it to be rewritten and
876
- every event naming those trees after that — a history rewrite whose failure mode is a run that no
877
- longer restores. So scrub *searches* the snapshot store and tells you when your string is still in
878
- there, rather than reporting a clean trace it could not clean. `--drop-fs` deletes the store
879
- outright, at the cost of being able to fork the run.
880
-
881
- ## What is open, and what is not
882
-
883
- Always open, under Apache-2.0: the trace format, the core, the CLI, the viewer, the adapters, and
884
- the provider interface.
885
-
886
- AgenticReplay is a fork of [OrcaReplay](https://github.com/Continuum-AI-Corp/OrcaReplay).
887
- Original copyright notices and the Apache-2.0 license are retained. This fork uses its own package
888
- names, configuration and trace format, and does not promote or select a gateway by default.
889
-
890
- Provider plugins use the public `Provider` interface in `@agenticreplay/plugin-api`.
891
- No vendor
892
- plugin exists yet, so the CI job that enforces this (`scripts/check-neutrality.mjs`) says so and
893
- passes as a no-op; it starts building against the published package rather than workspace source the
894
- moment one lands. If a plugin ever needs a capability, that capability goes into the public
895
- interface first, with a second implementation showing it is not shaped around one vendor.
896
-
897
- ## Documentation
898
-
899
- **Start here if you have a problem right now:**
51
+ For Codex with a ChatGPT subscription, use TLS interception:
900
52
 
901
- - [My agent broke something. How do I find out why?](docs/how-to/debug-a-failing-agent-run.md)
902
- - [Why did my agent delete that file?](docs/how-to/why-did-my-agent-delete-my-file.md)
903
- - [Would a different model have got this right?](docs/how-to/compare-models-on-the-same-failure.md)
904
-
905
- **Reference:**
906
-
907
- - [`spec/agenticreplay-trace-v0.md`](spec/agenticreplay-trace-v0.md) — the normative trace format
908
- - [`docs/integrations.md`](docs/integrations.md) — recording a framework, one command each, every number asserted in CI
909
- - [`docs/architecture.md`](docs/architecture.md) — how capture, replay and fork actually work
910
- - [`docs/validation.md`](docs/validation.md) — what broke the first time this met a real agent
911
- - [`docs/launch-path.md`](docs/launch-path.md) — what is built, what is not, and what is next
912
- - [`docs/plugins.md`](docs/plugins.md) — writing an adapter or a provider
913
- - [`CONTRIBUTING.md`](CONTRIBUTING.md) — five-minute dev loop
914
- - [Good first issues](docs/good-first-issues.md) — twelve of them, with the file to start in
915
-
916
- ## Help wanted
917
-
918
- The format is v0 and the walking skeleton works, which is the interesting point in a project's life:
919
- the decisions are still cheap to change and there is a lot of obvious work with the file to start in
920
- already written down.
53
+ ```console
54
+ agenticreplay record exec --tls-intercept -- codex
55
+ ```
921
56
 
922
- - **[Twelve good first issues](docs/good-first-issues.md)**, each naming the file and the test.
923
- - **Write an adapter.** One file, one fixture. If your harness reads a base-URL variable it is
924
- about twenty lines — [docs/plugins.md](docs/plugins.md). If it does not, `node` may already cover
925
- it; a recording that comes back empty from a harness not listed [above](#which-agents) is worth
926
- an issue either way.
927
- - **Add a framework to the integration checks.** `test/integrations/` records a real framework
928
- against a stub origin, kills the origin and replays it. Eight are covered; AutoGen, LlamaIndex,
929
- Pydantic AI and smolagents are not. A new one is a script and a row.
930
- - **Reimplement the reader.** The spec is CC BY 4.0 on purpose. There is already a Python reader;
931
- Go and Rust are open.
932
- - **Break the replay.** The matching ladder is the heart of this and the fastest way to improve it
933
- is a real recording it gets wrong. Open an issue with `agenticreplay export last -o bug.html` attached — it
934
- is one self-contained file, and `agenticreplay scrub` is there for anything you need out of it first.
57
+ Recording runs the harness and may incur API charges. Redaction is best-effort: traces may still
58
+ contain sensitive data. Review them before sharing.
935
59
 
936
- If it saved you an afternoon, a ⭐ helps other people find it.
60
+ See [integrations](docs/integrations.md) for other harnesses and
61
+ [debugging a failing run](docs/how-to/debug-a-failing-agent-run.md) for an example.
937
62
 
938
63
  ## License
939
64
 
940
- Apache-2.0 for the code. The trace specification is CC BY 4.0, so anyone may reimplement it.
941
-
65
+ Apache-2.0. Trace specification: CC BY 4.0.