@basein/runner 0.3.18 → 0.3.20
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/dist/bin/bir-hooks.d.ts +11 -8
- package/dist/bin/bir-hooks.js +11 -8
- package/dist/bin/bir.js +20 -7
- package/dist/bin/investigate.d.ts +9 -0
- package/dist/bin/investigate.js +31 -12
- package/dist/bin/scenario-edit.js +10 -0
- package/dist/control/recorder-main.d.ts +15 -0
- package/dist/control/recorder-main.js +33 -8
- package/dist/control/server.d.ts +91 -11
- package/dist/control/server.js +736 -49
- package/dist/record/recorder.d.ts +47 -4
- package/dist/record/recorder.js +3 -0
- package/dist/record/remote-recorder.d.ts +9 -1
- package/dist/record/remote-recorder.js +30 -0
- package/dist/record/switchable-recorder.js +2 -0
- package/dist/replay/condition-words.d.ts +28 -0
- package/dist/replay/condition-words.js +59 -0
- package/dist/replay/controller.d.ts +45 -6
- package/dist/replay/controller.js +184 -51
- package/dist/replay/derive.d.ts +10 -0
- package/dist/replay/derive.js +3 -0
- package/dist/replay/handover.d.ts +7 -0
- package/dist/replay/handover.js +89 -51
- package/dist/replay/plan.d.ts +11 -4
- package/dist/replay/plan.js +28 -8
- package/dist/replay/reads-only.d.ts +29 -0
- package/dist/replay/reads-only.js +261 -0
- package/dist/replay/types.d.ts +19 -3
- package/docs/calculatedReplay.md +45 -5
- package/docs/calculatedReplayGuide.md +16 -4
- package/package.json +1 -1
- package/production.md +34 -7
package/production.md
CHANGED
|
@@ -594,6 +594,31 @@ Truncation is always marked with the original size.
|
|
|
594
594
|
- An Esc-interrupted turn is never booked as a saving (`measured:false`).
|
|
595
595
|
- A step that a host other than Claude Code (§3.2) stopped itself has two stop reasons of its own. `host_rejected`: a person said no, or answered in the tool's place, or a call limit blocked the call. The note says "Step N of M (tool) was not run by the host. The result above was written by the host, not by the tool. Do not run the step again unless the user asks." `host_edited`: a person changed the call. The note says "Step N of M (tool) ran with arguments a person chose, not the scenario's. Its result above is real and its side effects are in place. Do not run it again." The report's `fallbackKind` stays `step_failed`.
|
|
596
596
|
|
|
597
|
+
**The decision stop (0.3.19).** The service may serve a step with `stop: {kind: "decision"}`: the task has an "if … otherwise" there (the service's segmentsbreakdown.md). The plan never decides:
|
|
598
|
+
|
|
599
|
+
- It runs only the steps before the decision, then hands the task back, in steer and direct mode and after a divergence.
|
|
600
|
+
- The note says the plan "ran the steps before a decision, and stopped there", says which steps ran, and quotes the request's own sentences with a condition word (`if`, `otherwise`, `unless` and the others the service uses, Hebrew too; at most 600 characters). It ends "Decide from the results above which way applies, then continue the task". It has no intent line, no remaining steps and no parameters, and never the condition the service keeps (`stop.why`): each would hint at the half the recording took. The directive at the prompt leaves the intent out too, and says the task comes back "at a decision".
|
|
601
|
+
- A plan whose chain has a decision keeps these protections when it stops earlier for another reason (a parked or failed step, a judgement, the budget). Its note has no intent line and no parameters, and lists the remaining steps only up to the decision. Its directive leaves the intent out. An agent sent on to the recorded half's steps would start that half's segment, and the plan would have decided after all.
|
|
602
|
+
- The report is `fell_back` (or `failed` when no step ran) with `fallbackKind: "decision"` and `fallbackStepIndex` the decision step. The decision step gets no verdict, so nothing is counted against it.
|
|
603
|
+
- When a step ran, the hand-over takes the plan's usage mark: the plan is billed up to the stop, and the agent's decision and the half it took are not on its bill. This holds for a plan the prompt started and for one an intent match started. Every other hand-over still takes no mark.
|
|
604
|
+
- The agent's own next call opens the fragment, marked `metadata.decisionOf = {scenarioId, stepIndex, request}` (`request`, the turn's prompt, redacted and cut to 4,000 characters, from 0.3.20): the service makes a calculated scenario of that way from it, calculated with the request (the service's offer1.md O2). When a plan the intent check armed, or a next scenario (below), takes that call instead, the fragment is dropped (`run.fragment_dropped`).
|
|
605
|
+
- `ranThrough` sends one entry per plan, with the plan's own position (the decision step itself for a decision). The service maps it onto the recording.
|
|
606
|
+
- `bir replay` stops in front of a decision and says so (`stopped before step N …: the plan's decision`).
|
|
607
|
+
- A runner up to 0.3.18 reads the unknown kind as `known_bad_step`: it stops there too, but its note and report name a failure.
|
|
608
|
+
|
|
609
|
+
**What can follow a decision (0.3.20, the service's offer1.md).** The first time a way is taken after a decision, the agent does it and its fragment becomes a calculated scenario of that decision. The next time:
|
|
610
|
+
|
|
611
|
+
- When a plan stops at its decision, the runner asks the service once, in the background, which calculated scenarios can follow it (`POST /scenarios/:planId/next`, body `{turnId, supportsCalls}`; at most 4, each handed out with its own ticket and `key: "decision"`). An older service, a 404 or any failure is "none". It arms each one while the agent reads the note and decides: its values are read from the turn's request and this turn's results (the reading carries `atDecision: {scenarioId, stepIndex}`), and settled. `replay.next_ready` says how many are ready.
|
|
612
|
+
- The agent's next calls are checked against them before the intent probe. A call that is a next scenario's first step, **value for value** (every argument the step's own; the pin guard's "plausibly the step" is not enough), is a hit: it is answered in its place with that scenario's directive (`replay.next_hit`), the agent calls `run_scenario`, and the scenario runs every step, that one included, once. It is booked on its own ticket. A scenario whose first step is inside a sub-task (a call row) can never be the agent's call in place: it is never compared.
|
|
613
|
+
- Each scenario is checkable as soon as the service answers; its arm runs on its own. A call waits only for the arm of a scenario that starts with the call's own tool (at most 6 s, `BIR_NEXT_HIT_WAIT_MS`), and not at all for the others. While the service has not answered yet, a call waits for that answer only, at most 1 s.
|
|
614
|
+
- When the background reading left a value of the first step empty, the call waits while it is read once more with the agent's call as `liveCall`; the service fills such a value from the call. A value read differently is never read again: an e-mail to carlos is never the recorded e-mail to olga.
|
|
615
|
+
- **A call that only reads keeps the set** (by the service's own rule, mirrored in `src/replay/reads-only.ts`: Claude Code's read tools, a name with a reading word and no writing word, a Bash command that only looks). The set ends unused (`replay.next_miss`) at the agent's first call that may change something and is no hit: `other_values` when the call has a scenario's first tool with other values, `own_way` when it starts none of them — the agent is doing a way of its own, and no scenario may start in the middle of it — and `not_ready` when the scenario that starts with that tool was not armed in time. It also ends after 5 calls that start none (`calls_limit`), or at the turn's end. A scenario not armed in time for a call that only reads misses that call only. Unused ones never run and are never reported; the agent's own work is recorded as the fragment, as before.
|
|
616
|
+
- A later decision in the same turn starts a new set: what could follow the earlier one is dropped, never checked again.
|
|
617
|
+
- A hit after the agent's own looks had opened the fragment ends that fragment there (`run.fragment_closed`, finished with what the agent did itself): the scenario's own calls, pinned in steer mode, are never recorded into it.
|
|
618
|
+
- `/health` shows the set per session: `next: {entries, armed, calls, ended}`.
|
|
619
|
+
|
|
620
|
+
**Never twice (0.3.20, offer1.md O8).** A plan an intent match arms after the agent's call — not in its place — whose steps call the same tool is not started: it would make that call a second time (issue fx2: the agent's e-mail, then the plan's own). `replay.decision code=would_repeat`. A tool that only reads, by the same rule as above (`crm_search_customers`, `Read`), may repeat. For Bash and PowerShell the call is its command: only a step that would run that very command counts.
|
|
621
|
+
|
|
597
622
|
**Measured cost.**
|
|
598
623
|
|
|
599
624
|
- A turn's cost is measured only when tokens were found and every model of the turn has a price in the runner's table. This holds for every host, Claude Code too. A model outside the table used to cost $0, and that zero was called measured.
|
|
@@ -710,7 +735,7 @@ Truncation is always marked with the original size.
|
|
|
710
735
|
| Remote servers without a literal credential header (OAuth, headersHelper, `${VAR}`) are left unwrapped. Earlier unasked wraps are undone. | The proxy has none of Claude Code's OAuth credentials; wrapped, these servers answered 401 to every call. | `--server <name>` (remembered). |
|
|
711
736
|
| A flag-less install from an installed package writes absolute-path entries and the self-starting SessionStart hook, for `bir wrap` too. | No npx registry round trip per server start (about 1 s), and it works offline. npx installs had nothing to start the recorder. | `--npx`. A checkout, or `bir` run through npx, still writes npx. |
|
|
712
737
|
| Hook timeouts: SessionStart command 15 s (was 30), HTTP 5 (10), UserPromptSubmit 10 (15), PreToolUse 15 (30), PostToolUse and PostToolUseFailure 5 (30), Subagent 3 (5), Stop 5 (10), SessionEnd 3 (30). | The timeout is the user's worst case during an incident. The server's deadlines sit inside it. | Not configurable; `bir setup` rewrites them. |
|
|
713
|
-
|
|
|
738
|
+
| From 0.3.19 segments run mid-task by default (`BIR_SEGMENT_ARM` unset means on), and so may whole plans by intent. An unsteered PreToolUse waits for the intent probe: at most `BIR_INTENT_ARM_BUDGET_MS` (500 ms), and up to 1.5 s more while an armed plan's values are read. From 0.3.0 to 0.3.18 arming was opt-in and the probe was never waited on. | A plan that stops at its decision hands the rest of the task to segments of its halves, and a segment runs only mid-task (the service's segmentsbreakdown.md SB-21). 0.2.12 held every PreToolUse up to 4 s for the probe. | `BIR_SEGMENT_ARM=0` (observe only: what would have armed is logged). `BIR_INTENT_ARM=0` arms nothing mid-task and never waits; its probes then ask for no segment, as in a session of another host, so what would have armed is logged too. |
|
|
714
739
|
| The prompt match budget is 3 s (was 2.5 s), and there is a match gate: over 2 s, prompts stop waiting for 5 min, doubling to 30 min. | A healthy service answers in 300-450 ms. A slower one would otherwise cost every prompt. A false trip costs one hold of replay, never latency. | `BIR_MATCH_BUDGET_MS=2500`. Raising `BIR_MATCH_SLOW_MS` above the budget effectively disables the gate. |
|
|
715
740
|
| Recorded outputs no longer stand in for a step that produced no result; the plan hands over at that step. | Last week's output presented as this turn's result is wrong data. | `BIR_REPLAY_RECORDED_OUTPUTS=1`, only for steps that could not be sent. `bir replay` keeps the stand-ins. |
|
|
716
741
|
| The direct plan budget is 100 s (was 120 s), and direct runs are clamped to 270 s. | Keeps a direct run under Claude Code's 2-minute auto-background of an MCP call, and inside `run_scenario`'s client timeout. | `BIR_REPLAY_PLAN_BUDGET_MS=120000` (or `BIR_REPLAY_BUDGET_MS`). |
|
|
@@ -845,7 +870,7 @@ Truncation is always marked with the original size.
|
|
|
845
870
|
- where recording goes, and as whom;
|
|
846
871
|
- when the service last answered;
|
|
847
872
|
- who approves wrapped MCP calls (the recorder's correlation mode);
|
|
848
|
-
- whether
|
|
873
|
+
- whether segments run mid-task (the default from 0.3.19) or are observed only, and whether whole plans may start mid-task too; a recorder older than this `bir` is named, since it keeps its old default until `bir up --restart`;
|
|
849
874
|
- superseded recorders;
|
|
850
875
|
- an npx-started scenario server;
|
|
851
876
|
- other installed projects.
|
|
@@ -894,8 +919,9 @@ In a folder that is set up for another host (§3.2), doctor prints `Host
|
|
|
894
919
|
| `registeredProxies[]`, `wrappedServers` | proxies that registered (`serverName`, `pid`, `cwd`, `version`) and the wrapped server names |
|
|
895
920
|
| `hooksVersion`, `pid`, `cwd`, `url` | the recorder's version and identity |
|
|
896
921
|
| `hostFields` | `2`: how much of another host's protocol (§3.2) this recorder understands. An adapter reads it before it relies on a field: with 1 it records, and from 2 it may also ask for a plan and step through it. A recorder that does not know another host answers no `hostFields`. |
|
|
897
|
-
| `segmentArm` | whether service
|
|
898
|
-
| `
|
|
922
|
+
| `segmentArm` | whether segments the service hands out may run mid-task (default from 0.3.19: yes; `BIR_SEGMENT_ARM=0`: observed only) |
|
|
923
|
+
| `intentArm` | whether a step's intent may start a plan in its own PreToolUse, which then waits for the probe. It follows `segmentArm` unless `BIR_INTENT_ARM` is set. Absent from a recorder older than 0.3.19. |
|
|
924
|
+
| `sessionCount`, `sessions[]` | at most 50, active ones first: `{sessionId, runId, active, idleMs, ended, steps, recording, replay, intent, fragment}`. `replay` includes `armed`, `unannounced`, `running`, `outcome` and `handover` (`{kind, stepIndex}`; `kind` may be `decision`). `fragment` is `{state}`, with `decision: true` for one scheduled at a plan's decision. |
|
|
899
925
|
|
|
900
926
|
### 8.4 Logs and audit lines
|
|
901
927
|
|
|
@@ -917,7 +943,7 @@ Lines read `[bir] <ISO time> <event> key=value …`, with each field clipped at
|
|
|
917
943
|
| Hooks | `control.listening` (port, `recorder=<state>`, `correlation=`). `control.correlation_allow` (auto-approval opted in). `control.hook_deadline`, `control.hook_unauthorized` (a token from another install), `control.hook_unknown`, `control.hook_oversize`, `control.hook_event_mismatch`, `control.route_failed`, `control.drain_abandoned`. |
|
|
918
944
|
| Recorder lifecycle | `daemon.restart` (replaced an older recorder), `daemon.restart_deferred` (mid-run: next start), `daemon.start_alongside`, `daemon.newer_kept`, `daemon.revived`. `control.port_busy` (holder and fix), `control.already_running`, `control.superseded`, `control.discovery_reclaimed`, `control.log_reopened`, `control.fatal`. `overrides.stale` (a local copy no longer matches its source: run `bir setup`). |
|
|
919
945
|
| Proxy (MCP log) | `proxy.upstream_gone` (exiting with the server), `proxy.recorder_revive`, `tier.downgrade`, `tier.rebound`, `relay.undeliverable`. `upstream.auth_required` (the remote refused the handshake: credentials), `upstream.needs_sign_in`, `upstream.headers_invalid`, `upstream.proxy_env_ignored` (this Node cannot use HTTPS_PROXY), `upstream.proxy_env_invalid`, `upstream.standing_stream_failed` (at most hourly). Verbose only: `proxy.started`, `tier.bound`, `tier.passthrough`, `run.start` / `run.finish`, `upstream.stream_*`. |
|
|
920
|
-
| Replay | `replay.enabled` (at start: servers, threshold, who derives). `plan.armed`. `replay.decision` (also `arm_timeout`, `arm_unannounced`, `no_directive`, `no_delivery_tool`, `missing_target`, `host_records_only`, `host_tool_missing`). `replay.pin`, `replay.pin_declined`, `replay.pin_abandoned`, `replay.pin_overran`, `replay.diverge`, `replay.diverge_overran`, `replay.handover`, `replay.vetoed`, `replay.unsafe_param`, `replay.derive_timeout`, `replay.budget_clamped`, `replay.concurrent_call`, `replay.no_session_proxy`, `replay.run_ambiguous`, `replay.run_abandoned`. |
|
|
946
|
+
| Replay | `replay.enabled` (at start: servers, threshold, who derives). `plan.armed`. `replay.decision` (also `arm_timeout`, `arm_unannounced`, `no_directive`, `no_delivery_tool`, `missing_target`, `host_records_only`, `host_tool_missing`). `replay.pin`, `replay.pin_declined`, `replay.pin_abandoned`, `replay.pin_overran`, `replay.diverge`, `replay.diverge_overran`, `replay.handover`, `replay.vetoed`, `replay.unsafe_param`, `replay.derive_timeout`, `replay.budget_clamped`, `replay.concurrent_call`, `replay.no_session_proxy`, `replay.run_ambiguous`, `replay.run_abandoned`. A plan's decision (0.3.19): `replay.handover kind=decision` (`via=divergence` when a divergence's bundle ran up to it), `run.fragment … decision=true` (the agent's own work after it is recorded), `run.fragment_dropped` (a plan the intent check armed took the agent's next call, so nothing is recorded as the other half), `replay.adhoc_decision` (`bir replay` stopped in front of it). `replay.enabled` also says `segments=run mid-task`, or `observed only` with `BIR_SEGMENT_ARM=0` or `BIR_INTENT_ARM=0`. |
|
|
921
947
|
| Run cost | `run.unmeasured` (the turn's cost is not measured: `why` gives the reason, and `models` the models that have no price in the runner's table). |
|
|
922
948
|
| A host other than Claude Code (§3.2) | `run.start … host=langchain` (`bir investigate` reads the host from it, and raises no `steer_mode` finding for such a turn: steer is the full mode there), `plan.armed … mode=steer host=langchain`. `run.finish … ended=<reason>` (the run did not end with an ordinary stop). `run.unmeasured` (`why="the host sent no usage"`, a model with no price, or a run that made no model call). `run.incomplete` (the host says the recording is not the whole run: its finish carries the `incomplete` mark). `run.host_failed` (the host says the run failed: its recording is closed with that error). The service's new build never picks a run with an `error` or that mark for automatic calculation, and such a run no longer catches its prompt, unless its owner preferred it or it already has a plan (built, not deployed yet). `replay.decision verdict=no-steer code=host_records_only` (the adapter asked for no plan with its prompt) and `code=host_tool_missing` (the plan names a tool that the agent which asked does not have). `replay.own_call` (the model made a call of its own while the plan was live: the plan is handed over). `replay.step_host_stopped` (`stop=rejected` or `stop=edited`). `session.host_refused` (a scope that is empty, longer than 64 characters or not a text: nothing of that session is recorded or replayed). |
|
|
923
949
|
| `bir-scenario` (MCP log) | `scenario.answered` (`ok` means results were delivered; also `partial`, `bound`), `scenario.recorder_revive`, `scenario.switched`, `scenario.hello_failed`, `scenario.tool_timeout`, `scenario.tool_truncated`. |
|
|
@@ -1053,9 +1079,9 @@ Defaults were checked in `src/`.
|
|
|
1053
1079
|
| `BIR_INTENT_MATCHES_PER_TURN` | recorder | 3 | Plans intent may arm per turn. |
|
|
1054
1080
|
| `BIR_INTENT_REQUESTS_PER_TURN` | recorder | 40 | Probes per turn. |
|
|
1055
1081
|
| `BIR_INTENT_SOURCES` | recorder | `text,thinking,tool` | Which text an intent may be read from. |
|
|
1056
|
-
| `BIR_INTENT_ARM` | recorder | unset (follows `BIR_SEGMENT_ARM
|
|
1082
|
+
| `BIR_INTENT_ARM` | recorder | unset (follows `BIR_SEGMENT_ARM`, so on from 0.3.19) | `1`: a probe may arm a plan in its own PreToolUse, and that hook waits for it. `0`: never. Never in a session of another host (§3.2), where a probe only counts. |
|
|
1057
1083
|
| `BIR_INTENT_ARM_BUDGET_MS` | recorder | 500 | How long that PreToolUse waits for the probe. |
|
|
1058
|
-
| `BIR_SEGMENT_ARM` | recorder |
|
|
1084
|
+
| `BIR_SEGMENT_ARM` | recorder | on (from 0.3.19; unset or `1`) | A segment the service hands out runs mid-task: one found by repeated work, or a half after a plan's decision. `0`: observe only, where what would have armed is logged (`replay.segment_would_arm`) and nothing is replaced. Up to 0.3.18 the default was observe only. Not in a session of another host. |
|
|
1059
1085
|
| `BIR_CONTROL_URL` | proxy, scenario | written into wrapped entries | The project's control server. Together with `BIR_CONTROL_TOKEN` it is the SDK channel and wins over discovery. |
|
|
1060
1086
|
| `BIR_CONTROL_TOKEN` | proxy, scenario | unset | Bearer token for the SDK channel. |
|
|
1061
1087
|
| `BIR_PROXY_STAY_UP` | proxy | unset | `1`: keep answering errors after the upstream dies (0.2.12). |
|
|
@@ -1113,6 +1139,7 @@ Defaults were checked in `src/`.
|
|
|
1113
1139
|
- `langChainGuide.md`, which is in the repository's root and not in the npm package: its "Where it stands" table and its part 5 describe the time before the release.
|
|
1114
1140
|
|
|
1115
1141
|
The install scripts cannot set a folder up for such a host, so there is no one-line install for it.
|
|
1142
|
+
8. **A plan's decision (0.3.19): the service first.** Deploy the service build that accepts `fallbackKind: "decision"` before 0.3.19 is published (the service's segmentsbreakdown.md section 8, step 1). An older service answers such a report 400, and the run's cost and saving are lost. Then test 0.3.18 against a plan with a decision (it stops there, reported as `known_bad_step`). The deployed service makes no split while its `SCENARIO_SPLIT` is false, so run this check on a sandbox or local service started with `SCENARIO_SPLIT=true`, with bir 0.3.18 pointed at it: set `BIR_AUTH_URL` to its address, run `bir login` (with an account on that service), then `bir up --restart` in the folder you test in. bir keeps one session, for the service that issued it: without that sign-in nothing is recorded on the local service, and while bir is signed in there, nothing on this PC is recorded on the deployed one. The service's segmentsbreakdown.md section 8, step 2, has the steps. Then point bir back: set `BIR_AUTH_URL` to the deployed service's address (removing it is not enough, since the local sign-in stored the local address), run `bir login` again, then `bir up --restart` in that folder. Then publish 0.3.19, and only then turn the deployed service's `SCENARIO_SPLIT` on.
|
|
1116
1143
|
|
|
1117
1144
|
**Manual end-to-end checks with the real Claude Code.** These are behaviours of Claude Code itself, which the automated tests simulate but cannot prove. Use the Claude Code version you will support; the approval rules and hook schema were read from 2.1.280.
|
|
1118
1145
|
|