@basein/runner 0.2.10 → 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 +22 -1
- package/dist/bin/bir-scenario.d.ts +18 -2
- package/dist/bin/bir-scenario.js +374 -4
- package/dist/bin/bir.d.ts +10 -0
- package/dist/bin/bir.js +140 -43
- 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/config/generate.d.ts +21 -0
- package/dist/config/generate.js +16 -0
- package/docs/calculatedReplay.md +51 -0
- package/docs/calculatedReplayGuide.md +318 -8
- package/docs/quickstart.md +3 -1
- package/package.json +1 -1
|
@@ -157,6 +157,18 @@ export interface ProjectRecord {
|
|
|
157
157
|
* a fleet's absolute paths into pinned-npx entries.
|
|
158
158
|
*/
|
|
159
159
|
invocation?: Invocation;
|
|
160
|
+
/**
|
|
161
|
+
* `bir scenario editing on|off` (editSteps.md D2, in the BaseIn repository):
|
|
162
|
+
* whether this project's `bir` MCP server also offers the tools that CHANGE
|
|
163
|
+
* a calculated scenario — `scenario_check`, `scenario_edit`, `scenario_undo`.
|
|
164
|
+
* Absent means off. The read tools are offered either way.
|
|
165
|
+
*
|
|
166
|
+
* Per project, and off by default, because the `bir` server runs in every
|
|
167
|
+
* session of every installed project — a fleet included — and a plan must
|
|
168
|
+
* not change because some agent there decided it should. The `bir scenario`
|
|
169
|
+
* commands ignore it: a person in a terminal is asked before each one.
|
|
170
|
+
*/
|
|
171
|
+
editing?: boolean;
|
|
160
172
|
}
|
|
161
173
|
export interface InstalledSidecar {
|
|
162
174
|
version: 1;
|
|
@@ -216,4 +228,13 @@ export declare function allocateFreeProjectPort(sidecar: InstalledSidecar, cwd:
|
|
|
216
228
|
export declare function setProjectRecord(sidecar: InstalledSidecar, cwd: string, patch: Partial<ProjectRecord> & Pick<ProjectRecord, "port">): ProjectRecord;
|
|
217
229
|
/** The bearer token this project's hooks and control server share. */
|
|
218
230
|
export declare function projectToken(sidecar: InstalledSidecar, cwd: string): string | undefined;
|
|
231
|
+
/** Whether `bir scenario editing on` was run for `cwd`; see {@link ProjectRecord.editing}. */
|
|
232
|
+
export declare function editingEnabled(sidecar: InstalledSidecar, cwd: string): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Store `bir scenario editing on|off` for `cwd`, under the same normalised key
|
|
235
|
+
* as the replay switches. `undefined` when the directory has no record: it was
|
|
236
|
+
* never installed, so there is no `bir` server here to offer anything, and
|
|
237
|
+
* inventing a record would invent a port the hooks do not use.
|
|
238
|
+
*/
|
|
239
|
+
export declare function setEditing(sidecar: InstalledSidecar, cwd: string, on: boolean): ProjectRecord | undefined;
|
|
219
240
|
//# sourceMappingURL=generate.d.ts.map
|
package/dist/config/generate.js
CHANGED
|
@@ -198,4 +198,20 @@ export function setProjectRecord(sidecar, cwd, patch) {
|
|
|
198
198
|
export function projectToken(sidecar, cwd) {
|
|
199
199
|
return projectRecord(sidecar, cwd)?.token ?? sidecar.token;
|
|
200
200
|
}
|
|
201
|
+
/** Whether `bir scenario editing on` was run for `cwd`; see {@link ProjectRecord.editing}. */
|
|
202
|
+
export function editingEnabled(sidecar, cwd) {
|
|
203
|
+
return projectRecord(sidecar, cwd)?.editing === true;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Store `bir scenario editing on|off` for `cwd`, under the same normalised key
|
|
207
|
+
* as the replay switches. `undefined` when the directory has no record: it was
|
|
208
|
+
* never installed, so there is no `bir` server here to offer anything, and
|
|
209
|
+
* inventing a record would invent a port the hooks do not use.
|
|
210
|
+
*/
|
|
211
|
+
export function setEditing(sidecar, cwd, on) {
|
|
212
|
+
const record = projectRecord(sidecar, cwd);
|
|
213
|
+
if (!record)
|
|
214
|
+
return undefined;
|
|
215
|
+
return setProjectRecord(sidecar, cwd, { port: record.port, editing: on });
|
|
216
|
+
}
|
|
201
217
|
//# sourceMappingURL=generate.js.map
|
package/docs/calculatedReplay.md
CHANGED
|
@@ -814,6 +814,23 @@ What actually constrains it:
|
|
|
814
814
|
If that is not enough for a deployment, the mitigation is a real one and is out of scope here:
|
|
815
815
|
evaluate the logic in a `node:vm` context with a frozen, minimal global. Filed as §20 question 3.
|
|
816
816
|
|
|
817
|
+
**A hand-edited step is the same code, and runs the same way.** Since 2026-09-25 (the service's
|
|
818
|
+
`editSteps.md`, not yet released) the owner — or Claude working for them — can change a step's
|
|
819
|
+
logic with `bir scenario edit`. The service checks the change against the recording before it saves
|
|
820
|
+
it, but that check is about *what the code computes*. Once saved, the step arrives in the same
|
|
821
|
+
payload and runs here unattended, with `new Function`, next to the same credentials, exactly like
|
|
822
|
+
calculated code. The provenance argument still holds: only the owner can edit, over their own token,
|
|
823
|
+
and anyone else — an admin included — gets `404`. What changes is that *"the model wrote it"* is no
|
|
824
|
+
longer always true.
|
|
825
|
+
|
|
826
|
+
The service now sandboxes its own evaluation of every logic body: one worker thread with an empty
|
|
827
|
+
environment and a memory cap, a fresh `node:vm` context per evaluation with a time limit and nothing
|
|
828
|
+
but JavaScript's own built-ins, values in and out as JSON text only. It had to: there, one account's
|
|
829
|
+
code runs in the process that holds every account's data and the service's secrets. **The runner
|
|
830
|
+
still does not sandbox.** Here the code runs on the owner's machine, for the owner's account. One
|
|
831
|
+
consequence to know: code that uses `fetch`, `process` or a timer throws in the service's check, so
|
|
832
|
+
it cannot be saved without `--force`; a forced step would run here with all of them.
|
|
833
|
+
|
|
817
834
|
### 13.2 Replay bypasses permission prompts
|
|
818
835
|
|
|
819
836
|
This is the sharpest edge in the whole design, and it must not be buried.
|
|
@@ -1038,12 +1055,46 @@ bir uninstall --replay reverse exactly that, leaving the rest of the inst
|
|
|
1038
1055
|
|
|
1039
1056
|
bir scenario list GET /recordings/runs runs, iterations, scenario state
|
|
1040
1057
|
bir scenario show <runId> GET /recordings/runs/:id/scenario intent, params, steps, logic
|
|
1058
|
+
bir scenario show <scnId> GET /scenarios/:id the same, by scenario id (segments too)
|
|
1059
|
+
[--step <n>: one step]
|
|
1041
1060
|
bir scenario calc <runId> POST /recordings/runs/:id/calculate [--force to re-derive in place]
|
|
1061
|
+
[--force --discard-edits: re-derive a plan with hand edits]
|
|
1042
1062
|
bir scenario replay <scnId> --prompt "…" [--dry]
|
|
1043
1063
|
|
|
1064
|
+
bir scenario check <id> --step <n> POST /scenarios/:id/steps/:n/check try a change; writes nothing
|
|
1065
|
+
bir scenario edit <id> --step <n> PATCH /scenarios/:id/steps/:n the same check, then save
|
|
1066
|
+
bir scenario edits <id> GET /scenarios/:id/edits the history, newest first
|
|
1067
|
+
bir scenario undo <id> [--edit <e>] POST /scenarios/:id/edits/:e/revert put one step back
|
|
1068
|
+
bir scenario editing on|off|status (no route) ~/.baseinstrunner/installed.json which `bir` MCP tools are offered
|
|
1069
|
+
|
|
1044
1070
|
bir replay --scenario <scnId> --prompt "…" [--dry] alias; the Tier 2 / debugging path (§12.1)
|
|
1045
1071
|
```
|
|
1046
1072
|
|
|
1073
|
+
The second group, and `show <scnId>`, `--step` and `--discard-edits`, were added on 2026-09-25 by
|
|
1074
|
+
the service's `editSteps.md` and are not yet released; their routes are new on the service. They
|
|
1075
|
+
are the one place `bir` changes a scenario, so the rules are strict:
|
|
1076
|
+
|
|
1077
|
+
- **Owner only.** Anyone else, an admin included, gets `404`, never `403`.
|
|
1078
|
+
- **A change is saved only if it reproduces the recording and is not a copy**, or on purpose with
|
|
1079
|
+
`--force --note "<why>"`. `check` and `edit` run the same check; `check` never writes.
|
|
1080
|
+
- `<id>` is a `run_` id, resolved to its whole-run scenario with `GET /recordings/runs/:id/scenario`,
|
|
1081
|
+
or a `scn_` id, used as it is. Logic goes in with `--input-logic` / `--output-logic` from a file,
|
|
1082
|
+
or `-` for stdin — never inline, because shell quoting mangles JavaScript. `edit` also takes
|
|
1083
|
+
`--freeze`, `--unfreeze`, `--note` and `--revision <n>` (the `expectedRevision` of the body).
|
|
1084
|
+
- `undo` without `--edit` undoes the newest edit that can be undone. Every save and every undo bumps
|
|
1085
|
+
the scenario's `chainRevision`.
|
|
1086
|
+
- `calc --force` answers `409 scenario_has_edits` on a plan with hand edits, unless
|
|
1087
|
+
`--discard-edits` is given.
|
|
1088
|
+
- Exit codes: `0` checked OK, saved or undone; `1` refused, not found, or a service error; `2`
|
|
1089
|
+
usage. `--json` prints the service's body unchanged.
|
|
1090
|
+
|
|
1091
|
+
The same commands are tools on the `bir` MCP server. `scenario_show`, `scenario_edits` and
|
|
1092
|
+
`investigate` only read, and are always offered. `scenario_check`, `scenario_edit` and
|
|
1093
|
+
`scenario_undo` are offered only in a project where `bir scenario editing on` was run, because that
|
|
1094
|
+
server runs in every session of every installed project. Each tool runs the installed `bir … --json`;
|
|
1095
|
+
a refusal comes back as an MCP error result carrying the report. The operator's walk-through is
|
|
1096
|
+
[calculatedReplayGuide.md](calculatedReplayGuide.md) §9.2.
|
|
1097
|
+
|
|
1047
1098
|
`--dry` maps to BaseIn's `POST /scenarios/:id/replay`, which evaluates the stored logic against the
|
|
1048
1099
|
source run's *recorded* outputs — no real tools, no side effects, one Haiku call. Without `--dry`,
|
|
1049
1100
|
`bir replay` runs the same plan through the same executor against the live proxies. `bir doctor`
|
|
@@ -148,7 +148,9 @@ Calculation is a background job. Poll `show` until `state` leaves `calculating`:
|
|
|
148
148
|
| `failed` | see the `error` field | fix and recalculate with `--force` |
|
|
149
149
|
|
|
150
150
|
`409 scenario_exists` means this run already has one. Re-derive it in place — keeping the id and its
|
|
151
|
-
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.
|
|
152
154
|
|
|
153
155
|
### 3.1 Check the scenario before you trust it
|
|
154
156
|
|
|
@@ -248,17 +250,19 @@ nothing. `bir install --replay` does exactly two things beyond a normal install,
|
|
|
248
250
|
`bir uninstall --replay` takes the scenario server out again and leaves everything else as it is:
|
|
249
251
|
|
|
250
252
|
- registers a first-party MCP server under the key **`bir`** in the project's `.mcp.json` (never
|
|
251
|
-
`~/.claude.json`), exposing
|
|
252
|
-
|
|
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);
|
|
253
256
|
- pre-approves that server in `.claude/settings.local.json` (`enabledMcpjsonServers`), because a
|
|
254
257
|
"No" to Claude Code's approval dialog leaves a direct replay with nowhere to deliver its results.
|
|
255
258
|
|
|
256
259
|
The `UserPromptSubmit` hook's 15 s timeout, which the match round trip needs, is not one of them:
|
|
257
260
|
every install writes it, replay or not.
|
|
258
261
|
|
|
259
|
-
The `bir`
|
|
260
|
-
more
|
|
261
|
-
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).
|
|
262
266
|
|
|
263
267
|
Confirm:
|
|
264
268
|
|
|
@@ -465,8 +469,8 @@ first prompt that states the task plainly.
|
|
|
465
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 |
|
|
466
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 |
|
|
467
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 |
|
|
468
|
-
| 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) |
|
|
469
|
-
| 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` |
|
|
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` |
|
|
470
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` |
|
|
471
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 |
|
|
472
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 |
|
|
@@ -506,6 +510,312 @@ fix*. The three questions it answers:
|
|
|
506
510
|
You see your own data. An admin of the service sees everyone's and may pass
|
|
507
511
|
`--user` to `executions`. `--json` on any form prints the raw merge.
|
|
508
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
|
+
|
|
509
819
|
---
|
|
510
820
|
|
|
511
821
|
## 10. What is built
|
package/docs/quickstart.md
CHANGED
|
@@ -255,7 +255,9 @@ answer. Then:
|
|
|
255
255
|
|
|
256
256
|
For a turn that did not do what you expected — a scenario that did not run, a
|
|
257
257
|
saving that was not made — `bir investigate` reads the journal and the service
|
|
258
|
-
and says why.
|
|
258
|
+
and says why. When it names a step whose code is wrong, you can fix that step
|
|
259
|
+
yourself with `bir scenario edit` (not yet released) — see
|
|
260
|
+
[calculatedReplayGuide.md](calculatedReplayGuide.md) §9.2.
|
|
259
261
|
|
|
260
262
|
---
|
|
261
263
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basein/runner",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "A recording MCP proxy: sits between any MCP client and its MCP servers, executes each call on the client's behalf, and records the run as a reusable BaseIn scenario.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|