@orkestrel/scaffold 0.0.103 → 0.0.104
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/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
- package/dist/bin/main.js +16 -9
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +3 -3
- package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
- package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
- package/dist/host/agents/transports/codex.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +54 -53
- package/dist/host/claude/agents/reviewer.md +4 -4
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/patterns.md +1 -1
- package/dist/host/claude/rules/quality.md +2 -2
- package/dist/host/claude/rules/styles.md +1 -1
- package/dist/host/claude/rules/tests.md +34 -8
- package/dist/host/claude/rules/typescript.md +29 -0
- package/dist/host/claude/rules/workspace.md +32 -22
- package/dist/host/claude/rules/writing.md +34 -26
- package/dist/host/claude/settings.json +2 -0
- package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +3 -3
- package/dist/host/codex/agents/reviewer.toml +4 -4
- package/dist/host/configs/policy.ts +13 -2
- package/dist/host/dotfiles/editorconfig +0 -1
- package/dist/host/dotfiles/gitattributes +0 -1
- package/dist/host/dotfiles/gitignore +0 -1
- package/dist/host/dotfiles/oxlintrc.json +24 -0
- package/dist/host/guides/agent.md +1638 -598
- package/dist/host/guides/budget.md +89 -29
- package/dist/host/guides/form.md +477 -91
- package/dist/host/guides/mcp.md +14 -2
- package/dist/host/guides/ollama.md +97 -73
- package/dist/host/guides/probe.md +1 -1
- package/dist/host/guides/scaffold.md +158 -101
- package/dist/host/guides/study.md +2635 -0
- package/dist/host/guides/table.md +225 -15
- package/dist/host/guides/terminal.md +22 -20
- package/dist/host/guides/test.md +689 -255
- package/dist/host/manifest.json +87 -63
- package/dist/host/tests/config.test.ts +136 -82
- package/dist/host/tests/policy.test.ts +39 -1
- package/dist/host/tests/setupPolicy.ts +86 -28
- package/dist/src/core/index.cjs +1387 -611
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +35 -20
- package/dist/src/core/index.d.ts +35 -20
- package/dist/src/core/index.js +1385 -612
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +12 -12
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +12 -12
- package/dist/src/server/index.d.ts +12 -12
- package/dist/src/server/index.js +12 -12
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
A budget handle is what an agent loop holds to keep a whole run inside one cost limit, in place of a cost check written at every call site. Hand its `signal` to the work, race that signal against a call, or fold it into a loop's bound. You define what is spent — tokens, bytes, dollars, or calls — so the same handle covers a provider's token usage and a pipeline's byte throughput.
|
|
7
7
|
|
|
8
|
-
A budget is the substrate's third bounding signal, a peer to a cancellation signal (fires on `abort()`) and a deadline signal (fires on expiry). All three are plain `AbortSignal`s by design, so an agent loop combines them into one bound with `AbortSignal.any([abort, timeout, budget])` and reacts to whichever trips first — cancel, deadline, or cost. The primitive deliberately carries no Emitter, clock, or I/O: its native signal is the complete observation boundary.
|
|
8
|
+
A budget is the substrate's third bounding signal, a peer to a cancellation signal (fires on `abort()`) and a deadline signal (fires on expiry). All three are plain `AbortSignal`s by design, so an agent loop combines them into one bound with `AbortSignal.any([abort, timeout, budget])` and reacts to whichever trips first — cancel, deadline, or cost. The primitive deliberately carries no Emitter, clock, or I/O: its native signal is the complete observation boundary. The budget is a functional counter with a signal bolted to its ceiling. The `Meter` beside it charges a budget as a stream arrives, for a cost that is a function of the whole streamed text rather than a sum of its pieces. When the ceiling is reached, the signal aborts with a `BudgetError` that names the budget and its readings, so a stop is traceable to the budget that caused it. Source: [`src/core`](../src/core). Surfaced through the `@src/core` barrel.
|
|
9
9
|
|
|
10
10
|
## Surface
|
|
11
11
|
|
|
@@ -32,17 +32,20 @@ budget.consume(7_000) // crosses 10_000 — fires `signal`
|
|
|
32
32
|
| `createBudget` | function | Creates a cumulative budget as a `BudgetInterface<T>` over a `max` ceiling and a `consumer`, whose native signal aborts the moment the tally reaches that ceiling. |
|
|
33
33
|
| `createTokenConsumer` | function | Creates a validated unary consumer that charges one selected `TokenUsage` field. |
|
|
34
34
|
| `createTokenBudget` | function | Creates a token budget as a `BudgetInterface<TokenUsage>` charging one validated `scope` field per provider call. |
|
|
35
|
+
| `createTokenMeter` | function | Creates a token meter as a `MeterInterface` that charges a token budget each growth of a streamed answer's estimated token count as completion tokens. |
|
|
35
36
|
|
|
36
37
|
### Validators
|
|
37
38
|
|
|
38
39
|
In a guard table a `Shape` cell holds the type the guard narrows to.
|
|
39
40
|
|
|
40
|
-
| API | Kind | Shape
|
|
41
|
-
| ---------------- | -------- |
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
41
|
+
| API | Kind | Shape | Summary |
|
|
42
|
+
| ---------------- | -------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `isBudget` | function | `BudgetInterface<unknown>` | Determines whether a value implements `BudgetInterface`, returning `false` rather than throwing on hostile input. |
|
|
44
|
+
| `isBudgetError` | function | `BudgetError` | Checks whether an unknown value is a `BudgetError` built by any copy of this package, so an abort reason can be read as a budget's exhaustion. |
|
|
45
|
+
| `isBudgetAmount` | function | `number` | Determines whether a value is a finite nonnegative budget amount. |
|
|
46
|
+
| `isBudgetSignal` | function | `AbortSignal` | Determines whether a value is a genuine native `AbortSignal`, returning `false` rather than throwing on hostile input. |
|
|
47
|
+
| `isTokenScope` | function | `TokenScope` | Determines whether a value is a supported `TokenScope` field selector. |
|
|
48
|
+
| `isTokenUsage` | function | `TokenUsage` | Determines whether a value is readable token usage carrying three finite nonnegative counts, returning `false` rather than throwing. |
|
|
46
49
|
|
|
47
50
|
### Helpers
|
|
48
51
|
|
|
@@ -50,30 +53,44 @@ In a guard table a `Shape` cell holds the type the guard narrows to.
|
|
|
50
53
|
| ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
|
|
51
54
|
| `validateBudgetOptions` | function | Validates and normalizes budget construction options into a fresh copy that omits absent optional keys. |
|
|
52
55
|
| `validateTokenBudgetOptions` | function | Validates and normalizes token-budget construction options into a fresh copy that omits absent optional keys. |
|
|
56
|
+
| `validateMeterOptions` | function | Validates and normalizes meter construction options into a fresh copy. |
|
|
57
|
+
| `validateTokenMeterOptions` | function | Validates and normalizes token-meter construction options into a fresh copy. |
|
|
53
58
|
|
|
54
59
|
### Classes
|
|
55
60
|
|
|
56
|
-
| API
|
|
57
|
-
|
|
|
58
|
-
| `Budget`
|
|
61
|
+
| API | Kind | Summary |
|
|
62
|
+
| ------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
63
|
+
| `Budget` | class | Implements `BudgetInterface` over a private `AbortController` the instance owns, aborting the composed signal the moment `consumed` reaches the `max` ceiling. |
|
|
64
|
+
| `Meter` | class | Implements `MeterInterface` for one stream, charging a budget the growth of the streamed text's estimated cost as each delta arrives. |
|
|
65
|
+
| `BudgetError` | class | Reports a budget whose cumulative tally reached its ceiling — the abort reason of the budget's own `signal`, carrying the machine-readable `code` `'exhaust'` and the budget's `id`, `max`, and `consumed` readings. |
|
|
66
|
+
|
|
67
|
+
### Constants
|
|
68
|
+
|
|
69
|
+
| API | Kind | Summary |
|
|
70
|
+
| -------------------- | ----- | ------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `BUDGET_ERROR_BRAND` | const | Holds the registry-global key used to recognize `BudgetError` values across package copies. |
|
|
59
72
|
|
|
60
73
|
### Types
|
|
61
74
|
|
|
62
75
|
A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional member and `plus` introducing its call-signature members, and a type alias's own type literal with a union's arms escaped as `\|`.
|
|
63
76
|
|
|
64
|
-
| Type | Kind | Shape | Summary
|
|
65
|
-
| -------------------- | --------- | -------------------------------------------------------------------------------- |
|
|
66
|
-
| `BudgetOptions` | interface | `{ id?, max, consumer, signal? }` | Represents the options for constructing a cumulative budget, accepted by the `createBudget` function and the `Budget` constructor.
|
|
67
|
-
| `TokenBudgetOptions` | interface | `{ id?, max, scope?, signal? }` | Represents the options for constructing a token budget, accepted by the `createTokenBudget` function.
|
|
68
|
-
| `BudgetInterface` | interface | `{ id, signal, max, consumed, remaining, exhausted } plus start, consume, clear` | Represents the cumulative cost handle contract: a lifetime tally, its validated ceiling, and the native signal that aborts when the tally reaches that ceiling.
|
|
69
|
-
| `TokenScope` | type | `'completion' \| 'total' \| 'prompt'` | Names the token-usage field selected as the charge for a token budget.
|
|
70
|
-
| `TokenUsage` | interface | `{ prompt, completion, total }` | Represents the canonical LLM cost unit: the finite nonnegative token counts reported for one provider call, and the typical `T` for an agent budget.
|
|
77
|
+
| Type | Kind | Shape | Summary |
|
|
78
|
+
| -------------------- | --------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `BudgetOptions` | interface | `{ id?, max, consumer, signal? }` | Represents the options for constructing a cumulative budget, accepted by the `createBudget` function and the `Budget` constructor. |
|
|
80
|
+
| `TokenBudgetOptions` | interface | `{ id?, max, scope?, signal? }` | Represents the options for constructing a token budget, accepted by the `createTokenBudget` function. |
|
|
81
|
+
| `BudgetInterface` | interface | `{ id, signal, max, consumed, remaining, exhausted } plus start, consume, clear` | Represents the cumulative cost handle contract: a lifetime tally, its validated ceiling, and the native signal that aborts when the tally reaches that ceiling. |
|
|
82
|
+
| `TokenScope` | type | `'completion' \| 'total' \| 'prompt'` | Names the token-usage field selected as the charge for a token budget. |
|
|
83
|
+
| `TokenUsage` | interface | `{ prompt, completion, total }` | Represents the canonical LLM cost unit: the finite nonnegative token counts reported for one provider call, and the typical `T` for an agent budget. |
|
|
84
|
+
| `MeterOptions` | interface | `{ budget, estimator, producer }` | Represents the options for constructing a meter, accepted by the `Meter` constructor. |
|
|
85
|
+
| `TokenMeterOptions` | interface | `{ budget, estimator }` | Represents the options for constructing a token meter, accepted by the `createTokenMeter` function. |
|
|
86
|
+
| `MeterInterface` | interface | `{ charged } plus charge` | Represents the stream meter contract: it measures text as it streams and charges a budget the growth of the text's estimated cost, keeping the cost already charged so a later reported total can be reconciled against it. |
|
|
87
|
+
| `BudgetErrorContext` | interface | `{ id, max, consumed }` | Represents the readings a `BudgetError` carries: the exhausted budget's trace `id`, its `max` ceiling, and its `consumed` tally at the moment the ceiling was reached. |
|
|
71
88
|
|
|
72
|
-
The `id`, `signal`, `max`, `consumed`, `remaining`, and `exhausted` members of `BudgetInterface` are `readonly` data members (the preceding Surface rows) — its call-signature methods are documented under [Methods](#methods).
|
|
89
|
+
The `id`, `signal`, `max`, `consumed`, `remaining`, and `exhausted` members of `BudgetInterface` are `readonly` data members (the preceding Surface rows) — its call-signature methods are documented under [Methods](#methods). The `charged` member of `MeterInterface` is a `readonly` data member; its call-signature method is documented under [Methods](#methods).
|
|
73
90
|
|
|
74
91
|
## Methods
|
|
75
92
|
|
|
76
|
-
The public methods of `BudgetInterface` — every call-signature member listed (
|
|
93
|
+
The public methods of `BudgetInterface` and `MeterInterface` — every call-signature member listed (the `readonly` data members `id` / `signal` / `max` / `consumed` / `remaining` / `exhausted` of the budget and `charged` of the meter stay Surface rows). `Budget` and `Meter` implement their interfaces exactly, so this doubles as each class's instance-method surface.
|
|
77
94
|
|
|
78
95
|
#### `BudgetInterface`
|
|
79
96
|
|
|
@@ -85,6 +102,14 @@ The public methods of `BudgetInterface` — every call-signature member listed (
|
|
|
85
102
|
| `consume` | `void` | Runs the configured consumer first, then validates and atomically adds its charge, tripping `signal` the moment the tally reaches `max`; a valid charge that overshoots the ceiling is accepted. |
|
|
86
103
|
| `clear` | `void` | Resets the tally to `0` and re-arms a fresh unaborted `signal`, opening the next window from zero. |
|
|
87
104
|
|
|
105
|
+
#### `MeterInterface`
|
|
106
|
+
|
|
107
|
+
`charge` extends the measured text by one delta and charges the budget the growth of its estimated cost; a meter serves one stream and has no reset.
|
|
108
|
+
|
|
109
|
+
| Method | Returns | Summary |
|
|
110
|
+
| -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
111
|
+
| `charge` | `void` | Extends the measured text by one delta and charges the budget the growth of its estimated cost, charging nothing when the estimate does not grow. |
|
|
112
|
+
|
|
88
113
|
## Contract
|
|
89
114
|
|
|
90
115
|
These invariants hold across `src/core` ↔ `budget.md`:
|
|
@@ -96,12 +121,14 @@ These invariants hold across `src/core` ↔ `budget.md`:
|
|
|
96
121
|
5. **Re-arm guard and zero ceiling.** `start()` on a budget already at or past `max` (`consumed >= max`) arms an immediately-aborted signal — a request opened on a spent budget is bounded from the very first tick. `max: 0` is derived as exhausted at construction and after `clear()`, while the newly composed signal remains un-aborted until `start()` or `consume()` applies that ceiling. (`clear()` on a positive `max` returns both derived state and signal to the unexhausted born state.)
|
|
97
122
|
6. **Parent links through `AbortSignal.any`.** With a parent `signal`, the exposed `signal` is `AbortSignal.any([own, parent])`, so it fires on EITHER exhaustion OR the parent aborting — without re-implementing listener wiring. Exhaustion still fires when the parent never aborts; a parent abort fires `signal` independent of consumption (the budget need not be exhausted). A parent ALREADY aborted at construction makes the current `signal` born aborted, carrying the parent's reason. The composite is computed once per arm (construction + each `start()` / `clear()`) and stored, never recomputed per read — so reads do not accumulate listeners (a peer to `Abort`'s composite handling).
|
|
98
123
|
7. **Composes with abort/timeout.** Because `signal` is a plain `AbortSignal`, a `Budget` folds into a combined bound with a cancellation signal and a deadline signal through `AbortSignal.any([abort, timeout, budget])` — the combined signal fires on whichever trips first (the agent-loop pattern).
|
|
99
|
-
8. **Traceable identity.** `id` is a stable string for the handle's lifetime — caller-supplied through `options.id`, including an empty string, or a `crypto.randomUUID()` default only when `id` is omitted. `max` is the validated ceiling, exposed read-only. The abort reason on owned exhaustion is the
|
|
100
|
-
9. **Strict once-read boundaries.** `validateBudgetOptions` and `
|
|
124
|
+
8. **Traceable identity.** `id` is a stable string for the handle's lifetime — caller-supplied through `options.id`, including an empty string, or a `crypto.randomUUID()` default only when `id` is omitted. `max` is the validated ceiling, exposed read-only. The abort reason on owned exhaustion is a `BudgetError` coded `'exhaust'` with the fixed message `budget reached its ceiling`, whose `context` holds the `id`, `max`, and `consumed` at the crossing; a parent abort keeps the parent's reason, including when the parent aborted before the crossing and nothing had read the composite signal. `consume` tests the composed signal, not the owned controller, before it aborts the controller, so the parent's reason survives whether or not the host's `AbortSignal.any` has propagated the parent's abort yet; on Node 24.22.0 the propagation is eager and an owned-controller test gives the same reason.
|
|
125
|
+
9. **Strict once-read boundaries.** `validateBudgetOptions`, `validateTokenBudgetOptions`, `validateMeterOptions`, and `validateTokenMeterOptions` accept the typed contracts while defending their hostile JavaScript boundary: they require a plain record, read each declared property exactly once inside a contained boundary, validate with structured `ContractError` taxonomy/context, and return a fresh normalized object that omits absent optional keys. `Budget` calls the budget helper before allocating its controller; `createTokenBudget` calls the token helper before creating its consumer and public budget; `Meter` calls the meter helper before it stores anything; `createTokenMeter` calls the token-meter helper before it composes the meter.
|
|
101
126
|
10. **Validated token accounting.** `TokenScope` is the public `'completion' | 'total' | 'prompt'` selector and defaults to `completion` in `createTokenBudget`. Token factories reject unsupported scopes and malformed usage; all three `TokenUsage` fields must be finite and nonnegative before the selected charge is used.
|
|
102
|
-
11. **Total guards.** `isBudgetAmount`, `isBudgetSignal`, `isTokenScope`, and `isTokenUsage` return `false`, rather than throw, for off-shape, hostile, or revoked input.
|
|
103
|
-
12. **Signal-only observation.**
|
|
104
|
-
13. **DOC ↔ SOURCE method bijection.** The `## Methods`
|
|
127
|
+
11. **Total guards and cross-copy recognition.** `isBudget`, `isBudgetAmount`, `isBudgetError`, `isBudgetSignal`, `isTokenScope`, and `isTokenUsage` return `false`, rather than throw, for off-shape, hostile, or revoked input. `isBudgetError` uses the identity brand instead of `instanceof BudgetError`, while retaining the native `Error` base check. The constructor stamps `BUDGET_ERROR_BRAND`, the registry-global `Symbol.for('@orkestrel/budget.error')`, as a non-enumerable own property whose value is the error itself. The guard requires that exact identity, the native `Error` base, a prototype other than `Error.prototype`, the name `BudgetError`, the code `'exhaust'`, and a `context` holding a string `id` and number `max` and `consumed`. One rule serves every copy: a reason built by another installation, or by the CommonJS entry when the guard came from the ESM entry, narrows exactly as one built by the same copy, and the reverse holds too. A transparent proxy over a genuine error is refused, because its forwarded descriptor stores the target. An unbranded `Error` carrying `code: 'exhaust'`, and an error from a copy that stamps no brand, such as 0.0.13, stay outside the type. The brand is a recognition marker, not proof of origin: a complete forgery that stamps its own identity under the key passes.
|
|
128
|
+
12. **Signal-only observation.** Neither `Budget` nor `Meter` has an Emitter, `EventMap`, or `on` hook. The budget's native `AbortSignal` is the observation mechanism.
|
|
129
|
+
13. **DOC ↔ SOURCE method bijection.** The `## Methods` tables list exactly the public methods of `BudgetInterface` and `MeterInterface` — exhaustive, both directions — and `Budget` and `Meter` expose the same public methods as their interfaces, no more.
|
|
130
|
+
14. **Growth metering.** `charge(delta)` refuses a non-string delta with a `literal`-coded `ContractError` before any state changes. It then counts the delta, estimates the whole text, refuses a nonfinite or negative estimate with a `range`-coded `ContractError`, and measures the estimate's growth over the growth the budget has accepted plus the growth of every charge still in flight. Only when that growth is positive does it consume `producer(growth)`. Each charge in flight holds a per-call claim of its growth from before the producer runs until the charge settles, so a charge that re-enters the meter synchronously, through the producer, the budget's consumer, or an abort listener the crossing fires, measures its own growth past the claims and the nested and outer charges sum to the serial result. `charged` is the total growth the budget accepted from this meter: it rises by a charge's growth only after the budget's `consume` returns, and a throw from the producer or the budget drops the claim and raises nothing. For a budget the meter alone charges and never clears, `charged` therefore equals the budget's `consumed` whenever no charge is in flight, in serial use, under re-entry through the producer or the budget's consumer, and under refusal. A nested charge an abort listener makes is added before the outer charge, which the budget added first, so with growths that are not integers the two can differ in the last digit. In serial use `charged` equals the latest accepted estimate up to floating-point rounding. A throw from the estimator, or a refused estimate, claims nothing. Every refusal leaves the delta counted, and the next charge measures the growth from the text that includes it. A meter has no reset; create one per stream.
|
|
131
|
+
15. **Token metering.** `createTokenMeter` consumes each growth `n` as `{ prompt: 0, completion: n, total: n }`. A `completion`- or `total`-scoped budget therefore tracks the answer, and a `prompt`-scoped budget is charged `0`. `charged` is the completion growth the budget accepted, which a reported usage is reconciled against.
|
|
105
132
|
|
|
106
133
|
## Patterns
|
|
107
134
|
|
|
@@ -144,6 +171,34 @@ while (!bound.aborted) {
|
|
|
144
171
|
}
|
|
145
172
|
```
|
|
146
173
|
|
|
174
|
+
### Meter a streamed answer
|
|
175
|
+
|
|
176
|
+
Charge a token budget while an answer streams, then charge the provider's reported usage less `meter.charged` so no token is charged twice.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { createTokenBudget, createTokenMeter } from '@orkestrel/budget'
|
|
180
|
+
|
|
181
|
+
const budget = createTokenBudget({ max: 1_000, scope: 'completion' })
|
|
182
|
+
const meter = createTokenMeter({ budget, estimator: (text) => Math.ceil(text.length / 4) })
|
|
183
|
+
meter.charge('Once upon') // estimate 3 — consumes 3 completion tokens
|
|
184
|
+
meter.charge(' a') // estimate still 3 — charges nothing
|
|
185
|
+
meter.charge(' time') // estimate 4 — consumes 1 more
|
|
186
|
+
budget.consumed // 4
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Trace an exhaustion
|
|
190
|
+
|
|
191
|
+
When the ceiling is reached, the budget's `signal` aborts with a `BudgetError`. Narrow the reason with `isBudgetError` to read which budget stopped the work and where it stood.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
import { createBudget, isBudgetError } from '@orkestrel/budget'
|
|
195
|
+
|
|
196
|
+
const budget = createBudget<number>({ id: 'tokens', max: 10, consumer: (tokens) => tokens })
|
|
197
|
+
budget.consume(12)
|
|
198
|
+
const reason = budget.signal.reason
|
|
199
|
+
if (isBudgetError(reason)) reason.context // { id: 'tokens', max: 10, consumed: 12 }
|
|
200
|
+
```
|
|
201
|
+
|
|
147
202
|
### Re-arm per request, spend across the session
|
|
148
203
|
|
|
149
204
|
`start()` re-arms the per-request `signal` while the cumulative spend carries forward — a session-long budget that bounds each request and is born exhausted after the lifetime ceiling is reached.
|
|
@@ -181,14 +236,19 @@ budget.consume(200) // spends against the new window
|
|
|
181
236
|
- **Fold into the loop's bound** — combine `budget.signal` with an abort and a timeout through `AbortSignal.any` so one bound covers cancel, deadline, and cost.
|
|
182
237
|
- **Cumulative, not per-request** — remember `consumed` is the lifetime spend; `start()` re-arms the signal, it does NOT reset the tally.
|
|
183
238
|
- **Observe the signal** — this is a functional primitive; its native `AbortSignal` is the complete observation boundary.
|
|
239
|
+
- **One meter per stream** — create a meter for each streamed answer; it has no reset, and its `charged` is the growth the budget accepted, which the final usage report reconciles against.
|
|
240
|
+
- **Read the reason, not the signal** — narrow `signal.reason` with `isBudgetError` to tell an exhaustion from a parent's cancel.
|
|
184
241
|
|
|
185
242
|
## Tests
|
|
186
243
|
|
|
187
|
-
- [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` bijection (value + type exports), the `BudgetInterface` ↔ `Budget` method
|
|
188
|
-
- [`tests/src/core/Budget.test.ts`](../tests/src/core/Budget.test.ts) — strict construction, cumulative and atomic consumption, valid overshoot, thrown-consumer identity, numeric overflow, zero-ceiling semantics, lifecycle re-arming/reset, parent reason preservation, and public type shape.
|
|
189
|
-
- [`tests/src/core/
|
|
190
|
-
- [`tests/src/core/
|
|
191
|
-
- [`tests/src/core/
|
|
244
|
+
- [`tests/guides.test.ts`](../tests/guides.test.ts) — the `## Surface` ↔ `src/core` bijection (value + type exports), the `BudgetInterface` ↔ `Budget` and `MeterInterface` ↔ `Meter` method bijections, and the equality gate: every `Summary` cell against its declaration's description paragraph, the titled `Race work against the ceiling`, `Meter a streamed answer`, and `Trace an exhaustion` fences against the `@example` blocks of those titles (each title asserted on both the guide side and the source side, so untitling any one reddens its own case), and the README pitch against this guide's tagline. It also runs the flagship fences, the meter fence, and the exhaustion fence, and asserts the values their comments claim.
|
|
245
|
+
- [`tests/src/core/Budget.test.ts`](../tests/src/core/Budget.test.ts) — strict construction, cumulative and atomic consumption, valid overshoot, thrown-consumer identity, numeric overflow, zero-ceiling semantics, lifecycle re-arming/reset, parent reason preservation, the `BudgetError` reason at both abort sites, and public type shape.
|
|
246
|
+
- [`tests/src/core/errors.test.ts`](../tests/src/core/errors.test.ts) — `BudgetError`'s brand stamp and `isBudgetError` recognition: the same copy, two source copies in both directions, the transparent proxy, a forgery whose brand stores another object, the complete self-branded forgery, renamed, recoded, and reshaped genuine errors, a branded plain `Error`, the unbranded `code: 'exhaust'` lookalike, a stripped brand, a `DOMException` `AbortError`, and `null`, primitives, hostile getters, and a revoked proxy without a throw.
|
|
247
|
+
- [`tests/src/core/Meter.test.ts`](../tests/src/core/Meter.test.ts) — the meter construction boundary, growth charging over the cumulative text, no-growth and decreasing estimates, throw atomicity of `charge`, refused estimates and deltas, re-entry through an abort listener and the producer with its nested and repeated refusals, `charged` equal to the budget's `consumed` after each, the budget signal trip, and the absence of a reset.
|
|
248
|
+
- [`tests/src/core/factories.test.ts`](../tests/src/core/factories.test.ts) — real generic/token factory behavior plus untyped scope, usage, options, hostile, and revoked boundary failures with exact structured errors, and the token-meter charge shape and boundary.
|
|
249
|
+
- [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — direct option-helper fresh-copy and optional-key omission, exactly-once property reads, hostile getter containment, generic preservation, and exact error taxonomy/context, for the budget, token-budget, meter, and token-meter helpers.
|
|
250
|
+
- [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — total amount, native-signal, budget, token-scope, and token-usage guards across valid, off-shape, hostile, and revoked values.
|
|
251
|
+
- [`tests/distribution.test.ts`](../tests/distribution.test.ts) — the packed package installed into a scratch consumer: its exports map, declarations, and runtime surface, and `isBudgetError` recognizing a reason built by the other module format, `import` against `require` and `require` against `import`.
|
|
192
252
|
|
|
193
253
|
## See also
|
|
194
254
|
|