instar 1.3.593 → 1.3.594
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +9 -13
- package/dist/commands/server.js.map +1 -1
- package/dist/core/selfQuotaState.d.ts +48 -0
- package/dist/core/selfQuotaState.d.ts.map +1 -0
- package/dist/core/selfQuotaState.js +49 -0
- package/dist/core/selfQuotaState.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.594.md +35 -0
- package/upgrades/side-effects/placement-llm-circuit-aware-quota.md +50 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selfQuotaState — compute a machine's placement-eligibility block for the capacity
|
|
3
|
+
* heartbeat (spec: docs/specs/placement-llm-circuit-aware-quota.md).
|
|
4
|
+
*
|
|
5
|
+
* `quotaState.blocked` is the signal `PlacementExecutor` reads to decide whether a machine
|
|
6
|
+
* may serve a new LLM session. It is a **placement-eligibility** signal — "this machine
|
|
7
|
+
* cannot serve LLM work right now" — NOT a pure account-quota readout. Until now its only
|
|
8
|
+
* cause was account-quota exhaustion; an OPEN llm-circuit (the machine's provider calls are
|
|
9
|
+
* actually failing, rate-limited) is a second, more direct cause and MUST also block. The
|
|
10
|
+
* live test caught the gap: a machine with an open circuit reported `blocked:false` and
|
|
11
|
+
* placement routed a session onto it that died on arrival (2026-06-16, the Mac Mini).
|
|
12
|
+
*
|
|
13
|
+
* Extracted as a pure function so the two-signal contract (account-quota OR circuit) is
|
|
14
|
+
* unit-testable and cannot silently regress to quota-only.
|
|
15
|
+
*/
|
|
16
|
+
export interface QuotaSnapshot {
|
|
17
|
+
blockedUntil?: string | null;
|
|
18
|
+
fiveHourPercent?: number | null;
|
|
19
|
+
blockReason?: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Type-level discriminator for the block CAUSE so consumers branch on a closed set, not a
|
|
23
|
+
* free string. `provider-block` / `five-hour-*` = account quota; `llm-circuit-open` =
|
|
24
|
+
* operational unavailability. Free-form legacy quota strings still flow through, typed as the
|
|
25
|
+
* open `string` arm (back-compat).
|
|
26
|
+
*/
|
|
27
|
+
export type SelfQuotaBlockReason = 'llm-circuit-open' | 'five-hour-exhausted' | 'provider-block' | (string & {});
|
|
28
|
+
export interface SelfQuotaBlock {
|
|
29
|
+
blocked: boolean;
|
|
30
|
+
blockedUntil?: string;
|
|
31
|
+
reason?: SelfQuotaBlockReason;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Compute this machine's placement-eligibility block from BOTH the account-quota snapshot and
|
|
35
|
+
* the live llm-circuit availability.
|
|
36
|
+
*
|
|
37
|
+
* @param quota the account-quota snapshot (`quotaTracker.getState()`), or null/undefined when
|
|
38
|
+
* there is no tracker.
|
|
39
|
+
* @param circuitAvailable `llmCircuitAvailable()` — `true` when the breaker is disabled OR
|
|
40
|
+
* closed; `false` only when it is enabled AND open/half-open.
|
|
41
|
+
* @returns a block object, `{ blocked: false }`, or `undefined` (unknown ≠ blocked).
|
|
42
|
+
*
|
|
43
|
+
* Fail-open is preserved: an open circuit is the ONLY thing that newly blocks, and only on a
|
|
44
|
+
* *positively observed* unavailable circuit — missing information (no tracker + closed
|
|
45
|
+
* circuit) still returns `undefined`, which `PlacementExecutor` treats as not-blocked.
|
|
46
|
+
*/
|
|
47
|
+
export declare function computeSelfQuotaState(quota: QuotaSnapshot | null | undefined, circuitAvailable: boolean, now?: number): SelfQuotaBlock | undefined;
|
|
48
|
+
//# sourceMappingURL=selfQuotaState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfQuotaState.d.ts","sourceRoot":"","sources":["../../src/core/selfQuotaState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAC5B,kBAAkB,GAClB,qBAAqB,GACrB,gBAAgB,GAChB,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACvC,gBAAgB,EAAE,OAAO,EACzB,GAAG,GAAE,MAAmB,GACvB,cAAc,GAAG,SAAS,CAgB5B"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* selfQuotaState — compute a machine's placement-eligibility block for the capacity
|
|
3
|
+
* heartbeat (spec: docs/specs/placement-llm-circuit-aware-quota.md).
|
|
4
|
+
*
|
|
5
|
+
* `quotaState.blocked` is the signal `PlacementExecutor` reads to decide whether a machine
|
|
6
|
+
* may serve a new LLM session. It is a **placement-eligibility** signal — "this machine
|
|
7
|
+
* cannot serve LLM work right now" — NOT a pure account-quota readout. Until now its only
|
|
8
|
+
* cause was account-quota exhaustion; an OPEN llm-circuit (the machine's provider calls are
|
|
9
|
+
* actually failing, rate-limited) is a second, more direct cause and MUST also block. The
|
|
10
|
+
* live test caught the gap: a machine with an open circuit reported `blocked:false` and
|
|
11
|
+
* placement routed a session onto it that died on arrival (2026-06-16, the Mac Mini).
|
|
12
|
+
*
|
|
13
|
+
* Extracted as a pure function so the two-signal contract (account-quota OR circuit) is
|
|
14
|
+
* unit-testable and cannot silently regress to quota-only.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Compute this machine's placement-eligibility block from BOTH the account-quota snapshot and
|
|
18
|
+
* the live llm-circuit availability.
|
|
19
|
+
*
|
|
20
|
+
* @param quota the account-quota snapshot (`quotaTracker.getState()`), or null/undefined when
|
|
21
|
+
* there is no tracker.
|
|
22
|
+
* @param circuitAvailable `llmCircuitAvailable()` — `true` when the breaker is disabled OR
|
|
23
|
+
* closed; `false` only when it is enabled AND open/half-open.
|
|
24
|
+
* @returns a block object, `{ blocked: false }`, or `undefined` (unknown ≠ blocked).
|
|
25
|
+
*
|
|
26
|
+
* Fail-open is preserved: an open circuit is the ONLY thing that newly blocks, and only on a
|
|
27
|
+
* *positively observed* unavailable circuit — missing information (no tracker + closed
|
|
28
|
+
* circuit) still returns `undefined`, which `PlacementExecutor` treats as not-blocked.
|
|
29
|
+
*/
|
|
30
|
+
export function computeSelfQuotaState(quota, circuitAvailable, now = Date.now()) {
|
|
31
|
+
// An open llm-circuit is a hard block regardless of the account-quota poll — the machine's
|
|
32
|
+
// provider calls are failing right now, so it cannot serve a session. This wins even when
|
|
33
|
+
// there is no quota snapshot (a machine with no tracker but an open circuit is still blocked).
|
|
34
|
+
if (!circuitAvailable)
|
|
35
|
+
return { blocked: true, reason: 'llm-circuit-open' };
|
|
36
|
+
if (!quota)
|
|
37
|
+
return undefined; // no tracker + circuit ok = unknown ≠ blocked
|
|
38
|
+
const blockActive = !!quota.blockedUntil && Date.parse(quota.blockedUntil) > now;
|
|
39
|
+
const fiveHourExhausted = (quota.fiveHourPercent ?? 0) >= 95;
|
|
40
|
+
if (!blockActive && !fiveHourExhausted)
|
|
41
|
+
return { blocked: false };
|
|
42
|
+
return {
|
|
43
|
+
blocked: true,
|
|
44
|
+
blockedUntil: quota.blockedUntil ?? undefined,
|
|
45
|
+
reason: quota.blockReason ??
|
|
46
|
+
(fiveHourExhausted ? `5-hour window at ${quota.fiveHourPercent}%` : 'provider block'),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=selfQuotaState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selfQuotaState.js","sourceRoot":"","sources":["../../src/core/selfQuotaState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA0BH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAuC,EACvC,gBAAyB,EACzB,MAAc,IAAI,CAAC,GAAG,EAAE;IAExB,2FAA2F;IAC3F,0FAA0F;IAC1F,+FAA+F;IAC/F,IAAI,CAAC,gBAAgB;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC5E,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC,CAAC,8CAA8C;IAC5E,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC;IACjF,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7D,IAAI,CAAC,WAAW,IAAI,CAAC,iBAAiB;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAClE,OAAO;QACL,OAAO,EAAE,IAAI;QACb,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,SAAS;QAC7C,MAAM,EACJ,KAAK,CAAC,WAAW;YACjB,CAAC,iBAAiB,CAAC,CAAC,CAAC,oBAAoB,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC,gBAAgB,CAAC;KACxF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-16T09:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-16T09:33:12.134Z",
|
|
5
|
+
"instarVersion": "1.3.594",
|
|
6
6
|
"entryCount": 201,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Fixed a multi-machine placement bug where a rate-limited machine could still be picked to serve
|
|
9
|
+
new conversations. The "can this machine take work?" health flag was computed only from the
|
|
10
|
+
slow account-usage poll and ignored the circuit breaker that trips when a machine's AI calls are
|
|
11
|
+
actually failing. So a machine with an open circuit advertised itself as available, and a new
|
|
12
|
+
conversation routed there died on arrival. Placement now treats an open (or recovering) circuit
|
|
13
|
+
as "blocked", so it steers new work to a machine that can actually answer. Caught by applying
|
|
14
|
+
the live-testing standard to the multi-machine transfer.
|
|
15
|
+
|
|
16
|
+
## What to Tell Your User
|
|
17
|
+
|
|
18
|
+
If one of your machines is rate-limited, I now send new conversations to a machine that can
|
|
19
|
+
actually answer, instead of into a dead end where you'd get a "session stopped" notice. The only
|
|
20
|
+
time nothing improves is when every machine is rate-limited at once — then there is genuinely
|
|
21
|
+
nowhere good to send it, and I say so honestly. Single-machine setups are unaffected, and there
|
|
22
|
+
is no new setting to learn.
|
|
23
|
+
|
|
24
|
+
## Summary of New Capabilities
|
|
25
|
+
|
|
26
|
+
| Capability | How to Use |
|
|
27
|
+
|-----------|-----------|
|
|
28
|
+
| Placement avoids a machine whose AI calls are currently failing | Automatic; a rate-limited machine shows as "blocked — llm-circuit-open" in the machines view and new conversations skip it |
|
|
29
|
+
|
|
30
|
+
## Evidence
|
|
31
|
+
|
|
32
|
+
- 8 new unit tests (`tests/unit/selfQuotaState.test.ts`, both sides of every boundary) + a new
|
|
33
|
+
PlacementExecutor test asserting a circuit-open machine is skipped; tsc clean.
|
|
34
|
+
- Release gate: live two-machine re-run — a circuit-open machine reports blocked in the pool view
|
|
35
|
+
and a new conversation lands on a machine that can serve it.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Side-Effects Review — Quota-aware placement must see the open LLM circuit
|
|
2
|
+
|
|
3
|
+
**Slug:** placement-llm-circuit-aware-quota
|
|
4
|
+
**Spec:** docs/specs/placement-llm-circuit-aware-quota.md
|
|
5
|
+
**Parent principle:** No Silent Degradation to a Brittle Fallback — a machine that cannot serve LLM work must report itself blocked, not present a healthy-looking signal that misroutes work onto it.
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
|
|
9
|
+
`selfQuotaState()` (the capacity-heartbeat block `PlacementExecutor` reads) derived `blocked`
|
|
10
|
+
ONLY from the account-quota poll, ignoring the `LlmCircuitBreaker`. So a machine whose CLI was
|
|
11
|
+
actually rate-limited (circuit OPEN) could still report `quotaState:{blocked:false}` and
|
|
12
|
+
placement would route a session onto it that dies on arrival. Caught live (2026-06-16, the Mac
|
|
13
|
+
Mini) by the gold-standard live test of the multi-machine transfer.
|
|
14
|
+
|
|
15
|
+
Fix: extract `computeSelfQuotaState(quota, circuitAvailable)` into `src/core/selfQuotaState.ts`
|
|
16
|
+
(pure, unit-tested), OR-ing an open/half-open circuit (`!llmCircuitAvailable()`) into the block.
|
|
17
|
+
`server.ts` calls it with the live quota snapshot AND `llmCircuitAvailable()`.
|
|
18
|
+
|
|
19
|
+
## Blast radius
|
|
20
|
+
|
|
21
|
+
- **Multi-machine placement (the target):** a circuit-open machine is now excluded from
|
|
22
|
+
placement; new sessions land on a machine that can serve them. This was BROKEN before — net
|
|
23
|
+
improvement, strictly better whenever ANY machine is available.
|
|
24
|
+
- **All-machines-circuit-open:** identical to today's all-account-quota-blocked case —
|
|
25
|
+
placement falls back to least-loaded and flags `all-machines-quota-blocked` (now possibly
|
|
26
|
+
carrying `llm-circuit-open` reasons). Not a regression; nowhere good to route by definition.
|
|
27
|
+
- **Single machine:** no peers to compare; the existing all-blocked least-loaded fallback
|
|
28
|
+
applies, unchanged.
|
|
29
|
+
- **Fail-open preserved:** only a positively-observed open circuit newly blocks. Missing info
|
|
30
|
+
(no tracker + closed circuit, or any throw) → `undefined` = unknown ≠ blocked, exactly as
|
|
31
|
+
before. A DISABLED breaker reports available → never a false block.
|
|
32
|
+
- **Wire field:** `quotaState` keeps its name (a replicated heartbeat field consumed
|
|
33
|
+
cross-machine + by the dashboard; renaming is a separate wire migration). Its MEANING is
|
|
34
|
+
pinned by the doc contract + a new `SelfQuotaBlockReason` enum (`llm-circuit-open` |
|
|
35
|
+
`five-hour-exhausted` | `provider-block` | string). No consumer keys on a specific reason
|
|
36
|
+
string, so widening the cause set is non-breaking.
|
|
37
|
+
|
|
38
|
+
## Reversibility
|
|
39
|
+
|
|
40
|
+
Governed by the existing `intelligence.circuitBreaker` config — disabling the breaker (or it
|
|
41
|
+
never tripping) keeps `llmCircuitAvailable()` true so this never blocks. Revertable by reverting
|
|
42
|
+
the two-file diff; no durable state is written (the block is recomputed per heartbeat).
|
|
43
|
+
|
|
44
|
+
## Risk / monitoring
|
|
45
|
+
|
|
46
|
+
Low. Pure-function change off the hot path (computed per 30s heartbeat). Observable via
|
|
47
|
+
`GET /pool` (`quotaState.reason: 'llm-circuit-open'`) + the existing placement decision flags +
|
|
48
|
+
the already-audited `[llm-circuit]` transitions in `logs/server.log`. Release gate: the live
|
|
49
|
+
two-machine re-run — a circuit-open machine shows `blocked:true` in `/pool` and a new
|
|
50
|
+
conversation lands on a machine that can serve it.
|