instar 1.3.231 → 1.3.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +100 -5
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +24 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +15 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +34 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/CorrectionCaptureBacklog.d.ts +85 -0
- package/dist/monitoring/CorrectionCaptureBacklog.d.ts.map +1 -0
- package/dist/monitoring/CorrectionCaptureBacklog.js +290 -0
- package/dist/monitoring/CorrectionCaptureBacklog.js.map +1 -0
- package/dist/monitoring/CorrectionCaptureLoop.d.ts +73 -3
- package/dist/monitoring/CorrectionCaptureLoop.d.ts.map +1 -1
- package/dist/monitoring/CorrectionCaptureLoop.js +172 -2
- package/dist/monitoring/CorrectionCaptureLoop.js.map +1 -1
- package/dist/monitoring/McpProcessReaper.d.ts +135 -0
- package/dist/monitoring/McpProcessReaper.d.ts.map +1 -0
- package/dist/monitoring/McpProcessReaper.js +229 -0
- package/dist/monitoring/McpProcessReaper.js.map +1 -0
- package/dist/monitoring/mcpProcessReaperDeps.d.ts +22 -0
- package/dist/monitoring/mcpProcessReaperDeps.d.ts.map +1 -0
- package/dist/monitoring/mcpProcessReaperDeps.js +125 -0
- package/dist/monitoring/mcpProcessReaperDeps.js.map +1 -0
- package/dist/monitoring/mcpProcessSignatures.d.ts +38 -0
- package/dist/monitoring/mcpProcessSignatures.d.ts.map +1 -0
- package/dist/monitoring/mcpProcessSignatures.js +55 -0
- package/dist/monitoring/mcpProcessSignatures.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +1 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +3 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts +3 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +13 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +64 -64
- package/src/scaffold/templates.ts +1 -0
- package/upgrades/1.3.232.md +47 -0
- package/upgrades/1.3.233.md +36 -0
- package/upgrades/side-effects/correction-capture-backlog.md +137 -0
- package/upgrades/side-effects/mcp-process-reaper.md +58 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: minor -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The Correction & Preference Learning Sentinel now survives an LLM throttle without losing corrections.
|
|
9
|
+
|
|
10
|
+
Previously, the capture hop distilled each detected correction through a rate-limited LLM queue behind the account-global circuit breaker. When that distill call was rejected for a capacity reason — daily spend cap reached, interactive-reserve breach, an aborted background call, or the breaker being open — `CorrectionCaptureLoop` dropped the capture silently with no retry and no backlog. On a production agent under sustained throttling that meant Layer 0 kept *detecting* corrections while the `CorrectionLedger` stayed permanently EMPTY, because every distill attempt hit the breaker and was discarded.
|
|
11
|
+
|
|
12
|
+
This release adds `CorrectionCaptureBacklog` — a bounded, durable SQLite store at `correction-capture-backlog.db`. When a distill is rejected for a capacity reason, the already-pre-scrubbed capture is PERSISTED to the backlog instead of dropped. A new `isCapacityThrow` classifier distinguishes a capacity rejection (worth retrying later) from a genuine fault (a malformed envelope still drops, preserving the old behavior). A new audit decision `distill-backlogged` records the persist.
|
|
13
|
+
|
|
14
|
+
A new `drainBacklog` function works the backlog down off the message hot path. It is triggered two ways: opportunistically right after a live capture's distill SUCCEEDS (proof the LLM has headroom *now*), and by a slow 5-minute periodic sweep. The drainer is breaker-gated — it consults `llmCircuitAvailable()` (a pure, non-probe-consuming read) and never drains while the breaker is open, since that would just re-fail every entry and re-trip the circuit. For each claimed entry it rebuilds the same distill prompt, records a real learning into the `CorrectionLedger`, then DELETES the backlog row (`markDistilled`); a failed attempt calls `bumpAttempt`, which drops the entry once it exceeds its retry budget.
|
|
15
|
+
|
|
16
|
+
Retention is bounded two ways: a max-entries cap (oldest evicted on overflow) and a TTL (stale entries pruned before each drain). The store persists ONLY the pre-scrubbed turns — the same secret scrub that guards the distill prompt runs before anything is written, plus a defensive re-scrub on enqueue — and no API route ever serves backlog contents. It is on by default whenever the feature is enabled (pure resilience) and is disabled by setting its max-entries to zero, which restores the old drop-on-throttle behavior. All four new dials backfill into existing agents via `applyDefaults` deep-merge.
|
|
17
|
+
|
|
18
|
+
The whole path is fail-open and signal-only: a backlog or drain error can never block, delay, or throw into message delivery.
|
|
19
|
+
|
|
20
|
+
## What to Tell Your User
|
|
21
|
+
|
|
22
|
+
- **Corrections survive a busy stretch now**: "When you correct me about the same thing and I happen to be rate-limited at that moment, I no longer just lose it. I tuck the correction away safely and learn from it a little later once I have headroom — so a busy period can't make me quietly forget what you taught me. It only ever keeps a private, scrubbed note of the lesson, never your raw words, and it tidies those away as soon as they're learned. This whole thing only runs when correction learning is switched on, and I can turn it off for you if you ever want the old behavior back."
|
|
23
|
+
|
|
24
|
+
## Summary of New Capabilities
|
|
25
|
+
|
|
26
|
+
| Capability | How to Use |
|
|
27
|
+
|-----------|-----------|
|
|
28
|
+
| Durable retry of throttled corrections | Automatic when correction learning is enabled |
|
|
29
|
+
| Disable the backlog (old drop behavior) | Set the correction-learning capture-backlog max-entries to 0 |
|
|
30
|
+
| See learned corrections (the ledger the backlog feeds) | GET /corrections |
|
|
31
|
+
|
|
32
|
+
## Evidence
|
|
33
|
+
|
|
34
|
+
- **Reported live**: on a production agent with correction learning enabled, Layer 0 was detecting corrections but GET /corrections returned an empty ledger. Root cause confirmed in `src/monitoring/CorrectionCaptureLoop.ts` (~line 204): the distill catch block dropped every capture on any LlmQueue throw, and the agent's account was sustained-throttled, so every distill hit the open circuit breaker and was discarded.
|
|
35
|
+
- **After (code path), full round-trip proven**: `tests/e2e/correction-capture-backlog-lifecycle.test.ts` drives a learning-signal capture whose distill is rate-limited → asserts decision `distill-backlogged` and a backlog count of 1 (NOT dropped); asserts the drain is SKIPPED while the breaker is open (entry retained); then flips the breaker closed, drains, and asserts the distilled record is recorded into the ledger, the backlog row is deleted, and the record is observable on the real GET /corrections route with only its scrubbed summary served (raw learning never crosses HTTP).
|
|
36
|
+
- **Unit coverage**: `tests/unit/CorrectionCaptureBacklog.test.ts` (11 cases: enqueue, max-entries eviction oldest-first, near-identical dedupe, claimBatch ordering + min-retry-gap, markDistilled, bumpAttempt-drops-at-maxRetries, TTL prune, pre-scrubbed-only persistence, fail-open). `tests/unit/CorrectionCaptureBacklog-drain.test.ts` (12 cases: the four-shape isCapacityThrow classifier, capacity-throw → backlog vs non-capacity → drop, no-backlog-wired old drop, backlog-fault fallback, drain breaker-gated/recorded/bumped/pruned/fail-open). Wiring-integrity in `tests/unit/correction-learning-wiring-integrity.test.ts` (backlog constructed iff enabled+maxEntries>0; a rate-limited capture lands in the backlog without throwing into the hook; drain skipped while breaker open). Integration round-trip in `tests/integration/corrections-routes.test.ts`. All green: 11 + 12 + 9 + 13 + 2 = the new/updated suites pass with zero failures.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Side-Effects Review — correction capture-backlog with retry
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `correction-capture-backlog`
|
|
4
|
+
**Date:** `2026-06-02`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `pending`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds `src/monitoring/CorrectionCaptureBacklog.ts` — a bounded, durable SQLite store
|
|
11
|
+
that persists pre-scrubbed captures the Correction & Preference Learning Sentinel
|
|
12
|
+
could not distill because the LLM was rate-limited (daily-cap / reserve-breach /
|
|
13
|
+
aborted / circuit-breaker-open). `CorrectionCaptureLoop.captureAndDistill` now, in
|
|
14
|
+
its distill catch block, classifies the rejection via the new `isCapacityThrow`: a
|
|
15
|
+
CAPACITY rejection enqueues the pre-scrubbed capture to the backlog (audit decision
|
|
16
|
+
`distill-backlogged`) instead of dropping it; a genuine fault still drops
|
|
17
|
+
(`distill-dropped`). A new `drainBacklog` distills backlogged entries into the
|
|
18
|
+
`CorrectionLedger` off the hot path, breaker-gated on `llmCircuitAvailable()`,
|
|
19
|
+
triggered after a successful live distill and by a 5-minute periodic sweep. Four
|
|
20
|
+
config dials (`captureBacklogMaxEntries` default 200, `captureBacklogTtlHours`
|
|
21
|
+
default 24, `captureBacklogDrainPerTick` default 5, `captureBacklogMaxRetries`
|
|
22
|
+
default 3) backfill via `applyDefaults`; maxEntries=0 disables the backlog.
|
|
23
|
+
|
|
24
|
+
## Decision-point inventory
|
|
25
|
+
|
|
26
|
+
- `captureAndDistill: distill threw — backlog or drop?` (CorrectionCaptureLoop.ts) —
|
|
27
|
+
**new branch** — a capacity throw now persists (was: always drop). A non-capacity
|
|
28
|
+
throw is unchanged (drops). Wrapped in try/catch → a backlog fault falls back to drop.
|
|
29
|
+
- `drainBacklog: is the LLM available?` (CorrectionCaptureLoop.ts) — **new gate** —
|
|
30
|
+
skips the whole drain while the breaker is open; re-checks between entries.
|
|
31
|
+
- `server.ts: construct the backlog?` — **new gate** — constructed IFF the feature is
|
|
32
|
+
enabled AND `captureBacklogMaxEntries > 0`. Else null (old drop behavior).
|
|
33
|
+
- `CorrectionCaptureBacklog.enqueue: at the cap?` — **new** — evicts oldest on overflow;
|
|
34
|
+
dedupes a near-identical entry instead of inserting a duplicate.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1. Over-block
|
|
39
|
+
|
|
40
|
+
**No block/allow surface — over-block not applicable.** Nothing in this change can
|
|
41
|
+
reject, block, delay, or gate a message or an operation. The backlog/drain only
|
|
42
|
+
move an internal observe-only record (a distilled correction) from a deferred state
|
|
43
|
+
to the ledger. The worst over-reach is a redundant LLM distill call on an entry that
|
|
44
|
+
would have been noise — bounded by `captureBacklogDrainPerTick` (5) per sweep and by
|
|
45
|
+
the per-sentinel daily LLM cap (the drain shares the same `correctionLlmQueue`
|
|
46
|
+
budget, so it cannot exceed the feature's existing spend ceiling).
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. Under-block
|
|
51
|
+
|
|
52
|
+
**What does it still miss?** `isCapacityThrow` is conservative — an unrecognized
|
|
53
|
+
error string is treated as a non-capacity fault and DROPS (preserving old behavior),
|
|
54
|
+
so a novel provider rate-limit phrasing not in the matcher would still be dropped
|
|
55
|
+
rather than backlogged. That is the safe direction (no false persistence). A capture
|
|
56
|
+
also still drops if the backlog enqueue itself faults (disk full) — again fail-open,
|
|
57
|
+
no worse than today. Entries that exhaust `captureBacklogMaxRetries` or age past the
|
|
58
|
+
TTL are intentionally discarded (bounded retention), so a persistently-un-distillable
|
|
59
|
+
capture is eventually dropped by design.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 3. Level-of-abstraction fit
|
|
64
|
+
|
|
65
|
+
Correct layer. The backlog mirrors `CorrectionLedger`'s own SQLite discipline (WAL,
|
|
66
|
+
prune-in-transaction, indexes, fail-open, `NativeModuleHealer` open, `SqliteRegistry`
|
|
67
|
+
close-on-exit) and lives beside it in `src/monitoring/`. The drain reuses the existing
|
|
68
|
+
`buildDistillPrompt` / `parseDistillEnvelope` rather than inventing a second distill
|
|
69
|
+
path, and rides the existing account-global `LlmCircuitBreaker` via its pure
|
|
70
|
+
`llmCircuitAvailable()` read rather than adding a parallel availability notion.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4. Signal vs authority compliance
|
|
75
|
+
|
|
76
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
77
|
+
|
|
78
|
+
- [x] No authority added — this change produces and defers a SIGNAL (a distilled
|
|
79
|
+
correction record) consumed by the existing `CorrectionAnalyzer` / `CorrectionLoopDriver`,
|
|
80
|
+
which own every downstream decision. The backlog cannot mint a proposal, write a
|
|
81
|
+
preference, post feedback, or change policy; it only delays then completes the same
|
|
82
|
+
ledger write the hot path would have done. The `CorrectionLoopDriver`'s
|
|
83
|
+
by-construction authority guard (no proposal-mint, no memory-write) is untouched.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 5. Interactions
|
|
88
|
+
|
|
89
|
+
- **Shadowing:** none — the backlog is a NEW db file (`correction-capture-backlog.db`),
|
|
90
|
+
separate from `correction-ledger.db`; no existing query path changes.
|
|
91
|
+
- **LLM budget:** the drain shares the per-sentinel `correctionLlmQueue` daily cap, so
|
|
92
|
+
it competes with (never exceeds) the existing distill budget — and it only runs when
|
|
93
|
+
`llmCircuitAvailable()` is true, so it cannot pile onto an already-throttled account.
|
|
94
|
+
- **Circuit breaker:** the drain is a pure consumer of the breaker's read state; it does
|
|
95
|
+
not trip, reset, or consume a half-open probe slot.
|
|
96
|
+
- **Multi-machine:** the db is per-stateDir (per machine), like the ledger; no mesh
|
|
97
|
+
coordination needed (the worst case is each machine independently distilling its own
|
|
98
|
+
backlogged captures, which dedupe-collapse in the ledger by `dedupeKey`).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 6. Privacy / data exposure
|
|
103
|
+
|
|
104
|
+
**This is the load-bearing dimension and the central design constraint.**
|
|
105
|
+
|
|
106
|
+
- **Persist-pre-scrubbed-only posture.** The backlog persists ONLY the
|
|
107
|
+
already-pre-scrubbed turns. The §3.3 deterministic `scrubSecrets` runs inside
|
|
108
|
+
`buildDistillPrompt` BEFORE the catch that enqueues, and `enqueue` re-scrubs every
|
|
109
|
+
turn defensively (belt-and-suspenders) so even a mis-wired caller cannot write raw
|
|
110
|
+
text. Unit-proven: a `ghp_…` token in a turn is `gh***_REDACTED` on disk
|
|
111
|
+
(`CorrectionCaptureBacklog.test.ts` + `CorrectionCaptureBacklog-drain.test.ts`).
|
|
112
|
+
- **Bounded retention.** Max-entries cap (oldest evicted) AND a TTL (stale pruned),
|
|
113
|
+
and every entry is DELETED the instant it distills (`markDistilled`) or exhausts its
|
|
114
|
+
retries (`bumpAttempt`). It is a bounded, time-limited extension of the existing
|
|
115
|
+
ephemeral look-back ring — not a new long-lived data sink.
|
|
116
|
+
- **No new exposure surface.** No API route serves backlog row content; the scrubbed
|
|
117
|
+
turn text never crosses HTTP. The only observable is a count. The E2E pins that the
|
|
118
|
+
drained record surfaces on GET /corrections with ONLY its scrubbed summary (raw
|
|
119
|
+
learning absent from the payload), exactly like the existing ledger guarantee.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 7. Failure modes / rollback
|
|
124
|
+
|
|
125
|
+
- **Fail-open everywhere.** Every backlog method swallows storage errors and returns a
|
|
126
|
+
safe default; `captureAndDistill`'s backlog enqueue is wrapped in try/catch and falls
|
|
127
|
+
back to the old drop; `drainBacklog` is fully detached (`void`/async), single-flight
|
|
128
|
+
guarded, and its top-level catch can never throw into a seam. A backlog fault degrades
|
|
129
|
+
to exactly the pre-change behavior (drop-on-throttle).
|
|
130
|
+
- **Rollback.** Per-agent: set `monitoring.correctionLearning.captureBacklogMaxEntries`
|
|
131
|
+
to 0 — the backlog is not constructed and the loop reverts to the old drop. Whole
|
|
132
|
+
feature: the existing `monitoring.correctionLearning.enabled` dark gate. No schema
|
|
133
|
+
migration is destructive (a new db file); deleting `correction-capture-backlog.db`
|
|
134
|
+
is safe and just discards any pending retries.
|
|
135
|
+
- **Boot cost.** The store opens an empty WAL db with two small indexes — no boot-time
|
|
136
|
+
full-scan (the `TokenLedger` backfill-boot-hang class is explicitly avoided; there is
|
|
137
|
+
no startup scan, only on-demand claim/prune).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Side-Effects Review — MCP-Process Reaper (Option B)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `mcp-process-reaper`
|
|
4
|
+
**Date:** `2026-06-04`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `recommended before dryRun:false is set on any agent (it kills processes)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
A new `McpProcessReaper` (separate sibling, NOT a change to `OrphanProcessReaper` /
|
|
11
|
+
`ReapGuard` / `ReapAuthority`) reaps leaked MCP-server children (playwright-mcp /
|
|
12
|
+
mcp-remote / instar-mcp-stdio) whose owning tmux session is dead/stale or fully
|
|
13
|
+
orphaned. Ships OFF + dry-run fleet-wide; the `developmentAgent` gate enables it on
|
|
14
|
+
echo but leaves `dryRun:true`, so it only observes + audits would-reap. Read-only
|
|
15
|
+
report at `GET /processes/mcp-reaper`.
|
|
16
|
+
|
|
17
|
+
## Decision-point inventory
|
|
18
|
+
|
|
19
|
+
One decision point: the per-proc keep/reap classifier (`classifyMcpProcess`). It is
|
|
20
|
+
pure and unit-tested on both sides of every boundary. The kill action is gated
|
|
21
|
+
behind `killsEnabled = enabled && !dryRun`.
|
|
22
|
+
|
|
23
|
+
## 1. Over-block (what legitimate things could it wrongly KILL?)
|
|
24
|
+
|
|
25
|
+
The risk is killing an MCP server that a live session still needs. Mitigations,
|
|
26
|
+
each unit-tested:
|
|
27
|
+
- A proc under a **live/tracked** session is KEPT regardless of age (`session-live`)
|
|
28
|
+
— a long-running autonomous session keeps its old MCP servers.
|
|
29
|
+
- A proc under an **external (non-instar)** tmux session is KEPT (`external-session`)
|
|
30
|
+
— the reaper never touches the user's own processes.
|
|
31
|
+
- A proc **younger than `minAgeMs` (2h)** is KEPT — a starting session's children are
|
|
32
|
+
never candidates.
|
|
33
|
+
- A **failed evaluation** KEEPS (never reap on error).
|
|
34
|
+
- Only **three exact signatures** match — never a broad node/npm match.
|
|
35
|
+
- Ships **dark + dry-run**; even on the dev agent it only observes. Kills require an
|
|
36
|
+
explicit `dryRun:false`.
|
|
37
|
+
- `maxReapsPerPass` (25) bounds the blast radius per pass.
|
|
38
|
+
|
|
39
|
+
## 2. Under-block (what leaks does it still MISS?)
|
|
40
|
+
|
|
41
|
+
- It does not prevent NEW leaks — that is Option A (tree-kill on session teardown),
|
|
42
|
+
noted as a follow-up. B is the backstop that cleans existing + future unclean deaths.
|
|
43
|
+
- A leaked MCP proc whose dead session's tmux pane somehow still lingers AND is not
|
|
44
|
+
recognized as an instar session would be treated as external (KEPT). This is the
|
|
45
|
+
safe failure direction (favor false-negatives).
|
|
46
|
+
- Non-MCP leaked processes are out of scope by design (precise allow-list).
|
|
47
|
+
|
|
48
|
+
## 3. Reversibility
|
|
49
|
+
|
|
50
|
+
SIGTERM on an already-orphaned helper is low-risk (its session is gone). The reaper
|
|
51
|
+
itself is fully reversible via config (`enabled:false`). No persistent state is
|
|
52
|
+
mutated except the append-only audit log.
|
|
53
|
+
|
|
54
|
+
## 4. Blast radius on the existing reaper
|
|
55
|
+
|
|
56
|
+
**Zero.** This is a separate class; the shared `OrphanProcessReaper` / `ReapGuard` /
|
|
57
|
+
`ReapAuthority` / session-reap authority path is untouched (the same Option-B
|
|
58
|
+
isolation used for #722).
|