instar 1.3.586 → 1.3.588
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 +229 -39
- package/dist/commands/server.js.map +1 -1
- package/dist/core/IntelligenceRouter.d.ts +10 -0
- package/dist/core/IntelligenceRouter.d.ts.map +1 -1
- package/dist/core/IntelligenceRouter.js +37 -3
- package/dist/core/IntelligenceRouter.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +17 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/WorkEvidence.d.ts +17 -0
- package/dist/core/WorkEvidence.d.ts.map +1 -1
- package/dist/core/WorkEvidence.js +17 -0
- package/dist/core/WorkEvidence.js.map +1 -1
- package/dist/core/gapBCommitmentEvidence.d.ts +143 -0
- package/dist/core/gapBCommitmentEvidence.d.ts.map +1 -0
- package/dist/core/gapBCommitmentEvidence.js +138 -0
- package/dist/core/gapBCommitmentEvidence.js.map +1 -0
- package/dist/core/internalFrameworkDefault.d.ts +50 -0
- package/dist/core/internalFrameworkDefault.d.ts.map +1 -0
- package/dist/core/internalFrameworkDefault.js +72 -0
- package/dist/core/internalFrameworkDefault.js.map +1 -0
- package/dist/core/types.d.ts +39 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/CommitmentTracker.d.ts +12 -0
- package/dist/monitoring/CommitmentTracker.d.ts.map +1 -1
- package/dist/monitoring/CommitmentTracker.js +14 -0
- package/dist/monitoring/CommitmentTracker.js.map +1 -1
- package/dist/monitoring/ReapLog.d.ts +11 -0
- package/dist/monitoring/ReapLog.d.ts.map +1 -1
- package/dist/monitoring/ReapLog.js +1 -0
- package/dist/monitoring/ReapLog.js.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.d.ts +15 -0
- package/dist/monitoring/ResumeQueueDrainer.d.ts.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.js +12 -1
- package/dist/monitoring/ResumeQueueDrainer.js.map +1 -1
- package/dist/scaffold/templates.js +4 -4
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +19 -19
- package/src/scaffold/templates.ts +4 -4
- package/upgrades/1.3.587.md +28 -0
- package/upgrades/1.3.588.md +27 -0
- package/upgrades/side-effects/autonomous-registration-guarantee.md +51 -0
- package/upgrades/side-effects/provider-fallback-default-policy.md +52 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-16T04:42:24.637Z",
|
|
5
|
+
"instarVersion": "1.3.588",
|
|
6
6
|
"entryCount": 201,
|
|
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": "
|
|
14
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
23
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
32
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
41
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
50
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
59
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
68
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
77
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
86
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
95
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
104
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
113
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
122
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
131
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
140
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
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": "
|
|
149
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
150
150
|
"since": "2025-01-01"
|
|
151
151
|
},
|
|
152
152
|
"job:health-check": {
|
|
@@ -1554,7 +1554,7 @@
|
|
|
1554
1554
|
"type": "subsystem",
|
|
1555
1555
|
"domain": "updates",
|
|
1556
1556
|
"sourcePath": "src/core/PostUpdateMigrator.ts",
|
|
1557
|
-
"contentHash": "
|
|
1557
|
+
"contentHash": "0a4291b73a00cf3d53aa583991e023077ccb206a395a917b7541eb11715062de",
|
|
1558
1558
|
"since": "2025-01-01"
|
|
1559
1559
|
},
|
|
1560
1560
|
"subsystem:scheduler": {
|
|
@@ -833,10 +833,10 @@ ${PLAYWRIGHT_PROFILE_REGISTRY_CLAUDEMD_SECTION(port)}
|
|
|
833
833
|
- Check: \`curl -H "Authorization: Bearer $AUTH" "http://localhost:${port}/parallel-work/activities"\` → \`{ count, runningCount, activities: [{ topicId, focus, tags, running, updatedAt }] }\`.
|
|
834
834
|
- **When to use** (PROACTIVE): before starting substantial new work in a topic, glance here to see if another topic is already on it; when the user asks "what am I working on across topics?" / "is another session already doing this?". (The proactive overlap councilor — ParallelWorkSentinel — is Phase B, ships dark.) Read-only; never gates. Spec: \`docs/specs/parallel-activity-coherence.md\`.
|
|
835
835
|
|
|
836
|
-
**Per-Component Framework Routing** —
|
|
837
|
-
- See current routing: \`curl -H "Authorization: Bearer $AUTH" "http://localhost:${port}/intelligence/routing"\` → \`{ defaultFramework, components: [{ component, category, framework, available }], coverage }\`.
|
|
838
|
-
-
|
|
839
|
-
- Each framework gets its own circuit breaker (a Claude trip can't pause Codex). If a routed framework's CLI is missing it degrades to the default and reports it
|
|
836
|
+
**Per-Component Framework Routing** — Your internal background components (sentinels, gates, reflectors) **run off Claude by default**: with no \`componentFrameworks\` set, they auto-route to the first ACTIVE off-Claude CLI in the preference chain \`codex-cli → pi-cli → gemini-cli → claude-code\` (Claude last), so that background chatter stops spending your Claude quota and a single provider's bad night can't strangle the agent. The remaining active frameworks become an ordered failure-swap tail: when a *gating* call's primary provider fails at runtime, it swaps DOWN the active chain (each circuit-checked) before falling closed — never silently degrading to a brittle heuristic. Model "size" is preserved automatically (a \`fast\` check becomes Haiku on Claude or a small GPT model on Codex). On a Claude-only agent (no off-Claude CLI installed) the default is a no-op — everything stays on Claude, byte-identical to before.
|
|
837
|
+
- See current routing: \`curl -H "Authorization: Bearer $AUTH" "http://localhost:${port}/intelligence/routing"\` → \`{ defaultFramework, components: [{ component, category, framework, available }], coverage }\`. On a Codex-active agent, sentinel/gate/reflector resolve to \`codex-cli\`; \`job\` (cost-bearing background work, e.g. CartographerSweep) stays on the agent default.
|
|
838
|
+
- Override in \`.instar/config.json\` → \`sessions.componentFrameworks\`, e.g. \`{ "categories": { "sentinel": "codex-cli", "gate": "codex-cli" }, "overrides": { "CoherenceReviewer": "claude-code" }, "fallback": "default" }\` — an explicitly-set block is used verbatim (the default no longer applies). Categories: \`sentinel | gate | job | reflector | other\`. Resolution: \`overrides[name] → categories[category] → default\`. Frameworks: \`claude-code | codex-cli | gemini-cli | pi-cli\`. **Rollback lever:** set \`componentFrameworks\` to \`{}\` (explicit empty) to force everything back to the default framework with no swap — today's pre-default behavior.
|
|
839
|
+
- Each framework gets its own circuit breaker (a Claude trip can't pause Codex). If a routed framework's CLI is missing it degrades to the default and reports it. A gating call's failure-swap is bounded by a per-attempt timeout (default 5s, \`intelligence.swapAttemptTimeoutMs\`) so a slow provider is abandoned at the cap, not waited on in full. Routes INTERNAL component calls only — spawned interactive sessions stay governed by \`topicFrameworks\`.
|
|
840
840
|
- **When to use** (PROACTIVE): when the user is hitting rate limits and asks how to spread load, or says "run my sentinels on Codex" / "move the background checks off Claude" → point them at \`sessions.componentFrameworks\` and \`GET /intelligence/routing\`. Restart sessions to apply (config is read into the router at the call path, but a file edit needs the server to pick it up). (Spec: \`docs/specs/per-component-framework-routing.md\`.)
|
|
841
841
|
|
|
842
842
|
**Topic Profile (per-topic model, thinking, framework pins)** — Every conversation topic can carry a durable profile pinning its BASELINE model (an explicit id OR a tier — never both), thinking depth (\`off\`/\`low\`/\`medium\`/\`high\`/\`max\`), and framework (\`claude-code\`/\`codex-cli\`/…). Pins survive restarts and follow the topic. **The conversational surface is PRIMARY** (PROACTIVE — these are the triggers): when the user says "use codex here", "pin this topic to Fable", or "set high thinking on this topic", that IS the request — propose the change back in plain words, confirm, and the pin is durable from then on. NEVER instruct the user to type \`/topic\`; the \`/topic\` command exists only as a power-user convenience.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Instar's internal background LLM work — sentinels, gates, and reflectors — now runs **off Claude by default** when the agent has another provider CLI installed. The agent picks the first available provider in the preference order **Codex → PI → Gemini → Claude**, with the existing failure-swap engine falling down the chain and Claude as the true last resort. A new bounded per-attempt timeout (`intelligence.swapAttemptTimeoutMs`, default 5s) keeps the longer chain from stacking slow providers into the very stall it exists to prevent — a timed-out provider is abandoned and the next one is tried.
|
|
9
|
+
|
|
10
|
+
The `job` category is deliberately **excluded** (cost-bearing background jobs stay on the agent default). Operators retain full control: an explicit `sessions.componentFrameworks` block wins verbatim, and setting it to `{}` reverts to exactly the prior behavior.
|
|
11
|
+
|
|
12
|
+
## What to Tell Your User
|
|
13
|
+
|
|
14
|
+
If your agent has Codex (or another non-Claude CLI) installed, its background safety checks now run off Claude by default — so a Claude outage or a maxed weekly quota can no longer freeze your sentinels, gates, or message delivery. You can see which provider is serving each check in the routing and metrics views, and you can override the routing or revert it entirely any time with one config setting. If you only run Claude, nothing changes — by design.
|
|
15
|
+
|
|
16
|
+
## Summary of New Capabilities
|
|
17
|
+
|
|
18
|
+
- Internal sentinels, gates, and reflectors auto-route off Claude via an active-filtered fallback chain (Codex then PI then Gemini then Claude), so no single provider's bad night can strangle the agent.
|
|
19
|
+
- A tunable per-attempt swap timeout keeps the fallback chain fast.
|
|
20
|
+
- A new degrade signal makes a slow provider being abandoned visible in the existing observability surfaces.
|
|
21
|
+
|
|
22
|
+
## Evidence
|
|
23
|
+
|
|
24
|
+
- `src/core/internalFrameworkDefault.ts` (new) — the active-filtered policy resolver + the `INTERNAL_FRAMEWORK_PREFERENCE` constant.
|
|
25
|
+
- `src/core/IntelligenceRouter.ts` — `Promise.race` per-attempt swap timeout + `swap-attempt-timeout` degrade signal; fail-open, fail-closed-if-all-down preserved.
|
|
26
|
+
- `src/commands/server.ts` — boot active-set probe + the live-read, layered `resolveConfig` (preserves CartographerSweep's runtime override).
|
|
27
|
+
- 31 new tests (3 unit files 24 green + integration + e2e/wiring-integrity); tsc clean; 14 lints clean; second-pass review concurred.
|
|
28
|
+
- Spec `docs/specs/provider-fallback-default-policy.md` — CONVERGED over 4 review rounds (6 internal lenses + codex/gemini external + conformance gate each).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
When an autonomous run is age-limit-reaped, the reaper can now recognize it as live work and revive it even if it was never formally registered — as long as it has a fresh, agent-owned open commitment for the topic AND a recent user message. This closes the exact gap that killed a run overnight: the operator said "go autonomous", the formal state file was never written, and the session died as a plain idle timeout, invisible to all the revival machinery.
|
|
9
|
+
|
|
10
|
+
The revival path ships dark (off by default; explicit enable, then a dry-run soak before going live). The one live change is that the reaper's "recently messaged" keep-guard — previously inert — now actually works, so a session you messaged recently is kept rather than reaped (always the safe direction). A new, deliberately shared check guarantees the keep-guard and the revive-check always agree, which structurally prevents the kill-then-revive loop that a naive version would re-create.
|
|
11
|
+
|
|
12
|
+
## What to Tell Your User
|
|
13
|
+
|
|
14
|
+
If a long autonomous run gets recycled by the reaper while it still has live work, it now revives itself instead of silently dying — even if it was never formally registered. This ships off by default and goes through a careful dry-run soak before it ever revives anything, so nothing changes for you until you turn it on. The only immediately-live improvement is that a session you messaged recently is now reliably kept rather than reaped.
|
|
15
|
+
|
|
16
|
+
## Summary of New Capabilities
|
|
17
|
+
|
|
18
|
+
- The reaper revives an unregistered-but-actively-working autonomous run (corroborated by a fresh commitment plus recent activity), instead of letting it die as a plain idle timeout.
|
|
19
|
+
- A previously-inert "recently messaged" keep-guard now actually protects active sessions from the reaper.
|
|
20
|
+
- A shared agreement-check structurally prevents the kill-then-revive loop, so the resilience fix cannot misfire.
|
|
21
|
+
|
|
22
|
+
## Evidence
|
|
23
|
+
|
|
24
|
+
- New src/core/gapBCommitmentEvidence.ts — the shared qualifying/freshness/agreement/dark-gate predicates (one source of truth for enqueue + drain so they cannot disagree).
|
|
25
|
+
- src/commands/server.ts — the dark-gated commitment-evidence injection in the reaped path + the inert keep-predicate promoted to real (shared across ReapGuard and SessionReaper).
|
|
26
|
+
- 54 new tests across unit/integration/e2e incl. the anti-loop regression and the dark-no-op; tsc clean; 14 lints clean; independent reaper-class second-pass concurred.
|
|
27
|
+
- Spec docs/specs/autonomous-registration-guarantee.md — converged (2 rounds + a focused Part-D re-converge that caught a build-blocking store error before any code).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Side-Effects Review — Autonomous-Registration Guarantee (GAP-B)
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/autonomous-registration-guarantee.md` (CONVERGED 2 rounds + Part-D focused re-converge, approved:true) · P1 GAP-B
|
|
4
|
+
**Change:** revive an UNregistered-but-actively-working autonomous run after an age-limit reap, by injecting commitment-evidence into the reaper's existing eligibility path (Part B, DARK) + promoting the inert `recentUserMessage` KEEP-predicate to real (Part D, the one live part) + an observe-only attention surface (Part A). Part C was already shipped (#1186).
|
|
5
|
+
|
|
6
|
+
## Phase 1 — Principle check (signal vs authority)
|
|
7
|
+
Touches the reaper eligibility decision, so the principle applies. **No new brittle blocking authority:** Part B injects a *signal* (a fresh qualifying open-commitment) into the **existing** `evidenceEligible`/ResumeQueue authority — it never decides revival itself. Part A is observe-only (an aggregated attention signal). Part D promotes a KEEP-*predicate* (a signal ReapGuard already consults). The load-bearing safety is the **D8 agreement-invariant**: KEEP and the new eligibility compute from ONE shared `recentUserMessage` predicate, so they cannot disagree (the 2026-06-13 13-session loop came from disagreement). Compliant (`docs/signal-vs-authority.md`).
|
|
8
|
+
|
|
9
|
+
## Phase 2 — Plan
|
|
10
|
+
Built in `.worktrees/gapb-autonomous-registration` (rebased onto current `JKHeadley/main c41b27db5`). Decision points: the `sessionReaped` evidence wiring (gains an additive, dark, corroborated source) + ReapGuard/SessionReaper KEEP behavior (Part D un-stub). Rollback: dark by default (Part B needs explicit `enabled:true`) + revert the PR.
|
|
11
|
+
|
|
12
|
+
## 1. Over-block
|
|
13
|
+
The change does not block — it injects evidence to REVIVE. The inverse risk is **over-revive** (resurrecting a finished/idle run). Bounded by: the D2 qualifying filter (pending + agent-driven + this-machine + not-beacon-paused), D1 createdAt-freshness (6h), the **D8 agreement** (also needs a recent user message — a stale-per-KEEP commitment can't revive), drain-time D9 re-validation (`commitmentStillActiveForTopic` — a since-closed commitment is invalidated), the existing **resurrection cap** (≤2 resumes/24h/topic, then gives up loudly), and — decisively — Part B ships **DARK** (no injection ⇒ no revival) until `enabled:true`.
|
|
14
|
+
|
|
15
|
+
## 2. Under-block
|
|
16
|
+
A genuinely-working unregistered run with **no open commitment AND no recent user message** is not revived — the structural registration guarantee (make "go autonomous" always write the state file) is the root fix, an explicitly tracked follow-up (Follow-ups section), not this PR. Part B is a *corroborated* backstop (commitment + recent activity), not a universal catch.
|
|
17
|
+
|
|
18
|
+
## 3. Level-of-abstraction fit
|
|
19
|
+
Correct layer. The evidence feeds the **existing** `evidenceEligible`/ResumeQueue authority (no parallel revival path; no new WorkEvidence enum value — reuses `build-or-autonomous-active` + a distinct reason tag). Part D shares ONE predicate across all consumers (`gapBCommitmentEvidence.recentUserMessageFromHistory`), so KEEP and eligibility are computed identically — the right place to enforce agreement.
|
|
20
|
+
|
|
21
|
+
## 4. Signal vs authority compliance
|
|
22
|
+
See Phase 1. No brittle check gains blocking authority. Part B = signal → existing gate; Part A = observe-only; Part D = a shared predicate. Fail-open throughout (D7). Compliant.
|
|
23
|
+
|
|
24
|
+
## 5. Interactions
|
|
25
|
+
- **The D8 loop-prevention** is the central interaction: ReapGuard's KEEP (`ReapGuard.ts:149`, 8h) and the new eligibility both call the same `recentUserMessage` — verified by the anti-loop regression test (a stale-per-KEEP commitment is NOT eligible).
|
|
26
|
+
- **Part D un-stubs FIVE live sites consistently** via one shared closure spread into the single `reapGuardDeps` object backing BOTH `ReapGuard` (:137 standalone 30min, :149 commitment 8h, :221/:239 terminate-path) AND `SessionReaper` (:489 `staleIdle` inversion). All safe-direction (keep-more) — none makes the reaper kill more.
|
|
27
|
+
- **Drain-time D9** re-checks liveness for `COMMITMENT_ACTIVE_RUN_REASON` (state-file source is untouched, routed by the distinct reason).
|
|
28
|
+
- No double-fire: the commitment branch is mutually exclusive with the state-file branch (PRESENT vs ABSENT).
|
|
29
|
+
|
|
30
|
+
## 6. External surfaces
|
|
31
|
+
- Part A: ONE aggregated attention signal (`unregistered-autonomous:<topic>`, dedup'd via the existing chokepoint) — only when Part B fires LIVE. No new HTTP route.
|
|
32
|
+
- Reap-log gains an optional `evidenceSource` field (additive, back-compat, no PII — D3).
|
|
33
|
+
- Observable via the existing reap-log; dark by default so no user-visible behavior change until enabled.
|
|
34
|
+
|
|
35
|
+
## 7. Multi-machine posture
|
|
36
|
+
**Machine-local by design.** Part B's qualifying filter requires `originMachineId` is this machine or absent — a *replicated peer* commitment is advisory data, never revival authority (it can't revive a session on the wrong machine). `recentUserMessage` reads THIS machine's `TelegramAdapter.getTopicHistory`. No replicated/persisted state introduced; the per-topic state file source is unchanged. No generated URL, no cross-machine notice.
|
|
37
|
+
|
|
38
|
+
## 8. Rollback cost
|
|
39
|
+
Cheap and dark. (a) **Dark by default:** Part B is inert unless `monitoring.resumeQueue.commitmentEvidence.enabled === true`; even when armed it defaults to `dryRun:true` (no spawn). (b) **The one live change** (Part D `recentUserMessage` promotion) is safe-direction (keep-more) and bounded by the window — to revert just that, restore the stub. (c) **Full back-out:** revert the PR — additive code, no data migration, no state repair. The config keys are code-defaulted (absent from ConfigDefaults) so removal is clean.
|
|
40
|
+
|
|
41
|
+
## No-deferrals (Phase 4.5)
|
|
42
|
+
The structural registration guarantee (root fix) and the stopGate per-topic plumbing are explicitly **tracked** follow-ups (Follow-ups section, `<!-- tracked -->` markers) — Part B is a deliberate *corroborated backstop*, the honest scope, not a partial fix papering over the root.
|
|
43
|
+
|
|
44
|
+
## Phase 5 — Second-pass review
|
|
45
|
+
*(appended below)*
|
|
46
|
+
**Concur with the review.** An independent reaper-class reviewer verified the load-bearing safety against the real code (file:line), not the artifact:
|
|
47
|
+
- D8 agreement: the SAME closure `recentUserMessageShared` backs both GAP-B eligibility (`gapBCommitmentEvidence.ts:150`) and ReapGuard's commitment KEEP (`ReapGuard.ts:149`/:239), with a single-sourced window (`staleCommitmentWindowMinutes ?? 480`); GAP-B's `commitmentQualifies` is a strict superset-filter of ReapGuard's, so a GAP-B revive structurally implies KEEP would have fired — the 2026-06-13 loop is impossible.
|
|
48
|
+
- Dark containment airtight: armed only on `enabled===true`, dryRun-default-true; dryRun fires the Part A surface only, tags/injects nothing; disarmed skips the cost-bearing eligibility entirely.
|
|
49
|
+
- All 5 `recentUserMessage` sites (ReapGuard 137/149/221/239 + SessionReaper 489) consume the real shared predicate (stub at server.ts removed); tsc-clean proves no mismatch; all safe-direction (keep-more).
|
|
50
|
+
- Fail-open (D7) + D9 drain-revalidation verified both sides (`safeBool(..., true)` → throw resolves still-active, never a wrong drop); multi-machine strand closed by the `originMachineId` local-origin filter.
|
|
51
|
+
- No new failure mode, double-fire (commitment vs state-file branches mutually exclusive), or cross-machine revival risk.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Side-Effects Review — Provider-Fallback Default Policy
|
|
2
|
+
|
|
3
|
+
**Spec:** `docs/specs/provider-fallback-default-policy.md` (CONVERGED 4 rounds, approved:true) · CMT-1554/1555
|
|
4
|
+
**Change:** ship the existing `IntelligenceRouter` failure-swap engine turned ON by default for internal gating/sentinel/reflector components (active-filtered chain Codex→PI→Gemini→Claude), plus a bounded per-attempt swap timeout so the longer default chain cannot re-create the stall it prevents.
|
|
5
|
+
|
|
6
|
+
## Phase 1 — Principle check (signal vs authority)
|
|
7
|
+
This touches a decision point (which provider serves a gating/safety call), so the principle applies. The change adds **no new brittle blocking authority**: the router's `failureSwap` fail-closed authority already exists and is preserved (it re-throws when primary + every swap target is down → the gating caller fails closed, never a silent brittle heuristic). The new pieces are (a) a default *preference order* (a policy, not an authority) and (b) a **fail-open** per-attempt timeout (timed-out attempt → next target → Claude tail → fail-closed only if all exhausted). The timeout is signal-shaped (abandon-slow → try-next) and cannot turn a fail-closed outcome into a silent pass. Observability rides the existing `DegradationReporter` (`onDegrade` reason `swap-attempt-timeout:<target>`). **Compliant.**
|
|
8
|
+
|
|
9
|
+
## Phase 2 — Plan
|
|
10
|
+
Built in the existing worktree `.worktrees/provider-fallback-chain` (verified current with `JKHeadley/main @ce13f42a3`; `git remote -v` → JKHeadley; tags `review-convergence`+`approved:true` present). Decision points touched: the router's `failureSwap` (existing authority — preserved). Existing detectors interacted with: `DegradationReporter` (observability), the per-framework circuit breakers (unchanged). Rollback: the no-op guarantee + the `{}` config lever (below).
|
|
11
|
+
|
|
12
|
+
## 1. Over-block
|
|
13
|
+
The change does not *block* inputs; it routes them to a different provider. The closest "over-block" risk is the §4.5 per-attempt timeout abandoning a provider that *would* have answered correctly just slowly. Mitigated: (a) the cap (5s) only fires on a swap attempt that is already past the failed primary, i.e. in a degraded path; (b) a timed-out attempt is *fail-open* — it advances to the next target and ultimately the Claude tail, never to a denial; (c) the cap is operator-tunable (`intelligence.swapAttemptTimeoutMs`). No legitimate gating decision is rejected — at worst it is served by the next provider or Claude.
|
|
14
|
+
|
|
15
|
+
## 2. Under-block
|
|
16
|
+
A provider that is reachable but returns **well-formed-but-semantically-wrong** output is not caught (the circuit breaker only trips on errors). This is a **pre-existing property of any LLM gate**, provider-independent, NOT introduced here — and round-3 grounding confirmed the gating *callers* already validate their own output (`MessagingToneGate.parseResponse` fail-opens on malformed JSON + validates the `VALID_RULES` allowlist; `MessageSentinel` try/catch fail-opens). This change only changes *which* provider can serve a weak answer, not the property. Documented (§6.4) as a tracked, non-owed hardening item, not part of this feature's scope.
|
|
17
|
+
|
|
18
|
+
## 3. Level-of-abstraction fit
|
|
19
|
+
Correct layer. The fallback mechanism, breakers, and gating-scope already live in `IntelligenceRouter` (the right place — one router every internal LLM call already flows through). This change is a thin **policy** computed at the router-construction site (`server.ts`) + one minimal engine touch (the swap-loop timeout). It does not duplicate a smarter gate; it *feeds* the existing `DegradationReporter`/`/metrics/features` observability rather than building a parallel one. The `job` category was deliberately EXCLUDED so a cost-bearing background feature (CartographerSweep) is never auto-armed as a side effect of this policy.
|
|
20
|
+
|
|
21
|
+
## 4. Signal vs authority compliance
|
|
22
|
+
See Phase 1. No brittle check gains blocking authority. The timeout is fail-open; the only blocking authority (router re-throw → caller fail-closed) is the pre-existing one, unchanged. **Compliant** (`docs/signal-vs-authority.md`).
|
|
23
|
+
|
|
24
|
+
## 5. Interactions
|
|
25
|
+
- **CartographerSweep** mutates `config.sessions.componentFrameworks` in memory at runtime. The §4.6 resolver reads config **live every call** and layers the computed default UNDER any live override, so CartographerSweep's injection still wins for its slot. A frozen memoized default would have silently disabled the freshness sweep — this was caught in convergence (R3-1) and is the reason the resolver is live-read + layered, with only the active-SET memoized.
|
|
26
|
+
- **Operator-set detection** is snapshotted once at the construction site (which runs before CartographerSweep's auto-vivify — verified ~line 4755 vs ~11331), so a later in-memory auto-vivify cannot masquerade as an operator override and disable the default (M5; covered by a unit test).
|
|
27
|
+
- **Per-framework circuit breakers** (existing): a broadly rate-limited Codex trips its own breaker → gating calls skip it fast, damping any herd onto the Claude tail (§6.2). Non-gating calls are untouched (they propagate to their heuristic — no herd).
|
|
28
|
+
- No double-fire / shadowing: the swap loop is unchanged except for wrapping each attempt in `Promise.race`; the re-throw-if-all-down path is preserved.
|
|
29
|
+
|
|
30
|
+
## 6. External surfaces
|
|
31
|
+
- `GET /intelligence/routing` and `/metrics/features` now reflect sentinels/gates/reflectors resolving off Claude on agents that have an off-Claude CLI — visible, intended observability (the operator can SEE the routing). No new route added (config-driven).
|
|
32
|
+
- CLAUDE.md template gains the corrected default-behavior text (new agents via `generateClaudeMd`; existing agents via `migrateClaudeMd` appended on the new `run off Claude by default` marker).
|
|
33
|
+
- Depends on runtime condition: which provider CLIs are installed on the machine (active-set). Honestly machine-local (see §7). No timing/conversation-state coupling.
|
|
34
|
+
|
|
35
|
+
## 7. Multi-machine posture
|
|
36
|
+
**Machine-local BY DESIGN.** The active-set is computed from `buildProvider(fw)!==null` against THIS machine's installed CLIs, at THIS machine's router-construction site, and is **never persisted or replicated** (runtime-computed default, no config block written). So machine A's installed-providers can never pin onto machine B — each machine routes by what it has. `/intelligence/routing` reflects the local machine's resolution (the route reads the local `resolveConfig`). This is the correct posture (a different machine genuinely has different CLIs) and is stated explicitly, not a silent single-machine assumption. No user-facing notice surface (no one-voice concern); no durable state that strands on topic transfer; no generated URL.
|
|
37
|
+
|
|
38
|
+
## 8. Rollback cost
|
|
39
|
+
Cheap and layered. (a) **No-op guarantee:** an agent with no off-Claude provider resolves to byte-identical-today behavior (claude-only → empty swap). (b) **`{}` lever:** operator sets `sessions.componentFrameworks: {}` → every category resolves to the agent default, empty swap — exactly today's behavior (unit-tested, M7). (c) **Timeout knob:** `intelligence.swapAttemptTimeoutMs` tunes/effectively-disables the cap (set very large). (d) **Full back-out:** revert the PR — purely additive code, no data migration, no agent-state repair (the default is runtime-computed, nothing persisted to roll back).
|
|
40
|
+
|
|
41
|
+
## No-deferrals (Phase 4.5)
|
|
42
|
+
The one out-of-scope item (semantically-wrong swap-target output, §6.4) is a pre-existing provider-independent property, explicitly NOT this feature's in-scope work, and carries an in-spec `<!-- tracked: -->` marker for No-Deferrals hygiene. No partial fix is shipped.
|
|
43
|
+
|
|
44
|
+
## Phase 5 — Second-pass review
|
|
45
|
+
**Concur with the review.** An independent reviewer audited the change against the real code (not the artifact's claims) and confirmed each high-risk dimension at named file:line:
|
|
46
|
+
- Fail-open / Signal-vs-Authority: `IntelligenceRouter.ts:212-268` — the cap only races already-degraded swap attempts (`continue` → next → Claude tail); the pre-existing fail-closed re-throw (`throw err`, line 268) and non-gating passthrough (line 206) are unchanged. No new blocking authority.
|
|
47
|
+
- `Promise.race` orphan-safety: `IntelligenceRouter.ts:228-238` uses `await Promise.race([...])` (never a detached handle); the unit test registers a real `unhandledRejection` listener, rejects the abandoned attempt after the cap, and asserts zero unhandled + late-result-not-used.
|
|
48
|
+
- §4.6 live-read+layer + boot-snapshot ordering: `server.ts:4750-4751` snapshots operator-set at construction; `resolveConfig` (4766-4781) reads live + layers computed default UNDER live overrides; CartographerSweep's auto-vivify (`server.ts:11329-11331`) runs later and cannot masquerade as operator-set — its `overrides` survive the layering.
|
|
49
|
+
- M11 honest+sufficient: the reviewer verified the REAL `ExternalOperationGate.consultLLM` (`ExternalOperationGate.ts:509-540`) fails closed (`'show-plan'` on catch) exactly as the test's synthetic fail-closed caller models; `MessagingToneGate` fails OPEN by design (delivery-path), correctly asserted as such.
|
|
50
|
+
- No double-fire/shadow/multi-machine strand: `target===framework` guard prevents re-firing the primary; active-set is machine-local and never persisted; `job` excluded so CartographerSweep's slot is never contended.
|
|
51
|
+
|
|
52
|
+
Non-blocking observation (already documented in spec §4.6): a runtime `PATCH /config {sessions:{componentFrameworks}}` changes gating routing live (resolveConfig reads live) — operator-scoped + every path fail-opens, so safe.
|