instar 1.3.345 → 1.3.347
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 +8 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +21 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/monitoring/PresenceProxy.d.ts +8 -0
- package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
- package/dist/monitoring/PresenceProxy.js +28 -9
- package/dist/monitoring/PresenceProxy.js.map +1 -1
- package/dist/monitoring/RateLimitSentinel.d.ts +5 -2
- package/dist/monitoring/RateLimitSentinel.d.ts.map +1 -1
- package/dist/monitoring/RateLimitSentinel.js.map +1 -1
- package/dist/monitoring/StuckSignatureClassifier.d.ts +52 -0
- package/dist/monitoring/StuckSignatureClassifier.d.ts.map +1 -0
- package/dist/monitoring/StuckSignatureClassifier.js +133 -0
- package/dist/monitoring/StuckSignatureClassifier.js.map +1 -0
- package/dist/monitoring/sentinelWiring.d.ts +13 -4
- package/dist/monitoring/sentinelWiring.d.ts.map +1 -1
- package/dist/monitoring/sentinelWiring.js +10 -8
- package/dist/monitoring/sentinelWiring.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +19 -19
- package/upgrades/1.3.347.md +76 -0
- package/upgrades/honest-turn-receipts.eli16.md +47 -0
- package/upgrades/rate-limit-resume-nudge-internal-channel.eli16.md +35 -0
- package/upgrades/side-effects/honest-turn-receipts.md +90 -0
- package/upgrades/side-effects/rate-limit-resume-nudge-internal-channel.md +102 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Side-Effects Review — Honest Turn-Receipts
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `honest-turn-receipts`
|
|
4
|
+
**Date:** `2026-06-06`
|
|
5
|
+
**Author:** `Echo (instar-dev agent, session-robustness topic — Justin: "finish out item number four" + the "conversation too long is noise" observation)`
|
|
6
|
+
**Second-pass reviewer:** `self-adversarial pass over false-positive surface (the one way an honest classifier can become a new liar) + double-messaging`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
A pure tail-gated `StuckSignatureClassifier` recognizes a live-but-failing
|
|
11
|
+
session (rate-limited / policy-wedge / context-wedge / context-too-long) from
|
|
12
|
+
its LIVE tmux tail and returns an honest user-facing message. PresenceProxy's
|
|
13
|
+
`fireTier3` runs it after the existing quota check and before the process-tree
|
|
14
|
+
"working" assessment, surfacing the real reason instead of "working", deferring
|
|
15
|
+
to an owning recovery sentinel, and preserving the context-exhaustion recovery
|
|
16
|
+
path. The un-tail-gated context-exhaustion block it replaces was the source of
|
|
17
|
+
the "conversation too long" noise. Files: `StuckSignatureClassifier.ts` (new),
|
|
18
|
+
`PresenceProxy.ts`, `server.ts` wiring, `PostUpdateMigrator.ts` section, tests.
|
|
19
|
+
|
|
20
|
+
## Decision-point inventory
|
|
21
|
+
|
|
22
|
+
- Stuck classification — **add (detector)** — signal only; tail-gated.
|
|
23
|
+
- fireTier3 placement (after quota, before process-tree) — **modify** — the
|
|
24
|
+
exact seam where a live process forced the "working" lie.
|
|
25
|
+
- context-exhaustion block — **replace** — same recovery path, now tail-gated
|
|
26
|
+
(fixes the noise) and folded into the unified honest block.
|
|
27
|
+
- `isStuckRecoveryActive` deference — **add (suppression)** — net reduction in
|
|
28
|
+
messaging authority (yields to an owning sentinel).
|
|
29
|
+
|
|
30
|
+
## 1. Over-block
|
|
31
|
+
|
|
32
|
+
Nothing is blocked — no authority added. The honest message can only fire at
|
|
33
|
+
tier-3 (5 minutes of an unanswered user message), so a session that replied
|
|
34
|
+
promptly never reaches it. The deference callback REMOVES a double-message case
|
|
35
|
+
(standby + sentinel both speaking).
|
|
36
|
+
|
|
37
|
+
## 2. Under-block (false-positive surface — the real risk)
|
|
38
|
+
|
|
39
|
+
The danger of an honest classifier is becoming a NEW liar (saying "stuck" about
|
|
40
|
+
a healthy session). Defenses, each tested:
|
|
41
|
+
- **Tail-gating:** the signature must be the live tail, not scrollback — kills
|
|
42
|
+
the stale "conversation too long" mention (the reported noise) and a session
|
|
43
|
+
that quoted an error then kept working.
|
|
44
|
+
- **Prose-vs-block rate-limit patterns:** "you've hit your limit" / "limit ·
|
|
45
|
+
resets" match; "when you hit your usage limit, the session pauses" does not.
|
|
46
|
+
- **AUP repetition gate** (inherited from classifyWedgeTail): a single benign
|
|
47
|
+
policy rejection is not a wedge.
|
|
48
|
+
- **Normal-compaction suppression:** the compaction lifecycle banner is not
|
|
49
|
+
treated as context-too-long.
|
|
50
|
+
Residual under-block: a stuck session whose signature the provider rewords
|
|
51
|
+
escapes — inherent to signature matching; the tier-3 LLM path still runs as the
|
|
52
|
+
fallback for the unmatched case (unchanged).
|
|
53
|
+
|
|
54
|
+
## 3. Level-of-abstraction fit
|
|
55
|
+
|
|
56
|
+
The classifier is a pure module beside the sentinels whose detectors it reuses
|
|
57
|
+
(`classifyWedgeTail`). The wiring lives in `fireTier3` (which owns the
|
|
58
|
+
assessment) and reuses the existing `sendProxyMessage` surface and the composed
|
|
59
|
+
`wedgeRecoveryActive` checker (same one the SessionReaper veto uses). No new
|
|
60
|
+
message path, no new state store.
|
|
61
|
+
|
|
62
|
+
## 4. Signal vs authority compliance
|
|
63
|
+
|
|
64
|
+
**Required reference:** `docs/signal-vs-authority.md`
|
|
65
|
+
|
|
66
|
+
- [x] Pure signal. The classifier decides nothing; it answers a question.
|
|
67
|
+
Recovery is unchanged (the sentinels'). The only behavioral delta is WHICH
|
|
68
|
+
honest string the standby sends, plus a new SUPPRESSION (deference). No new
|
|
69
|
+
decision-maker, and a net reduction in messaging authority.
|
|
70
|
+
|
|
71
|
+
## 5. Interactions
|
|
72
|
+
|
|
73
|
+
- **Quota-exhaustion block (runs first):** owns the bare usage-limit form; the
|
|
74
|
+
honest classifier is the backstop for the forms it misses + the wedges +
|
|
75
|
+
tail-gated context-too-long. No double-handling (the first to match returns).
|
|
76
|
+
- **RateLimitSentinel:** already suppresses ALL tiers via
|
|
77
|
+
`hasActiveRateLimitRecovery` (unchanged); the honest rate-limit branch is the
|
|
78
|
+
backstop when no sentinel owns it (e.g. the cross-machine EXO case).
|
|
79
|
+
- **ContextWedgeSentinel:** when it is mid-recovery, `isStuckRecoveryActive`
|
|
80
|
+
makes the standby defer; otherwise the honest message is the only voice.
|
|
81
|
+
- **Existing tests:** all 119 presence-proxy tests pass unmodified; the
|
|
82
|
+
context-exhaustion test's source-grep section was updated to the new wiring.
|
|
83
|
+
|
|
84
|
+
## 6. External surfaces / 7. Rollback
|
|
85
|
+
|
|
86
|
+
No API, no route, no config key (default-on behavioral change; the new config
|
|
87
|
+
field is an optional injected callback wired in server.ts). One idempotent
|
|
88
|
+
CLAUDE.md section (marker 'Honest standby (turn-receipts)'). Rollback = revert;
|
|
89
|
+
the standby returns to classifying live-but-failing sessions as "working" and
|
|
90
|
+
the context-too-long noise returns.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Side-Effects Review — Rate-limit resume nudge routed through internal channel (not the user-message path)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `rate-limit-resume-nudge-internal-channel`
|
|
4
|
+
**Date:** `2026-06-05`
|
|
5
|
+
**Author:** `Echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
The RateLimitSentinel's post-backoff resume nudge ("the throttle should have cleared — please continue where you left off") was injected into the throttled session via `injectTopicNudge`, which prefixes the text with `[telegram:${topicId}]` — the exact wire format of a real inbound user message. The agent therefore could not distinguish the infrastructure's own poke from a message from the user: it answered the nudge conversationally ("no throttle on my end, still rolling") and, because the `[telegram:N]` prefix triggers the mandatory "relay your reply to the user" rule, posted that denial into the topic between the sentinel's own throttle notices — appearing to contradict itself. The change routes the resume nudge through the internal recovery channel (`injectInternalNudge` → `injectInternalMessage`) for ALL sessions, topic-bound or not. The internal path converges on the same low-level `rawInject` (identical un-stick behavior) but carries no user-message prefix, so the agent never mistakes it for the user. The now-dead `injectTopicNudge` method is removed from `RateLimitRecoverySurface` and its `server.ts` wiring. Files: `src/monitoring/sentinelWiring.ts`, `src/commands/server.ts`, plus the two rate-limit recovery test files.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `buildRateLimitRecoveryDeps.resumeFn` (sentinelWiring.ts) — **modify** — was: topic-bound → `[telegram:N]`-prefixed inject; non-topic-bound → internal inject. Now: ALL sessions → internal inject; topic recorded in audit detail only.
|
|
15
|
+
- `RateLimitRecoverySurface.injectTopicNudge` (interface + server.ts wiring) — **remove** — the prefixed-inject path is deleted so the resume nudge cannot use it again.
|
|
16
|
+
- The user-facing notice path (`notifyFn` → `deliverNotice` → Telegram) — **pass-through** — unchanged; the "throttled / back online" heads-ups still post exactly as before.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Over-block
|
|
21
|
+
|
|
22
|
+
No block/allow surface — over-block not applicable. This change selects an injection channel; it neither rejects nor admits inputs.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 2. Under-block
|
|
27
|
+
|
|
28
|
+
No block/allow surface — under-block not applicable. The recovery still fires on every detected throttle; nothing is newly suppressed. The user-facing throttle notices are unchanged.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 3. Level-of-abstraction fit
|
|
33
|
+
|
|
34
|
+
Correct layer. The resume nudge is infrastructure→session communication; the right primitive for that is `injectInternalMessage` (`source: 'sentinel-recovery'`), which exists precisely for "trusted internal nudge that bypasses the topic-prefix requirement" and logs the trusted bypass to the security log. The previous design borrowed the user-message primitive (`injectMessage` with a topic prefix) for InputGuard-provenance convenience, which was the wrong layer — it made an infra signal indistinguishable from user speech. The fix moves the resume to the primitive built for it. No higher-level gate is bypassed; the InputGuard provenance check the old path ran is intentionally not needed for a known-internal nudge (and `injectInternalMessage` records the bypass).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 4. Signal vs authority compliance
|
|
39
|
+
|
|
40
|
+
**Required reference:** docs/signal-vs-authority.md
|
|
41
|
+
|
|
42
|
+
- [x] No — this change has no block/allow surface.
|
|
43
|
+
|
|
44
|
+
The change chooses a delivery channel for a recovery nudge. It holds no blocking authority and adds no brittle gate. It removes an accidental coupling (infra wearing the user's identity), which strictly reduces surface.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 5. Interactions
|
|
49
|
+
|
|
50
|
+
- **Shadowing:** None. The resume nudge and the user-facing notice are separate paths (`resumeFn` vs `notifyFn`); this change touches only `resumeFn`. The notice path is untouched.
|
|
51
|
+
- **Double-fire:** Reduced. Previously the agent burned a full conversational-reply turn answering each nudge (and relayed it). That spurious turn is eliminated — fewer LLM turns hitting the already-throttled account during a fleet-wide throttle.
|
|
52
|
+
- **Races:** No new shared state. `rawInject` resets the same idle-prompt timer it always did; the verify()/JSONL-growth recovery detection is unchanged. The internal path was already the live path for non-topic-bound sessions, so its reliability is proven in production.
|
|
53
|
+
- **Feedback loops:** This CLOSES one. The old behavior was a feedback loop — sentinel poke → agent reply → relayed to topic → contradicts sentinel notice. Removing the user-message framing breaks the loop at its source.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 6. External surfaces
|
|
58
|
+
|
|
59
|
+
- **Other agents / users (install base):** behavior change ships to every instar agent on update. It strictly improves coherence (removes self-contradiction during throttle recovery); no capability is added or removed.
|
|
60
|
+
- **Telegram:** fewer messages posted to topics during a throttle (the spurious "no throttle on my end" replies stop). The intended "throttled / back online" notices are unchanged.
|
|
61
|
+
- **Persistent state:** the audit line in `logs/sentinel-events.jsonl` / recovery records now reads "resume nudge injected via internal recovery channel (topic N)" instead of "via topic". Read-only observability; no consumer parses the prior exact string for control flow.
|
|
62
|
+
- **Timing/runtime:** none introduced. Same inject mechanism, same timers.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 7. Rollback cost
|
|
67
|
+
|
|
68
|
+
Pure code change — revert the two source edits and ship as the next patch. No persistent state, no schema, no migration, no agent-state repair. The only user-visible effect during a rollback window would be the return of the original incoherence, not a hard failure. Recovery itself never depended on the prefix (both channels converge on `rawInject`), so reverting cannot break un-sticking.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Conclusion
|
|
73
|
+
|
|
74
|
+
The review found no block/allow surface and no new authority — the change removes an accidental identity coupling (infrastructure injecting a nudge that looked like a user message) and replaces it with the purpose-built internal channel. It reduces external surface (fewer spurious Telegram messages, fewer wasted LLM turns under throttle) and closes a self-reinforcing feedback loop. The one related site found in the sibling sweep — the compaction-resume re-inject in `server.ts`, which also uses a `[telegram:N]` prefix — is left unchanged because it only fires when there is a genuinely unanswered user message, where a relayed reply is wanted; it is tracked for audit under the new Truthful Provenance standard at JKHeadley/instar#894 rather than altered here. Clear to ship. Regression coverage pins the contract (the resume nudge can never again carry a `[telegram:` prefix).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Second-pass review (if required)
|
|
79
|
+
|
|
80
|
+
**Reviewer:** independent reviewer subagent
|
|
81
|
+
**Independent read of the artifact: CONCUR**
|
|
82
|
+
|
|
83
|
+
The reviewer independently read the diff and the SessionManager injection internals and concurred the fix is correct, minimal, and the artifact is honest:
|
|
84
|
+
|
|
85
|
+
- **Resume parity is real (the load-bearing claim).** `injectInternalNudge` → `injectInternalMessage` (SessionManager.ts:3060) and the old `injectTopicNudge` → `injectMessage` both converge on the identical `rawInject` (3080): same bracketed-paste, same framework-aware Enter (codex double-Enter), same `verifyInjection`/`recordStrandedDraftMarker`. The fix does not weaken un-sticking. The redundant `isSessionAlive` guard inside `injectInternalMessage` is harmless (resumeFn already guards it).
|
|
86
|
+
- **The relay-trigger cannot fire on the new path.** The mandatory "relay your reply" behavior keys strictly on the anchored `^\[telegram:(\d+)` prefix (InputGuard.ts:114; UserPromptSubmit hook regex). The bare nudge text carries no such prefix; the new test asserts `not.toContain('[telegram:')`, pinning the contract.
|
|
87
|
+
- **Audit/recordRecovery preserved.** Collapses to one `recordRecovery(..., ['internal-injection'])`; topic retained in the detail string, verified by the updated unit test.
|
|
88
|
+
- **No dead/broken code.** `injectTopicNudge` fully removed from interface, server.ts wiring, and both test harnesses (grep returns zero hits); typecheck clean.
|
|
89
|
+
- **The "compaction-resume is defensible to leave" judgment is correct, with a concrete reason.** That sibling inject (server.ts:6053-6055) is gated on `lastReal?.fromUser` — it fires only when the most recent real message is a genuinely unanswered user message, where a relayed reply is wanted. The RateLimitSentinel resume has no such gate, which is exactly why a user prefix there manufactured a phantom user turn. Structurally different cases.
|
|
90
|
+
- **No edge-case regression** for InputGuard provenance (the internal path auditably bypasses it via the logged `internal-recovery-injection` event), multi-line handling (nudge is single-line), or codex-vs-claude submit (shared `rawInject`).
|
|
91
|
+
|
|
92
|
+
**Reviewer nits, both resolved:**
|
|
93
|
+
1. Stale comments that still described the nudge as "topic-tagged so InputGuard accepts it" (RateLimitSentinel.ts:85 + sentinelWiring.ts:329) — **fixed** in this change so no future reader re-introduces the prefix.
|
|
94
|
+
2. The compaction-resume "leave unchanged" decision must be tracked durably, not left as prose — **filed as JKHeadley/instar#894** (audit it under the new Truthful Provenance standard).
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Evidence pointers
|
|
99
|
+
|
|
100
|
+
- Root cause confirmed live in `logs/sentinel-events.jsonl` (2026-06-06 ~04:31Z): a fleet-wide throttle drove ~7 sessions through this recovery, each logging "resume nudge injected via topic".
|
|
101
|
+
- Smoking-gun line: `src/commands/server.ts` `injectTopicNudge: (name, topicId, text) => sessionManager.injectMessage(name, \`[telegram:${topicId}] ${text}\`)` (pre-fix).
|
|
102
|
+
- Tests: `tests/unit/rate-limit-recovery-reachability.test.ts` (incl. new anti-impersonation regression) + `tests/integration/rate-limit-recovery-sentinel-lifecycle.test.ts`; 59 tests green across the rate-limit/sentinel suites.
|