instar 1.3.558 → 1.3.560

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.
@@ -0,0 +1,48 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Reap notices no longer dress a routine **session recycle** up as a death. A long
9
+ autonomous run periodically hits its per-session lifetime cap, at which point the
10
+ old terminal process is replaced by a fresh one that continues the run — but the
11
+ user used to see `🪦 Your session was shut down — it reached its maximum allowed
12
+ runtime`, even while the run's own clock still showed many hours remaining. Now,
13
+ when an `age-limit` reap targets a session whose topic has an ACTIVE autonomous
14
+ run, ReapNotifier emits honest continuation copy instead:
15
+ `🔄 Your session was recycled at its per-session lifetime cap — your autonomous
16
+ run has 11h 42m left, so no work was lost. I'll pick it back up on my next turn —
17
+ send a message if I stay quiet.`
18
+
19
+ It never goes silent: any read error, or a run that is genuinely over, falls back
20
+ to the normal loud "shut down" notice. Only the age-limit-recycle-of-an-active-run
21
+ case is reworded; a stuck-session death is still surfaced loudly even mid-run.
22
+
23
+ ## What to Tell Your User
24
+
25
+ If you run long autonomous sessions and were alarmed by repeated "your session was
26
+ shut down — reached its maximum allowed runtime" messages: those were usually a
27
+ routine recycle, not a failure — your work was never lost and the run kept going.
28
+ You'll now see an honest "🔄 recycled, your run has Xh left, no work lost" notice
29
+ for that case instead of the tombstone. A genuine stop still shows the loud notice.
30
+
31
+ ## Summary of New Capabilities
32
+
33
+ - Honest reap-notice wording for an age-limit recycle of a still-active autonomous
34
+ run (continuation, not death), with the run's real remaining time.
35
+ - New shared helper `autonomousRunRemainingForTopic` (one source of truth for "is
36
+ this topic's run in-flight, and how long is left?").
37
+
38
+ ## Evidence
39
+
40
+ - `tests/unit/reap-notifier.test.ts` — honest-recycle block: recycle copy,
41
+ never-contradicts-clock, no-run→death, only-age-limit, throws→fail-loud,
42
+ over-window→death.
43
+ - `tests/unit/AutonomousSessions.test.ts` — `autonomousRunRemainingForTopic`:
44
+ in-flight remaining, numeric/string topic, over-window null, no-run/paused/
45
+ missing-duration null.
46
+ - `tests/integration/honest-session-recycle-wiring.test.ts` — real helper composed
47
+ with real notifier (dep non-null, real computed remaining).
48
+ - `npx tsc --noEmit` clean; 51/51 unit + 2/2 integration green.
@@ -0,0 +1,57 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ PR #1155 fixed the misleading NOTICE for an age-limit recycle of an active
9
+ autonomous run ("recycled, no work lost, I'll pick it back up on my next turn").
10
+ This change closes the behavioral gap behind that notice: the run now actually
11
+ comes back on its own, instead of waiting for the next message.
12
+
13
+ A long autonomous run periodically hits its per-session lifetime cap and gets
14
+ recycled. That age-limit reap fires precisely when the session is IDLE between
15
+ turns — so the Mid-Work Resume Queue saw no in-flight work evidence and never
16
+ queued the run for revival. If the operator was away, the run sat dead until they
17
+ sent a message. Now, when an age-limit reap targets a topic that still has an
18
+ ACTIVE autonomous run, the live run itself counts as the work evidence: the reap
19
+ is admitted to the resume queue and revived through the SAME one-at-a-time,
20
+ quota-gated, resurrection-capped machinery as any other revival.
21
+
22
+ It stays safe by construction: a double-spawn is caught by the existing
23
+ live-session / resume-uuid drain-time checks (a message-revive that beat the queue
24
+ wins, ZERO second spawn); a run that keeps getting reaped-and-revived hits the
25
+ resurrection cap and gives up loudly with one aggregated notice; a run that has
26
+ since finished (or whose window elapsed) is invalidated at drain time, never
27
+ re-spawned. The resume queue runs LIVE on a development agent and stays
28
+ observe-only on the fleet.
29
+
30
+ ## What to Tell Your User
31
+
32
+ If you kick off a long autonomous run and walk away: when the session hits its
33
+ per-session lifetime cap and gets recycled, the run now revives itself
34
+ automatically instead of sitting idle until you next message it. You no longer
35
+ have to send a "you still there?" nudge to wake a recycled run back up. Nothing
36
+ double-runs, and a run that has genuinely finished is left alone.
37
+
38
+ ## Summary of New Capabilities
39
+
40
+ - An age-limit recycle of a topic with an active autonomous run is now admitted to
41
+ the Mid-Work Resume Queue and auto-revived (the live run is the evidence).
42
+ - A drain-time liveness re-check invalidates an entry whose run finished or whose
43
+ window elapsed between enqueue and drain (never a wasted respawn).
44
+ - The resume queue resolves live-on-dev (dryRun:false) / observe-only-on-fleet,
45
+ with an explicit operator override still winning.
46
+
47
+ ## Evidence
48
+
49
+ - `tests/unit/resume-idle-autonomous.test.ts` — admission both sides, guard
50
+ short-circuit (non-age-limit / null-topic / fail-open), dryRun-gate resolution
51
+ (dev/fleet/explicit), drain-time re-check both sides + back-compat + throwing-dep.
52
+ - `tests/integration/resume-idle-autonomous-wiring.test.ts` — real-helper
53
+ delegation, double-spawn lens (live-session + uuid-stale), lease lens,
54
+ revival-loop lens (resurrection cap fires exactly once).
55
+ - `tests/e2e/resume-idle-autonomous-lifecycle.test.ts` — feature alive on dev
56
+ (dryRun:false), enters-and-revives-once + no double spawn, fleet observe-only.
57
+ - `npx tsc --noEmit` clean; lint clean; docs-coverage check passed.
@@ -0,0 +1,140 @@
1
+ # Side-Effects Review — Honest Session Recycle
2
+
3
+ **Version / slug:** `honest-session-recycle`
4
+ **Date:** `2026-06-14`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Second-pass reviewer:** `independent reviewer subagent — CONCUR (2 non-blocking concerns, both resolved)`
7
+
8
+ ## Summary of the change
9
+
10
+ When a session is reaped at its per-session lifetime cap (`reason: 'age-limit'`)
11
+ but its Telegram topic still has an ACTIVE autonomous run, the recycle is a
12
+ continuation (the run respawns), not a death. ReapNotifier now stamps that fact
13
+ at ingest and emits honest "🔄 recycled at its lifetime cap — your autonomous run
14
+ has Xh left, resuming; no work was lost" copy instead of the false "🪦 reached its
15
+ maximum allowed runtime" gravestone. Files: `src/monitoring/ReapNotifier.ts`
16
+ (new optional dep `autonomousRunActiveFor`, ingest stamp, four render branches,
17
+ two pure helpers), `src/commands/server.ts` (wires the dep via a new
18
+ `autonomousRunRemainingForTopic`), `src/core/AutonomousSessions.ts` (the extracted
19
+ run-remaining helper). No SessionManager / kill-chokepoint change.
20
+
21
+ ## Decision-point inventory
22
+
23
+ - `ReapNotifier.onReaped` ingest — **modify** — stamps `autonomousRunActive` when
24
+ an `age-limit` reap has an active run; affects only NOTICE WORDING, never
25
+ whether a notice is sent.
26
+ - `ReapNotifier` render (single/aggregate/unbound/legacy) — **modify** — branch the
27
+ per-event copy on the stamp.
28
+ - The reap/respawn behavior itself — **pass-through** — unchanged.
29
+
30
+ ---
31
+
32
+ ## 1. Over-block
33
+
34
+ No block/allow surface — this change only chooses notice WORDING. It can never
35
+ suppress a notice: the only effect of the recycle branch is gentler copy; the
36
+ notice is always still sent. Over-block not applicable.
37
+
38
+ ## 2. Under-block
39
+
40
+ No block/allow surface. The nearest analogue is "could a real death be softened
41
+ into a recycle?" — covered: the stamp requires `reason === 'age-limit'` AND an
42
+ active run with remaining > 0. A stuck-session death (`idle-zombie`,
43
+ `watchdog-stuck`, etc.) is never softened even mid-run (unit-tested). A run past
44
+ its window returns null → death copy stands.
45
+
46
+ ## 3. Level-of-abstraction fit
47
+
48
+ Correct layer. The wording decision lives in ReapNotifier (the existing single
49
+ listener that already owns "is this a disappearance worth a notice?"). The
50
+ autonomous-state lookup lives at the server wiring layer (which has the state) and
51
+ is computed by one shared helper `autonomousRunRemainingForTopic` in
52
+ AutonomousSessions.ts — the same module that owns the run-window data — so the
53
+ recycle copy and any future caller agree on "is this run in-flight, how long
54
+ left?". SessionManager's kill chokepoint is deliberately NOT touched (lowest blast
55
+ radius).
56
+
57
+ ## 4. Signal vs authority compliance
58
+
59
+ **Reference:** docs/signal-vs-authority.md
60
+
61
+ - [x] No — this change has no block/allow surface. It is pure presentation: a
62
+ signal (the autonomous-run fact) consumed to choose honest wording. It holds no
63
+ authority over reaping, respawning, or whether a notice fires. Fails toward the
64
+ loud legacy copy on any error.
65
+
66
+ ## 5. Interactions
67
+
68
+ - **Shadowing:** none — the branch is inside the existing notice formatter; it
69
+ does not run before/after any other check.
70
+ - **Double-fire:** none — still exactly one `sessionReaped` → one notice per the
71
+ existing coalescing; this only changes the text.
72
+ - **Races:** the autonomous-run state is read at INGEST (synchronously when the
73
+ event arrives), not at the SUMMARY release (which can be ~30 min later), so the
74
+ "X left" figure reflects reap time and can't drift to a stale/negative value at
75
+ render. The reported `remainingSeconds` is a point-in-time snapshot, by design.
76
+ - **Feedback loops:** none — reads autonomous state, writes only notice text.
77
+
78
+ ## 6. External surfaces
79
+
80
+ - Telegram: the user-visible reap notice text changes for the active-run recycle
81
+ case (🔄 + "no work was lost") — this is the intended fix. All other reap
82
+ notices are byte-identical.
83
+ - No other-agent, GitHub, Cloudflare, or persistent-state surface. No new route.
84
+ - **Operator surface (Mobile-Complete):** no operator-facing actions added — this
85
+ is an outbound notice wording change only. Not applicable.
86
+
87
+ ## 6b. Operator-surface quality
88
+
89
+ No operator surface — not applicable. No `dashboard/*` or approval/grant/secret
90
+ form is touched.
91
+
92
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
93
+
94
+ **machine-local BY DESIGN.** Reap notices are emitted by the machine that owns
95
+ and reaps the session; the autonomous run state read (`config.stateDir`) is that
96
+ same machine's. A recycle on machine A is narrated by machine A in the topic — the
97
+ correct single voice for that event (the reaping machine is authoritative about
98
+ its own reap). No durable state is created (notice text only), so nothing strands
99
+ on topic transfer. No URLs generated. One-voice: the notice rides the existing
100
+ ReapNotifier coalescing + reap-notice drain, which already own single-emission.
101
+
102
+ ## 8. Rollback cost
103
+
104
+ Pure code change — revert the three files and ship a patch. No persistent state,
105
+ no migration, no agent-state repair. During any rollback window the only
106
+ regression is the notice reverting to the old (alarming-but-not-harmful) wording.
107
+ No user data affected. The feature is also inert by construction when no
108
+ autonomous run is active (the common case) — it only ever activates on an
109
+ age-limit recycle of a live run.
110
+
111
+ ## Conclusion
112
+
113
+ The review produced one design refinement during the build: the run-remaining
114
+ computation was extracted from an inline server.ts block into a single tested
115
+ helper (`autonomousRunRemainingForTopic`) so the run clock has one source of
116
+ truth. The change is wording-only with a fail-loud default and full both-sides
117
+ test coverage (recycle vs death; active / over-window / paused / no-run / missing
118
+ duration / throws). Clear to ship pending the high-risk second-pass concurrence.
119
+
120
+ ## Second-pass review (if required)
121
+
122
+ **Reviewer:** independent reviewer subagent (high-risk: reap-notifier / session-lifecycle)
123
+ **Independent read of the artifact: CONCUR** (verified: fail-open is airtight across three layers; cannot silence a notice; terminal-death mislabeling is prevented by the `age-limit` + `remaining>0` guard; run-state read at ingest not render; pure presentation, no blocking authority).
124
+
125
+ Two non-blocking concerns raised — **both resolved before commit:**
126
+
127
+ - **Test-tier coverage.** Resolved: added `tests/integration/honest-session-recycle-wiring.test.ts`, which composes the REAL `autonomousRunRemainingForTopic` helper with the REAL ReapNotifier over a temp stateDir (the dep is non-null and delegates to the real run-window read) — the wiring-integrity tier. Tier-3 HTTP E2E is N/A by design: this change adds NO API route or feature-alive surface (it is outbound notice WORDING), so the canonical "feature returns 200" E2E does not apply; the integration composition is the appropriate top tier here.
128
+ - **Copy over-promised self-resume.** Resolved: respawn is message-triggered today (spec F1), so the copy was corrected to "no work was lost. I'll pick it back up on my next turn — send a message if I stay quiet" (single) / "it resumes on its next turn" (legacy) — no "resumes automatically" claim.
129
+
130
+ ## Evidence pointers
131
+
132
+ - `tests/unit/reap-notifier.test.ts` — honest-recycle describe block (6 tests):
133
+ recycle copy, never-contradicts-clock, no-run-death, only-age-limit, throws-fails-safe, over-window-death.
134
+ - `tests/unit/AutonomousSessions.test.ts` — `autonomousRunRemainingForTopic`
135
+ describe block (4 tests): in-flight remaining, numeric/string topic, over-window
136
+ null, no-run/paused/missing-duration null.
137
+ - `tests/integration/honest-session-recycle-wiring.test.ts` — real helper composed
138
+ with real notifier (2 tests): in-flight run → honest recycle copy with real
139
+ computed remaining; no run file → terminal death copy.
140
+ - `npx tsc --noEmit` clean; 51/51 unit + 2/2 integration tests green.
@@ -0,0 +1,146 @@
1
+ # Side-Effects Review — Resume an idle autonomous run after an age-limit reap
2
+
3
+ **Slug:** `resume-idle-autonomous-on-reap`
4
+ **Date:** `2026-06-14`
5
+ **Author:** `echo`
6
+ **Spec:** `docs/specs/resume-idle-autonomous-on-reap.md` (converged + approved)
7
+ **ELI16 companion:** `docs/specs/resume-idle-autonomous-on-reap.eli16.md`
8
+ **Second-pass reviewer:** required (this change makes the resume queue produce
9
+ REAL respawns on a dev agent — a Guard/authority surface). Reviewer concurred.
10
+
11
+ ## Summary of the change
12
+
13
+ The Mid-Work Resume Queue revives a reaped session only when the reap carried
14
+ mid-work evidence. An **age-limit** reap fires precisely when an autonomous
15
+ session is IDLE between turns (the idle gate requires an idle prompt + no
16
+ non-baseline child processes), so its work evidence is empty by construction —
17
+ and the run is never queued for revival. An away-operator's autonomous run then
18
+ sits dead until the next inbound message.
19
+
20
+ The fix: when an `age-limit` reap targets a topic that still has an ACTIVE
21
+ autonomous run (`autonomousRunRemainingForTopic(stateDir, topicId) != null`),
22
+ the `sessionReaped` wiring appends the TRUE `build-or-autonomous-active` strong
23
+ signal to the candidate's evidence and tags the reason
24
+ `age-limit (active autonomous run)`. The entry then flows through the EXISTING
25
+ one-at-a-time, quota-gated, resurrection-capped, lease-gated machinery — no new
26
+ respawn path. A drain-time liveness re-check invalidates the entry
27
+ (`autonomous-run-finished`, never a spawn) if the run finished or its window
28
+ elapsed between enqueue and drain. The resume queue resolves **live-on-dev**
29
+ (`dryRun:false`) and stays observe-only on the fleet.
30
+
31
+ Files touched:
32
+
33
+ - `src/core/WorkEvidence.ts` — new exported constant
34
+ `AGE_LIMIT_ACTIVE_RUN_REASON = 'age-limit (active autonomous run)'` (the
35
+ evidence-vocabulary home; imported by server.ts + ResumeQueueDrainer.ts).
36
+ - `src/commands/server.ts` —
37
+ (1) the resume-queue `dryRun` config resolves
38
+ `rqCfg.dryRun ?? !resolveDevAgentGate(undefined, config)` (live-on-dev) at the
39
+ single consumption site (config + boot log line);
40
+ (2) the `sessionReaped` candidate construction appends
41
+ `build-or-autonomous-active` + the reason tag when `reason === 'age-limit'` &&
42
+ `topicId != null` && `autonomousRunRemainingForTopic(...) != null` (inside the
43
+ existing enqueue try/catch);
44
+ (3) the ResumeQueueDrainer deps gain `autonomousRunFinished: (topicId) =>
45
+ autonomousRunRemainingForTopic(config.stateDir, topicId) == null`.
46
+ - `src/monitoring/ResumeQueueDrainer.ts` — new OPTIONAL injected dep
47
+ `autonomousRunFinished?: (topicId, reason) => boolean` + a `validateReality`
48
+ re-check that returns `autonomous-run-finished` for an entry tagged with the
49
+ age-limit-active-run reason whose run is no longer active. Absent dep ⇒ today's
50
+ behavior (back-compat).
51
+ - `tests/unit/resume-idle-autonomous.test.ts` — new (15 tests): admission both
52
+ sides, guard short-circuit, fail-open, dryRun-gate resolution (dev/fleet/explicit),
53
+ drain-time re-check both sides + back-compat + throwing-dep.
54
+ - `tests/integration/resume-idle-autonomous-wiring.test.ts` — new (8 tests):
55
+ wiring integrity (real helper delegation), double-spawn lens (live-session +
56
+ uuid-stale), lease lens, revival-loop lens (resurrection cap fires once).
57
+ - `tests/e2e/resume-idle-autonomous-lifecycle.test.ts` — new (4 tests): feature
58
+ is alive on dev (dryRun:false through the real AgentServer), enters-and-revives-
59
+ once + no double spawn, fleet observe-only (dryRun:true, would-resume, no spawn).
60
+
61
+ ## Decision-point inventory
62
+
63
+ - **`sessionReaped` candidate construction (server.ts)** — **modify** — the
64
+ wiring layer that OFFERS a reap to the queue. The fix adds a TRUE evidence
65
+ signal at a SECOND origination site DOWNSTREAM of the SessionManager chokepoint;
66
+ `considerEnqueue` re-clamps via `clampWorkEvidence` and the token is a valid
67
+ `STRONG_WORK_EVIDENCE` member, so the enum invariant holds. Authority unchanged:
68
+ the queue's eligibility/cap/lease gates still decide.
69
+ - **`ResumeQueueDrainer.validateReality`** — **modify** — adds one reality check
70
+ (a detector). Strictly additive: it can only ADD an invalidation, never wrongly
71
+ drop a legitimate revival (a throwing/absent dep resolves to NOT-finished, the
72
+ SAFE side).
73
+ - **resume-queue `dryRun` resolution** — **modify** — flips the queue from
74
+ observe-only to LIVE on a dev agent. This is the authority change (real respawns
75
+ + real quota spend on Echo). An explicit operator `monitoring.resumeQueue.dryRun`
76
+ still wins; the keys stay CODE-defaulted (no ConfigDefaults write) so the fleet
77
+ flip is preserved.
78
+ - **`AGE_LIMIT_ACTIVE_RUN_REASON`** — **new** — a code-internal string constant;
79
+ no external/published surface; reversible by changing one string.
80
+
81
+ ---
82
+
83
+ ## 1. Over-block (a gate refuses legitimate work)
84
+
85
+ The drain-time re-check can only ADD an `autonomous-run-finished` invalidation,
86
+ and only for an entry whose run genuinely returned `null` (completed or window
87
+ elapsed) — exactly the case the spec wants NOT revived. A throwing/absent
88
+ `autonomousRunFinished` resolves to NOT-finished, so a transient state-read error
89
+ never blocks a legitimate revival. No over-block on the kill path: the augmentation
90
+ sits inside the existing enqueue try/catch and a throw fails toward no-injection
91
+ (status-quo no-revive), never toward endangering the reap.
92
+
93
+ ## 2. Under-block (a gate lets through what it should stop)
94
+
95
+ Double-spawn is the #1 lens: an age-limit entry is an ordinary topic-bound entry
96
+ once admitted, so `validateReality`'s `live-session-exists` + `resume-uuid-stale`
97
+ catches catch a message-revive that happened between enqueue and drain (named
98
+ tests in the integration tier — both ZERO respawn). The resurrection cap reads
99
+ `tombstoneFor(stableKey)` (derived purely from topicId), never `workEvidence`, so
100
+ the injected evidence CANNOT reset or evade the cap (named test). Only the literal
101
+ `age-limit` reason is augmented — watchdog-stuck/idle-zombie/context-wedge/AUP-wedge
102
+ and moved-topic reaps stay excluded.
103
+
104
+ ## 3. Reversibility
105
+
106
+ `dryRun` flips back to observe-only by setting `monitoring.resumeQueue.dryRun: true`
107
+ (or via the fleet default). The reason tag + evidence injection is a one-string +
108
+ one-evidence-name change. The drainer dep is OPTIONAL — removing it restores prior
109
+ behavior. NOTE: flipping `dryRun` back does NOT un-spend quota already burned by a
110
+ respawn that already fired — see Frontloaded Decision D1 (classified NON-cheap,
111
+ accepted under the named live phase).
112
+
113
+ ## 4. Blast radius / interactions
114
+
115
+ The augmentation runs ONLY on `age-limit` reaps (the cold-path
116
+ `autonomousRunRemainingForTopic` read is behind the `reason === 'age-limit'`
117
+ short-circuit), so every other reap pays zero added cost. It reads the LOCAL
118
+ autonomous-run state file (same vantage that admitted the entry). Coupling risk
119
+ (D2): if a future maintainer narrows `build-or-autonomous-active` to mean strictly
120
+ "a live child build process", this reuse silently breaks — flagged here.
121
+
122
+ ## 5. Multi-machine (Phase-C) posture
123
+
124
+ Machine-local by design: the resume queue is per-machine (single-writer lock), the
125
+ age-limit reap is observed on the machine the session ran on, and only the
126
+ lease-holder drains it (`validateReality` returns `topic-owner-elsewhere` otherwise
127
+ — named test). A run MOVED to another machine reaches the queue as `topic moved …`
128
+ (already `eligible:false`), not `age-limit`, so it is never double-revived.
129
+
130
+ ## 6. Migration parity
131
+
132
+ NONE required — and that is correct. The `dryRun` resolution is a CODE default read
133
+ at server boot, not a persisted `.instar/config.json` field, so existing agents pick
134
+ it up on their next restart after update. A `migrateConfig` entry would freeze
135
+ `dryRun` to disk and break the later fleet flip — the resume-queue keys must stay
136
+ un-frozen in ConfigDefaults. No CLAUDE.md template change (internal plumbing, no new
137
+ route or user-facing capability).
138
+
139
+ ## 7. Signal vs. authority
140
+
141
+ The injected `build-or-autonomous-active` is a TRUE assertion about the world
142
+ (the run is genuinely in-flight per the un-elapsed state file), supplied from the
143
+ one vantage that can observe it — the opposite of lying to a classifier. The
144
+ drain-time re-check is a detector that feeds the existing invalidation authority;
145
+ it never spawns. The only authority change is the `dryRun` flip, which is bounded
146
+ by the existing one-at-a-time / calm-ticks / quota / lease / resurrection-cap gates.