instar 1.3.233 → 1.3.235

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 (53) 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/config/ConfigDefaults.d.ts.map +1 -1
  6. package/dist/config/ConfigDefaults.js +14 -4
  7. package/dist/config/ConfigDefaults.js.map +1 -1
  8. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  9. package/dist/core/PostUpdateMigrator.js +22 -2
  10. package/dist/core/PostUpdateMigrator.js.map +1 -1
  11. package/dist/core/SessionManager.d.ts +12 -0
  12. package/dist/core/SessionManager.d.ts.map +1 -1
  13. package/dist/core/SessionManager.js +30 -0
  14. package/dist/core/SessionManager.js.map +1 -1
  15. package/dist/core/types.d.ts +22 -0
  16. package/dist/core/types.d.ts.map +1 -1
  17. package/dist/core/types.js.map +1 -1
  18. package/dist/monitoring/CommitmentSentinel.d.ts +6 -0
  19. package/dist/monitoring/CommitmentSentinel.d.ts.map +1 -1
  20. package/dist/monitoring/CommitmentSentinel.js +46 -1
  21. package/dist/monitoring/CommitmentSentinel.js.map +1 -1
  22. package/dist/monitoring/ResourceLedger.d.ts +62 -0
  23. package/dist/monitoring/ResourceLedger.d.ts.map +1 -1
  24. package/dist/monitoring/ResourceLedger.js +152 -2
  25. package/dist/monitoring/ResourceLedger.js.map +1 -1
  26. package/dist/monitoring/ResourceSampler.d.ts +63 -0
  27. package/dist/monitoring/ResourceSampler.d.ts.map +1 -0
  28. package/dist/monitoring/ResourceSampler.js +277 -0
  29. package/dist/monitoring/ResourceSampler.js.map +1 -0
  30. package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
  31. package/dist/monitoring/SessionWatchdog.js +5 -0
  32. package/dist/monitoring/SessionWatchdog.js.map +1 -1
  33. package/dist/scaffold/templates.js +4 -4
  34. package/dist/scaffold/templates.js.map +1 -1
  35. package/dist/server/AgentServer.d.ts +1 -0
  36. package/dist/server/AgentServer.d.ts.map +1 -1
  37. package/dist/server/AgentServer.js +48 -5
  38. package/dist/server/AgentServer.js.map +1 -1
  39. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  40. package/dist/server/CapabilityIndex.js +4 -2
  41. package/dist/server/CapabilityIndex.js.map +1 -1
  42. package/dist/server/routes.d.ts.map +1 -1
  43. package/dist/server/routes.js +43 -0
  44. package/dist/server/routes.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/data/builtin-manifest.json +65 -65
  47. package/src/scaffold/templates.ts +4 -4
  48. package/upgrades/1.3.234.md +33 -0
  49. package/upgrades/1.3.235.md +110 -0
  50. package/upgrades/commitment-bare-continuation-guard.eli16.md +26 -0
  51. package/upgrades/side-effects/commitment-bare-continuation-guard.md +42 -0
  52. package/upgrades/side-effects/esm-require-fixes-gate-rearm.md +110 -0
  53. 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,26 @@
1
+ # CommitmentSentinel bare-continuation guard — plain English
2
+
3
+ ## The problem
4
+ The agent has a watcher that tries to notice when it has made a promise it should
5
+ follow through on ("I'll report back when X"). But it was too eager: it treated
6
+ almost every message you sent — even a plain "yes" or "please proceed" — as if it
7
+ created a promise to track. The result was a registry full of fake "commitments"
8
+ (36 of them showed as broken), which is exactly why it got hard to tell what was
9
+ actually open.
10
+
11
+ ## The fix
12
+ Before the watcher even considers an exchange, a simple deterministic check drops
13
+ the ones where your message was just an approval or a "keep going" — those ask for
14
+ nothing durable, so they can't be a promise. Real requests are untouched: anything
15
+ with an action word ("deploy", "set", "restart", "turn off") still goes through, so
16
+ "go ahead and deploy the latest" is correctly kept.
17
+
18
+ ## How safe is it?
19
+ Very. The watcher only *detects* promises — it never deletes or changes one. The
20
+ worst case is missing a promise from a one-word message with no verb (almost never),
21
+ which is the safe direction. Explicitly opening a commitment via the API still works
22
+ exactly as before. 50 tests cover both sides (drops the bare ones, keeps the real
23
+ requests).
24
+
25
+ ## What you need to decide
26
+ Nothing — it just makes the commitment registry trustworthy again.
@@ -0,0 +1,42 @@
1
+ # Side-Effects Review — CommitmentSentinel bare-continuation guard
2
+
3
+ **Version / slug:** `commitment-bare-continuation-guard`
4
+ **Date:** `2026-06-04`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required (Tier-1: signal-only, favors false-negatives)`
7
+
8
+ ## Summary of the change
9
+
10
+ The CommitmentSentinel registered a false-positive "commitment" for nearly every
11
+ user message — including bare approvals/continuations ("please proceed", "yes") —
12
+ because its LLM detector ran on every user→agent exchange. A deterministic
13
+ pre-filter (`isBareContinuation`) now drops bare-approval exchanges before the LLM
14
+ sees them. Reinforced with one line in the detection prompt.
15
+
16
+ ## Decision-point inventory
17
+
18
+ One pure decision: `isBareContinuation(text)` — drop the exchange (true) or keep it
19
+ (false). Unit-tested on both sides (50 cases).
20
+
21
+ ## 1. Over-block (what legitimate commitments could it wrongly DROP?)
22
+
23
+ A genuine durable request phrased as a bare phrase under 30 chars with no
24
+ imperative verb. Mitigated by the verb guard: any message containing a durable
25
+ verb (change/set/turn/deploy/restart/report/…) is KEPT, so "go ahead and deploy",
26
+ "sure, set the model to opus", "ok now restart the gemini server" all pass through
27
+ (tested). The residual risk is a one-word durable instruction with no verb, which
28
+ is vanishingly rare and favors the SAFE direction (a missed detection, not a false
29
+ commitment) — the infrastructure `POST /commitments` path still registers explicit
30
+ commitments regardless.
31
+
32
+ ## 2. Under-block (what false positives does it still MISS?)
33
+
34
+ Substantive-but-non-committal exchanges (a real question the agent answered) still
35
+ reach the LLM; the prompt reinforcement is the second line of defense there. This
36
+ PR targets the dominant, deterministic false-positive class (bare approvals).
37
+
38
+ ## 3. Reversibility / blast radius
39
+
40
+ Signal-only. The sentinel only DETECTS unregistered commitments; dropping an
41
+ exchange cannot delete or alter an existing commitment. No persistent state, no
42
+ migration, no route change. Fully reversible by reverting the filter.
@@ -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,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.