instar 1.3.234 → 1.3.236

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.
Files changed (69) hide show
  1. package/dashboard/index.html +140 -0
  2. package/dist/commands/reflect.d.ts.map +1 -1
  3. package/dist/commands/reflect.js +5 -0
  4. package/dist/commands/reflect.js.map +1 -1
  5. package/dist/commands/server.d.ts.map +1 -1
  6. package/dist/commands/server.js +41 -0
  7. package/dist/commands/server.js.map +1 -1
  8. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  9. package/dist/config/ConfigDefaults.js +14 -4
  10. package/dist/config/ConfigDefaults.js.map +1 -1
  11. package/dist/core/CircuitBreakingIntelligenceProvider.d.ts +1 -1
  12. package/dist/core/CircuitBreakingIntelligenceProvider.d.ts.map +1 -1
  13. package/dist/core/CircuitBreakingIntelligenceProvider.js +5 -2
  14. package/dist/core/CircuitBreakingIntelligenceProvider.js.map +1 -1
  15. package/dist/core/IntelligenceRouter.d.ts +82 -0
  16. package/dist/core/IntelligenceRouter.d.ts.map +1 -0
  17. package/dist/core/IntelligenceRouter.js +108 -0
  18. package/dist/core/IntelligenceRouter.js.map +1 -0
  19. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  20. package/dist/core/PostUpdateMigrator.js +40 -2
  21. package/dist/core/PostUpdateMigrator.js.map +1 -1
  22. package/dist/core/SessionManager.d.ts +12 -0
  23. package/dist/core/SessionManager.d.ts.map +1 -1
  24. package/dist/core/SessionManager.js +30 -0
  25. package/dist/core/SessionManager.js.map +1 -1
  26. package/dist/core/componentCategories.d.ts +37 -0
  27. package/dist/core/componentCategories.d.ts.map +1 -0
  28. package/dist/core/componentCategories.js +84 -0
  29. package/dist/core/componentCategories.js.map +1 -0
  30. package/dist/core/intelligenceProviderFactory.d.ts +8 -0
  31. package/dist/core/intelligenceProviderFactory.d.ts.map +1 -1
  32. package/dist/core/intelligenceProviderFactory.js +3 -3
  33. package/dist/core/intelligenceProviderFactory.js.map +1 -1
  34. package/dist/core/types.d.ts +48 -0
  35. package/dist/core/types.d.ts.map +1 -1
  36. package/dist/core/types.js.map +1 -1
  37. package/dist/monitoring/ResourceLedger.d.ts +62 -0
  38. package/dist/monitoring/ResourceLedger.d.ts.map +1 -1
  39. package/dist/monitoring/ResourceLedger.js +152 -2
  40. package/dist/monitoring/ResourceLedger.js.map +1 -1
  41. package/dist/monitoring/ResourceSampler.d.ts +63 -0
  42. package/dist/monitoring/ResourceSampler.d.ts.map +1 -0
  43. package/dist/monitoring/ResourceSampler.js +277 -0
  44. package/dist/monitoring/ResourceSampler.js.map +1 -0
  45. package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
  46. package/dist/monitoring/SessionWatchdog.js +5 -0
  47. package/dist/monitoring/SessionWatchdog.js.map +1 -1
  48. package/dist/scaffold/templates.d.ts.map +1 -1
  49. package/dist/scaffold/templates.js +10 -4
  50. package/dist/scaffold/templates.js.map +1 -1
  51. package/dist/server/AgentServer.d.ts +1 -0
  52. package/dist/server/AgentServer.d.ts.map +1 -1
  53. package/dist/server/AgentServer.js +51 -5
  54. package/dist/server/AgentServer.js.map +1 -1
  55. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  56. package/dist/server/CapabilityIndex.js +5 -2
  57. package/dist/server/CapabilityIndex.js.map +1 -1
  58. package/dist/server/routes.d.ts +2 -0
  59. package/dist/server/routes.d.ts.map +1 -1
  60. package/dist/server/routes.js +70 -0
  61. package/dist/server/routes.js.map +1 -1
  62. package/package.json +1 -1
  63. package/src/data/builtin-manifest.json +65 -65
  64. package/src/scaffold/templates.ts +10 -4
  65. package/upgrades/1.3.235.md +110 -0
  66. package/upgrades/1.3.236.md +53 -0
  67. package/upgrades/side-effects/esm-require-fixes-gate-rearm.md +110 -0
  68. package/upgrades/side-effects/per-component-framework-routing.md +88 -0
  69. package/upgrades/side-effects/resource-ledger.md +110 -0
@@ -0,0 +1,110 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ **Fixed three latent ESM `require()` bugs and re-armed the two standards-gates that
9
+ should have caught them.** Instar ships as ESM (`"type":"module"`, tsconfig Node16),
10
+ where a bare CommonJS `require(...)` is undefined and throws `ReferenceError` the
11
+ moment that line runs. Three runtime files had one on a live path:
12
+ `reflect.ts` (hit by `instar reflect run`), `SessionWatchdog.ts` (the stuck-session
13
+ watchdog's child-detection), and `PostUpdateMigrator.ts` (Codex-path detection — it
14
+ silently returned null instead of throwing).
15
+
16
+ The fix uses the repo's existing `createRequire(import.meta.url)` idiom for the first
17
+ two (same lazy load, now ESM-legal), and for `PostUpdateMigrator.ts` simply calls the
18
+ `detectCodexPath` it was already statically importing (the lazy require was redundant).
19
+
20
+ These bugs reached `main` because the `esm-compliance` and `no-silent-fallbacks`
21
+ gates — whose entire job is catching this class of issue — had been parked in
22
+ `vitest.push.config.ts`'s flaky-exclude list, so CI stopped running them. This change
23
+ re-arms both (taught `esm-compliance` to recognize the one legitimate ESM use of
24
+ `require` — `createRequire`, needed to re-load a native module after a rebuild) and
25
+ corrects the `no-silent-fallbacks` baseline to the true count.
26
+
27
+ The per-agent **ResourceLedger** gains **Phase B: continuous CPU + memory
28
+ sampling**, closing the gap where Instar tracked token usage well (the
29
+ `TokenLedger`) but tracked *zero* CPU and *zero* memory per agent.
30
+
31
+ A new `ResourceSampler` (mirroring `TokenLedgerPoller`) runs on a cadence and
32
+ samples:
33
+ - the agent's **own server process** — CPU% from a `process.cpuUsage()` delta over
34
+ the tick interval, plus RSS and V8 `heapUsed` from `process.memoryUsage()`;
35
+ - each **spawned session** — by its tmux pane PID, via a single batched
36
+ `ps -o pid=,%cpu=,rss= -p …` call (one child process per tick, dead-PID tolerant);
37
+ - a computed **aggregate** (server + all sessions).
38
+
39
+ Each reading is written to the existing `ResourceLedger` SQLite store (a new
40
+ `resource_samples` table, bounded by a retention prune). Two read-only routes
41
+ expose it — `GET /resources/summary` (current + windowed avg/peak CPU%/RSS per
42
+ source) and `GET /resources/samples` (recent raw samples) — alongside a new
43
+ dashboard **Resource Usage** tab that renders it in plain language.
44
+
45
+ The sampler rides the `developmentAgent` dark-feature gate: it is **live on
46
+ development agents and dark on the fleet** until promoted. Configuration lives
47
+ under `monitoring.resourceLedger` (`sampleIntervalMs`, `idleSampleIntervalMs`,
48
+ `retentionDays`). It is read-only observability — it never gates, throttles, or
49
+ mutates anything, and it fails open (a sampling error never crashes the agent).
50
+ It is also cheap by design: a free CPU-usage delta, one batched `ps` per tick,
51
+ bounded retention, and an idle-cadence backoff when no sessions are running.
52
+
53
+ ## What to Tell Your User
54
+
55
+ A few features that could have thrown a runtime error on a rarely-hit path — job
56
+ reflection, the stuck-session watchdog, and Codex detection during updates — are now
57
+ fixed. Two safety checks that had been accidentally switched off in CI are switched
58
+ back on, so this class of bug can't quietly return. Nothing for you to configure.
59
+
60
+ I can now show you how much CPU and memory I'm actually using — my own server
61
+ process and each session I have running — sampled continuously. Just ask "how
62
+ much CPU or memory am I using right now?" and I'll give you the current numbers
63
+ plus the recent peaks, or open the new Resource Usage tab on the dashboard to see
64
+ it laid out: a right-now headline, a breakdown by process, and a recent trend.
65
+
66
+ This is purely a window into resource use — it watches and reports, it never
67
+ changes how I work or slows anything down. On most agents it ships turned off for
68
+ now while it proves itself; if you'd like it switched on, just say so.
69
+
70
+ ## Summary of New Capabilities
71
+
72
+ - Three latent ESM require() ReferenceErrors repaired (reflect, SessionWatchdog,
73
+ PostUpdateMigrator) using the established createRequire idiom or an existing static import.
74
+ - The esm-compliance and no-silent-fallbacks CI gates are re-armed (removed from
75
+ FLAKY_TESTS), restoring enforcement of two standards that had silently stopped gating.
76
+
77
+ - See current and recent CPU and memory use per agent, broken down by the server
78
+ and each running session, plus a combined total.
79
+ - New read-only endpoints: GET /resources/summary and GET /resources/samples.
80
+ - New dashboard Resource Usage tab.
81
+ - Tunable cadence and retention; ships live on development agents, dark on the fleet.
82
+
83
+ ## Evidence
84
+
85
+ - `tsc --noEmit` clean across the three source edits.
86
+ - Re-armed gates verified green under the REAL CI config:
87
+ `vitest run --config vitest.push.config.ts tests/unit/esm-compliance.test.ts tests/unit/no-silent-fallbacks.test.ts`
88
+ → 2 files / 7 tests passed.
89
+ - Baseline correction is evidence-backed: the heuristic count at d0fe838 (the
90
+ `[skip ci]` release that set 186) was already 431; at HEAD it is 437; a set-diff of
91
+ the two match-lists shows the +6 are the same catch blocks at shifted line numbers,
92
+ not new silent fallbacks.
93
+
94
+ - New unit tests (`tests/unit/ResourceLedger.test.ts` Phase B block,
95
+ `tests/unit/ResourceSampler.test.ts`): record/summary/recentSamples/prune/count,
96
+ CPU% from a `cpuUsage` delta, batched-PID sampling, dead-PID tolerance, aggregate,
97
+ idle backoff, and fail-open-never-throws — 22 passing.
98
+ - Wiring-integrity (`tests/unit/resource-sampler-wiring.test.ts`): the sampler is
99
+ constructed behind the `developmentAgent` gate, stopped on shutdown, delegates to
100
+ the real ledger (not a no-op), and is read-only / off the hot path — 8 passing.
101
+ - Integration (`tests/integration/resources-summary-routes.test.ts`):
102
+ `/resources/summary` + `/resources/samples` return 401 without a bearer token,
103
+ 503 when disabled, and 200 with per-source data; no raw event bodies leak —
104
+ 5 passing.
105
+ - E2E (`tests/e2e/resources-summary-lifecycle.test.ts`): on the production
106
+ `AgentServer` boot path the route is alive (200 under `developmentAgent`) and
107
+ 503-stubs when the ledger is disabled — 5 passing.
108
+ - `npm run build`, `npm run lint`, `node scripts/docs-coverage.mjs --check`, and the
109
+ parity guards (feature-delivery-completeness, capabilities-discoverability,
110
+ builtin-manifest, ConfigDefaults) all green.
@@ -0,0 +1,53 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ **You can now run different internal components on different agentic frameworks** —
9
+ for example, keep the agent's conversation on Claude Code but route ALL of its
10
+ sentinels and gates to Codex, so that background LLM chatter stops spending your Claude
11
+ rate-limit budget. A new `IntelligenceRouter` sits at the single LLM funnel and resolves
12
+ each call's framework at call time from the calling component's category (with a
13
+ per-component override), reading config live so changes take effect without a restart.
14
+
15
+ Each framework gets its **own** circuit breaker, so a Claude rate-limit trip no longer
16
+ pauses Codex (the previous global-singleton breaker did). Model "size" is preserved
17
+ automatically — a `fast` check becomes Haiku on Claude or a small GPT model on Codex —
18
+ so nothing at the call sites changes. Fallback is circuit-aware: a missing CLI degrades
19
+ to the default framework and reports it, while a merely rate-limited framework lets the
20
+ component fall back to its own heuristic instead of stampeding the default.
21
+
22
+ This is **opt-in and absent by default** — with no `componentFrameworks` config, every
23
+ component stays on your default framework, byte-identical to before.
24
+
25
+ ## What to Tell Your User
26
+
27
+ If you keep hitting rate limits on one provider, you can now spread the load: route your
28
+ background checks (sentinels and gates) to a second framework like Codex while your main
29
+ conversation stays where it is. Turn it on under sessions.componentFrameworks in your
30
+ config; check what's routed where at the intelligence/routing endpoint. With no config,
31
+ nothing changes.
32
+
33
+ ## Summary of New Capabilities
34
+
35
+ - `sessions.componentFrameworks` config — route internal components by category
36
+ (sentinel/gate/job/reflector/other) or per-component name to any enabled framework.
37
+ - `GET /intelligence/routing` — read-only: the resolved framework per known component,
38
+ per-framework availability, and how many are routed off the default.
39
+ - Per-framework circuit breakers (true rate-limit isolation between frameworks).
40
+ - Framework-aware model-size passthrough (a routed component keeps its fast/balanced/
41
+ capable size, mapped to the right concrete model for its framework).
42
+
43
+ ## Evidence
44
+
45
+ - Converged spec (2-reviewer adversarial + integration pass that corrected the
46
+ breaker-isolation and resolution-point design before any code was written):
47
+ docs/specs/per-component-framework-routing.md.
48
+ - All three test tiers green: 11 unit (tests/unit/intelligence-router.test.ts — resolution
49
+ precedence, per-framework provider-instance isolation, both fallback modes, live-config
50
+ hot-reload, the read surface), 3 integration (200/503), 3 e2e (feature-alive on the real
51
+ AgentServer init path + Bearer-auth + read-only). tsc clean.
52
+ - Unconfigured zero-change is proven by a unit test (no config ⇒ the default provider
53
+ answers every call and no other provider is ever built).
@@ -0,0 +1,110 @@
1
+ # Side-Effects Review — ESM require() bug fixes + standards-gate re-arm
2
+
3
+ **Version / slug:** `esm-require-fixes-gate-rearm`
4
+ **Date:** `2026-06-03`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Tier:** `1` (small / low-risk — bugfix + test/config; no new capability, no block/allow surface)
7
+ **Second-pass reviewer:** `not required`
8
+
9
+ ## Summary of the change
10
+
11
+ Fixes three genuine latent ESM bugs and re-arms the two standards-gates that should
12
+ have caught them but were parked in the CI flaky-exclude list.
13
+
14
+ The package is ESM (`"type":"module"`, tsconfig `Node16`), where a bare CommonJS
15
+ `require(...)` is undefined and throws `ReferenceError` at runtime. Three runtime
16
+ files used a bare `require()`:
17
+ - `src/commands/reflect.ts:355` — lazy `require('../core/intelligenceProviderFactory.js')`
18
+ inside `resolveIntelligence()`; `instar reflect run` would throw when it hits this.
19
+ - `src/monitoring/SessionWatchdog.ts:713` — lazy `require('./frameworkProcessSignals.js')`
20
+ during child-process detection; throws on the watchdog's polling path.
21
+ - `src/core/PostUpdateMigrator.ts:1137` — lazy `require('./Config.js')` for
22
+ `detectCodexPath` inside a try/catch (so it silently returned null → codex never
23
+ detected).
24
+
25
+ Fixes:
26
+ - `reflect.ts`, `SessionWatchdog.ts` — bind a real `require` via
27
+ `createRequire(import.meta.url)` (the repo's existing idiom, see
28
+ `NativeModuleHealer.ts:44`). Preserves the deliberate lazy-load semantics exactly,
29
+ but now ESM-legal.
30
+ - `PostUpdateMigrator.ts` — `detectCodexPath` is ALREADY statically imported at the
31
+ top of the module (line 56), so the lazy require was redundant; replaced with a
32
+ direct call to the static import. No shim needed.
33
+
34
+ Gate re-arm + test corrections:
35
+ - `tests/unit/esm-compliance.test.ts` — teach the guard to recognize the legitimate
36
+ `require = createRequire(import.meta.url)` ESM binding (otherwise the naive
37
+ `require(` regex false-positives on `node-abi-mismatch.ts`, which already uses the
38
+ pattern correctly to re-load a native module after an ABI rebuild — which static
39
+ `import` cannot express).
40
+ - `tests/unit/no-silent-fallbacks.test.ts` — baseline corrected 186→437 with
41
+ evidence (the 186 was set by a `[skip ci]` release while the true count was already
42
+ 431; the +6 to 437 is line-shift re-counts, zero new fallbacks).
43
+ - `tests/unit/feature-delivery-completeness.test.ts` — track the previously-untracked
44
+ `Token-Burn Alerts` migrator section.
45
+ - `src/data/builtin-manifest.json` — regenerated against current source.
46
+ - `vitest.push.config.ts` — RE-ARM `esm-compliance` + `no-silent-fallbacks` (removed
47
+ from `FLAKY_TESTS`) so these standards-gates run in CI again. They were quarantined,
48
+ which is exactly how the three bare-require bugs reached main unnoticed.
49
+
50
+ ## Decision-point inventory
51
+ - `reflect.ts / SessionWatchdog.ts` require binding — **modify (mechanics only)** —
52
+ same lazy load, now via a real ESM `require`. No behavior change beyond "no longer
53
+ throws."
54
+ - `PostUpdateMigrator.ts` — **modify** — use the existing static import; drops a
55
+ silent-null path (codex detection now actually works).
56
+ - Test guards + push config + manifest — **modify/add** — no runtime surface.
57
+
58
+ ## 1. Over-block
59
+ No block/allow surface. The two re-armed gates are CI test gates, not runtime gates.
60
+ They cannot block a running agent; they can only fail a PR's CI (their intended job).
61
+
62
+ ## 2. Under-block
63
+ The esm-compliance guard previously UNDER-blocked (it was excluded, so it caught
64
+ nothing — three real require() bugs slipped through). Re-arming restores the intended
65
+ coverage. The createRequire recognition is precise (`require\s*=\s*createRequire\s*\(`)
66
+ so it does not newly under-block: a file only earns the skip by establishing a real
67
+ ESM `require` binding.
68
+
69
+ ## 3. Level-of-abstraction fit
70
+ Correct. The runtime fixes operate at the module-loading layer (the bug's layer). The
71
+ gate changes operate at the CI-config layer. Neither reaches into business logic.
72
+
73
+ ## 4. Signal vs authority compliance
74
+ **Required reference:** docs/signal-vs-authority.md
75
+ - [x] No block/allow runtime surface of its own. The re-armed tests are SIGNALS to CI
76
+ (a PR fails), not runtime authorities. No agent decision is gated by this change.
77
+
78
+ ## 5. Interactions
79
+ - **Double-fire / shadowing:** none. The require fixes are byte-equivalent loads.
80
+ - **PostUpdateMigrator:** the static `detectCodexPath` import already existed and is
81
+ used elsewhere; calling it directly cannot introduce a cycle that the static import
82
+ didn't already have.
83
+ - **no-silent-fallbacks baseline:** exact-count ratchet on a hyperactive main may need
84
+ a future bump if a large mechanical sweep lands (as happened 3× before) — that is the
85
+ ratchet working, not a regression. Re-measured fresh before merge.
86
+ - **esm-compliance:** a future dependency-introduced `require()` will now (correctly)
87
+ fail CI — that is the gate doing its job.
88
+
89
+ ## 6. External surfaces
90
+ None. No new route, no new persistent state, no config default change. `vitest.push.config.ts`
91
+ is build/test tooling, not shipped agent config.
92
+
93
+ ## 7. Rollback cost
94
+ Trivial. Revert the commit: the require fixes go back to throwing (status quo ante),
95
+ and the two gates return to FLAKY_TESTS. No state, no migration, no user-visible change.
96
+
97
+ ## Conclusion
98
+ Low-risk Tier-1 bugfix. Three real latent ESM `require()` ReferenceErrors fixed using
99
+ the repo's established `createRequire` idiom (or, for PostUpdateMigrator, the existing
100
+ static import). The standards-gates that exist to catch this class of bug are re-armed
101
+ and verified green under the real CI push config. Clear to ship.
102
+
103
+ ## Evidence pointers
104
+ - `tsc --noEmit` clean on the three src edits.
105
+ - Re-armed gates pass under the real CI config:
106
+ `npx vitest run --config vitest.push.config.ts tests/unit/esm-compliance.test.ts tests/unit/no-silent-fallbacks.test.ts`
107
+ → 2 files / 7 tests passed.
108
+ - Root-cause evidence for the baseline correction: heuristic count at d0fe838
109
+ (the `[skip ci]` release that set 186) = 431; at HEAD = 437; set-diff shows the +6
110
+ are line-shifted re-counts of the same catch blocks, not new fallbacks.
@@ -0,0 +1,88 @@
1
+ # Side-Effects Review — Per-component framework routing (B1)
2
+
3
+ **Version / slug:** `per-component-framework-routing`
4
+ **Date:** `2026-06-03`
5
+ **Author:** `Echo (instar-dev agent)`
6
+ **Tier:** `2` (new capability; converged + approved spec: docs/specs/per-component-framework-routing.md)
7
+ **Second-pass reviewer:** `not required (the converged spec already carries a 2-reviewer adversarial+integration pass)`
8
+
9
+ ## Summary of the change
10
+ Adds an `IntelligenceRouter` at the single LLM funnel so different internal components
11
+ (sentinels, gates, …) can be routed to different agentic frameworks (e.g. all sentinels on
12
+ Codex while the agent stays on Claude), to move that LLM chatter off the Claude rate-limit
13
+ budget. ABSENT-by-default config ⇒ byte-identical to today.
14
+
15
+ New / changed:
16
+ - `src/core/componentCategories.ts` (NEW) — component-name → category registry + resolver.
17
+ - `src/core/IntelligenceRouter.ts` (NEW) — funnel-level, per-call, per-framework routing with
18
+ per-framework circuit breakers and circuit-aware fallback.
19
+ - `src/core/intelligenceProviderFactory.ts` — additive `breaker?` option threaded to the wrap
20
+ calls (lets the router give each framework its OWN breaker).
21
+ - `src/core/CircuitBreakingIntelligenceProvider.ts` — `wrapIntelligenceWithCircuitBreaker`
22
+ gains an optional `breaker` param (default unchanged = global singleton).
23
+ - `src/core/types.ts` — additive `attribution.category?` on IntelligenceOptions; additive
24
+ `sessions.componentFrameworks?` config (TYPE-ONLY — NOT added to ConfigDefaults).
25
+ - `src/commands/server.ts` — wrap the shared provider in the router in its OWN try/catch.
26
+ - `tests/unit/intelligence-router.test.ts` (NEW) — 11 tests.
27
+
28
+ ## Decision-point inventory
29
+ - `IntelligenceRouter.evaluate` routing — **add** — chooses WHICH framework answers; no
30
+ block/allow authority (providers are signal). The one safety branch is fallback (D4).
31
+ - `wrapIntelligenceWithCircuitBreaker` / factory `breaker?` — **modify (additive)** — default
32
+ path unchanged; only the router passes a distinct breaker.
33
+
34
+ ## 1. Over-block / 2. Under-block
35
+ No block/allow surface — providers are SIGNAL. The router cannot block a running agent; the
36
+ worst it does is degrade-and-report (D4). Unconfigured, it is a transparent passthrough.
37
+
38
+ ## 3. Level-of-abstraction fit
39
+ Correct — the router sits at the existing `CircuitBreakingIntelligenceProvider` funnel (the
40
+ one chokepoint every `.evaluate()` already passes), so it covers ALL callers (including the
41
+ config-path and inline-closure ones) without editing 38 call sites — Structure > Willpower.
42
+
43
+ ## 4. Signal vs authority compliance
44
+ **Reference:** docs/signal-vs-authority.md. [x] No block/allow surface of its own. Routing
45
+ changes which model answers; it adds no gate. The per-framework breakers are the existing
46
+ breaker authority, now correctly ISOLATED per framework (a Claude trip no longer pauses Codex)
47
+ — a correctness improvement, not a new authority.
48
+
49
+ ## 5. Interactions
50
+ - **Breaker isolation:** each non-default framework gets a distinct `new LlmCircuitBreaker()`
51
+ (proven by unit test — a routed call reaches a different provider instance than the default).
52
+ The default framework keeps the global singleton, so status/health surfaces are unchanged for
53
+ it. (Per-framework breaker states are additionally surfaced via GET /intelligence/routing —
54
+ part 2 of B1, see "Remaining".)
55
+ - **Fallback (D4):** binary-missing → degrade to default + DegradationReporter (no silent
56
+ fallback — composes with Task-1's re-armed gate); rate-limited → LlmCircuitOpenError
57
+ propagates and the caller swallows it into its heuristic (NO herd onto Claude).
58
+ - **Cascade-503:** the router is built in its OWN try/catch in server boot; a build failure
59
+ leaves the raw provider, never 503s the server.
60
+ - **Config:** type-only; NOT in ConfigDefaults (applyDefaults deep-merge would otherwise inject
61
+ it everywhere and break absent-equals-unchanged).
62
+
63
+ ## 6. External surfaces
64
+ - (Remaining in this PR) `GET /intelligence/routing` read-only surface + its CapabilityIndex
65
+ classification (the #727 lesson). No persistent state. Config: `sessions.componentFrameworks`
66
+ (opt-in, absent by default).
67
+
68
+ ## 7. Rollback cost
69
+ Pure additive + opt-in. Remove `componentFrameworks` (or revert) → every call routes to the
70
+ single shared provider exactly as today. No state, no migration.
71
+
72
+ ## Conclusion
73
+ B1 complete across all three test tiers (tsc clean): per-call funnel resolution,
74
+ per-framework breaker isolation, circuit-aware fallback, live-config (hot), the category
75
+ registry, factory breaker-threading, config type, server wiring, the `GET /intelligence/routing`
76
+ read surface (+ CapabilityIndex INTERNAL_PREFIXES classification — the #727 lesson),
77
+ CLAUDE.md agent-awareness (template + migrateClaudeMd for existing agents). Tests: 11 unit
78
+ (`intelligence-router.test.ts`) + 3 integration (`intelligence-routing-routes.test.ts`,
79
+ 200/503) + 3 e2e (`intelligence-routing-lifecycle.test.ts`, feature-alive on the real
80
+ AgentServer init path + Bearer-auth + read-only). The design was converged (2-reviewer
81
+ adversarial+integration) which corrected the breaker-isolation and resolution-point design
82
+ before any code — see the spec's Convergence Report. B2 (gates/reflectors + per-framework
83
+ spend accounting + dashboard view) is a deliberate follow-up.
84
+
85
+ ## Evidence pointers
86
+ - `tsc --noEmit` clean.
87
+ - `vitest run tests/unit/intelligence-router.test.ts` → 11/11 pass (resolution precedence,
88
+ dispatch, per-framework instance isolation, fallback both modes, live-config hot, for() surface).
@@ -0,0 +1,110 @@
1
+ # Side-Effects Review — ResourceLedger Phase B (CPU + memory sampling)
2
+
3
+ **Version / slug:** `resource-ledger`
4
+ **Date:** `2026-06-03`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Add continuous CPU + memory sampling to the per-agent `ResourceLedger`. A new
11
+ `ResourceSampler` (mirroring `TokenLedgerPoller`) samples the agent's own server
12
+ process (CPU% via a `process.cpuUsage()` delta, RSS + heapUsed via
13
+ `process.memoryUsage()`) and each spawned session by its pane PID (one batched `ps`
14
+ call, dead-PID tolerant), plus an aggregate, into a new `resource_samples` table.
15
+ Two read-only routes (`/resources/summary`, `/resources/samples`) and a dashboard
16
+ "Resource Usage" tab expose it. Constructed at boot in `AgentServer` behind the
17
+ `developmentAgent` dark-feature gate; bounded by a retention prune.
18
+
19
+ ## Decision-point inventory
20
+
21
+ One gate: whether to construct + start the sampler. It resolves
22
+ `monitoring.resourceLedger.enabled ?? !!config.developmentAgent` — explicit config
23
+ wins; otherwise live on dev agents, dark on the fleet. No other decision logic; the
24
+ sampler is pure observation (no kill/throttle/route-block verdicts).
25
+
26
+ ## 1. Over-block
27
+
28
+ **What legitimate inputs does this change reject?** Nothing is rejected. The sampler
29
+ gates nothing — it only reads `ps`/`process.*` and writes the ledger. The routes
30
+ return data or a `503` stub when the ledger is null; they never block a caller.
31
+
32
+ ## 2. Under-block
33
+
34
+ **What does this still miss?** CPU% for sessions comes from the pane PID's `ps
35
+ %cpu`, which on macOS/Linux is a lifetime-average for the process, not a precise
36
+ instantaneous interval rate (the own-process reading IS a true interval delta). It
37
+ samples the pane shell PID, not a full descendant-tree roll-up, so a session's
38
+ total tree CPU is approximated by the pane process line `ps` returns. Rate-limit
39
+ aggregation across the fleet and mitigation actions are explicitly later slices —
40
+ this slice only measures.
41
+
42
+ ## 3. Level-of-abstraction fit
43
+
44
+ **Right layer?** Yes. The sampler lives in `monitoring/` beside `TokenLedgerPoller`
45
+ and writes the existing `ResourceLedger`; boot wiring lives in `AgentServer`
46
+ alongside the Phase-A ledger construction; the routes are inline in `routes.ts` next
47
+ to `/resources/rate-limits` and `/tokens/*`. The pane-PID resolver is a read-only
48
+ method on `SessionManager` (the owner of session/tmux truth), reusing the same
49
+ `tmux list-panes #{pane_pid}` pattern already there.
50
+
51
+ ## 4. Signal vs authority compliance
52
+
53
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
54
+
55
+ No blocking authority added. The ledger and sampler are pure signal: every write
56
+ swallows its own error, every OS call is guarded, and a sampling failure records
57
+ nothing and retries next tick (`@silent-fallback-ok`). The sampler never reaches
58
+ back into any runtime flow — it cannot affect the `LlmCircuitBreaker`, the reaper,
59
+ sessions, or messaging.
60
+
61
+ ## 5. Interactions
62
+
63
+ Reads `SessionManager.getRunningSessionPanePids()` (new, read-only) once per tick;
64
+ no mutation of session state. Shares the `ResourceLedger` SQLite handle with the
65
+ Phase-A rate-limit path (same DB, separate `resource_samples` table — no schema
66
+ collision; the rate-limit methods are untouched). The `ps` call is a child process,
67
+ isolated from the event loop. No interaction with the SessionReaper, sentinels,
68
+ compaction recovery, or the multi-machine lease. Idempotent boot (additive table
69
+ DDL), clean shutdown (sampler stopped before the ledger closes).
70
+
71
+ ## 6. External surfaces
72
+
73
+ Two new HTTP routes (`GET /resources/summary`, `GET /resources/samples`,
74
+ Bearer-authed, read-only), registered in `CapabilityIndex`. One new dashboard tab
75
+ (static HTML/JS, XSS-safe via escaping, friendly "not turned on" state on 503). New
76
+ config block `monitoring.resourceLedger.{sampleIntervalMs,idleSampleIntervalMs,
77
+ retentionDays}` with `applyDefaults` backfill. CLAUDE.md template + idempotent
78
+ `migrateClaudeMd` backfill so existing agents learn the capability. No new
79
+ notifications, no Telegram, no external network calls. One new SQLite table inside
80
+ the already-present `resource-ledger.db`.
81
+
82
+ ## 7. Performance (a resource tracker must itself be cheap)
83
+
84
+ This is the dimension that matters most here — the tracker must not become a
85
+ resource hog.
86
+
87
+ - **Sampling cost per tick is bounded and small.** The own-process reading is a free
88
+ `process.cpuUsage()` + `process.memoryUsage()` pair (no syscall fork). All session
89
+ PIDs are measured in **one** batched `ps` child process per tick — never one fork
90
+ per PID — with a 5s timeout and a 1 MB output cap. Default cadence is 60s
91
+ (active), backing off to 5 minutes when no sessions are running, so the idle CPU
92
+ floor is barely moved.
93
+ - **Write cost is bounded.** Each tick writes a handful of rows (server + N sessions
94
+ + 1 aggregate) in a single transaction. No JSONL scan, no file walk (unlike the
95
+ TokenLedger), so there is no large-history boot hazard.
96
+ - **Storage is bounded.** The `resource_samples` table is pruned every tick to a
97
+ retention window (default 7 days). At a 60s cadence with a few sessions that is on
98
+ the order of tens of thousands of small rows — kilobytes-to-low-megabytes, indexed
99
+ on `(ts)` and `(source, ts)` for cheap windowed queries.
100
+ - **Fail-open never amplifies cost.** A slow/failed `ps` is caught (no stacking — a
101
+ re-entrant tick is skipped), and the timer is `unref()`'d so it can't keep the
102
+ process alive. The feature ships dark on the fleet (developmentAgent gate), so the
103
+ fleet-wide cost is zero until each agent is deliberately promoted.
104
+
105
+ ## Rollback
106
+
107
+ Set `monitoring.resourceLedger.enabled` to `false` (disables the whole ledger; the
108
+ routes 503) — or, to keep Phase A but stop sampling, the sampler simply is not
109
+ constructed when the `developmentAgent` gate resolves off. No data migration to
110
+ reverse; the `resource_samples` table is additive and inert when unused.