instar 1.3.640 → 1.3.642

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.
@@ -8,9 +8,14 @@ export class LlmQueue {
8
8
  maxConcurrent;
9
9
  interactiveReservePct;
10
10
  maxDailyCents;
11
+ backgroundDispatchMinGapMs;
11
12
  now;
12
13
  inFlight = new Set();
13
14
  waiters = [];
15
+ /** Wall-clock (via `now`) of the last BACKGROUND-lane dispatch — herd-pacing window anchor. */
16
+ lastBackgroundStartAt = 0;
17
+ /** At most one pending paced re-drain timer; null when none scheduled. */
18
+ pacingTimer = null;
14
19
  /** Daily spend ledger: { dateKey: 'YYYY-MM-DD', cents: number, interactive: number } */
15
20
  dailySpendCents = 0;
16
21
  dailyInteractiveCents = 0;
@@ -19,6 +24,7 @@ export class LlmQueue {
19
24
  this.maxConcurrent = opts.maxConcurrent ?? 3;
20
25
  this.interactiveReservePct = opts.interactiveReservePct ?? 0.4;
21
26
  this.maxDailyCents = opts.maxDailyCents ?? 100;
27
+ this.backgroundDispatchMinGapMs = opts.backgroundDispatchMinGapMs ?? 0;
22
28
  this.now = opts.now ?? (() => Date.now());
23
29
  }
24
30
  /**
@@ -62,6 +68,23 @@ export class LlmQueue {
62
68
  while (this.waiters.length > 0) {
63
69
  const next = this.waiters[0];
64
70
  if (this.inFlight.size < this.maxConcurrent) {
71
+ // Herd guard (§3c): pace BACKGROUND-lane dispatches so a burst of queued calls can't re-trip
72
+ // a just-recovered provider. Interactive bypasses (latency-sensitive + preemptive). When a
73
+ // background dispatch is too soon after the previous one, schedule a paced re-drain instead of
74
+ // starting now. Because interactive waiters sort ahead, a background head means no interactive
75
+ // is waiting — so deferring it blocks nothing else. OFF when backgroundDispatchMinGapMs <= 0.
76
+ if (next.lane === 'background' && this.backgroundDispatchMinGapMs > 0 && this.lastBackgroundStartAt > 0) {
77
+ const since = this.now() - this.lastBackgroundStartAt;
78
+ // Jitter the gap (full-jitter low half) so independent recoveries don't re-sync into a herd.
79
+ const gap = Math.floor(this.backgroundDispatchMinGapMs * (0.5 + Math.random() * 0.5));
80
+ if (since < gap) {
81
+ this.schedulePacedDrain(gap - since);
82
+ break;
83
+ }
84
+ }
85
+ if (next.lane === 'background' && this.backgroundDispatchMinGapMs > 0) {
86
+ this.lastBackgroundStartAt = this.now();
87
+ }
65
88
  this.waiters.shift();
66
89
  this.start(next);
67
90
  continue;
@@ -82,6 +105,19 @@ export class LlmQueue {
82
105
  break;
83
106
  }
84
107
  }
108
+ /**
109
+ * Schedule a single paced re-drain (§3c herd guard). Coalesces to at most one pending timer so a
110
+ * burst of background enqueues can't stack timers. `unref`'d so it never holds the process open.
111
+ */
112
+ schedulePacedDrain(delayMs) {
113
+ if (this.pacingTimer)
114
+ return;
115
+ this.pacingTimer = setTimeout(() => {
116
+ this.pacingTimer = null;
117
+ this.drain();
118
+ }, Math.max(1, delayMs));
119
+ this.pacingTimer.unref?.();
120
+ }
85
121
  start(w) {
86
122
  const controller = new AbortController();
87
123
  const inflight = {
@@ -1 +1 @@
1
- {"version":3,"file":"LlmQueue.js","sourceRoot":"","sources":["../../src/monitoring/LlmQueue.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LlmQueue.js","sourceRoot":"","sources":["../../src/monitoring/LlmQueue.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.640",
3
+ "version": "1.3.642",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-22T03:15:59.405Z",
5
- "instarVersion": "1.3.640",
4
+ "generatedAt": "2026-06-22T07:19:14.502Z",
5
+ "instarVersion": "1.3.642",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -11,7 +11,7 @@
11
11
  "domain": "identity",
12
12
  "sourcePath": "src/core/PostUpdateMigrator.ts",
13
13
  "installedPath": ".instar/hooks/instar/session-start.sh",
14
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
14
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
15
15
  "since": "2025-01-01"
16
16
  },
17
17
  "hook:dangerous-command-guard": {
@@ -20,7 +20,7 @@
20
20
  "domain": "safety",
21
21
  "sourcePath": "src/core/PostUpdateMigrator.ts",
22
22
  "installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
23
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
23
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
24
24
  "since": "2025-01-01"
25
25
  },
26
26
  "hook:grounding-before-messaging": {
@@ -29,7 +29,7 @@
29
29
  "domain": "safety",
30
30
  "sourcePath": "src/core/PostUpdateMigrator.ts",
31
31
  "installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
32
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
32
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
33
33
  "since": "2025-01-01"
34
34
  },
35
35
  "hook:compaction-recovery": {
@@ -38,7 +38,7 @@
38
38
  "domain": "identity",
39
39
  "sourcePath": "src/core/PostUpdateMigrator.ts",
40
40
  "installedPath": ".instar/hooks/instar/compaction-recovery.sh",
41
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
41
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
42
42
  "since": "2025-01-01"
43
43
  },
44
44
  "hook:external-operation-gate": {
@@ -47,7 +47,7 @@
47
47
  "domain": "safety",
48
48
  "sourcePath": "src/core/PostUpdateMigrator.ts",
49
49
  "installedPath": ".instar/hooks/instar/external-operation-gate.js",
50
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
50
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
51
51
  "since": "2025-01-01"
52
52
  },
53
53
  "hook:deferral-detector": {
@@ -56,7 +56,7 @@
56
56
  "domain": "safety",
57
57
  "sourcePath": "src/core/PostUpdateMigrator.ts",
58
58
  "installedPath": ".instar/hooks/instar/deferral-detector.js",
59
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
59
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
60
60
  "since": "2025-01-01"
61
61
  },
62
62
  "hook:self-stop-guard": {
@@ -65,7 +65,7 @@
65
65
  "domain": "coherence",
66
66
  "sourcePath": "src/core/PostUpdateMigrator.ts",
67
67
  "installedPath": ".instar/hooks/instar/self-stop-guard.js",
68
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
68
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
69
69
  "since": "2025-01-01"
70
70
  },
71
71
  "hook:post-action-reflection": {
@@ -74,7 +74,7 @@
74
74
  "domain": "evolution",
75
75
  "sourcePath": "src/core/PostUpdateMigrator.ts",
76
76
  "installedPath": ".instar/hooks/instar/post-action-reflection.js",
77
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
77
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
78
78
  "since": "2025-01-01"
79
79
  },
80
80
  "hook:external-communication-guard": {
@@ -83,7 +83,7 @@
83
83
  "domain": "safety",
84
84
  "sourcePath": "src/core/PostUpdateMigrator.ts",
85
85
  "installedPath": ".instar/hooks/instar/external-communication-guard.js",
86
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
86
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
87
87
  "since": "2025-01-01"
88
88
  },
89
89
  "hook:scope-coherence-collector": {
@@ -92,7 +92,7 @@
92
92
  "domain": "coherence",
93
93
  "sourcePath": "src/core/PostUpdateMigrator.ts",
94
94
  "installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
95
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
95
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
96
96
  "since": "2025-01-01"
97
97
  },
98
98
  "hook:scope-coherence-checkpoint": {
@@ -101,7 +101,7 @@
101
101
  "domain": "coherence",
102
102
  "sourcePath": "src/core/PostUpdateMigrator.ts",
103
103
  "installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
104
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
104
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
105
105
  "since": "2025-01-01"
106
106
  },
107
107
  "hook:free-text-guard": {
@@ -110,7 +110,7 @@
110
110
  "domain": "safety",
111
111
  "sourcePath": "src/core/PostUpdateMigrator.ts",
112
112
  "installedPath": ".instar/hooks/instar/free-text-guard.sh",
113
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
113
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
114
114
  "since": "2025-01-01"
115
115
  },
116
116
  "hook:claim-intercept": {
@@ -119,7 +119,7 @@
119
119
  "domain": "coherence",
120
120
  "sourcePath": "src/core/PostUpdateMigrator.ts",
121
121
  "installedPath": ".instar/hooks/instar/claim-intercept.js",
122
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
122
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
123
123
  "since": "2025-01-01"
124
124
  },
125
125
  "hook:claim-intercept-response": {
@@ -128,7 +128,7 @@
128
128
  "domain": "coherence",
129
129
  "sourcePath": "src/core/PostUpdateMigrator.ts",
130
130
  "installedPath": ".instar/hooks/instar/claim-intercept-response.js",
131
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
131
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
132
132
  "since": "2025-01-01"
133
133
  },
134
134
  "hook:stop-gate-router": {
@@ -137,7 +137,7 @@
137
137
  "domain": "safety",
138
138
  "sourcePath": "src/core/PostUpdateMigrator.ts",
139
139
  "installedPath": ".instar/hooks/instar/stop-gate-router.js",
140
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
140
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
141
141
  "since": "2025-01-01"
142
142
  },
143
143
  "hook:auto-approve-permissions": {
@@ -146,7 +146,7 @@
146
146
  "domain": "safety",
147
147
  "sourcePath": "src/core/PostUpdateMigrator.ts",
148
148
  "installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
149
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
149
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
150
150
  "since": "2025-01-01"
151
151
  },
152
152
  "job:health-check": {
@@ -1562,7 +1562,7 @@
1562
1562
  "type": "subsystem",
1563
1563
  "domain": "updates",
1564
1564
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1565
- "contentHash": "b8cabbf22cdb28187bc70a5337a9e340996eb8eace88790b9bf8ba2d9a2b5630",
1565
+ "contentHash": "80fdbcd7e2b0c600a1766e7ebc736aa394e0eb8ccf73417a10a9643903d7a7b4",
1566
1566
  "since": "2025-01-01"
1567
1567
  },
1568
1568
  "subsystem:scheduler": {
@@ -0,0 +1,51 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ The last rung of the rate-limit principle: **wait for capacity before falling back.** When an internal
9
+ background check gets rate-limited and has already tried slowing-down-and-retrying and switching
10
+ frameworks, it can now WAIT in a small queue for capacity to free up — instead of immediately giving up
11
+ and dropping to a basic rule. The wait is bounded and rate-aware (it rides the same circuit-breaker
12
+ timing that protects the account), so a burst of queued checks can't re-trip the limit the moment it
13
+ recovers. Only background, non-urgent checks queue; anything the agent is actively waiting on still
14
+ fails fast. If the queue is full or over its daily budget, the check falls through to its basic rule
15
+ exactly as before — never silently dropped.
16
+
17
+ This is **off by default** (on for the dev agent first), reuses the existing wedge-safe queue, and
18
+ changes nothing for any other queue user.
19
+
20
+ ## What to Tell Your User
21
+
22
+ Nothing changes unless you turn it on. Once on, if a background check hits a rate limit, it will
23
+ briefly wait its turn for capacity to come back before falling back to a simpler rule — so you lose the
24
+ smart answer less often. Urgent checks you are waiting on still respond fast, and if there is no spare
25
+ capacity the check just uses its basic rule like before.
26
+
27
+ ## Summary of New Capabilities
28
+
29
+ - `IntelligenceRouter` DEFERRABLE queue rung (`tryDeferrableQueue`): a non-gating call that exhausts
30
+ backoff + framework-swap enqueues onto a dedicated `LlmQueue` (background lane) and WAITS for
31
+ capacity (the enqueued `provider.evaluate` honors the account-global breaker's `retryAfterMs`)
32
+ before dropping to the caller's heuristic. Inserted at both fall-through points.
33
+ - A GATING call is NEVER queued (`deferrable = !gating && …`, code-enforced — D5). An enqueue
34
+ rejection (daily-cap / interactive-reserve) or queued-call failure falls through to the heuristic,
35
+ never dropped. Two new `onDegrade` reasons (`queued` / `queue-rejected`) for `/metrics/features` (D7).
36
+ - `LlmQueue` opt-in `backgroundDispatchMinGapMs` (§3c herd guard): a jittered minimum gap between
37
+ background dispatches; interactive bypasses; default 0 = off (zero change for existing callers).
38
+ - Config `intelligence.degradationLadder.queue` → `{ enabled?, attemptTimeoutMs?, drainMinGapMs? }`;
39
+ `DEV_GATED_FEATURES` entry `degradationLadderQueue` (live-on-dev / dark-on-fleet).
40
+
41
+ ## Evidence
42
+
43
+ **Before:** a deferrable internal call that exhausted backoff + framework-swap dropped STRAIGHT to its
44
+ heuristic — even when capacity would have freed up moments later. **After:** with the rung enabled, it
45
+ WAITS for capacity in a bounded, rate-aware queue first, and only falls back if the queue rejects it or
46
+ the queued call fails. Reproduced by `tests/unit/degradation-ladder.test.ts` (7 queue cases: success +
47
+ auto-resolve; order backoff/swap → queue → success; GATING never queued; enqueue-rejection ⇒ heuristic
48
+ fallthrough; non-deferrable never queued; no-llmQueue no-op; queueEnabled:false not enqueued) and
49
+ `tests/unit/LlmQueue.test.ts` (3 pacing cases). The rung reuses the existing wedge-safe `LlmQueue` and
50
+ never calls the DegradationReporter alert machinery, so the 2026-06-21 event-loop wedge cannot recur.
51
+ Full `tsc` + dark-gate lint green; `no-silent-fallbacks` holds at baseline 476.
@@ -0,0 +1,46 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ A new guard in the agent's Stop hook catches a specific, recurring self-sabotage: the agent names
9
+ clear work it knows how to do next, then stops anyway with a self-protective excuse — "this session
10
+ is too long," "it's late," "I made mistakes so I'll be careful," "I don't want to rush," "it's
11
+ tracked so it won't slip," "I'll do it next session." Those are false reasons to stop. The guard
12
+ detects the pattern (a named next action plus one of those rationalizations) and blocks the stop
13
+ once, re-feeding a reminder that the excuse is false and to proceed now. It can never trap the agent
14
+ in a loop, and it leaves a genuine stop alone (a real external blocker, work actually finished, or a
15
+ decision only the user can make).
16
+
17
+ ## What to Tell Your User
18
+
19
+ Your agent should stop wasting your time by quitting partway through work it clearly knows how to
20
+ finish. If it tries to stop with an excuse like "it's late" or "let's do this next session" while
21
+ there's an obvious next step, it now gets caught and nudged to just do the thing — so you no longer
22
+ have to come back and say "okay, do the thing you just said you'd do."
23
+
24
+ ## Summary of New Capabilities
25
+
26
+ - `stop-gate-router` hook gains a mode-independent `falseExcuseDeferralGuard`: when the final message
27
+ contains BOTH a named piece of remaining work AND a self-protective deferral rationalization
28
+ (session-length, time-of-day, made-mistakes, don't-rush, tracked-so-it-won't-slip, next-session),
29
+ it blocks the stop once and re-feeds a "this is false — proceed now" directive.
30
+ - Pure substring matching, fires once per stop (the `stop_hook_active` loop guard prevents traps),
31
+ and requires the AND of both signals so a genuine completion or a no-work time reference is not
32
+ blocked.
33
+ - Ships to every agent automatically via the always-overwrite hook update path (no per-agent
34
+ migration needed).
35
+
36
+ ## Evidence
37
+
38
+ **Before:** the Stop gate only caught "stated-continuation" (the agent saying "I'll do X now" then
39
+ not). It did NOT catch the inverse — the agent explicitly justifying NOT doing the clear next thing
40
+ ("after a long session I won't rush this; it's tracked"). That pattern repeatedly ended sessions
41
+ early and forced the operator to re-issue "do the thing you just said." **After:** the new guard
42
+ catches it. Reproduced by `tests/unit/stop-gate-false-excuse-deferral.test.ts` (6 cases): it renders
43
+ valid JS, blocks the real-world excuse-stop and a session-length/next-session deferral, does NOT
44
+ block a genuine completion or a time reference with no pending work, and does NOT re-block under
45
+ `stop_hook_active`. The existing `stop-gate-stated-continuation` + `generated-hooks-parse` suites
46
+ stay green.
@@ -0,0 +1,75 @@
1
+ # Side-Effects Review — False-Excuse Deferral Stop-Guard
2
+
3
+ **Slug:** `false-excuse-deferral-stop-guard` · **Tier:** 1 (small, low-risk hook behavior addition;
4
+ operator-requested direct fix, no spec). Touches `PostUpdateMigrator.getStopGateRouterHook()` (a
5
+ hook template), so the tier signal flags `belowFloor` (a PostUpdateMigrator touch raises the floor) —
6
+ accepted: the change is a self-contained substring guard, fully reversible, no external surface.
7
+
8
+ ## Summary of the change
9
+
10
+ `stop-gate-router` hook gains `falseExcuseDeferralGuard`, mirroring the existing
11
+ `statedContinuationGuard`: a mode-independent IIFE that, on a Stop, blocks ONCE when the final
12
+ assistant message contains BOTH (a) a named piece of remaining work AND (b) a self-protective
13
+ deferral rationalization (session-length / time-of-day / made-mistakes / don't-rush /
14
+ tracked-so-it-won't-slip / next-session), re-feeding a "this excuse is false — proceed" directive.
15
+ Same change in the deployed copy is overwritten from this source on the next update (Migration
16
+ Parity — always-overwrite built-in hooks), so it reaches every agent.
17
+
18
+ ## 1. Over-block / false positive
19
+
20
+ The AND-of-both-signals requirement is the false-positive control: a self-protective phrase alone
21
+ (e.g. "it's late, but everything is done") does NOT block — `knownWork` must also match. Tested:
22
+ a genuine completion and a time-reference-with-no-pending-work both pass through (no block). Cost of
23
+ any residual false positive is bounded to ONE extra turn (the agent re-affirms and re-stops), because
24
+ the guard fires once.
25
+
26
+ ## 2. Under-block
27
+
28
+ If the agent stops with an excuse but uses wording outside the phrase lists, it slips through (a
29
+ false negative) — acceptable: the guard is a high-precision catch for the documented recurring
30
+ phrasings, not a complete classifier. The lists cover the operator-cited forms and the agent's actual
31
+ observed messages. Tunable by extending the arrays.
32
+
33
+ ## 4. Signal vs authority
34
+
35
+ The guard is a one-shot re-feed (block decision with a reminder), exactly like the
36
+ stated-continuation guard. It never silences or rewrites a message and takes no destructive action.
37
+ The `stop_hook_active` loop guard guarantees the agent is never trapped — a legitimate stop (real
38
+ external blocker / work complete / a user-only decision) re-stops cleanly on the next attempt.
39
+
40
+ ## 5. Interactions
41
+
42
+ Sits directly after `statedContinuationGuard` in the same hook, before the server round-trip — so it
43
+ works even when the server-side stop-gate is in shadow/off mode (which is exactly when these stalls
44
+ slip through). No new dependency, no network call, no state. Pure substring matching. Cannot recurse
45
+ or grow unbounded.
46
+
47
+ ## 6. External surfaces
48
+
49
+ None. No route, no egress, no spend (no LLM call — deterministic substring matching in the hook
50
+ subprocess).
51
+
52
+ ## 7. Multi-machine posture
53
+
54
+ Machine-local: the hook runs per-session on whichever machine hosts the session. No replicated state.
55
+ Each agent on each machine gets the guard via the standard hook update.
56
+
57
+ ## 8. Rollback cost
58
+
59
+ Trivial: delete the `falseExcuseDeferralGuard` IIFE from `getStopGateRouterHook()`; the next update
60
+ overwrites the deployed copy back to guard-free. No data, no migration to unwind.
61
+
62
+ ## Evidence pointers
63
+
64
+ - `tests/unit/stop-gate-false-excuse-deferral.test.ts` (6): renders valid JS containing the guard;
65
+ blocks the real-world excuse-stop (named work + self-protective excuse); blocks a
66
+ session-length/next-session deferral; does NOT block a genuine completion; does NOT block a time
67
+ reference with no deferred work (false-positive control); does NOT re-block under `stop_hook_active`.
68
+ - `tests/unit/stop-gate-stated-continuation.test.ts` + `tests/unit/generated-hooks-parse.test.ts`
69
+ stay green (the template literal still renders valid JS). Full `tsc` clean.
70
+
71
+ ## Conclusion
72
+
73
+ Delivers the operator-requested structural catch for the recurring false-excuse early-stop, as an
74
+ instar feature that ships to every agent. High-precision, loop-safe, reversible, no external surface.
75
+ Ship.
@@ -0,0 +1,108 @@
1
+ # Side-Effects Review — Resilient Degradation Ladder Increment 3 (queue rung)
2
+
3
+ **Slug:** `resilient-degradation-ladder-increment-3` · **Tier:** 2 (spec-driven; the spec converged +
4
+ operator-approved, rode Increments 1/2). **Spec:** `docs/specs/resilient-degradation-ladder.md` §3b.3,
5
+ §3c, D1/D5/D7.
6
+
7
+ ## Summary of the change
8
+
9
+ The third (and final) rung of the operator's degradation principle — "prefer slowing down over
10
+ falling back". Dark/dev-gated:
11
+
12
+ - `IntelligenceRouter` gains a DEFERRABLE **queue rung** (`tryDeferrableQueue`): a non-gating call
13
+ that has exhausted backoff + framework-swap now WAITS for capacity in a dedicated `LlmQueue`
14
+ (`background` lane) before dropping to the caller's heuristic. The enqueued `provider.evaluate`
15
+ honors the account-global breaker's `retryAfterMs` via `acquireOrWait` — that IS the §3b.3
16
+ rate-awareness. Inserted at BOTH fall-through points (no-swap-configured AND swap-exhausted) before
17
+ the existing `onHeuristicFallthrough` + throw.
18
+ - A structural `DeferrableQueue` interface declared in `core` (the real `LlmQueue` satisfies it) so
19
+ `core` does NOT import `monitoring` (no layering cycle).
20
+ - `LlmQueue` gains an opt-in `backgroundDispatchMinGapMs` (§3c herd guard): a jittered minimum gap
21
+ between BACKGROUND-lane dispatches; interactive bypasses. Default 0 = OFF (today's greedy drain).
22
+ - Server wires a DEDICATED `LlmQueue` for the rung (built ONLY when the gate-resolved `queueEnabled`
23
+ is true), gate-resolves `queueEnabled`/`queueAttemptTimeoutMs` via `resolveDevAgentGate`, and the
24
+ ladder object is now present when ANY rung (backoff OR queue) is active.
25
+ - `DEV_GATED_FEATURES` registration (`degradationLadderQueue`).
26
+ - Config type extended: `degradationLadder.queue` → `{ enabled?, attemptTimeoutMs?, drainMinGapMs? }`.
27
+
28
+ ## Decision-point inventory (frozen)
29
+
30
+ D1 (config + dark rollout), D5 (`gating:true` ⇒ queue-skipped, code-enforced), D7 (reason taxonomy
31
+ `queued` / `queue-rejected`). Increment-3 bounds (sensible defaults, dark/reversible, fit D1's
32
+ `{enabled, …bounds}` structure): `queueAttemptTimeoutMs` 60000, dedicated queue `maxConcurrent` 1 +
33
+ `maxDailyCents` 25, `drainMinGapMs` 0 (off).
34
+
35
+ **D9 (new, recorded here) — the §3c herd guard is opt-in, realized two ways:** (1) the PRIMARY
36
+ rate-awareness is the provider-layer `acquireOrWait` — each enqueued `evaluate` waits for the
37
+ account-global breaker window, so queued calls don't hammer a rate-limited account; (2) the dedicated
38
+ queue's `maxConcurrent: 1` serializes deferrable retries (naturally herd-safe); (3) `drainMinGapMs`
39
+ adds an OPTIONAL jittered inter-dispatch gap (off by default). Explicit pacing is opt-in so existing
40
+ `LlmQueue` callers (PresenceProxy / PromiseBeacon) see ZERO behavior change. This faithfully delivers
41
+ §3b.3/§3c without rebuilding shared infrastructure (round-1 "extend, don't rebuild").
42
+
43
+ ## 1. Over-block / false positive
44
+
45
+ The rung only ADDS a wait-for-capacity step on a DEFERRABLE call that already exhausted swap; it never
46
+ blocks an interactive/gating call. A queued call that fails or is rejected falls through to exactly
47
+ today's heuristic — strictly no worse than before. No new block surface.
48
+
49
+ ## 2. Under-block
50
+
51
+ A GATING call NEVER reaches the queue rung: `deferrable = !gating && options.deferrable === true`, so
52
+ `if (deferrable) …tryDeferrableQueue` is structurally unreachable for a gate (D5). Unit-tested
53
+ (`GATING is NEVER queued`). The gating fail-closed boundary is unchanged.
54
+
55
+ ## 4. Signal vs authority
56
+
57
+ The queue rung takes no destructive action — it waits for capacity then returns a real answer, or
58
+ falls through. The two `onDegrade` emissions (`queued` / `queue-rejected`) are observability signals
59
+ (D7), never gates. Consistent with Signal-vs-Authority.
60
+
61
+ ## 5. Interactions — wedge-safety
62
+
63
+ Reuses the EXISTING `LlmQueue` (the wedge-safe, bounded, daily-capped queue). The new drain-pacing is
64
+ a bounded `setTimeout` coalesced to at most one pending timer (no stacking), `unref`'d (never holds the
65
+ process open). No new recursion, no growing array, no `report()`/`reportEvent()` call from the rung —
66
+ the 2026-06-21 DegradationReporter wedge class cannot recur here. The dedicated queue is built only
67
+ when the rung is active, so the fleet allocates nothing.
68
+
69
+ ## 6. External surfaces
70
+
71
+ No new route. No new egress — a queued call is the SAME provider.evaluate that would otherwise have
72
+ run; it just waits for capacity. The new config (`intelligence.degradationLadder.queue.*`) is opt-in.
73
+
74
+ ## Framework generality
75
+
76
+ Framework-agnostic — the rung keys on the resolved framework and enqueues whatever provider the
77
+ component routes to.
78
+
79
+ ## 7. Multi-machine posture
80
+
81
+ Machine-local: each machine's router + its dedicated queue are independent. No replicated state, no
82
+ cross-machine contract (the dedicated queue is per-process, same posture as the existing per-sentinel
83
+ queues).
84
+
85
+ ## 8. Rollback cost
86
+
87
+ Trivial: dark on the fleet (`queueEnabled` resolves dark; no dedicated queue is built, the ladder's
88
+ `queueEnabled` is false, and `tryDeferrableQueue` returns `{ ok: false }` immediately ⇒ exactly
89
+ today's heuristic-on-exhaustion). Revert = remove an unused-on-fleet code path. `drainMinGapMs` defaults
90
+ 0 so existing `LlmQueue` callers are untouched.
91
+
92
+ ## Evidence pointers
93
+
94
+ - `tests/unit/degradation-ladder.test.ts` (+7 queue cases): queue success + onResolved; order
95
+ backoff/swap → queue → success; GATING never queued (D5); enqueue REJECTION ⇒ heuristic fallthrough
96
+ + onHeuristicFallthrough; non-deferrable never queued; queueEnabled-but-no-llmQueue no-op;
97
+ queueEnabled:false not enqueued.
98
+ - `tests/unit/LlmQueue.test.ts` (+3 pacing cases): paces a 2nd background dispatch when the gap is set;
99
+ no pacing when off (both start at once); interactive bypasses pacing.
100
+ - `tests/unit/devGatedFeatures-wiring.test.ts` auto-covers `degradationLadderQueue` (live-on-dev /
101
+ dark-on-fleet). `tests/unit/no-silent-fallbacks.test.ts` stays at baseline 476. Full `tsc` +
102
+ `lint-dev-agent-dark-gate` green.
103
+
104
+ ## Conclusion
105
+
106
+ Delivers the final rung of the operator's degradation principle — a deferrable call now WAITS for
107
+ capacity (slow down) before ever dropping to a brittle heuristic, with the provider breaker supplying
108
+ rate-awareness and an opt-in herd gap on top. Dark/dev-gated, no-op when off, wedge-safe. Ship.