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.
- package/dashboard/index.html +140 -0
- package/dist/commands/reflect.d.ts.map +1 -1
- package/dist/commands/reflect.js +5 -0
- package/dist/commands/reflect.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +14 -4
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +22 -2
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +12 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +30 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/types.d.ts +22 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/CommitmentSentinel.d.ts +6 -0
- package/dist/monitoring/CommitmentSentinel.d.ts.map +1 -1
- package/dist/monitoring/CommitmentSentinel.js +46 -1
- package/dist/monitoring/CommitmentSentinel.js.map +1 -1
- package/dist/monitoring/ResourceLedger.d.ts +62 -0
- package/dist/monitoring/ResourceLedger.d.ts.map +1 -1
- package/dist/monitoring/ResourceLedger.js +152 -2
- package/dist/monitoring/ResourceLedger.js.map +1 -1
- package/dist/monitoring/ResourceSampler.d.ts +63 -0
- package/dist/monitoring/ResourceSampler.d.ts.map +1 -0
- package/dist/monitoring/ResourceSampler.js +277 -0
- package/dist/monitoring/ResourceSampler.js.map +1 -0
- package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
- package/dist/monitoring/SessionWatchdog.js +5 -0
- package/dist/monitoring/SessionWatchdog.js.map +1 -1
- package/dist/scaffold/templates.js +4 -4
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +1 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +48 -5
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +4 -2
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +43 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +65 -65
- package/src/scaffold/templates.ts +4 -4
- package/upgrades/1.3.234.md +33 -0
- package/upgrades/1.3.235.md +110 -0
- package/upgrades/commitment-bare-continuation-guard.eli16.md +26 -0
- package/upgrades/side-effects/commitment-bare-continuation-guard.md +42 -0
- package/upgrades/side-effects/esm-require-fixes-gate-rearm.md +110 -0
- 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.
|