@basein/runner 0.2.8 → 0.2.11
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 +86 -22
- package/dist/auth/client.d.ts +40 -1
- package/dist/auth/client.js +77 -9
- package/dist/bin/bir-hooks.d.ts +18 -3
- package/dist/bin/bir-hooks.js +124 -38
- package/dist/bin/bir-scenario.d.ts +18 -2
- package/dist/bin/bir-scenario.js +374 -4
- package/dist/bin/bir.d.ts +12 -0
- package/dist/bin/bir.js +501 -81
- package/dist/bin/investigate.js +1 -1
- package/dist/bin/scenario-edit.d.ts +173 -0
- package/dist/bin/scenario-edit.js +771 -0
- package/dist/bin/setup.d.ts +72 -0
- package/dist/bin/setup.js +286 -0
- package/dist/config/adapters/claude-code.d.ts +90 -4
- package/dist/config/adapters/claude-code.js +164 -16
- package/dist/config/generate.d.ts +114 -1
- package/dist/config/generate.js +106 -3
- package/dist/control/client.d.ts +5 -0
- package/dist/control/client.js +8 -0
- package/dist/control/daemon.d.ts +116 -0
- package/dist/control/daemon.js +339 -0
- package/dist/control/discovery.d.ts +26 -0
- package/dist/control/discovery.js +41 -9
- package/dist/control/ensure-hook.d.ts +39 -0
- package/dist/control/ensure-hook.js +98 -0
- package/dist/control/paths.d.ts +14 -0
- package/dist/control/paths.js +20 -0
- package/dist/control/server.d.ts +28 -0
- package/dist/control/server.js +15 -2
- package/dist/proxy/session.d.ts +8 -1
- package/dist/proxy/session.js +28 -6
- package/docs/calculatedReplay.md +51 -0
- package/docs/calculatedReplayGuide.md +471 -74
- package/docs/installRun.md +457 -111
- package/docs/loginWeb.md +1 -1
- package/docs/quickstart.md +195 -158
- package/package.json +2 -1
- package/scripts/install.ps1 +669 -0
- package/scripts/install.sh +586 -0
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
> one says *what to type*, in order, from an empty machine to a prompt that answers itself.
|
|
5
5
|
>
|
|
6
6
|
> **Read this first.** Replay executes tool calls with arguments a scenario computed, and it does so
|
|
7
|
-
> **without the permission prompts you would normally see** (design §13.2).
|
|
8
|
-
>
|
|
7
|
+
> **without the permission prompts you would normally see** (design §13.2). `bir setup` (§1) turns it
|
|
8
|
+
> on. Read §5 before you leave it on; `bir replay off` turns it off, and §2–§4 work either way.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -29,23 +29,40 @@ end-to-end smoke test.
|
|
|
29
29
|
|
|
30
30
|
| # | You need | Check it with |
|
|
31
31
|
|---|---|---|
|
|
32
|
-
| 1 | A BaseIn service you can reach | `curl -fsS "
|
|
32
|
+
| 1 | A BaseIn service you can reach | `bir status` — its `BaseIn :` line is the address `bir setup` stored; `curl -fsS "<that address>/health"` proves it answers |
|
|
33
33
|
| 2 | Anthropic configured **on the server** — calculation and derivation both need it | a `503 anthropic_not_configured` from `/calculate` means it is not |
|
|
34
34
|
| 3 | `SIMILARITY_DETECTION_ENABLED=true` on the server (default) | otherwise no prompt ever matches |
|
|
35
35
|
| 4 | BaseInstRunnerMCP built and installed in your project | `bir status` |
|
|
36
|
-
| 5 | Tier 1 — the hooks wired and
|
|
36
|
+
| 5 | Tier 1 — the hooks wired and the recorder running | `bir doctor`; the SessionStart hook starts it |
|
|
37
37
|
| 6 | *(replay only)* signed in, so the service can read what each request acts on | `bir doctor` — `derive=the service`; an `ANTHROPIC_API_KEY` here replaces it, see §5.3 |
|
|
38
38
|
|
|
39
39
|
Tier 1 is not optional for replay. A match is a match on **the prompt**, and a standalone proxy never
|
|
40
|
-
sees one (design §1.1). If `bir doctor` says `tier: standalone`, replay cannot
|
|
41
|
-
honest ceiling rather than a bug.
|
|
40
|
+
sees one (design §1.1). If `bir doctor` says `Recording tier : standalone (Tier 2)`, replay cannot
|
|
41
|
+
arm, and that is the honest ceiling rather than a bug.
|
|
42
|
+
|
|
43
|
+
One line, pasted into a terminal opened in the project you start Claude Code in. The console's *Set
|
|
44
|
+
up the runner* page shows it with a one-time setup token filled in. Pasted in the home folder or a
|
|
45
|
+
drive root it stops before downloading anything — `cd` to the project and paste the same line again:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
$env:BIR_SETUP_TOKEN="<token>"; irm https://api.bi2202.com/install.ps1 | iex # Windows (PowerShell)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
curl -fsSL https://api.bi2202.com/install.sh | BIR_SETUP_TOKEN="<token>" sh # macOS / Linux
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
From cmd.exe: `powershell -NoProfile -Command "$env:BIR_SETUP_TOKEN='<token>'; irm https://api.bi2202.com/install.ps1 | iex"`.
|
|
56
|
+
|
|
57
|
+
It installs Node and Claude Code if they are missing, installs the package, and runs `bir setup`:
|
|
58
|
+
sign-in, the service address stored in `~/.baseinstrunner/config.json`, the project's MCP servers
|
|
59
|
+
wrapped, the hooks wired into `.claude/settings.local.json`, the scenario server added, the recorder
|
|
60
|
+
started in the background. Nothing stays open: the SessionStart hook starts the recorder whenever
|
|
61
|
+
it is missing, and its audit lines go to `~/.baseinstrunner/logs/<key>.log`. Re-running the line
|
|
62
|
+
upgrades. With the package already installed, the same thing is:
|
|
42
63
|
|
|
43
64
|
```bash
|
|
44
|
-
|
|
45
|
-
npm install && npm run build
|
|
46
|
-
node dist/bin/bir.js install # or `bir install` once linked/published
|
|
47
|
-
bir login # prints a link and a code; approve in the browser
|
|
48
|
-
bir-hooks # leave running in its own terminal
|
|
65
|
+
bir setup --auth-url https://api.bi2202.com # without a token it signs in through the browser
|
|
49
66
|
```
|
|
50
67
|
|
|
51
68
|
---
|
|
@@ -81,12 +98,17 @@ bir scenario list # or: GET /recordings/runs
|
|
|
81
98
|
```
|
|
82
99
|
|
|
83
100
|
```
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
run_a812…
|
|
87
|
-
|
|
101
|
+
run_5f3a… Fleet risk sweep
|
|
102
|
+
iterations=3 steps=14 scenario=scn_9c1b… (ready)
|
|
103
|
+
run_a812… Rewrite the billing README
|
|
104
|
+
iterations=1 steps=9 scenario=none — run `bir scenario calc <runId>`
|
|
105
|
+
run_c004… Check open PRs for stale reviews
|
|
106
|
+
iterations=1 steps=6 scenario=scn_44d0… (calculating)
|
|
88
107
|
```
|
|
89
108
|
|
|
109
|
+
Ids are printed in full on their own line (shortened here), so they can be copied straight into
|
|
110
|
+
`calc` and `replay`.
|
|
111
|
+
|
|
90
112
|
---
|
|
91
113
|
|
|
92
114
|
## 3. Calculate the scenario
|
|
@@ -100,17 +122,20 @@ bir scenario calc run_5f3a… # POST /recordings/runs/:runId/calculat
|
|
|
100
122
|
bir scenario show run_5f3a… # GET /recordings/runs/:runId/scenario (poll)
|
|
101
123
|
```
|
|
102
124
|
|
|
103
|
-
Raw, if you prefer — the
|
|
125
|
+
Raw, if you prefer — the service address lives in `~/.baseinstrunner/config.json` (`BIR_AUTH_URL`
|
|
126
|
+
is normally unset after `bir setup`) and the token in `~/.baseinstrunner/credentials.json`:
|
|
104
127
|
|
|
105
128
|
```bash
|
|
129
|
+
BASEIN=$(node -p "require(require('os').homedir()+'/.baseinstrunner/config.json').authUrl")
|
|
106
130
|
TOKEN=$(node -p "require(require('os').homedir()+'/.baseinstrunner/credentials.json').accessToken")
|
|
107
|
-
curl -fsS -X POST "$
|
|
131
|
+
curl -fsS -X POST "$BASEIN/recordings/runs/run_5f3a…/calculate" \
|
|
108
132
|
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{}'
|
|
109
133
|
```
|
|
110
134
|
|
|
111
135
|
```powershell
|
|
136
|
+
$b = (Get-Content "$HOME\.baseinstrunner\config.json" | ConvertFrom-Json).authUrl
|
|
112
137
|
$t = (Get-Content "$HOME\.baseinstrunner\credentials.json" | ConvertFrom-Json).accessToken
|
|
113
|
-
Invoke-RestMethod -Method Post -Uri "$
|
|
138
|
+
Invoke-RestMethod -Method Post -Uri "$b/recordings/runs/run_5f3a…/calculate" `
|
|
114
139
|
-Headers @{ authorization = "Bearer $t" } -ContentType 'application/json' -Body '{}'
|
|
115
140
|
```
|
|
116
141
|
|
|
@@ -123,7 +148,9 @@ Calculation is a background job. Poll `show` until `state` leaves `calculating`:
|
|
|
123
148
|
| `failed` | see the `error` field | fix and recalculate with `--force` |
|
|
124
149
|
|
|
125
150
|
`409 scenario_exists` means this run already has one. Re-derive it in place — keeping the id and its
|
|
126
|
-
replay history — with `bir scenario calc run_… --force`.
|
|
151
|
+
replay history — with `bir scenario calc run_… --force`. To change one step instead, without
|
|
152
|
+
recalculating, see §9.2; a plan with steps edited by hand needs `--force --discard-edits` to be
|
|
153
|
+
rebuilt.
|
|
127
154
|
|
|
128
155
|
### 3.1 Check the scenario before you trust it
|
|
129
156
|
|
|
@@ -139,7 +166,7 @@ bir scenario replay scn_9c1b… --prompt "which devices in the fleet look risky
|
|
|
139
166
|
params { "fleetId": "eu-west-1", "riskThreshold": 0.8 }
|
|
140
167
|
step 0 mcp__chrome-devtools__navigate_page { "url": "https://fleet.internal/eu-west-1" }
|
|
141
168
|
step 1 mcp__chrome-devtools__take_snapshot {}
|
|
142
|
-
→ emitted: devices
|
|
169
|
+
→ emitted: devices
|
|
143
170
|
step 2 mcp__fleet-api__device_detail { "id": "dev_88f1" }
|
|
144
171
|
→ emitted: highestRiskDeviceId, riskScore
|
|
145
172
|
response { "fleet": "eu-west-1", "atRisk": 3, "worst": "dev_88f1" }
|
|
@@ -151,8 +178,13 @@ Read it for three things:
|
|
|
151
178
|
1. **Did the parameters come out right** for a prompt phrased differently from the original?
|
|
152
179
|
2. **Do the step inputs look computed**, or are they the original run's constants copied through? A
|
|
153
180
|
scenario whose inputs never vary with the parameters is a recording, not a scenario.
|
|
154
|
-
3. **Is `emitted` non-empty** for steps that later steps depend on?
|
|
155
|
-
|
|
181
|
+
3. **Is `emitted` non-empty** for steps that later steps depend on? `emitted` lists key names only;
|
|
182
|
+
`→ emitted: (nothing — toolOutputLogic derived no keys)` means every downstream input reading
|
|
183
|
+
that step will be wrong.
|
|
184
|
+
|
|
185
|
+
A `↑ not named by the prompt:` line under `params` lists targets the dry run filled from the
|
|
186
|
+
recording. A live turn finds those in an earlier step, or does not run — so a green trace with
|
|
187
|
+
that line is not yet evidence that a session would steer.
|
|
156
188
|
|
|
157
189
|
A dry replay is recorded as `outcome: 'dry'` and is excluded from savings by construction — it costs
|
|
158
190
|
you a Haiku call and nothing else.
|
|
@@ -175,7 +207,7 @@ you: can you check the eu-west fleet and tell me which boxes are in trouble?
|
|
|
175
207
|
That line is the whole feature working up to the point of replay. If it does not appear, the prompt
|
|
176
208
|
did not clear the server's `SIMILARITY_THRESHOLD` (default 0.9) — see §8.
|
|
177
209
|
|
|
178
|
-
**With replay off (
|
|
210
|
+
**With replay off (`bir replay off`), this is where it stops.** The server keeps its canonical run, bumps
|
|
179
211
|
its `iterations`, this turn is not recorded, and the model answers the ordinary way. Nothing is lost
|
|
180
212
|
and nothing is skipped.
|
|
181
213
|
|
|
@@ -198,28 +230,39 @@ When replay arms:
|
|
|
198
230
|
That is what makes replay fast, and it is the whole risk. Two things follow:
|
|
199
231
|
|
|
200
232
|
1. Only enable it in a project whose scenarios you have dry-replayed and read (§3.1).
|
|
201
|
-
2.
|
|
233
|
+
2. Run `bir replay allow <servers>` (§5.3). An allowlist of the servers you are comfortable having
|
|
202
234
|
called unattended is cheap insurance, and it is the difference between "replay reads a dashboard"
|
|
203
235
|
and "replay can do anything any wrapped server can do".
|
|
204
236
|
|
|
205
237
|
### 5.2 Enable it
|
|
206
238
|
|
|
239
|
+
`bir setup` already did this: it installed the scenario server and pre-approved it, so Claude Code
|
|
240
|
+
asks no question about it. By hand:
|
|
241
|
+
|
|
207
242
|
```bash
|
|
208
|
-
bir install --replay # adds the `bir` MCP server
|
|
209
|
-
|
|
210
|
-
bir-hooks # restart it
|
|
243
|
+
bir install --replay # adds the `bir` MCP server (pre-approved)
|
|
244
|
+
bir up --restart # the recorder reads the change when it restarts
|
|
211
245
|
```
|
|
212
246
|
|
|
213
|
-
`bir install
|
|
214
|
-
|
|
247
|
+
A flag-less `bir install` keeps the invocation the project was set up with — `bir setup` installs
|
|
248
|
+
with `--global` from an installed package and `--local` from a checkout — so this downgrades
|
|
249
|
+
nothing. `bir install --replay` does exactly two things beyond a normal install, and
|
|
250
|
+
`bir uninstall --replay` takes the scenario server out again and leaves everything else as it is:
|
|
251
|
+
|
|
252
|
+
- registers a first-party MCP server under the key **`bir`** in the project's `.mcp.json` (never
|
|
253
|
+
`~/.claude.json`), exposing `mcp__bir__run_scenario` — the channel a fully-wrapped scenario's
|
|
254
|
+
results come back through — and three tools that only read a scenario (`scenario_show`,
|
|
255
|
+
`scenario_edits`, `investigate`; §9.2, not yet released);
|
|
256
|
+
- pre-approves that server in `.claude/settings.local.json` (`enabledMcpjsonServers`), because a
|
|
257
|
+
"No" to Claude Code's approval dialog leaves a direct replay with nowhere to deliver its results.
|
|
215
258
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
- raises `UserPromptSubmit`'s hook timeout from 5 s to 15 s, so the match round trip fits.
|
|
259
|
+
The `UserPromptSubmit` hook's 15 s timeout, which the match round trip needs, is not one of them:
|
|
260
|
+
every install writes it, replay or not.
|
|
219
261
|
|
|
220
|
-
The `bir`
|
|
221
|
-
more
|
|
222
|
-
than part of `bir install`.
|
|
262
|
+
The `bir` tools are visible to the model in every session in this project. That is a real cost — a
|
|
263
|
+
few more tools in the list, a little context per turn — and it is why replay is a separate flag
|
|
264
|
+
rather than part of `bir install`. The tools that change a scenario are not among them until you
|
|
265
|
+
run `bir scenario editing on` (§9.2).
|
|
223
266
|
|
|
224
267
|
Confirm:
|
|
225
268
|
|
|
@@ -228,23 +271,48 @@ bir doctor
|
|
|
228
271
|
```
|
|
229
272
|
|
|
230
273
|
```
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
274
|
+
Replay : ON servers=chrome-devtools,fleet-api minSimilarity=0.92 source=sidecar
|
|
275
|
+
derive=the service (no key needed here)
|
|
276
|
+
Wrapped in config : chrome-devtools, fleet-api
|
|
277
|
+
Registered proxies: chrome-devtools, fleet-api
|
|
278
|
+
Control server : http://127.0.0.1:53411
|
|
279
|
+
BaseIn (shell) : https://api.bi2202.com (answers /health)
|
|
280
|
+
Recording tier : bound (Tier 1)
|
|
281
|
+
Recording : yes
|
|
282
|
+
|
|
283
|
+
· recording to https://api.bi2202.com as you@example.com
|
|
284
|
+
· the recorder runs in the background (pid 41820); its audit log is ~/.baseinstrunner/logs/<key>.log
|
|
285
|
+
· recurring sub-tasks are observed only — nothing is replaced (BIR_SEGMENT_ARM unset)
|
|
286
|
+
|
|
287
|
+
No problems found.
|
|
236
288
|
```
|
|
237
289
|
|
|
290
|
+
`BaseIn (shell)` is the address this terminal's `bir` commands would use — `BIR_AUTH_URL` if it
|
|
291
|
+
is set, else the one `bir setup` stored — and `recording to` is the one the running recorder
|
|
292
|
+
uses, with the account it is signed in as. The two normally agree.
|
|
293
|
+
|
|
238
294
|
### 5.3 Environment
|
|
239
295
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
296
|
+
The first three switches are stored per project, and that is where to set them:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
bir replay allow chrome-devtools,fleet-api # the servers direct execution may call; `allow all` clears it
|
|
300
|
+
bir replay off # and back with `bir replay on`
|
|
301
|
+
bir replay status # what is stored for this project
|
|
302
|
+
bir up --restart # a running recorder reads them when it restarts
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
They are kept in `~/.baseinstrunner/installed.json`. A recorder started by the SessionStart hook has
|
|
306
|
+
Claude Code's environment, not your terminal's, and reads them from there. The environment variables
|
|
307
|
+
below override them wherever the recorder sees them; `bir doctor` says which is in effect
|
|
308
|
+
(`source=sidecar` or `source=env`). The proxies need no replay configuration of their own — each one
|
|
309
|
+
is told whether to open its work loop when it registers, so there is exactly one switch and it cannot
|
|
310
|
+
get out of step with itself.
|
|
243
311
|
|
|
244
312
|
| Variable | Default | What it does |
|
|
245
313
|
|---|---|---|
|
|
246
314
|
| `BIR_REPLAY` | *(on)* | `0` disables replay. Unset or any other value keeps it on; nothing below matters while it is `0` |
|
|
247
|
-
| `BIR_REPLAY_ALLOW_SERVERS` | *(all wrapped)* | Comma-separated server keys eligible for **direct** execution.
|
|
315
|
+
| `BIR_REPLAY_ALLOW_SERVERS` | *(the stored list, else all wrapped)* | Comma-separated server keys eligible for **direct** execution. Overrides `bir replay allow` |
|
|
248
316
|
| `BIR_MIN_STEER_SIMILARITY` | `0.92` | Below this a match is detected but not replayed (§8) |
|
|
249
317
|
| `ANTHROPIC_API_KEY` | *(unset)* | **Not required.** Derivation — reading what this request acts on — is done by the service on its key for a signed-in runner. Set this to keep the reading on this machine instead: the prompt then never leaves it, and it is one round trip faster. Signed out *and* unset, only a scenario with nothing to work out replays |
|
|
250
318
|
| `BIR_DERIVE_MODEL` | `claude-haiku-4-5-20251001` | The derivation model, when this machine does the reading |
|
|
@@ -254,10 +322,14 @@ there is exactly one switch and it cannot get out of step with itself.
|
|
|
254
322
|
| `BIR_STEP_TIMEOUT_MS` | `60000` | Per-step ceiling for one direct `tools/call` |
|
|
255
323
|
| `BIR_VERBOSE` | *(unset)* | `1` adds per-step `replay.*` detail lines |
|
|
256
324
|
|
|
257
|
-
**No API key is a supported configuration, not a broken one.**
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
325
|
+
**No API key is a supported configuration, not a broken one.** Signed in, the service reads the
|
|
326
|
+
prompt on its own key and the replay is the same replay. Signed out *and* without a key, nothing
|
|
327
|
+
can read the prompt: a *setting* takes the value it had in the recorded run, so a scenario whose
|
|
328
|
+
parameters are all settings — a dashboard sweep, a fixed report — is still a complete, free replay;
|
|
329
|
+
but a scenario with a *target* (the thing the task acts on: "check *eu-west*") is declined rather
|
|
330
|
+
than run on last week's value, and the agent does the task normally. `bir doctor` names the state
|
|
331
|
+
you are in: `derive=this machine (ANTHROPIC_API_KEY)`, `derive=the service (no key needed here)`,
|
|
332
|
+
or `derive=recorded sample values — scenarios with a target will NOT run`.
|
|
261
333
|
|
|
262
334
|
---
|
|
263
335
|
|
|
@@ -271,12 +343,12 @@ Ask the matching question again, with replay on.
|
|
|
271
343
|
[bir] … run.matched run=run_… matchedRun=run_5f3a… similarity=0.94
|
|
272
344
|
[bir] … plan.armed scenario=scn_9c1b… mode=direct steps=3
|
|
273
345
|
tools="mcp__chrome-devtools__navigate_page,mcp__chrome-devtools__take_snapshot,mcp__fleet-api__device_detail"
|
|
274
|
-
[bir] … replay.derived params=2 costUsd=0.0011
|
|
346
|
+
[bir] … replay.derived scenario=scn_9c1b… params=2 costUsd=0.0011 source=the service
|
|
275
347
|
[bir] … replay.step n=0 tool=navigate_page server=chrome-devtools ms=612 ok=true
|
|
276
348
|
[bir] … replay.step n=1 tool=take_snapshot server=chrome-devtools ms=1104 ok=true emitted=devices
|
|
277
349
|
[bir] … replay.step n=2 tool=device_detail server=fleet-api ms=210 ok=true emitted=highestRiskDeviceId,riskScore
|
|
278
350
|
[bir] … replay.done scenario=scn_9c1b… mode=direct steps=3/3 outcome=steered_full ms=2766
|
|
279
|
-
[bir] … execution.reported scenario=scn_9c1b… outcome=steered_full derive=0.0011 session=0.0083 fallback=0 savedUsd=0.114
|
|
351
|
+
[bir] … execution.reported scenario=scn_9c1b… outcome=steered_full derive=0.0011 session=0.0083 fallback=0.0000 savedUsd=0.114
|
|
280
352
|
```
|
|
281
353
|
|
|
282
354
|
In the session you will see one tool call, `mcp__bir__run_scenario`, returning the composed results.
|
|
@@ -284,31 +356,35 @@ The model reads them and answers. It never emitted the three underlying calls.
|
|
|
284
356
|
|
|
285
357
|
### 6.2 A mixed scenario — steer
|
|
286
358
|
|
|
287
|
-
The model emits each call itself; `bir` overrides the arguments.
|
|
359
|
+
The model emits each call itself; `bir` overrides the arguments. The per-step `replay.pin` and
|
|
360
|
+
`replay.thread` lines are detail lines, printed only under `BIR_VERBOSE=1`; without it a steer run
|
|
361
|
+
shows `plan.armed`, `replay.done`, and any `replay.thread_fallback` between them.
|
|
288
362
|
|
|
289
363
|
```
|
|
290
364
|
[bir] … plan.armed scenario=scn_44d0… mode=steer steps=4
|
|
291
365
|
[bir] … replay.pin n=0 tool=mcp__github__list_pull_requests
|
|
292
|
-
[bir] … replay.thread n=0 via=proxy emitted=prNumbers
|
|
366
|
+
[bir] … replay.thread n=0 via=proxy emitted=prNumbers done=false
|
|
293
367
|
[bir] … replay.pin n=1 tool=Read
|
|
294
|
-
[bir] … replay.thread n=1 via=hook emitted=reviewers
|
|
368
|
+
[bir] … replay.thread n=1 via=hook emitted=reviewers done=false
|
|
295
369
|
[bir] … replay.done scenario=scn_44d0… mode=steer steps=4/4 outcome=steered_full
|
|
296
370
|
```
|
|
297
371
|
|
|
298
372
|
`via=proxy` versus `via=hook` matters: a wrapped MCP step's output is threaded from the **proxy's**
|
|
299
373
|
report, because that is the shape the scenario's logic was written against (design §7.2). A
|
|
300
|
-
`
|
|
301
|
-
|
|
374
|
+
`replay.thread_fallback run=… tool=… why="no proxy report — threading the hook's view; shapes may differ"`
|
|
375
|
+
line — printed whether or not you are verbose — means the proxy did not report within its grace
|
|
376
|
+
window and the hook's differently-shaped view was used instead. Expect derived values to be empty,
|
|
377
|
+
and treat repeats as a bug.
|
|
302
378
|
|
|
303
379
|
### 6.3 The model goes off-script
|
|
304
380
|
|
|
305
381
|
```
|
|
306
382
|
[bir] … replay.diverge scenario=scn_44d0… expected=Read called=Bash step=2/4
|
|
307
|
-
[bir] … replay.compose remaining=2
|
|
308
|
-
[bir] … replay.done
|
|
383
|
+
[bir] … replay.compose scenario=scn_44d0… remaining=2 executed=1 recorded=1 skipped=0 bytes=8412 costUsd=0.00
|
|
384
|
+
[bir] … replay.done scenario=scn_44d0… mode=steer steps=2/4 outcome=diverged
|
|
309
385
|
```
|
|
310
386
|
|
|
311
|
-
`costUsd=0` on the compose line is the point of the whole design: the remaining steps ran through
|
|
387
|
+
`costUsd=0.00` on the compose line is the point of the whole design: the remaining steps ran through
|
|
312
388
|
proxies that were already connected, so recovering from a divergence cost no tokens at all.
|
|
313
389
|
|
|
314
390
|
### 6.4 The audit vocabulary
|
|
@@ -321,13 +397,16 @@ Everything replay decides is one line. `grep` for these:
|
|
|
321
397
|
| `replay.decision … verdict=no-steer` | Matched, and declined. `why=` says which gate |
|
|
322
398
|
| `plan.armed` | A plan exists. Carries mode, step count and the ordered tool names — **before** anything runs |
|
|
323
399
|
| `replay.derived` / `replay.derive_failed` | Parameters resolved, or fell back to sample values |
|
|
324
|
-
| `replay.pin` / `replay.thread` | One steered step's input pinned / output threaded |
|
|
400
|
+
| `replay.pin` / `replay.thread` | One steered step's input pinned / output threaded — detail lines, only under `BIR_VERBOSE=1` |
|
|
401
|
+
| `replay.thread_fallback` | A wrapped step's output was threaded from the hook's view because the proxy did not report in time |
|
|
325
402
|
| `replay.step` | One direct step executed |
|
|
326
403
|
| `replay.diverge` / `replay.compose` | Off-script, and the recovery |
|
|
327
404
|
| `replay.done` | Final outcome, steps completed, elapsed |
|
|
328
405
|
| `execution.reported` | The ticket was redeemed and the saving booked |
|
|
329
406
|
|
|
330
|
-
|
|
407
|
+
A recorder started in the background — by the SessionStart hook, `bir up` or `bir setup` — keeps
|
|
408
|
+
them in `~/.baseinstrunner/logs/<key>.log`; `bir status` names the file. One started in a terminal
|
|
409
|
+
writes them to that terminal; keep them yourself: `bir-hooks 2>&1 | tee -a ~/.baseinstrunner/audit.log`.
|
|
331
410
|
|
|
332
411
|
---
|
|
333
412
|
|
|
@@ -381,19 +460,22 @@ first prompt that states the task plainly.
|
|
|
381
460
|
| Symptom | Cause | Fix |
|
|
382
461
|
|---|---|---|
|
|
383
462
|
| No `run.matched`, ever | Prompt below the server's `SIMILARITY_THRESHOLD` | Rephrase closer, or lower it server-side. Confirm the original run is in the list — a sub-`RECORDING_MIN_ACTIONS` run is never embedded |
|
|
384
|
-
| `run.matched` but no `plan.armed` | A gate declined | Read the `replay.decision` line's `why=`. Ranked by frequency: `BIR_REPLAY=0` · scenario not `ready` · similarity below threshold · no step is executable |
|
|
463
|
+
| `run.matched` but no `plan.armed` | A gate declined | Read the `replay.decision` line's `why=`. Ranked by frequency: `bir replay off` or `BIR_REPLAY=0` · scenario not `ready` · similarity below threshold · no step is executable |
|
|
385
464
|
| `why="no step is executable"` | None of the scenario's tools is a wrapped MCP server and none is a built-in reachable in this session | Wrap the servers the scenario uses (`bir install --server …`) and recalculate |
|
|
386
465
|
| `plan.armed mode=steer` where you expected `direct` | At least one step is a built-in, an unwrapped MCP server, or `claude-in-chrome` | `bir status` shows what is wrapped. `claude-in-chrome` is `scope: "dynamic"` and can never be wrapped |
|
|
387
466
|
| Model ignores the directive and diverges every turn | Steering is advisory — the model chooses; `bir` only pins arguments | Expected occasionally. Persistent divergence usually means the scenario's tools do not fit the live task; check `intent` |
|
|
388
|
-
| `replay.thread
|
|
389
|
-
| `
|
|
467
|
+
| A `replay.thread` line with no `emitted=` (under `BIR_VERBOSE=1`) | `toolOutputLogic` derived nothing — usually the output shape changed since recording | Dry-replay (§3.1). If the dry run emits and the live run does not, the tool's output shape has drifted; recalculate |
|
|
468
|
+
| `replay.thread_fallback` on a wrapped step | The proxy did not report within the grace window | Check that proxy is alive (`bir doctor`). Repeats mean a slow or dying upstream |
|
|
390
469
|
| `run_scenario` returns "no scenario is armed" | The model called it on a turn with no plan | Harmless. It happens when the model remembers the tool from an earlier turn |
|
|
391
|
-
| `
|
|
470
|
+
| `recorder.send_failed … error="HTTP 409 … invalid_ticket"` | The execution report's ticket was already redeemed, or belongs to another scenario | Harmless if the body says `duplicate: true`. Otherwise a rollover raced `SessionEnd`; the server books once |
|
|
392
471
|
| `savedUsd` looks impossible | Pricing drift between the runner's table and the server's | Compare `PRICING_VERSION` on both sides. Design §11.3 documents a known live drift between two existing copies |
|
|
393
|
-
| The hand-over note says a step "did not run: its input needs a judgement" | The service marked the step non-deterministic when it calculated the plan: the generated logic could not compute its input from the prompt or from earlier outputs, so the recorded value was a copy (plan-services
|
|
394
|
-
| A parked step ran again after an hour, or a plan was recalculated by itself | The service's grace period (D8): a parked step is served with `stop: null` once an hour for a day and a success clears its count; if it keeps failing, or its tool is gone, the next repeat of the prompt is recorded in full and the plan is calculated again from it | Nothing: that is the repair. `bir investigate`
|
|
472
|
+
| The hand-over note says a step "did not run: its input needs a judgement" | The service marked the step non-deterministic when it calculated the plan: the generated logic could not compute its input from the prompt or from earlier outputs, so the recorded value was a copy (the service's plan-services design, D8 kind 3 — it lives in the BaseIn repository, not this one). `nondeterministicWhy: by_hand` means someone marked it with `bir scenario edit --freeze` | If the code only has a slip, fix the step yourself: `bir scenario show scn_… --step <n>`, write the input logic to a file, then `bir scenario edit scn_… --step <n> --input-logic <file>` — the service checks it against the recording before it saves it (§9.2). A real judgement — a choice with no fixed rule — cannot be written as code: the agent makes that choice and finishes the task; a model step for it is planned (W3.1). The journal's `plan.armed` line lists the step under `stops=` |
|
|
473
|
+
| A parked step ran again after an hour, or a plan was recalculated by itself | The service's grace period (D8): a parked step is served with `stop: null` once an hour for a day and a success clears its count; if it keeps failing, or its tool is gone, the next repeat of the prompt is recorded in full and the plan is calculated again from it | Nothing: that is the repair. `bir investigate` says which it is — a step still being retried, a parked step whose repair is due, or a plan being calculated again. To repair now by hand: `bir scenario calc <runId> --force`. A plan with steps edited by hand is not repaired by itself: the parked step stays parked, and `bir investigate` says the repair was skipped. Fix the step (§9.2), or add `--discard-edits` |
|
|
395
474
|
| `sessionCostUsd` grows every prompt in a session | The per-run usage watermark is missing or not taken | Design §11.4. Reports without a mark must carry `measured: false` |
|
|
396
475
|
| Everything works, nothing is saved | A replayed turn is deliberately not recorded (design §12) | Correct. The matched run stays canonical and its `iterations` is bumped |
|
|
476
|
+
| `bir doctor`: the SessionStart hook points at a Node that is gone | The hook names Node by absolute path. That Node was uninstalled, moved, or was a version manager's per-shell copy; the hook then fails on every session, no recorder is started, and runs record Tier 2 | `bir setup` here again — it rewrites the hook to the Node that runs it |
|
|
477
|
+
| `control.port_busy` in the log; the recorder is not running | Another program holds this project's port, or the system excludes it (on Windows, a range Hyper-V or WSL reserved). A background recorder with a recorded port does not fall back to another, because every hook but SessionStart names this one; only a `bir-hooks` you run by hand in a terminal still does | `bir setup --port <another>` in this directory — the fix the log line itself names. It rewrites the hook URLs and the scenario server's address together and restarts the recorder |
|
|
478
|
+
| The recorder was started by the hook, and `bir doctor` says `servers=(all wrapped)` although you set `BIR_REPLAY_ALLOW_SERVERS` | The hook starts the recorder with Claude Code's environment, not your terminal's; a variable set in one terminal is not seen | `bir replay allow <a,b>` — stored for the project, read by every recorder — then `bir up --restart` |
|
|
397
479
|
|
|
398
480
|
---
|
|
399
481
|
|
|
@@ -401,8 +483,9 @@ first prompt that states the task plainly.
|
|
|
401
483
|
|
|
402
484
|
Every audit line that explains a turn is also kept in a **journal**, one JSON
|
|
403
485
|
object per line, under `~/.baseinstrunner/control/journal/` (one file per
|
|
404
|
-
directory, rotated at 4 MB).
|
|
405
|
-
|
|
486
|
+
directory, rotated at 4 MB). The recorder names it at start — the `journal=`
|
|
487
|
+
field of its `control.listening` line, in the audit log for a background
|
|
488
|
+
recorder. Ask the journal and the service together:
|
|
406
489
|
|
|
407
490
|
```
|
|
408
491
|
bir investigate # the newest turn in this directory
|
|
@@ -427,6 +510,312 @@ fix*. The three questions it answers:
|
|
|
427
510
|
You see your own data. An admin of the service sees everyone's and may pass
|
|
428
511
|
`--user` to `executions`. `--json` on any form prints the raw merge.
|
|
429
512
|
|
|
513
|
+
### 9.2 Fix a step — `bir scenario check`, `edit`, `edits`, `undo`
|
|
514
|
+
|
|
515
|
+
> Built 2026-09-25, **not yet released**. It needs the next `@basein/runner` version *and* the
|
|
516
|
+
> service deploy that adds the `/scenarios/:id/steps/…` routes. The design is `editSteps.md` in the
|
|
517
|
+
> BaseIn repository.
|
|
518
|
+
|
|
519
|
+
`bir investigate` says *what* is wrong. This section is how you fix it. A calculated step is code:
|
|
520
|
+
its **input logic** says what to send to the tool, and its **output logic** says what to keep from
|
|
521
|
+
the answer. When that code is wrong, you do not have to recalculate and hope any more. You change
|
|
522
|
+
the step, and the service checks your change against the recording before it saves it.
|
|
523
|
+
|
|
524
|
+
**A change is saved only if it reproduces the recording and is not a copy — or on purpose, with
|
|
525
|
+
`--force --note "why"`.**
|
|
526
|
+
|
|
527
|
+
```
|
|
528
|
+
bir scenario show <runId|scnId> [--step <n>]
|
|
529
|
+
bir scenario check <runId|scnId> --step <n> [--input-logic <file|->] [--output-logic <file|->] [--unfreeze]
|
|
530
|
+
bir scenario edit <runId|scnId> --step <n> [--input-logic <file|->] [--output-logic <file|->]
|
|
531
|
+
[--freeze | --unfreeze] [--note "<why>"] [--force --note "<why>"] [--revision <n>]
|
|
532
|
+
bir scenario edits <runId|scnId>
|
|
533
|
+
bir scenario undo <runId|scnId> [--edit <sedit_id>]
|
|
534
|
+
bir scenario calc <runId> [--force [--discard-edits]]
|
|
535
|
+
bir scenario editing on|off|status
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
A `run_` id finds the run's scenario; a `scn_` id (what `bir investigate` prints) is used as it is.
|
|
539
|
+
A sub-task scenario has only a `scn_` id. `--step` is the `stepIndex` that `show` prints; it starts
|
|
540
|
+
at 0. `--revision <n>` saves only if the plan is still at revision `n` (the `chainRevision` that
|
|
541
|
+
`show` printed). `--json` on `check`, `edit`, `edits` or `undo` prints the service's answer
|
|
542
|
+
unchanged. Exit codes: `0`
|
|
543
|
+
checked OK, saved or undone · `1` refused, not found, or a service error · `2` a wrong command line.
|
|
544
|
+
|
|
545
|
+
#### What the code is
|
|
546
|
+
|
|
547
|
+
Each logic is the *body* of a JavaScript function. It must end in `return { … }`.
|
|
548
|
+
|
|
549
|
+
| Logic | Reads | Returns |
|
|
550
|
+
|---|---|---|
|
|
551
|
+
| input logic (`toolInputLogic`) | `parameters` — this request's values · `respParams` — what earlier steps kept · `intent` | the tool's input, as an object |
|
|
552
|
+
| output logic (`toolOutputLogic`) | `toolOutput` — the tool's answer, as text · and the same three | the values to keep for later steps, as an object |
|
|
553
|
+
|
|
554
|
+
When the service runs it, only JavaScript itself is there: `JSON`, `Math`, `Date`, `RegExp` and the
|
|
555
|
+
like. There is no `require`, `process`, `fetch` or timer, and code that uses one fails the check.
|
|
556
|
+
|
|
557
|
+
**Only a step's code and its mark can be changed.** Not the parameters, not their sample values, not
|
|
558
|
+
the logic that builds the final answer. Only tool steps: a sub-task call row belongs to the service.
|
|
559
|
+
|
|
560
|
+
#### The fix, step by step
|
|
561
|
+
|
|
562
|
+
On 2026-09-25 two SQL steps of the hiring-year scenario were marked *needs a judgement*. Their code
|
|
563
|
+
wrote `schema + '.' + summary_table`, but the table name already carried its schema. Step 4 is shown
|
|
564
|
+
here; step 3 is the same.
|
|
565
|
+
|
|
566
|
+
**1. Read the step.**
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
bir scenario show scn_4a17a126-… --step 4
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
```
|
|
573
|
+
{
|
|
574
|
+
"stepIndex": 4,
|
|
575
|
+
"toolName": "mcp__postgres__execute_sql",
|
|
576
|
+
"toolInputLogic": "return {\"sql\":\"SELECT * FROM employees.hiring_year_summary ORDER BY hire_year;\"};",
|
|
577
|
+
"nondeterministic": true,
|
|
578
|
+
"nondeterministicWhy": "input_mismatch",
|
|
579
|
+
"sampleInput": "{\"sql\":\"SELECT * FROM employees.hiring_year_summary ORDER BY hire_year;\"}",
|
|
580
|
+
…
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
The stored input logic is a copy of the recorded call. A copy does the old job, on the old table,
|
|
585
|
+
whatever the new request says. That is why the step is marked (`nondeterministic: true`), and why
|
|
586
|
+
runs hand over to the agent in front of it.
|
|
587
|
+
|
|
588
|
+
**2. Write the new code to a file.**
|
|
589
|
+
|
|
590
|
+
```bash
|
|
591
|
+
cat step4.js
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
```
|
|
595
|
+
return { sql: "SELECT * FROM " + parameters.summary_table + " ORDER BY hire_year;" };
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Code always goes in from a file, or from stdin with `-`. Never on the command line: the shell's
|
|
599
|
+
quoting breaks JavaScript.
|
|
600
|
+
|
|
601
|
+
**3. Check it.** Nothing is saved.
|
|
602
|
+
|
|
603
|
+
```bash
|
|
604
|
+
bir scenario check scn_4a17a126-… --step 4 --input-logic step4.js
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
```
|
|
608
|
+
Step 4 (mcp__postgres__execute_sql) — checked against run_67afb010-…
|
|
609
|
+
input reproduces the recorded call
|
|
610
|
+
later no later steps
|
|
611
|
+
mark needs a judgement (input_mismatch) → runs by itself
|
|
612
|
+
Nothing saved. To save: bir scenario edit scn_4a17a126-… --step 4 --input-logic step4.js
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
- `input` — does the code give the recorded call?
|
|
616
|
+
- `later` — do the steps after it still give theirs?
|
|
617
|
+
- `mark` — what happens to the *needs a judgement* mark. `→ runs by itself` means the mark goes
|
|
618
|
+
when you save, and runs stop handing over in front of this step.
|
|
619
|
+
|
|
620
|
+
**4. Save it.** `edit` runs the same check, and saves only what the check approved. Add a `--note`
|
|
621
|
+
that says why; the history keeps it.
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
bir scenario edit scn_4a17a126-… --step 4 --input-logic step4.js --note "summary_table already carries its schema"
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
```
|
|
628
|
+
Step 4 (mcp__postgres__execute_sql) — checked against run_67afb010-…
|
|
629
|
+
input reproduces the recorded call
|
|
630
|
+
mark needs a judgement (input_mismatch) → runs by itself
|
|
631
|
+
Saved as sedit_7c1e… (revision 4). Undo: bir scenario undo scn_4a17a126-… --edit sedit_7c1e…
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
**5. Try it without real tools.** A dry replay (§3.1) runs the new code over the recorded outputs:
|
|
635
|
+
`bir scenario replay scn_4a17a126-… --prompt "…" --dry`. The next real prompt uses the new code.
|
|
636
|
+
There is nothing to restart.
|
|
637
|
+
|
|
638
|
+
#### How the check decides
|
|
639
|
+
|
|
640
|
+
The service runs your code the way the recorded run would have run it: with the recording's
|
|
641
|
+
parameter values, and with what the earlier steps kept from their recorded answers.
|
|
642
|
+
|
|
643
|
+
| It asks | It refuses when |
|
|
644
|
+
|---|---|
|
|
645
|
+
| Does the code give the recorded call? Spaces inside strings do not count | it gives something else (`differs`), or it crashes (`throws`) |
|
|
646
|
+
| Is it a copy? It changes one parameter, or one earlier output, at a time | the call never moves: the code returns the recorded call whatever the request says |
|
|
647
|
+
| Do the later steps still give their calls? | a later step that gave its recorded call before does not any more |
|
|
648
|
+
| Does the final answer still get its values? | it did before, and does not after |
|
|
649
|
+
|
|
650
|
+
A recorded input of `{}` is never a copy. A recorded input that is plain text, not an object, is
|
|
651
|
+
`not_checked`.
|
|
652
|
+
|
|
653
|
+
A refused change says where it differs:
|
|
654
|
+
|
|
655
|
+
```bash
|
|
656
|
+
bir scenario edit scn_4a17a126-… --step 4 --input-logic wrong.js
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
```
|
|
660
|
+
Step 4 (mcp__postgres__execute_sql) — checked against run_67afb010-…
|
|
661
|
+
input differs from the recorded call at character 14
|
|
662
|
+
computed …FROM employees.employees.hiring_year_summary ORDER…
|
|
663
|
+
recorded …FROM employees.hiring_year_summary ORDER BY hire_…
|
|
664
|
+
Not saved: the change does not reproduce the recording.
|
|
665
|
+
Fix the code and try again, or save it on purpose: add --force --note "why".
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
A pasted value is refused the same way:
|
|
669
|
+
`input is a copy: it returns the recorded call whatever the parameters say`.
|
|
670
|
+
|
|
671
|
+
#### `--force --note`: save it on purpose
|
|
672
|
+
|
|
673
|
+
**Use `--force` only when the recording itself was wrong** — for example, the recorded run sent a
|
|
674
|
+
call with a mistake you do not want repeated. Then no correct code can reproduce it.
|
|
675
|
+
|
|
676
|
+
- `--force` needs a `--note` that says why (500 characters at most). Without one: `note_required`.
|
|
677
|
+
- A forced save removes the mark, unless you also pass `--freeze`.
|
|
678
|
+
- It is saved as *forced*, and `bir investigate` keeps saying so, as a `warn`.
|
|
679
|
+
- A step after a sub-task call row can only be saved with `--force`: the check cannot rebuild the
|
|
680
|
+
outputs that step reads.
|
|
681
|
+
|
|
682
|
+
#### Mark or unmark a step by hand
|
|
683
|
+
|
|
684
|
+
- `--freeze` marks the step *needs a judgement* (`nondeterministicWhy: by_hand`). Runs then hand
|
|
685
|
+
over to the agent in front of it. No check blocks this: a mark only makes replay more careful.
|
|
686
|
+
- `--unfreeze` asks to remove a mark. The check decides: the step's input must reproduce.
|
|
687
|
+
`bir scenario check … --unfreeze` asks the same question without saving.
|
|
688
|
+
|
|
689
|
+
#### The history, and undo
|
|
690
|
+
|
|
691
|
+
```bash
|
|
692
|
+
bir scenario edits scn_4a17a126-…
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
```
|
|
696
|
+
sedit_7c1e… 2026-09-25 09:12:04Z step 4 edit revision 3 → 4 "summary_table already carries its schema"
|
|
697
|
+
input logic changed; needs a judgement (input_mismatch) → runs by itself
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
bir scenario undo scn_4a17a126-…
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
```
|
|
705
|
+
Step 4 is back as it was before sedit_7c1e… (revision 5). Redo: bir scenario undo scn_4a17a126-… --edit sedit_9a02…
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
- `undo` puts **one step** back as it was before its newest edit: its code and its mark. Without
|
|
709
|
+
`--edit` it undoes the newest edit that can be undone.
|
|
710
|
+
- The undo is a new line in the history. Undo the undo to redo.
|
|
711
|
+
- Only the newest edit of a step can be undone. For an older one you get `not_latest_edit` and the
|
|
712
|
+
id of the newer edit: undo that one first.
|
|
713
|
+
|
|
714
|
+
#### What a save changes
|
|
715
|
+
|
|
716
|
+
- **The next prompt uses the new code.** The service hands out the plan fresh on every prompt. A
|
|
717
|
+
turn that is already running keeps the old plan until it ends.
|
|
718
|
+
- **The revision goes up by one, and the step's failure count is cleared.** Reports from turns that
|
|
719
|
+
started before the change do not count against the new code. An undo does the same.
|
|
720
|
+
- **A saved step runs unattended, exactly like calculated code** (§5.1). Read your change as
|
|
721
|
+
carefully as you read a calculated plan, and dry-replay it (§3.1).
|
|
722
|
+
- **Edits are protected.** A recalculation rebuilds every step from the recording, and would throw
|
|
723
|
+
your fix away. So `calc --force` refuses:
|
|
724
|
+
|
|
725
|
+
```bash
|
|
726
|
+
bir scenario calc run_67afb010-… --force
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
```
|
|
730
|
+
Not recalculated: steps 3, 4 were edited by hand, and a recalculation rebuilds every step.
|
|
731
|
+
To recalculate anyway and discard the edits: bir scenario calc run_67afb010-… --force --discard-edits
|
|
732
|
+
(the edits stay in `bir scenario edits`, marked replaced)
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
The console's *Recalculate* is refused too. The service's own rebuilds — the repair of a parked
|
|
736
|
+
step (§9) and the adoption of a sub-task — skip a plan with hand edits, and `bir investigate` says
|
|
737
|
+
so. After `--discard-edits` the old edits stay readable, but they cannot be undone
|
|
738
|
+
(`edit_replaced`).
|
|
739
|
+
|
|
740
|
+
#### What `bir investigate` says
|
|
741
|
+
|
|
742
|
+
It names the fix, and it reports edits. Here, after step 4 was saved and before step 3 was:
|
|
743
|
+
|
|
744
|
+
```
|
|
745
|
+
1. [warn] Step 3 (mcp__postgres__execute_sql) needs a judgement, so runs hand over in front of it.
|
|
746
|
+
fix: Recalculate once (`bir scenario calc run_… --force`) … Or fix it yourself: read it with
|
|
747
|
+
`bir scenario show scn_… --step 3`, write the input logic to a file, and run
|
|
748
|
+
`bir scenario edit scn_… --step 3 --input-logic <file>`; the service checks it against the recording.
|
|
749
|
+
2. [info] Step 4 (mcp__postgres__execute_sql) was edited by hand on 2026-09-25 (revision 4).
|
|
750
|
+
fix: To see or undo it: `bir scenario edits scn_…`. A recalculation keeps it unless you pass --discard-edits.
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
A forced edit is a `warn`, not an `info`. So is a repair the service skipped because of hand edits.
|
|
754
|
+
|
|
755
|
+
#### Let Claude do it
|
|
756
|
+
|
|
757
|
+
Claude Code can do all of this for you, in two ways. Both use the same service routes and the same
|
|
758
|
+
check.
|
|
759
|
+
|
|
760
|
+
1. **In its terminal.** Claude runs `bir scenario …` like any other command. You see each command,
|
|
761
|
+
and Claude Code asks you before it runs one, unless you allowed it. This works in every project.
|
|
762
|
+
2. **As tools on the `bir` MCP server** — the server `bir setup` or `bir install --replay` adds
|
|
763
|
+
(§5.2). This is smoother for the model. **Reading is always offered. Changing is off until you
|
|
764
|
+
turn it on, per project:**
|
|
765
|
+
|
|
766
|
+
```bash
|
|
767
|
+
bir scenario editing on # this project's `bir` server also offers the tools that change a scenario
|
|
768
|
+
bir scenario editing off # back to the tools that only read
|
|
769
|
+
bir scenario editing status # what is stored for this project
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
| Tool (`mcp__bir__…`) | Offered | Runs |
|
|
773
|
+
|---|---|---|
|
|
774
|
+
| `scenario_show` | always | `bir scenario show` |
|
|
775
|
+
| `scenario_edits` | always | `bir scenario edits` |
|
|
776
|
+
| `investigate` | always | `bir investigate` |
|
|
777
|
+
| `scenario_check` | after `bir scenario editing on` | `bir scenario check` |
|
|
778
|
+
| `scenario_edit` | after `bir scenario editing on` | `bir scenario edit` |
|
|
779
|
+
| `scenario_undo` | after `bir scenario editing on` | `bir scenario undo` |
|
|
780
|
+
|
|
781
|
+
Why it is off by default: the `bir` server runs in every session of every project it is installed
|
|
782
|
+
in, a fleet included. A plan must not change because some agent there decided to change it. The
|
|
783
|
+
switch is kept in `~/.baseinstrunner/installed.json`, next to the replay switches. It only decides
|
|
784
|
+
which tools the `bir` server offers; the `bir scenario` commands work everywhere. **Restart the
|
|
785
|
+
Claude Code session after you switch it** — a running session does not see the change.
|
|
786
|
+
|
|
787
|
+
**Do this.** In the Claude Code session, in the project folder, paste:
|
|
788
|
+
|
|
789
|
+
```
|
|
790
|
+
Run `bir investigate` in this folder. If a step needs a judgement or its code
|
|
791
|
+
is wrong, read it with `bir scenario show`, write the corrected code to a file,
|
|
792
|
+
and test it with `bir scenario check`. Show me the check and explain it in plain
|
|
793
|
+
words. Do not save anything until I say yes. Then save it with
|
|
794
|
+
`bir scenario edit` and a --note that says why. Never use --force.
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
For the hiring-year case, Claude's report would be: *step 4 was marked because its code added the
|
|
798
|
+
schema twice; the new code builds the query from `summary_table`; the check says it reproduces the
|
|
799
|
+
recording, and the step will run by itself.*
|
|
800
|
+
|
|
801
|
+
#### When it does not work
|
|
802
|
+
|
|
803
|
+
| Symptom | Cause | Fix |
|
|
804
|
+
|---|---|---|
|
|
805
|
+
| `input differs from the recorded call at character N` | The code gives a different call from the recorded one | Read the `computed` and `recorded` lines, fix the code, check again |
|
|
806
|
+
| `input is a copy` | The code returns the recorded call whatever the request says — usually a pasted value | Build the call from `parameters.…` or `respParams.…` |
|
|
807
|
+
| `input throws` | The code crashed; the message is shown | Fix it. `require`, `process`, `fetch` and timers do not exist in the check |
|
|
808
|
+
| A `later` step no longer reproduces | The change broke a step after it — usually output logic that stopped keeping a value a later step reads | Keep every key the later steps read |
|
|
809
|
+
| `not_a_tool_step` | The index is a sub-task call row, not a tool step | Call rows cannot be edited; the service writes them |
|
|
810
|
+
| `note_required` | `--force` without `--note` | Add `--note "why"` |
|
|
811
|
+
| `revision_changed` | The plan changed since you read it: another edit, or a recalculation | `show` it again, check again, save again |
|
|
812
|
+
| `not_ready` or `scenario_calculating` | The scenario is not `ready`, or a recalculation is running | Wait for it to finish |
|
|
813
|
+
| `not_latest_edit` | A newer edit of the same step follows the one you named | Undo the newer one first; the answer names it |
|
|
814
|
+
| `edit_replaced` | A recalculation replaced the plan after that edit | Nothing to undo; the edit is history now |
|
|
815
|
+
| `rate_limited` | More checks, edits and undos in a minute than the service allows (`SCENARIO_EDITS_PER_MINUTE`, 30 by default) | Wait a minute |
|
|
816
|
+
| `not found` for a scenario you can see | It is not yours. **Only the owner can change a scenario** — an admin gets `not found` too | Ask the owner |
|
|
817
|
+
| The `scenario_check`, `scenario_edit` and `scenario_undo` tools are missing | `bir scenario editing on` was not run in this project, or the session started before it | Run it here, then restart the session |
|
|
818
|
+
|
|
430
819
|
---
|
|
431
820
|
|
|
432
821
|
## 10. What is built
|
|
@@ -446,8 +835,8 @@ tested; everything this guide describes is code you can run.
|
|
|
446
835
|
| **R7** | Usage watermark, reporting, ticket, doctor | [control/transcript.ts](../src/control/transcript.ts), [remote-recorder.ts](../src/record/remote-recorder.ts) |
|
|
447
836
|
|
|
448
837
|
```bash
|
|
449
|
-
npm test
|
|
450
|
-
npm run build &&
|
|
838
|
+
npm test # ~400 tests, incl. test/replay.test.ts and test/replay-server.test.ts
|
|
839
|
+
npm run build && npm run pretest && npm run test:smoke # the smoke test needs the test build too
|
|
451
840
|
```
|
|
452
841
|
|
|
453
842
|
The smoke test is the one worth running after any change to the seams. It starts a stand-in BaseIn
|
|
@@ -473,10 +862,18 @@ Three things the implementation settled that the design left open:
|
|
|
473
862
|
Three levels, least to most.
|
|
474
863
|
|
|
475
864
|
```bash
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
865
|
+
bir replay off # stop arming. Matches still detected; recording still stops on a match.
|
|
866
|
+
# Stored for this project; `bir up --restart` applies it, `bir replay on` undoes it
|
|
867
|
+
export BIR_REPLAY=0 # the same, wherever the recorder is started from (the SessionStart hook
|
|
868
|
+
# gives it Claude Code's environment, not your terminal's)
|
|
869
|
+
bir uninstall --replay # remove the `bir` MCP server only; proxies, hooks and the prompt-hook timeout stay
|
|
870
|
+
bir uninstall # remove everything: proxies, hooks, the pre-approval
|
|
479
871
|
```
|
|
480
872
|
|
|
481
|
-
|
|
873
|
+
`bir uninstall` restores a file byte-for-byte when nothing else edited it since the install, and
|
|
874
|
+
repairs it entry by entry otherwise. A file the install created — usually
|
|
875
|
+
`.claude/settings.local.json` — stays behind with our entries removed, and the line that hid it in
|
|
876
|
+
`.git/info/exclude` goes with them.
|
|
877
|
+
|
|
878
|
+
With replay off the system is exactly v1 again: it recognises a repeated prompt, declines to
|
|
482
879
|
record it a second time, and lets the model do the work.
|