instar 1.3.403 → 1.3.405

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-07T08:45:36.629Z",
5
- "instarVersion": "1.3.403",
4
+ "generatedAt": "2026-06-07T17:10:48.879Z",
5
+ "instarVersion": "1.3.405",
6
6
  "entryCount": 199,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -11,7 +11,7 @@
11
11
  "domain": "identity",
12
12
  "sourcePath": "src/core/PostUpdateMigrator.ts",
13
13
  "installedPath": ".instar/hooks/instar/session-start.sh",
14
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
14
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
15
15
  "since": "2025-01-01"
16
16
  },
17
17
  "hook:dangerous-command-guard": {
@@ -20,7 +20,7 @@
20
20
  "domain": "safety",
21
21
  "sourcePath": "src/core/PostUpdateMigrator.ts",
22
22
  "installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
23
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
23
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
24
24
  "since": "2025-01-01"
25
25
  },
26
26
  "hook:grounding-before-messaging": {
@@ -29,7 +29,7 @@
29
29
  "domain": "safety",
30
30
  "sourcePath": "src/core/PostUpdateMigrator.ts",
31
31
  "installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
32
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
32
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
33
33
  "since": "2025-01-01"
34
34
  },
35
35
  "hook:compaction-recovery": {
@@ -38,7 +38,7 @@
38
38
  "domain": "identity",
39
39
  "sourcePath": "src/core/PostUpdateMigrator.ts",
40
40
  "installedPath": ".instar/hooks/instar/compaction-recovery.sh",
41
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
41
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
42
42
  "since": "2025-01-01"
43
43
  },
44
44
  "hook:external-operation-gate": {
@@ -47,7 +47,7 @@
47
47
  "domain": "safety",
48
48
  "sourcePath": "src/core/PostUpdateMigrator.ts",
49
49
  "installedPath": ".instar/hooks/instar/external-operation-gate.js",
50
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
50
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
51
51
  "since": "2025-01-01"
52
52
  },
53
53
  "hook:deferral-detector": {
@@ -56,7 +56,7 @@
56
56
  "domain": "safety",
57
57
  "sourcePath": "src/core/PostUpdateMigrator.ts",
58
58
  "installedPath": ".instar/hooks/instar/deferral-detector.js",
59
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
59
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
60
60
  "since": "2025-01-01"
61
61
  },
62
62
  "hook:self-stop-guard": {
@@ -65,7 +65,7 @@
65
65
  "domain": "coherence",
66
66
  "sourcePath": "src/core/PostUpdateMigrator.ts",
67
67
  "installedPath": ".instar/hooks/instar/self-stop-guard.js",
68
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
68
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
69
69
  "since": "2025-01-01"
70
70
  },
71
71
  "hook:post-action-reflection": {
@@ -74,7 +74,7 @@
74
74
  "domain": "evolution",
75
75
  "sourcePath": "src/core/PostUpdateMigrator.ts",
76
76
  "installedPath": ".instar/hooks/instar/post-action-reflection.js",
77
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
77
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
78
78
  "since": "2025-01-01"
79
79
  },
80
80
  "hook:external-communication-guard": {
@@ -83,7 +83,7 @@
83
83
  "domain": "safety",
84
84
  "sourcePath": "src/core/PostUpdateMigrator.ts",
85
85
  "installedPath": ".instar/hooks/instar/external-communication-guard.js",
86
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
86
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
87
87
  "since": "2025-01-01"
88
88
  },
89
89
  "hook:scope-coherence-collector": {
@@ -92,7 +92,7 @@
92
92
  "domain": "coherence",
93
93
  "sourcePath": "src/core/PostUpdateMigrator.ts",
94
94
  "installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
95
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
95
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
96
96
  "since": "2025-01-01"
97
97
  },
98
98
  "hook:scope-coherence-checkpoint": {
@@ -101,7 +101,7 @@
101
101
  "domain": "coherence",
102
102
  "sourcePath": "src/core/PostUpdateMigrator.ts",
103
103
  "installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
104
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
104
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
105
105
  "since": "2025-01-01"
106
106
  },
107
107
  "hook:free-text-guard": {
@@ -110,7 +110,7 @@
110
110
  "domain": "safety",
111
111
  "sourcePath": "src/core/PostUpdateMigrator.ts",
112
112
  "installedPath": ".instar/hooks/instar/free-text-guard.sh",
113
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
113
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
114
114
  "since": "2025-01-01"
115
115
  },
116
116
  "hook:claim-intercept": {
@@ -119,7 +119,7 @@
119
119
  "domain": "coherence",
120
120
  "sourcePath": "src/core/PostUpdateMigrator.ts",
121
121
  "installedPath": ".instar/hooks/instar/claim-intercept.js",
122
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
122
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
123
123
  "since": "2025-01-01"
124
124
  },
125
125
  "hook:claim-intercept-response": {
@@ -128,7 +128,7 @@
128
128
  "domain": "coherence",
129
129
  "sourcePath": "src/core/PostUpdateMigrator.ts",
130
130
  "installedPath": ".instar/hooks/instar/claim-intercept-response.js",
131
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
131
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
132
132
  "since": "2025-01-01"
133
133
  },
134
134
  "hook:stop-gate-router": {
@@ -137,7 +137,7 @@
137
137
  "domain": "safety",
138
138
  "sourcePath": "src/core/PostUpdateMigrator.ts",
139
139
  "installedPath": ".instar/hooks/instar/stop-gate-router.js",
140
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
140
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
141
141
  "since": "2025-01-01"
142
142
  },
143
143
  "hook:auto-approve-permissions": {
@@ -146,7 +146,7 @@
146
146
  "domain": "safety",
147
147
  "sourcePath": "src/core/PostUpdateMigrator.ts",
148
148
  "installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
149
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
149
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
150
150
  "since": "2025-01-01"
151
151
  },
152
152
  "job:health-check": {
@@ -1538,7 +1538,7 @@
1538
1538
  "type": "subsystem",
1539
1539
  "domain": "updates",
1540
1540
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1541
- "contentHash": "3e0828b523c2cece3ea4c3c73e77867c2d6884b7493c93e523318e63f5c09e30",
1541
+ "contentHash": "91751f5d379223d5219959314caaeff35e6534ade7acf5193b5768bc1fc79198",
1542
1542
  "since": "2025-01-01"
1543
1543
  },
1544
1544
  "subsystem:scheduler": {
@@ -695,6 +695,13 @@ This routes feedback to the Instar maintainers automatically. Valid types: \`bug
695
695
  - Returns \`{ available, usage: { primary, secondary, model, planType, rateLimitReachedType } }\` where each window has \`usedPercent\`, \`remainingPercent\`, \`windowMinutes\`, \`resetsAt\`/\`resetsAtIso\`, \`resetsInSeconds\`. \`available:false\` means no codex session data on disk yet (e.g. a pure-Claude agent).
696
696
  - **When to use**: when asked "how much codex usage is left?" / "am I near the limit?", before scheduling heavy codex work, or to drive a model-swap when a window is exhausted (\`rateLimitReachedType\` is non-null, or \`secondary.remainingPercent\` is low).
697
697
 
698
+ **Subscription Pool (multi-account quota + auto-swap + enrollment)** — Hold ALL of your subscriptions for a provider (e.g. several Claude logins) and use them as one pool: I read each account's live quota, drain each before its reset, and when a session hits an account's limit I resume it on another account instead of letting it die. The registry stores each account's login LOCATION (its config home), NEVER a token.
699
+ - See the pool + each account's live quota: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/subscription-pool\` · one account's quota + burn: \`GET /subscription-pool/:id/quota\` · poll all now: \`POST /subscription-pool/poll\`.
700
+ - **Continuity guarantee** — a long session that hits its account's quota resumes on another eligible account (conversation preserved via \`--resume\`), never dies. Manual lever: \`POST /subscription-pool/swap\` \`{"sessionName":"...","exhaustedAccountId":"..."}\`. Auto-swap on rate-limit ships OFF (opt-in via \`subscriptionPool.autoSwapOnRateLimit\` — it moves a live session, real authority).
701
+ - **Enroll a new account from your phone** — \`POST /subscription-pool/enroll\` \`{"id","label","provider","framework","configHome"}\` starts a login and returns a public code/URL (never a token); \`GET /subscription-pool/pending-logins\` is the surface; expired codes are auto-reissued. Mark done with \`POST /subscription-pool/enroll/:id/complete\`.
702
+ - **Dashboard**: the **Subscriptions tab** shows live quota bars (5h + weekly + reset countdown), status, and the Pending Logins panel — share the dashboard URL + PIN.
703
+ - **When to use** (PROACTIVE): "how much quota is left across my accounts?" / "am I about to hit a limit?" → \`GET /subscription-pool\`; the user wants to add another subscription → drive the enrollment wizard (never ask them to paste a token); a long job is at risk of a quota wall → the continuity guarantee + \`/swap\` keep it alive. Single-account pools are a no-op.
704
+
698
705
  **Per-Feature LLM Metrics** — See what each of your LLM-driven gates/sentinels actually costs and how often it fires, so tuning them is evidence-based (which to thin, which to strengthen). Read-only observability (like token usage) — it never gates anything.
699
706
  - Check: \`curl -H "Authorization: Bearer $AUTH" "http://localhost:${port}/metrics/features?sinceHours=24"\`
700
707
  - Returns \`{ totals, features: [{ feature, calls, tokensIn, tokensOut, fired, noop, fireRate, p50LatencyMs, p95LatencyMs, ... }] }\` — one row per system (e.g. MessagingToneGate, CoherenceReviewer). Filter with \`?feature=<name>\`.
@@ -0,0 +1,37 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The Subscription & Auth Standard's `/subscription-pool` routes graduated from the
9
+ internal/dark list to a surfaced capability, now that all its phases are merged —
10
+ the multi-account registry (P1.1), quota poller (P1.2), quota-aware auto-swap
11
+ scheduler with the session-continuity guarantee (P1.3), mobile-first enrollment
12
+ wizard (P2.1), and dashboard Subscriptions tab (P2.2).
13
+
14
+ Three no-behavior edits: `/subscription-pool` moves from `INTERNAL_PREFIXES` to a
15
+ `CAPABILITY_INDEX` entry (so it appears in `/capabilities` with its account count,
16
+ quota poller / scheduler / enrollment-wizard wiring status, and full endpoint
17
+ list); the CLAUDE.md template gains a "Subscription Pool" awareness blurb for new
18
+ agents; and an idempotent `migrateClaudeMd` migration appends the same blurb to
19
+ existing agents on update. No route, class, or runtime behavior is added — this
20
+ surfaces an already-shipped, already-tested capability honestly (the original
21
+ internal note set exactly this maturity bar).
22
+
23
+ ## What to Tell Your User
24
+
25
+ I can now manage several of your subscriptions for a provider as one pool — show
26
+ you how much of each is left and when it resets, keep a long session alive by
27
+ moving it to another account when one hits its limit, and help you enroll a new
28
+ account from your phone. Ask me "how much quota is left across my accounts?" or
29
+ tell me you want to add another subscription.
30
+
31
+ ## Summary of New Capabilities
32
+
33
+ - **`/subscription-pool` is now a surfaced capability** — visible in
34
+ `/capabilities` and documented in the agent's CLAUDE.md (new + existing agents).
35
+ - **No behavior change** — the routes, scheduler, continuity guarantee, and
36
+ enrollment wizard already shipped (P1.1–P2.2); this makes the finished capability
37
+ discoverable and known.
@@ -0,0 +1,44 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The idle-session reaper checked "is this session working?" with a pattern that matched
9
+ tool-call names (Read(/Write(/Bash() and the bare word "claude" anywhere in the captured
10
+ terminal — but those persist in an IDLE session's scrollback, so every idle Claude
11
+ session read as "working" and the reaper never reaped one (0 reaps in 9.5h live; the
12
+ residual blocker after #961). Fix: `isPositivelyIdle` now uses a new live-only signal
13
+ (`liveActivity`: spinner glyphs + "Working (Ns" + "generating"), which vanish when a turn
14
+ ends), not the scrollback-persistent `toolCallOrSpinner`. Also removed the bare word
15
+ "claude" from the Claude activity pattern (the documented codex "do not match bare
16
+ 'codex'" lesson).
17
+
18
+ ## What to Tell Your User
19
+
20
+ Nothing required — internal reaper classifier; reaper stays opt-in. For operators who
21
+ enable it: the reaper can now correctly recognize a genuinely-idle session and reclaim it
22
+ (it previously could not). The change also makes the silence/presence sentinels stop
23
+ mis-reading idle Claude sessions as "working".
24
+
25
+ ## Summary of New Capabilities
26
+
27
+ - `FrameworkActivitySignal.liveActivity` — live-generation-only markers per framework, so
28
+ callers that need "working RIGHT NOW" don't trip over scrollback history.
29
+ - `SessionReaper.isPositivelyIdle` uses `liveActivity`; bare "claude" removed from the
30
+ Claude `toolCallOrSpinner`.
31
+
32
+ ## Evidence
33
+
34
+ `tests/unit/session-reaper.test.ts`: idle Claude pane (scrollback tool-names + "claude",
35
+ no live marker) → positively idle; working pane (spinner / spinner+esc) → not idle; bare
36
+ "claude" alone no longer forces not-idle. 57/57 green. `tsc --noEmit` clean. causalAutopsy:
37
+ latent (exposed by the #955/#958/#961 chain reaching the positive-idle check).
38
+
39
+ ## Scope (honest)
40
+
41
+ Final functional fix in the chain (#952 → #955 → #958 → #961 → this): the reaper can now
42
+ tell idle from working and actually reclaim. Validated by dry-run before any live flip.
43
+ Does NOT address the transcript pile-up (151k files / 5.9 GB) — separate retention
44
+ follow-up.
@@ -0,0 +1,46 @@
1
+ # Side-Effects Review - Reaper live-activity idle detection
2
+
3
+ **Version / slug:** `reaper-live-activity-idle`
4
+ **Date:** `2026-06-07`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required (Tier 1)`
7
+
8
+ ## Summary of the change
9
+
10
+ `SessionReaper.isPositivelyIdle` used `sig.toolCallOrSpinner` to decide "is this session active?", but that regex matches tool-call names (Read(/Write(/Bash(/…) and the bare word "claude" — both of which PERSIST in an idle session's 200-line scrollback. So every idle Claude session read as active → never positively idle → reaper kept everything (0 reaps in 9.5h live; the residual blocker after #961 made transcripts resolve). Adds a `liveActivity` regex to `FrameworkActivitySignal` (live-generation-only: spinner glyphs + "Working (Ns" + "generating") and uses it in `isPositivelyIdle`; removes the bare `claude|` from the Claude `toolCallOrSpinner` (the documented codex "do not match bare 'codex'" lesson).
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `FrameworkActivitySignal.liveActivity` (new field) on all 4 signals (claude/codex/gemini/pi) — live-only markers.
15
+ - `SessionReaper.isPositivelyIdle`: active-check now `sig.liveActivity || sig.escapeToInterrupt || sig.runningIndicator` (was `sig.toolCallOrSpinner || …`).
16
+ - `CLAUDE_CODE_SIGNAL.toolCallOrSpinner`: bare `claude|` removed (tool-names + spinner retained).
17
+
18
+ ## 1. Behavior change / gating
19
+
20
+ This makes `isPositivelyIdle` correctly recognize idle sessions; it does not change any other gate. The only behavioral effect: the reaper can now reach reap-eligibility for genuinely-idle sessions (it previously could not). Removing bare "claude" from toolCallOrSpinner also makes OTHER consumers (silence/presence sentinels) stop mis-reading idle Claude sessions as working — strictly more correct, matching the existing codex behavior. No API/route/state change.
21
+
22
+ ## 2. Over/under-signal
23
+
24
+ Direction of risk: a session genuinely working but whose captured frame momentarily shows no live marker (between tool calls) could read idle. Mitigations: (a) transcript-growth fires first and KEEPS anything whose JSONL is growing — a working session is growing its transcript; (b) confirmObservations (3 ticks / render-stasis) requires sustained idle; (c) 8h-silence + the full guard chain still apply. So a momentary live-marker miss cannot produce a wrong reap. UNDER-signal (the prior "never recognizes idle") is the bug being fixed. `liveActivity` deliberately keeps every genuine live marker per framework (spinner always; +Working-status for codex; +generating/thinking for gemini/pi).
25
+
26
+ ## 3. Blast radius
27
+
28
+ Pure regex/classifier logic in the shared signal module + one call-site in the reaper. No I/O, no new deps, no persistent state, no migration. Other consumers of `toolCallOrSpinner` are unaffected except they no longer match the bare word "claude" (a correctness improvement, not a regression — no test depended on it; 57 unit tests incl. the framework-signal suite pass).
29
+
30
+ ## 4. Failure modes
31
+
32
+ `liveActivity` is a required field with a concrete regex on every signal (tsc-enforced — a missing one is a compile error). A malformed frame is just text the regexes test against; no throw path. The reaper's downstream stateful checks (transcript-growth, positive-idle prompt match, confirmObservations) all still run after this gate.
33
+
34
+ ## 5. Migration parity
35
+
36
+ No agent-installed file changes; internal classifier logic shipped in code, effective on next server start. No config surface. The reaper remains opt-in + dry-run-first, so nothing changes for an operator until they enable it.
37
+
38
+ ## 6. Scope honesty (what this is NOT)
39
+
40
+ - Final functional fix in the chain (#952 → #955 → #958 → #961 → this). With it the reaper can distinguish idle from working and actually reclaim; validated by dry-run before any live flip.
41
+ - Does NOT touch the gemini/codex/pi live-marker sets beyond adding the live-only subset (codex/gemini/pi toolCallOrSpinner were already largely live-only or carefully tuned; only Claude carried the bare-word + tool-name-in-idle bug at the reaper call-site).
42
+ - Does NOT address the transcript pile-up (151k files / 5.9 GB) — separate retention follow-up.
43
+
44
+ ## 7. Causal autopsy
45
+
46
+ Origin: **latent**. The reaper's idle-check has used the scrollback-matching `toolCallOrSpinner` (incl. bare "claude") since `isPositivelyIdle` was written — always wrong for the "is it active NOW" question, but never hit because the earlier KEEP-guards short-circuited before the positive-idle check ran. The 2026-06-07 #955/#958/#961 chain peeled those layers (and #961 made transcripts resolve), so the reaper finally REACHED the positive-idle check and the latent bug surfaced as 0 reaps / universal `no-positive-idle`. No prior PR regressed it; it is the deepest layer of the same conservatism stack. The bare-"claude" half is the exact analogue of the already-fixed codex bare-word bug.
@@ -0,0 +1,72 @@
1
+ # Side-Effects Review — Graduate the Subscription Pool capability (P-graduate)
2
+
3
+ ## Scope of change
4
+
5
+ - `src/server/CapabilityIndex.ts` — remove `subscription-pool` from `INTERNAL_PREFIXES`; add a `subscriptionPool` entry to `CAPABILITY_INDEX` (key, prefixes, description, build → configured/accounts/quotaPoller/scheduler/enrollmentWizard/endpoints).
6
+ - `src/scaffold/templates.ts` (`generateClaudeMd`) — add the Subscription Pool awareness blurb so NEW agents know the capability.
7
+ - `src/core/PostUpdateMigrator.ts` (`migrateClaudeMd`) — add a content-sniffed, idempotent migration so EXISTING agents get the same blurb on update (Migration Parity).
8
+ - `upgrades/next/subscription-auth-graduate.md` — release-note fragment.
9
+
10
+ ## What this does (and does NOT) change
11
+
12
+ This is a **classification + awareness** change. It surfaces an already-shipped,
13
+ already-tested capability (P1.1 registry, P1.2 poller, P1.3 scheduler + continuity
14
+ guarantee, P2.1 enrollment, P2.2 dashboard) to `/capabilities` and to the agent's
15
+ CLAUDE.md. It adds **no route, no class, no runtime behavior** — the routes, the
16
+ scheduler, the wizard already exist and are wired. The `build()` function is pure
17
+ (reads `ctx.subscriptionPool` etc. already on the context) and runs only when
18
+ `/capabilities` is probed.
19
+
20
+ ## Why now (maturity honesty)
21
+
22
+ The original `INTERNAL_PREFIXES` entry explicitly said `subscription-pool`
23
+ "graduates to a surfaced CAPABILITY_INDEX entry (+ CLAUDE.md awareness blurb) once
24
+ the quota-aware scheduler (P1.3) and mobile enrollment wizard (P2.1) make it
25
+ user-usable. Surfacing the bare registry now would overclaim an unfinished
26
+ capability." P1.3 + P2.1 (+ P2.2 dashboard) are now merged — the maturity bar the
27
+ note set is met, so surfacing it is honest, not overclaiming.
28
+
29
+ ## Authority / autonomy analysis
30
+
31
+ - **No new authority.** The auto-swap of live sessions remains OFF by default
32
+ (`subscriptionPool.autoSwapOnRateLimit`); the blurb documents it as opt-in. The
33
+ enrollment blurb tells the agent to drive the wizard and NEVER ask the user to
34
+ paste a token — reinforcing the existing credential-safety posture.
35
+ - **The migration edits agent-installed CLAUDE.md** — but only appends a doc
36
+ section, content-sniffed on a distinctive marker (idempotent: re-running is a
37
+ no-op), and never touches a custom/user-authored section. This is the standard
38
+ `migrateClaudeMd` append pattern used by ~30 prior sections.
39
+
40
+ ## Failure modes considered
41
+
42
+ - Capability probed on an agent with no pool wired → `build()` returns
43
+ `{ configured:false, accounts:0, ... }` (no throw; the routes already answer
44
+ `200 { enabled:false }`).
45
+ - Migration run twice → content-sniff marker present → skipped (idempotent).
46
+ - Migration on an agent whose CLAUDE.md predates the section → appended once.
47
+ - The capabilities-discoverability lint: `/subscription-pool` is now claimed by
48
+ exactly one CAPABILITY_INDEX entry and absent from INTERNAL_PREFIXES (verified:
49
+ CapabilityIndex.test.ts green).
50
+
51
+ ## Blast radius
52
+
53
+ Minimal. Classification metadata + a documentation blurb + an idempotent doc
54
+ migration. No behavior path changes; `/capabilities` gains one block; new + existing
55
+ agents gain one CLAUDE.md section.
56
+
57
+ ## Framework generality
58
+
59
+ Framework-agnostic in the surface, Claude-first in the wired mechanism (matching the
60
+ standard's scope, Justin's decision 3): the CAPABILITY_INDEX entry + blurb describe
61
+ the pool generically (provider/framework-parameterized), and the enrollment + quota
62
+ machinery already carry `provider`/`framework` fields. The continuity-guarantee swap
63
+ is implemented for `claude-code` today (per-account `CLAUDE_CONFIG_DIR`); other
64
+ frameworks slot into the same registry + selector when their per-account config is
65
+ wired. Surfacing the capability does not bake in any Claude-only assumption beyond
66
+ what the underlying (already-merged) phases already chose.
67
+
68
+ ## Migration / parity
69
+
70
+ This change IS the parity work: `generateClaudeMd` (new agents) + `migrateClaudeMd`
71
+ (existing agents) both get the blurb, satisfying the Agent Awareness + Migration
72
+ Parity standards. No config/hook/skill change. Ships via dist.