@basein/runner 0.2.11 → 0.3.0
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.
- package/README.md +540 -350
- package/dist/auth/client.d.ts +65 -3
- package/dist/auth/client.js +305 -53
- package/dist/bin/bir-hooks.d.ts +72 -31
- package/dist/bin/bir-hooks.js +176 -284
- package/dist/bin/bir-proxy.d.ts +17 -2
- package/dist/bin/bir-proxy.js +222 -28
- package/dist/bin/bir-scenario.d.ts +61 -10
- package/dist/bin/bir-scenario.js +704 -135
- package/dist/bin/bir.d.ts +14 -8
- package/dist/bin/bir.js +2937 -488
- package/dist/bin/investigate.js +41 -16
- package/dist/bin/scenario-edit.d.ts +43 -10
- package/dist/bin/scenario-edit.js +281 -80
- package/dist/bin/scenario-liveness.d.ts +194 -0
- package/dist/bin/scenario-liveness.js +459 -0
- package/dist/bin/scenario-stderr.d.ts +43 -0
- package/dist/bin/scenario-stderr.js +76 -0
- package/dist/bin/service-read.d.ts +88 -0
- package/dist/bin/service-read.js +128 -0
- package/dist/bin/setup.d.ts +19 -0
- package/dist/bin/setup.js +146 -20
- package/dist/config/adapters/claude-code.d.ts +92 -14
- package/dist/config/adapters/claude-code.js +291 -101
- package/dist/config/adapters/generic.d.ts +4 -1
- package/dist/config/adapters/generic.js +13 -14
- package/dist/config/generate.d.ts +392 -14
- package/dist/config/generate.js +666 -19
- package/dist/config/mcp-approval.d.ts +76 -0
- package/dist/config/mcp-approval.js +210 -0
- package/dist/config/node-path.d.ts +42 -0
- package/dist/config/node-path.js +102 -0
- package/dist/config/overrides.d.ts +62 -0
- package/dist/config/overrides.js +136 -0
- package/dist/config/resolve.d.ts +70 -8
- package/dist/config/resolve.js +186 -15
- package/dist/control/client.d.ts +22 -0
- package/dist/control/client.js +26 -7
- package/dist/control/correlation.d.ts +23 -5
- package/dist/control/correlation.js +0 -0
- package/dist/control/daemon.d.ts +400 -18
- package/dist/control/daemon.js +1105 -102
- package/dist/control/discovery.d.ts +77 -5
- package/dist/control/discovery.js +186 -11
- package/dist/control/ensure-hook.d.ts +61 -0
- package/dist/control/ensure-hook.js +160 -8
- package/dist/control/paths.d.ts +26 -0
- package/dist/control/paths.js +34 -0
- package/dist/control/pin-guard.d.ts +83 -0
- package/dist/control/pin-guard.js +534 -0
- package/dist/control/recorder-keeper.d.ts +229 -0
- package/dist/control/recorder-keeper.js +721 -0
- package/dist/control/recorder-main.d.ts +73 -0
- package/dist/control/recorder-main.js +778 -0
- package/dist/control/server.d.ts +707 -31
- package/dist/control/server.js +2803 -306
- package/dist/control/transcript.d.ts +21 -1
- package/dist/control/transcript.js +378 -112
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/jsonrpc/framing.d.ts +76 -12
- package/dist/jsonrpc/framing.js +216 -59
- package/dist/proxy/intercept.d.ts +49 -5
- package/dist/proxy/intercept.js +188 -59
- package/dist/proxy/relay.d.ts +16 -0
- package/dist/proxy/relay.js +77 -5
- package/dist/proxy/revive.d.ts +122 -0
- package/dist/proxy/revive.js +251 -0
- package/dist/proxy/session.d.ts +222 -14
- package/dist/proxy/session.js +708 -99
- package/dist/record/housekeeping.d.ts +71 -0
- package/dist/record/housekeeping.js +417 -0
- package/dist/record/recorder.d.ts +51 -3
- package/dist/record/recorder.js +3 -0
- package/dist/record/redact.d.ts +103 -9
- package/dist/record/redact.js +659 -30
- package/dist/record/remote-recorder.d.ts +327 -32
- package/dist/record/remote-recorder.js +1517 -187
- package/dist/record/switchable-recorder.d.ts +126 -0
- package/dist/record/switchable-recorder.js +351 -0
- package/dist/record/truncate.d.ts +40 -1
- package/dist/record/truncate.js +111 -4
- package/dist/replay/bash-channel.d.ts +35 -0
- package/dist/replay/bash-channel.js +133 -0
- package/dist/replay/bundle.d.ts +10 -0
- package/dist/replay/bundle.js +10 -0
- package/dist/replay/controller.d.ts +371 -20
- package/dist/replay/controller.js +1185 -111
- package/dist/replay/derive.d.ts +1 -0
- package/dist/replay/derive.js +81 -24
- package/dist/replay/executor.d.ts +51 -16
- package/dist/replay/executor.js +114 -41
- package/dist/replay/handover.d.ts +54 -1
- package/dist/replay/handover.js +55 -5
- package/dist/replay/logic.d.ts +62 -10
- package/dist/replay/logic.js +140 -22
- package/dist/replay/plan.d.ts +201 -27
- package/dist/replay/plan.js +581 -141
- package/dist/replay/safety.d.ts +72 -0
- package/dist/replay/safety.js +303 -0
- package/dist/replay/source-run.d.ts +10 -0
- package/dist/replay/source-run.js +80 -35
- package/dist/replay/targets.d.ts +15 -1
- package/dist/replay/targets.js +62 -4
- package/dist/replay/tool-error.d.ts +15 -1
- package/dist/replay/tool-error.js +33 -1
- package/dist/replay/types.d.ts +7 -4
- package/dist/upstream/client.d.ts +56 -3
- package/dist/upstream/client.js +61 -4
- package/dist/upstream/headers.d.ts +43 -0
- package/dist/upstream/headers.js +90 -0
- package/dist/upstream/http-client.d.ts +213 -22
- package/dist/upstream/http-client.js +1034 -168
- package/dist/upstream/http-transport.d.ts +143 -0
- package/dist/upstream/http-transport.js +391 -0
- package/dist/upstream/lazy-client.d.ts +8 -0
- package/dist/upstream/lazy-client.js +29 -1
- package/dist/upstream/sse.d.ts +78 -0
- package/dist/upstream/sse.js +189 -0
- package/dist/upstream/stdio-client.d.ts +61 -1
- package/dist/upstream/stdio-client.js +356 -33
- package/dist/util/atomic-write.d.ts +29 -0
- package/dist/util/atomic-write.js +96 -0
- package/dist/util/journal.d.ts +15 -0
- package/dist/util/journal.js +48 -3
- package/dist/util/log.d.ts +10 -0
- package/dist/util/log.js +28 -1
- package/dist/util/off-switch.d.ts +46 -0
- package/dist/util/off-switch.js +91 -0
- package/dist/util/service-health.d.ts +133 -0
- package/dist/util/service-health.js +217 -0
- package/docs/BaseInstRunner.md +694 -621
- package/docs/calculatedReplay.md +1625 -1244
- package/docs/calculatedReplayGuide.md +1212 -879
- package/docs/installRun.md +1013 -772
- package/docs/loginWeb.md +627 -607
- package/docs/mcpmark.md +49 -16
- package/docs/my-first-sample.md +569 -545
- package/docs/quickstart.md +384 -271
- package/docs/t-bench.md +406 -394
- package/package.json +58 -57
- package/production.md +1371 -0
- package/scripts/install.ps1 +97 -6
- package/scripts/install.sh +104 -7
package/README.md
CHANGED
|
@@ -1,350 +1,540 @@
|
|
|
1
|
-
# BaseInstRunnerMCP
|
|
2
|
-
|
|
3
|
-
A recording MCP **proxy**. It sits between any MCP client (Claude Code, Cursor,
|
|
4
|
-
Codex, an Agent SDK session) and every MCP server that client uses, executes each
|
|
5
|
-
call on the client's behalf, and records the run as a reusable scenario in the
|
|
6
|
-
BaseIn service.
|
|
7
|
-
|
|
8
|
-
The design this implements is [docs/BaseInstRunner.md](docs/BaseInstRunner.md).
|
|
9
|
-
This README is the operator's half: how to run it, what it records, and what it
|
|
10
|
-
cannot. For v2 — running a calculated scenario when a prompt means the same thing
|
|
11
|
-
as one you have run before — see [docs/calculatedReplay.md](docs/calculatedReplay.md)
|
|
12
|
-
and its runbook [docs/calculatedReplayGuide.md](docs/calculatedReplayGuide.md).
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
[
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
bir
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
bir
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
bir
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
`bir
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
bir
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
`
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
`
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
1
|
+
# BaseInstRunnerMCP
|
|
2
|
+
|
|
3
|
+
A recording MCP **proxy**. It sits between any MCP client (Claude Code, Cursor,
|
|
4
|
+
Codex, an Agent SDK session) and every MCP server that client uses, executes each
|
|
5
|
+
call on the client's behalf, and records the run as a reusable scenario in the
|
|
6
|
+
BaseIn service.
|
|
7
|
+
|
|
8
|
+
The design this implements is [docs/BaseInstRunner.md](docs/BaseInstRunner.md).
|
|
9
|
+
This README is the operator's half: how to run it, what it records, and what it
|
|
10
|
+
cannot. For v2 — running a calculated scenario when a prompt means the same thing
|
|
11
|
+
as one you have run before — see [docs/calculatedReplay.md](docs/calculatedReplay.md)
|
|
12
|
+
and its runbook [docs/calculatedReplayGuide.md](docs/calculatedReplayGuide.md).
|
|
13
|
+
The production contract, the full environment-variable reference and the
|
|
14
|
+
operations runbook are in [production.md](production.md).
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
model ──mcp__chrome-devtools__*──▶ bir-proxy ──stdio/http──▶ chrome-devtools-mcp
|
|
18
|
+
hooks ──built-ins, prompt, stop──▶ bir-hooks (control server) ──▶ BaseIn /recordings/*
|
|
19
|
+
▲
|
|
20
|
+
bir-proxy ────┘ step reports
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
One line, pasted into a terminal **opened in the project you start Claude Code
|
|
28
|
+
in**. The console's *Set up the runner* page shows it with a one-time setup
|
|
29
|
+
token filled in, so there is no sign-in step either:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
$env:BIR_SETUP_TOKEN="<token>"; irm https://api.bi2202.com/install.ps1 | iex # Windows (PowerShell)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
curl -fsSL https://api.bi2202.com/install.sh | BIR_SETUP_TOKEN="<token>" sh # macOS / Linux
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That installs Node if the machine has none (a private copy, never on PATH),
|
|
40
|
+
installs this package, installs Claude Code if it is missing, and runs
|
|
41
|
+
`bir setup`: it signs in, stores the service address, wraps the project's MCP
|
|
42
|
+
servers, wires Claude Code's hooks, adds the scenario server, and starts the
|
|
43
|
+
recorder in the background. It ends with the one thing left to do: `claude`.
|
|
44
|
+
Without a token the same line signs in through the browser. Re-running it is
|
|
45
|
+
safe, and is also how you upgrade. To record another project: `cd` there and
|
|
46
|
+
run `bir setup` (no token needed; the sign-in is kept).
|
|
47
|
+
|
|
48
|
+
The scripts are [scripts/install.ps1](scripts/install.ps1) and
|
|
49
|
+
[scripts/install.sh](scripts/install.sh); the API serves them with its own
|
|
50
|
+
address filled in. Step by step, and what each line does:
|
|
51
|
+
[docs/quickstart.md](docs/quickstart.md). For a fleet — packaging, releases,
|
|
52
|
+
offline installs, updates: [docs/installRun.md](docs/installRun.md).
|
|
53
|
+
|
|
54
|
+
By hand, or when developing BaseInstRunner itself:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm i -g @basein/runner # or: npm install && npm run build, in a checkout
|
|
58
|
+
cd the-project-you-record-from
|
|
59
|
+
bir setup --auth-url https://api.bi2202.com # everything below, in one go
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
or the pieces `bir setup` is made of:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bir login # once; approve it in the browser (or --token <t> from the console)
|
|
66
|
+
bir install --replay # absolute paths to this package; --npx for pinned npx, --local from a checkout
|
|
67
|
+
bir up # the recorder, in the background; the SessionStart hook does this too
|
|
68
|
+
claude # your session, now recorded
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`bir install` leaves your shared MCP configuration as it is. Each server it wraps gets
|
|
72
|
+
a wrapped copy in this project's *local scope* — in `~/.claude.json`
|
|
73
|
+
(`$CLAUDE_CONFIG_DIR/.claude.json` when that is set), under the key Claude Code
|
|
74
|
+
uses for the project — which takes precedence over the `.mcp.json` or
|
|
75
|
+
user-scope entry of the same name, so `mcp__<server>__<tool>` names never
|
|
76
|
+
change (a server defined in the local scope itself is wrapped where it is).
|
|
77
|
+
`.mcp.json`, the file a team commits, and your user scope are never
|
|
78
|
+
written; wraps an older install made there are put back. `--mcp-json` (or
|
|
79
|
+
`BIR_WRAP_MCP_JSON=1`) is that old in-place shape, for a `.mcp.json` nobody
|
|
80
|
+
commits. The hooks go into `.claude/settings.local.json` — your file, not the
|
|
81
|
+
one a team commits; in a git repository it is added to `.git/info/exclude`.
|
|
82
|
+
Uninstall repairs `~/.claude.json` (and a `--config` file) entry by entry;
|
|
83
|
+
`settings.local.json` it puts back byte-for-byte when nothing else has edited
|
|
84
|
+
it since, and repairs entry by entry when something has. A project remembers
|
|
85
|
+
whether it was installed with `--global` (absolute paths, the default for an
|
|
86
|
+
installed `bir`), `--local` or `--npx`, so a later `bir install` with no flag
|
|
87
|
+
keeps that shape.
|
|
88
|
+
|
|
89
|
+
Some servers are left as Claude Code runs them. Their calls are still recorded
|
|
90
|
+
by the hooks, and `bir setup` says why for each:
|
|
91
|
+
|
|
92
|
+
- a `.mcp.json` server Claude Code has not approved yet: approve it in Claude
|
|
93
|
+
Code, then run `bir setup` again (`bir install --server <name>` wraps it now,
|
|
94
|
+
and it then starts without Claude Code's question). A rejected one is never
|
|
95
|
+
wrapped.
|
|
96
|
+
- a remote server Claude Code signs in itself (see
|
|
97
|
+
[What it will not do](#what-it-will-not-do)).
|
|
98
|
+
- every server, under a managed policy that matches servers by command or URL,
|
|
99
|
+
or allows only the organisation's own servers.
|
|
100
|
+
- every server, in a subdirectory or linked worktree of a repository: Claude
|
|
101
|
+
Code keeps the local scope per repository, so run `bir setup` in its root.
|
|
102
|
+
Linked worktrees then run the root's wrapped servers too.
|
|
103
|
+
|
|
104
|
+
A local copy remembers the entry it was made from. When that entry changes,
|
|
105
|
+
`bir status` marks the server STALE and `bir doctor` reports it; `bir setup`
|
|
106
|
+
copies it again.
|
|
107
|
+
|
|
108
|
+
**Nothing has to stay open.** The SessionStart hook is `bir-hooks ensure`, for
|
|
109
|
+
every install shape (an `--npx` one runs it through npx): it starts the recorder
|
|
110
|
+
for the project when none is running, in the background, with its audit log at
|
|
111
|
+
`~/.baseinstrunner/logs/<project-key>.log`, and never holds a session start
|
|
112
|
+
longer than about 11.5 s. `bir up` starts it by hand, `bir down` stops it,
|
|
113
|
+
`bir status` shows it. Running `bir-hooks` in a terminal still works and is what
|
|
114
|
+
the hook does for you. A recorder that dies mid-session is started again by that
|
|
115
|
+
session's proxies or `bir` server. `bir down` stops only the recorder, which
|
|
116
|
+
then stays down until the next session start, `bir up` or `bir setup`; the
|
|
117
|
+
hooks stay installed, so a session open there shows a "hook error" on each
|
|
118
|
+
prompt and tool call until it ends. To take the runner out of the way instead:
|
|
119
|
+
`bir off` ([When something goes wrong](#when-something-goes-wrong)).
|
|
120
|
+
|
|
121
|
+
The service address is stored by `bir setup` and `bir login` in
|
|
122
|
+
`~/.baseinstrunner/config.json`; `BIR_AUTH_URL` in the environment overrides
|
|
123
|
+
it. A cached session is bound to the service that issued it and is never sent
|
|
124
|
+
anywhere else.
|
|
125
|
+
|
|
126
|
+
`bir login` prints a link and a short code and waits for you to approve it in
|
|
127
|
+
the browser — the OAuth device grant, the same shape `gh auth login` uses. It
|
|
128
|
+
never handles a password, so it works for accounts that only sign in with
|
|
129
|
+
Google, and the link can be opened on any device, which is what makes it usable
|
|
130
|
+
on a headless runner. `--no-browser` prints the link without opening one;
|
|
131
|
+
`--token <value>` redeems a one-time setup token made on the console's *Set up
|
|
132
|
+
the runner* page, with no browser step at all (service 2026-09 or newer);
|
|
133
|
+
`--password` is the deprecated email-and-password prompt, removed next release.
|
|
134
|
+
The link is opened only when it is `https` (`http` only for an `http` service),
|
|
135
|
+
and on Windows without `cmd.exe`. `bir logout` removes this machine's session
|
|
136
|
+
first, then asks the service that issued it to revoke it (best effort, at most
|
|
137
|
+
8 s); running recorders stop sending within seconds, and start again by
|
|
138
|
+
themselves after the next `bir login`.
|
|
139
|
+
|
|
140
|
+
Check it:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
bir status # what is wrapped, which config scope won, whether the runner is on
|
|
144
|
+
bir doctor # is it working *right now* — exits 1 only for what breaks a session here
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`bir doctor` sorts what it finds into problems — a server, hook or recorder a
|
|
148
|
+
Claude Code session here would fail on: exit 1 — and warnings, which sessions
|
|
149
|
+
work through (not recording, a proxy not registered yet, the service
|
|
150
|
+
unreachable): exit 0, or 1 with `--strict`. `--quick` does not call the service.
|
|
151
|
+
`bir status` also shows the recorder's view of the service, the off switch, a
|
|
152
|
+
port the recorder could not take, a STALE local copy, and the other projects set
|
|
153
|
+
up on this machine.
|
|
154
|
+
|
|
155
|
+
### Any other MCP client
|
|
156
|
+
|
|
157
|
+
Nothing here knows where Cursor or Codex keep their settings, and it does not
|
|
158
|
+
guess. Two paths:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bir install --config /path/to/that/client/mcp.json
|
|
162
|
+
bir wrap --server-name my-server -- node my-server.js # prints an entry to paste
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Taking it out
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
bir uninstall # this project: its wrapped copies, hooks and scenario server; stops its recorder
|
|
169
|
+
bir uninstall --all # every project on this machine — run it before removing the package
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`bir uninstall` touches only this project, and names the other projects still
|
|
173
|
+
set up. It stops the project's recorder or, while a session is still using it,
|
|
174
|
+
leaves it running switched off for this project: restart that session, then
|
|
175
|
+
`bir down` (`--keep-recorder` leaves the recorder alone). While anything here
|
|
176
|
+
still runs through the runner it exits 1 with `Not everything could be undone`
|
|
177
|
+
and a list; fix what it names and run it again. `--server <name>` takes out only
|
|
178
|
+
that server, and `--replay` only the scenario server, switching replay off.
|
|
179
|
+
|
|
180
|
+
Remove the package (`npm rm -g @basein/runner`, or delete `~/.baseinstrunner`,
|
|
181
|
+
where the install line's copy lives) only after `bir uninstall --all` has
|
|
182
|
+
finished cleanly: otherwise every other project's wrapped servers and hooks name
|
|
183
|
+
files that are gone. (`bir logout` first revokes this machine's sign-in, which
|
|
184
|
+
deleting `~/.baseinstrunner` alone does not.) With `BIR_UNINSTALL=1` the install line runs
|
|
185
|
+
`bir uninstall --all` for you, downloading nothing; deleting the files is left
|
|
186
|
+
to you:
|
|
187
|
+
|
|
188
|
+
```powershell
|
|
189
|
+
$env:BIR_UNINSTALL="1"; irm https://api.bi2202.com/install.ps1 | iex # Windows (PowerShell)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
curl -fsSL https://api.bi2202.com/install.sh | BIR_UNINSTALL=1 sh # macOS / Linux
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Going back to an older runner (`BIR_VERSION=0.2.12`, or
|
|
197
|
+
`npm i -g @basein/runner@0.2.12`) needs `bir uninstall --all` first, run with
|
|
198
|
+
the current one.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## The two tiers
|
|
203
|
+
|
|
204
|
+
Only Claude Code has hooks, so run ownership is negotiated at proxy startup.
|
|
205
|
+
|
|
206
|
+
| Tier | When | Records |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| **1 — Bound** | a control server (`bir-hooks`) is discoverable for this cwd | built-ins **and** MCP, one ordered step stream, prompt and final answer |
|
|
209
|
+
| **2 — Standalone** | no control server within 10 s | MCP calls only — no prompt, no final answer, no built-in steps |
|
|
210
|
+
| **Passthrough** | as Tier 2, but for a user-scope entry an older install wrapped, started in a directory that was never set up and has no recorder | nothing — it only relays |
|
|
211
|
+
|
|
212
|
+
Tier 2 is not a bug; it is the honest ceiling of what an MCP proxy can observe. A
|
|
213
|
+
run records which tier produced it in `metadata.tier`, so nothing downstream
|
|
214
|
+
mistakes a partial trace for a complete one. A proxy that is not bound keeps
|
|
215
|
+
looking every 15 s and binds to a recorder that starts late or was restarted; it
|
|
216
|
+
records at most one standalone run of its own.
|
|
217
|
+
|
|
218
|
+
Steps taken while the tier is still being decided are **buffered**, not dropped —
|
|
219
|
+
the host may spawn MCP servers before `SessionStart` fires, and the ordering
|
|
220
|
+
between the two is not guaranteed.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## What it will not do
|
|
225
|
+
|
|
226
|
+
- **Non-MCP tools without the hook binary.** `Bash`, `Read`, `Edit` and friends
|
|
227
|
+
are not MCP traffic. An MCP server sees only MCP traffic addressed to it. That
|
|
228
|
+
is a structural fact, not a limitation of this implementation.
|
|
229
|
+
- **`claude-in-chrome`.** It is `scope: "dynamic"`, present in no config file, so
|
|
230
|
+
there is no entry to rewrite.
|
|
231
|
+
- **Wrap a remote server Claude Code signs in itself.** An `oauth` block, a
|
|
232
|
+
`headersHelper`, `${VAR}` headers or no credential header means Claude Code
|
|
233
|
+
holds the credentials, and a proxy would get none (the server would answer
|
|
234
|
+
`upstream requires sign-in`). Such a server is left unwrapped, its calls
|
|
235
|
+
recorded by the hooks, unless `bir install --server <name>` asks for it,
|
|
236
|
+
which is remembered. A static credential header travels in the wrapped
|
|
237
|
+
entry's env (`BIR_UPSTREAM_HEADERS`), not on its command line.
|
|
238
|
+
- **Approve your MCP calls.** Outside a matched replay, and unless you opt back
|
|
239
|
+
in, Claude Code asks about a wrapped MCP call exactly as it would unwrapped.
|
|
240
|
+
0.2.12 approved them all; see [below](#two-things-to-know-before-you-turn-it-on).
|
|
241
|
+
- **Replay, unless you install it.** A first plain `bir install` records and nothing
|
|
242
|
+
else. Calculated replay — running a matched prompt's scenario instead of
|
|
243
|
+
rediscovering it — ships, but needs `bir install --replay`, which `bir setup`
|
|
244
|
+
runs unless given `--no-replay`. Once installed it is on; `bir replay off` or
|
|
245
|
+
`BIR_REPLAY=0` turns it off, for the reason in the next section.
|
|
246
|
+
- **Change what the model does.** Tool descriptions are never edited. The
|
|
247
|
+
model-visible changes are one declared schema key (below), the first-party
|
|
248
|
+
`bir` server's `run_scenario` (only with `--replay`), and replay's own
|
|
249
|
+
`[BaseInstRunner calculated replay]` notes when a prompt matches a scenario.
|
|
250
|
+
The `bir` server's three tools that only read a scenario (`scenario_show`,
|
|
251
|
+
`scenario_edits`, `investigate`) are listed only after `bir scenario editing on`,
|
|
252
|
+
or with `BIR_SCENARIO_TOOLS=read` in its environment: their definitions ride
|
|
253
|
+
along with every request. The three for changing one (`scenario_check`,
|
|
254
|
+
`scenario_edit`, `scenario_undo`) appear only after `bir scenario editing on`
|
|
255
|
+
in that project (not yet released, see below). With the runner off it lists
|
|
256
|
+
no tools at all.
|
|
257
|
+
- **Record its own `bir` calls** (next version, not yet released). A session
|
|
258
|
+
that fixes a plan is not a task to replay. The `bir` server's tools other
|
|
259
|
+
than `run_scenario`, and a `Bash` or `PowerShell` command made only of `bir`
|
|
260
|
+
commands, are never recorded as steps and never count as leaving a plan.
|
|
261
|
+
`npm test && bir investigate` is still recorded: `npm test` is real work.
|
|
262
|
+
- **Sandbox scenario code on this machine.** A calculated or hand-edited step
|
|
263
|
+
runs here, in the recorder's own process and context (Node's `vm`, not a
|
|
264
|
+
sandbox), next to your sign-in and your wrapped servers. It has a time limit
|
|
265
|
+
(`BIR_LOGIC_TIMEOUT_MS`, 1 s by default) and no memory limit.
|
|
266
|
+
The service will sandbox its own runs of the same code (with its next deploy);
|
|
267
|
+
the runner does not. The service's check of a hand edit only judges what the
|
|
268
|
+
code returns for the recording's values, so it cannot prove what the code
|
|
269
|
+
does here: read a hand edit before it is saved. See
|
|
270
|
+
[the design's §13.1](docs/calculatedReplay.md).
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Calculated replay
|
|
275
|
+
|
|
276
|
+
When the service recognises that a prompt means the same thing as one you have
|
|
277
|
+
run before, it hands back the *calculated scenario* derived from that earlier
|
|
278
|
+
run: an intent, a parameter schema, and one bit of logic per step that recomputes
|
|
279
|
+
that step's arguments. `bir` can run it instead of letting the model rediscover
|
|
280
|
+
the plan token by token.
|
|
281
|
+
|
|
282
|
+
The design is [docs/calculatedReplay.md](docs/calculatedReplay.md); the runbook —
|
|
283
|
+
record, calculate, check, enable, read the audit lines — is
|
|
284
|
+
[docs/calculatedReplayGuide.md](docs/calculatedReplayGuide.md).
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
bir install --replay # adds the `bir` MCP server (local scope: Claude Code does not ask about it), replay on
|
|
288
|
+
bir up # or let the SessionStart hook start the recorder
|
|
289
|
+
bir replay allow fleet # the servers replay may call unattended — stored for this project
|
|
290
|
+
bir replay off # and back on with `bir replay on`; `bir replay status` shows it
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
`bir setup` does the first two; `bir setup --no-replay` stores replay off, and so
|
|
294
|
+
does a plain `bir install` where replay was never set up. `bir install --replay`
|
|
295
|
+
never turns back on a `bir replay off` of yours. The switches are kept per
|
|
296
|
+
project in `~/.baseinstrunner/installed.json`, so a recorder the hook starts — with Claude
|
|
297
|
+
Code's environment, not your terminal's — keeps them. A recorder reads them when
|
|
298
|
+
it starts: `bir up --restart` applies a change. `BIR_REPLAY`,
|
|
299
|
+
`BIR_REPLAY_ALLOW_SERVERS` and `BIR_MIN_STEER_SIMILARITY` in the environment
|
|
300
|
+
still override them, and `bir doctor` says which source is in effect.
|
|
301
|
+
|
|
302
|
+
Two switches, not one, because of what replay is: **a scenario is a pre-approved
|
|
303
|
+
list of tool calls with computed arguments.** A steered step is answered
|
|
304
|
+
`permissionDecision: "allow"`, so a pinned `Bash` or `Edit` runs without the
|
|
305
|
+
prompt you would have seen — only a call that plausibly is the step, though (the
|
|
306
|
+
same command, the same file, the same kind of arguments); any other call runs as
|
|
307
|
+
written and is asked about as usual. A *directly executed* step never reaches the
|
|
308
|
+
permission system at all — it is dispatched to a `bir-proxy` and called on a
|
|
309
|
+
connection that is already open — and `run_scenario`, the call that starts the
|
|
310
|
+
run, is answered `allow` while the recorder has announced the plan. In plan mode
|
|
311
|
+
nothing is announced and nothing is approved. Set `BIR_REPLAY_ALLOW_SERVERS` to
|
|
312
|
+
the servers you are content to have called unattended (it also covers a steered
|
|
313
|
+
plan's recovery), and read [the guide's §5.1](docs/calculatedReplayGuide.md)
|
|
314
|
+
before enabling it anywhere that matters.
|
|
315
|
+
|
|
316
|
+
What it buys: on a scenario whose every step is a wrapped MCP tool, the whole
|
|
317
|
+
sequence runs through connections the proxies already hold — **zero model tokens**
|
|
318
|
+
— and the model's only job is one tool call that reads the results. Such a plan
|
|
319
|
+
is announced only while a `bir` server has told the recorder it is connected,
|
|
320
|
+
and runs only through `run_scenario`: if the model calls anything else first,
|
|
321
|
+
the plan is retired and nothing runs. Mixed scenarios steer the live session
|
|
322
|
+
instead, pinning each step's arguments. Either way the turn reports what it
|
|
323
|
+
cost, so the savings ledger measures something real.
|
|
324
|
+
|
|
325
|
+
**Fixing a step.** Built 2026-09-25, not yet released: it needs the next
|
|
326
|
+
`@basein/runner` version and a service deploy. A calculated step is code, and
|
|
327
|
+
code can be wrong. `bir investigate` names such a step; `bir scenario edit`
|
|
328
|
+
fixes it, and the service checks the change against the recording before it
|
|
329
|
+
saves it. **A change is saved only if it reproduces the recording and is not a
|
|
330
|
+
copy — or on purpose, with `--force --note "why"`.** Only the owner can change
|
|
331
|
+
a scenario. `bir scenario undo` takes changes back one at a time: run it again
|
|
332
|
+
to go one further back. An undo is checked too, and is refused if it would
|
|
333
|
+
break a later step or the final answer, unless you add `--force --note "why"`.
|
|
334
|
+
Claude can do all of this: in its terminal, or with the `bir` server's tools
|
|
335
|
+
once `bir scenario editing on` was run in the project. Step by step, with real
|
|
336
|
+
output:
|
|
337
|
+
[the guide's §9.2](docs/calculatedReplayGuide.md).
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
bir scenario show scn_… --step 3 # read one step (scenarioId and chainRevision first)
|
|
341
|
+
bir scenario check scn_… --step 3 --input-logic step3.js # try a change; nothing is saved
|
|
342
|
+
bir scenario edit scn_… --step 3 --input-logic step3.js --note "why" # the same check, then save
|
|
343
|
+
bir scenario edits scn_… # the history, newest first
|
|
344
|
+
bir scenario undo scn_… # take back the newest edit; again to go further back
|
|
345
|
+
bir scenario undo scn_… --step 3 # take back step 3's newest edit only
|
|
346
|
+
bir scenario calc scn_… --force --discard-edits # recalculate this plan, dropping its hand edits
|
|
347
|
+
bir scenario editing on # let Claude's `bir` tools change scenarios here
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Two things to know before you turn it on
|
|
353
|
+
|
|
354
|
+
**1. Wrapped MCP calls are not auto-approved.** Merging the hook's view of a
|
|
355
|
+
call with the proxy's is exact only when an id rides through the tool's
|
|
356
|
+
arguments, and the mechanism for that is answering `PreToolUse` with
|
|
357
|
+
`permissionDecision: "allow"` — which skips the permission prompt. So by default
|
|
358
|
+
the recorder answers an ordinary wrapped MCP call with no decision: Claude Code
|
|
359
|
+
asks exactly as it would without the runner, and the two views are joined on
|
|
360
|
+
`(server, tool, hash(args))` instead, which is lossy only under identical
|
|
361
|
+
concurrent calls. By default an id is carried only where that changes nothing
|
|
362
|
+
you are asked: a `bypassPermissions` session, and a replay-pinned step. Never in
|
|
363
|
+
plan mode, and never into a server whose proxy is not running.
|
|
364
|
+
|
|
365
|
+
0.2.12 answered every wrapped call `allow`. Under `claude -p` in a workspace
|
|
366
|
+
whose `permissions.allow` is not read, those calls are now denied: allow them
|
|
367
|
+
with `permissions.allow` in a trusted workspace or `--allowedTools`, or opt back
|
|
368
|
+
in to auto-approval where the recorder starts — the shell you restart it from,
|
|
369
|
+
and the environment Claude Code starts in, since its SessionStart hook starts
|
|
370
|
+
the next one:
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
BIR_CORRELATION_DECISION=allow bir up --restart
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
`BIR_CORRELATION_DECISION=ask` carries the id and keeps the prompt. Hosts that
|
|
377
|
+
ignore `updatedInput` on a non-allow decision then fall back to fingerprint
|
|
378
|
+
matching.
|
|
379
|
+
|
|
380
|
+
**2. One schema key is visible to the model.** Carrying that id needs a place in
|
|
381
|
+
the tool's arguments, so while a recorder may inject one — a bound proxy, or one
|
|
382
|
+
still looking for its recorder in a project set up for it — every wrapped tool's
|
|
383
|
+
input schema declares one more property, `__bir_call_id__`
|
|
384
|
+
(`"Reserved. Leave unset."`), on every `tools/list`, whether or not an id is
|
|
385
|
+
ever sent. `additionalProperties` is left as the server declared it, and the id
|
|
386
|
+
is stripped before the server sees a call. Nothing is declared with
|
|
387
|
+
`--standalone` or while the runner is off. `--no-correlation` turns it off
|
|
388
|
+
entirely; steps then always join on `(server, tool, hash(args))`.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Configuration
|
|
393
|
+
|
|
394
|
+
| Variable | Effect |
|
|
395
|
+
|---|---|
|
|
396
|
+
| `BIR_AUTH_URL` | BaseIn auth-service — the **API** address (normally `https://api.<your-domain>`), not the docs or app website. Overrides the address `bir setup` / `bir login` stored in `~/.baseinstrunner/config.json`. **Neither set means nothing is recorded** — everything else still works. |
|
|
397
|
+
| `BIR_SETUP_TOKEN` | A one-time setup token from the console, read by `bir setup` when `--token` is not given; the install scripts pass it this way. Never inherited by the recorder. |
|
|
398
|
+
| `BIR_AUTH_DISABLE=1` | Skip authentication (local dev / CI). |
|
|
399
|
+
| `BIR_OFF=1` | The kill switch for one Claude Code window: set in the environment Claude Code starts in, that window's hooks answer nothing, its wrapped servers run as plain pipes and its `bir` server lists no tools; other windows keep recording. `bir off` switches off the whole machine, `bir off --here` one project. |
|
|
400
|
+
| `BIR_CONTROL_PORT` | The recorder's control-server port. Default: the project's own, which `bir install` picks from 53411 up so no two projects share one. `0` is any free port (tests; installed hooks name a fixed one); a value that is not a port is ignored. |
|
|
401
|
+
| `BIR_CONTROL_URL` / `BIR_CONTROL_TOKEN` | Together, point a proxy (and the `bir` server) at a control server directly, bypassing discovery — the only reliable channel for an SDK session. Generated entries carry `BIR_CONTROL_URL` alone, which marks them hooks-wired; they bind to their directory's recorder through its discovery file. |
|
|
402
|
+
| `BIR_CORRELATION_DECISION` | Unset (default: no decision; fingerprint join), `allow` (every wrapped MCP call auto-approved, as 0.2.12 did) or `ask` — see above. Read by the recorder. |
|
|
403
|
+
| `BIR_NO_CORRELATION=1` | Never inject a call id. |
|
|
404
|
+
| `BIR_WRAP_MCP_JSON=1` | Same as `--mcp-json`: wrap `.mcp.json` servers in place. |
|
|
405
|
+
| `BIR_HOME` | Where state lives (default `~/.baseinstrunner`). |
|
|
406
|
+
| `BIR_CLAUDE_JSON` | Override the `~/.claude.json` location. Unset, it is `$CLAUDE_CONFIG_DIR/.claude.json` when `CLAUDE_CONFIG_DIR` is set, as in Claude Code. |
|
|
407
|
+
| `BIR_VERBOSE=1` | Detail lines on stderr: per message, plus the routine start-up, binding and stream lines that are otherwise left out. |
|
|
408
|
+
|
|
409
|
+
Calculated replay adds these, all read by `bir-hooks` only — the proxies are told
|
|
410
|
+
what to do when they register, so there is one switch and no second copy of it:
|
|
411
|
+
|
|
412
|
+
| Variable | Effect |
|
|
413
|
+
|---|---|
|
|
414
|
+
| `BIR_REPLAY` | `0` turns replay off and any other value on, over the project's stored switch (`bir replay on` / `off`; a first plain `bir install` stores off). Nothing below matters while it is off. |
|
|
415
|
+
| `BIR_REPLAY_ALLOW_SERVERS` | Server keys bir may call unattended: **direct** execution, and a steered plan's recovery. Unset means the list `bir replay allow` stored for the project, else every wrapped server; narrowing it is the recommendation. |
|
|
416
|
+
| `BIR_MIN_STEER_SIMILARITY` | Minimum match similarity to replay (default `0.92`, above the service's own `0.9` detection threshold). |
|
|
417
|
+
| `BIR_DERIVE_API_KEY` | **Optional.** Working out what a new request is about — which fleet, which file, which date — is done for you by the service on its own key, as long as you are signed in. Set this (an Anthropic key) only to keep that reading on your machine, on your key. Signed out *and* unset, a scenario whose values change between requests is declined rather than replayed on stale ones. |
|
|
418
|
+
| `ANTHROPIC_API_KEY` | Not used by the background recorder, which has Claude Code's environment and would bill your own key — unless `BIR_USE_ANTHROPIC_API_KEY=1`. A `bir-hooks` run by hand in a terminal uses that terminal's key. |
|
|
419
|
+
| `BIR_DERIVE_MODEL` | Derivation model, when you set a key of your own (default `claude-haiku-4-5-20251001`). |
|
|
420
|
+
| `BIR_MATCH_BUDGET_MS` | How long the prompt hook waits for a match (default `1500`). After a match slower than `BIR_MATCH_SLOW_MS` (default `800`), or one that timed out, prompts stop waiting for 5 min, doubling up to 30 min; a fast match ends that. |
|
|
421
|
+
| `BIR_DERIVE_BUDGET_MS` / `BIR_HOOK_DERIVE_BUDGET_MS` | How long a direct run, and a hook, waits for parameters (default `8000` / `4000`; a hook waits the smaller). |
|
|
422
|
+
| `BIR_PRETOOL_REPLAY_BUDGET_MS` | Everything replay does inside one `PreToolUse` (default `9000`, at most `10000`). |
|
|
423
|
+
| `BIR_REPLAY_BUDGET_MS` / `BIR_STEP_TIMEOUT_MS` | Whole-plan and per-step ceilings for direct execution (default `100000` / `60000`; `BIR_REPLAY_PLAN_BUDGET_MS` also sets the first). A direct run's parameter wait plus its plan are held under 270 s. |
|
|
424
|
+
| `BIR_LOGIC_TIMEOUT_MS` | How long one scenario logic body may run before it is stopped and the plan hands over (default `1000`, at most `10000`). |
|
|
425
|
+
| `BIR_REPLAY_RECORDED_OUTPUTS=1` | Let a step's recorded output stand in when the step could not be sent. Unset, the plan stops at that step and hands the task back. |
|
|
426
|
+
|
|
427
|
+
The `bir` server reads its own: `BIR_SCENARIO_TOOLS=read` lists its three reading
|
|
428
|
+
tools without `bir scenario editing on`, and `BIR_SCENARIO_RUN_TIMEOUT_MS` is how
|
|
429
|
+
long `run_scenario` waits for a run (default `290000`). Everything else — remote
|
|
430
|
+
upstreams, the recorder's backlog, service timeouts, the `bir` server's output
|
|
431
|
+
caps — is in [production.md](production.md).
|
|
432
|
+
|
|
433
|
+
### The audit log
|
|
434
|
+
|
|
435
|
+
Every decision is one `key=value` line on **stderr** — never stdout, which is the
|
|
436
|
+
host's JSON-RPC stream. A recorder started in the background (by `bir up`, `bir
|
|
437
|
+
setup` or the SessionStart hook) writes them to
|
|
438
|
+
`~/.baseinstrunner/logs/<project-key>.log` — `bir status` names the file. One
|
|
439
|
+
started in a terminal writes them to that terminal, and persistence is the
|
|
440
|
+
shell's job:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
bir-hooks 2>&1 | tee -a ~/.baseinstrunner/audit.log
|
|
444
|
+
grep run.lossy ~/.baseinstrunner/audit.log
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Failure behaviour
|
|
450
|
+
|
|
451
|
+
The governing rule: **a host session never fails because of BaseInstRunner.**
|
|
452
|
+
|
|
453
|
+
| Failure | Behaviour |
|
|
454
|
+
|---|---|
|
|
455
|
+
| No recorder running at session start | the SessionStart hook starts one in the background; it always exits 0 and never holds the start longer than about 11.5 s |
|
|
456
|
+
| Control server unreachable at startup | steps buffered ≤ 10 s, then Tier 2 (passthrough: nothing); the proxy keeps looking every 15 s and binds when one answers |
|
|
457
|
+
| Control server dies mid-session | the proxy records to a Tier 2 run of its own (flagged lossy), starts the recorder again in the background (not after `bir down`), and binds to it once it answers |
|
|
458
|
+
| A hook the recorder cannot answer in time | every route has a hard deadline (prompt 7 s, `PreToolUse` 12 s, the others 1–2 s) and answers `{}`; so does a hook with a stale token or a malformed body |
|
|
459
|
+
| BaseIn service slow or down | hooks stop waiting on it after one failure (a cool-down of 5 s, doubling to 60 s); sends go on in the background, retried once only when they cannot have arrived (429, 503, a refused connection); past 500 queued sends or 32 MiB the oldest steps are dropped |
|
|
460
|
+
| Upstream fails to start, or exits | calls in flight are answered (`MCP server "<name>" is not available`, `isError: true` for a `tools/call`), then `bir-proxy` exits with the server's own status, so Claude Code shows the server as failed, as it would unwrapped. `BIR_PROXY_STAY_UP=1` keeps the proxy up answering that instead |
|
|
461
|
+
| Remote upstream unreachable or refusing | the request gets a short error (`upstream unreachable: …`, `upstream requires sign-in (HTTP 401)`), never the server's body; only connecting is bounded (10 s), never how long a tool runs |
|
|
462
|
+
| Result over 128 MiB | the call ends with an error saying the result was too large to deliver, instead of hanging |
|
|
463
|
+
| Unknown JSON-RPC method | forwarded; any rejection is relayed back |
|
|
464
|
+
| Step queue overflow | oldest dropped, run flagged lossy |
|
|
465
|
+
| Host sends unparseable input | `-32700` and the relay keeps going |
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## When something goes wrong
|
|
470
|
+
|
|
471
|
+
**`bir off` first.** It takes the runner out of the way at once, in every
|
|
472
|
+
project, and uninstalls nothing: hooks answer with nothing within about a
|
|
473
|
+
second, in sessions already open too, and nothing is recorded, replayed or sent;
|
|
474
|
+
wrapped MCP servers run as plain pipes to the real server from their next start
|
|
475
|
+
(a new session, or `/mcp` → reconnect), and keep recording until then; the `bir`
|
|
476
|
+
server lists no tools. `bir off --here` does the same for one project (from a
|
|
477
|
+
subdirectory, the installed project it is in), and `bir on` or `bir on --here`
|
|
478
|
+
undoes it. If the problem goes away, the runner was the cause; if it stays, the
|
|
479
|
+
runner is ruled out. A recorder older than the switch (0.2.12) ignores it:
|
|
480
|
+
`bir off` replaces one no session is using and names any other, which
|
|
481
|
+
`bir up --restart` replaces once its session ends. Not `bir down`: it leaves
|
|
482
|
+
the hooks, and an open session then shows a hook error on every prompt and tool
|
|
483
|
+
call.
|
|
484
|
+
|
|
485
|
+
**Then `bir doctor`**, in the project: what breaks a session here (exit 1), what
|
|
486
|
+
only keeps the runner from doing all of its job (warnings), and the fix for
|
|
487
|
+
each. `bir status` names the recorder's log, where every decision is one line
|
|
488
|
+
([The audit log](#the-audit-log)).
|
|
489
|
+
|
|
490
|
+
**Then [production.md](production.md)**: the production contract, the full
|
|
491
|
+
environment-variable reference, the operations runbook and the findings ledger.
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Development
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
npm run build # src → dist
|
|
499
|
+
npm test # ~1,000 tests: transcript identity, passthrough, correlation, config, replay, setup
|
|
500
|
+
npm run typecheck
|
|
501
|
+
npm run test:smoke # end-to-end replay with real processes (needs build + pretest first)
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
The core test is **transcript identity**: the same scripted conversation is run
|
|
505
|
+
against a fake upstream directly and through the proxy, and every host-visible
|
|
506
|
+
message must match. If a change breaks transparency, that test says so first.
|
|
507
|
+
|
|
508
|
+
`test/smoke-replay.mjs` is the other one worth running by hand. Everything else
|
|
509
|
+
runs in-process; this starts a stand-in BaseIn service, a real `bir-hooks`, a real
|
|
510
|
+
`bir-proxy` wrapping a real MCP server and a real `bir-scenario`, then drives a
|
|
511
|
+
two-step scenario through actual stdio. It is what catches the seams between
|
|
512
|
+
processes — and it is how the "a proxy between polls is not a missing proxy" bug
|
|
513
|
+
was found.
|
|
514
|
+
|
|
515
|
+
`test/fake-upstream.ts` is a scriptable MCP server — arbitrary capabilities,
|
|
516
|
+
prompts, resources, `list_changed` notifications, reverse-direction
|
|
517
|
+
`elicitation/create`, delays, failures, and dying on command. Everything else
|
|
518
|
+
builds on it.
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Known gaps
|
|
523
|
+
|
|
524
|
+
- **`metadata.lossy` is the value known at run start.** A run that turns lossy
|
|
525
|
+
later says so on the audit log (`run.lossy`) and in the step gap, but the
|
|
526
|
+
metadata is not updated: the service exposes no metadata patch, and this is
|
|
527
|
+
exactly open question 1 in the design. Fixing it properly needs a server-side
|
|
528
|
+
decision, not a client-side workaround.
|
|
529
|
+
- **Subagent steps are attributed, not partitioned.** `SubagentStart` /
|
|
530
|
+
`SubagentStop` give us `agent_id`, which is prefixed onto a step's `context`.
|
|
531
|
+
A real agent column is a schema change, and out of scope for v1 (D6).
|
|
532
|
+
- **`--lazy` defers to the first host message, not to `tools/list`.** Deferring
|
|
533
|
+
further would mean answering `initialize` before the upstream exists — that is,
|
|
534
|
+
fabricating its capabilities, which is the thing Phase 3 exists to prevent. For
|
|
535
|
+
a client that connects at session start (Claude Code does) the flag saves
|
|
536
|
+
nothing; for one that connects on demand it saves the whole process. See
|
|
537
|
+
`src/upstream/lazy-client.ts`.
|
|
538
|
+
- **Coexistence with RRepeat is untested.** Both would record the same session.
|
|
539
|
+
Runs carry `metadata.recorder`, so server-side dedupe is possible, but nothing
|
|
540
|
+
here detects the other (design §13, open question 2).
|