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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +59 -5
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AutonomousSessions.d.ts +12 -0
- package/dist/core/AutonomousSessions.d.ts.map +1 -1
- package/dist/core/AutonomousSessions.js +23 -0
- package/dist/core/AutonomousSessions.js.map +1 -1
- package/dist/core/WorkEvidence.d.ts +13 -0
- package/dist/core/WorkEvidence.d.ts.map +1 -1
- package/dist/core/WorkEvidence.js +13 -0
- package/dist/core/WorkEvidence.js.map +1 -1
- package/dist/monitoring/ReapNotifier.d.ts +19 -0
- package/dist/monitoring/ReapNotifier.d.ts.map +1 -1
- package/dist/monitoring/ReapNotifier.js +65 -3
- package/dist/monitoring/ReapNotifier.js.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.d.ts +9 -0
- package/dist/monitoring/ResumeQueueDrainer.d.ts.map +1 -1
- package/dist/monitoring/ResumeQueueDrainer.js +14 -0
- package/dist/monitoring/ResumeQueueDrainer.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.559.md +48 -0
- package/upgrades/1.3.560.md +57 -0
- package/upgrades/side-effects/honest-session-recycle.md +140 -0
- package/upgrades/side-effects/resume-idle-autonomous-on-reap.md +146 -0
|
@@ -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.
|