instar 1.3.1007 → 1.3.1009
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/scheduler/MentorOnboardingRunner.d.ts +2 -2
- package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
- package/dist/scheduler/MentorOnboardingRunner.js +1 -1
- package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
- package/dist/scheduler/MentorOnboardingTick.d.ts +8 -1
- package/dist/scheduler/MentorOnboardingTick.d.ts.map +1 -1
- package/dist/scheduler/MentorOnboardingTick.js +15 -7
- package/dist/scheduler/MentorOnboardingTick.js.map +1 -1
- package/dist/scheduler/OutstandingPromptTracker.d.ts +52 -0
- package/dist/scheduler/OutstandingPromptTracker.d.ts.map +1 -1
- package/dist/scheduler/OutstandingPromptTracker.js +135 -3
- package/dist/scheduler/OutstandingPromptTracker.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +107 -61
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +29 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/1.3.1008.md +39 -0
- package/upgrades/1.3.1009.md +71 -0
- package/upgrades/eli16/bounded-mentor-onboarding-retries.md +53 -0
- package/upgrades/side-effects/bounded-mentor-onboarding-retries.md +142 -0
- package/upgrades/side-effects/completion-claim-stats-route.md +120 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Side-Effects Review — Bounded mentor-onboarding retries
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `bounded-mentor-onboarding-retries`
|
|
4
|
+
**Date:** `2026-07-27`
|
|
5
|
+
**Author:** `Instar Agent (instar-codey)`
|
|
6
|
+
**Review tier:** Tier 1, user-directed
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`OutstandingPromptTracker` extends its existing durable correlation ledger with a content-keyed retry ledger. Live mentor delivery reserves an attempt before transport, permits at most three attempts for the same unanswered normalized content, keeps new content independent, and persists a one-shot exhaustion escalation. `runMentorTick` now awaits the delivery callback and exposes the delivery layer's structured reason.
|
|
11
|
+
|
|
12
|
+
The tracker persists only SHA-256 content keys plus mentee, attempt, timestamp, and escalation metadata. The ledger holds at most 64 unresolved content keys. Existing version-1 files load and upgrade on the next write.
|
|
13
|
+
|
|
14
|
+
## Decision-point inventory
|
|
15
|
+
|
|
16
|
+
- `OutstandingPromptTracker.reserveSend` — **new authority inside an existing authority** — permits or refuses one mentor send based on in-flight state, content-key attempts, storage availability, and the fixed ledger capacity.
|
|
17
|
+
- `OutstandingPromptTracker.clearByCorr` — **modified** — a correlated reply now closes both the outstanding correlation and that content's retry episode.
|
|
18
|
+
- `OutstandingPromptTracker.markDeliveryFailed` — **new transition** — removes the in-flight correlation while retaining the consumed attempt.
|
|
19
|
+
- `AgentServer.deliverToMentee` — **modified actuation boundary** — durably reserves before transport and returns a structured delivery outcome.
|
|
20
|
+
- `runMentorTick` — **modified observer** — awaits the delivery boundary and records its reason; it does not decide the retry policy.
|
|
21
|
+
- `DegradationReporter.report` — **existing signal path** — emits one delivery-exhaustion event after the durable latch admits it.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 1. Over-block
|
|
26
|
+
|
|
27
|
+
The brake can suppress a prompt whose wording is identical even if the mentor intended a semantically separate repetition. That only happens after three sends without a correlated reply. A confirmed reply deletes the content episode, so later reuse after a real conversation is allowed. A genuinely different agenda item hashes separately and remains eligible.
|
|
28
|
+
|
|
29
|
+
Whitespace is normalized before hashing. Cosmetic spacing cannot evade the brake, but punctuation or wording changes produce a new key. This is deliberate: the gate refuses exact normalized repetition, not semantic similarity judged by a brittle classifier.
|
|
30
|
+
|
|
31
|
+
The 64-key ledger cap can refuse a novel prompt after 64 unresolved content episodes. That is a fail-closed capacity bound. It prefers pausing the autonomous mentor over allowing either unbounded state growth or eviction that would silently reopen old breakers.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Under-block
|
|
36
|
+
|
|
37
|
+
An LLM can paraphrase the same intent and receive a new content key. This patch closes the observed identical-content loop, not semantic-repetition detection. Semantic dedupe would require judgment and risks suppressing legitimately revised questions; it is intentionally outside this Tier 1 correction.
|
|
38
|
+
|
|
39
|
+
A corrupt version-2 file follows the tracker's existing corruption behavior and starts with fresh in-memory state. Normal restarts over valid state preserve the brake, which is the requested failure mode. Hardening corrupt-state recovery into a fail-closed operator repair state is a broader storage-policy change and is not smuggled into this patch.
|
|
40
|
+
|
|
41
|
+
Version-1 outstanding entries have no historical content key. They preserve the existing in-flight refusal but cannot retroactively consume a content attempt. The first post-upgrade content reservation begins the new bounded history.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Level-of-abstraction fit
|
|
46
|
+
|
|
47
|
+
The retry authority belongs in `OutstandingPromptTracker`, which already owns the durable question “may this mentor send another prompt to this mentee?” Adding a second disconnected store would create competing truth for the same actuation boundary.
|
|
48
|
+
|
|
49
|
+
Content normalization and hashing live beside the attempt state. `AgentServer` owns orchestration: reserve, call transport, mark immediate failure, append sent history, and emit degradation. `runMentorTick` only awaits and surfaces the structured outcome. No transport adapter is taught mentor-specific policy.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 4. Signal vs authority compliance
|
|
54
|
+
|
|
55
|
+
**Required reference:** `docs/signal-vs-authority.md`
|
|
56
|
+
|
|
57
|
+
The static retry count is appropriate hard authority because “never perform identical autonomous sends without a bound” is a safety invariant, not a competing-signals judgment. The gate uses exact normalized content, explicit counters, and durable state. It does not guess user intent, message quality, or semantic equivalence.
|
|
58
|
+
|
|
59
|
+
The degradation event is signal-only. It cannot reopen the breaker, alter the agenda, change transport routing, or authorize another send. The durable tracker remains the sole authority.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 4b. Judgment-point check
|
|
64
|
+
|
|
65
|
+
No LLM or heuristic decides whether the retry budget is exhausted. The decision inputs are a stable content key, integer attempt count, fixed maximum, outstanding correlation, and store health.
|
|
66
|
+
|
|
67
|
+
The number three is a conservative structural ceiling for an existing retry loop that previously had no ceiling. It is not exposed as configuration in this patch, avoiding a migration surface and preventing an operator from accidentally configuring the invariant away.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 5. Interactions
|
|
72
|
+
|
|
73
|
+
- **Outstanding timeout:** an expired correlation is still swept, but its content attempt remains. Expiry permits evaluation of the next attempt; it no longer resets history.
|
|
74
|
+
- **Immediate transport refusal:** the pre-send reservation remains counted while the outstanding correlation is removed, so a dead transport reaches the same breaker without waiting twenty minutes per attempt.
|
|
75
|
+
- **Successful delivery:** the correlation stays outstanding until its matching reply or timeout.
|
|
76
|
+
- **Matching reply:** clears the outstanding row and content retry episode.
|
|
77
|
+
- **Late reply after timeout:** remains classified by the existing late/unknown-correlation behavior and does not silently reopen an exhausted content key.
|
|
78
|
+
- **Restart:** entries, retry counts, and `escalatedAt` reload from one atomic JSON file.
|
|
79
|
+
- **Concurrent tick:** the runner's existing single in-flight guard plus the tracker's per-mentee outstanding check prevents overlapping reservations in this process.
|
|
80
|
+
- **Multi-machine:** state remains machine-local, matching the existing mentor runner's single active process. This patch adds no replicated authority.
|
|
81
|
+
- **Escalation flood:** `recordRetryExhaustionEscalated` persists the latch before the degradation call. Repeated ticks see the open breaker and decline the signal.
|
|
82
|
+
- **State growth:** 64 unresolved keys is a hard ceiling; raw message content is absent from this store.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 6. External surfaces
|
|
87
|
+
|
|
88
|
+
`GET /mentor/status.lastResult` can now include `deliveryReason` for a live tick. Existing fields and status codes remain unchanged; JSON omits the optional field when no delivery-layer reason exists.
|
|
89
|
+
|
|
90
|
+
Operators may receive one existing-style degradation alert under the new feature name `mentor.delivery-unconfirmed-retry-exhausted`. Its content describes a delivery failure and the automatic suppression. It does not ask the operator to edit files, run commands, or diagnose raw internals.
|
|
91
|
+
|
|
92
|
+
No Telegram API call shape, Threadline identity rule, bot token handling, topic selection, endpoint, config key, or dashboard surface changes.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 6b. Operator-surface quality
|
|
97
|
+
|
|
98
|
+
The only operator-facing addition is the existing degradation channel's plain-language event. It leads with the condition and effect: prompt delivery could not be confirmed, so identical sends stopped. It exposes no hash, correlation id, state path, token, or stack trace as primary content. There is no destructive action. The message is short enough for phone width and introduces no form or technical input.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 7. Multi-machine posture
|
|
103
|
+
|
|
104
|
+
**Posture: intentionally machine-local.** The mentor runner and its outstanding-prompt tracker already live on the machine executing Echo's mentor tick. A send is reserved and actuated by that same process. Replicating attempt authority without a cross-machine single-writer contract would create two counters that could each admit three sends.
|
|
105
|
+
|
|
106
|
+
This patch preserves the existing one-process ownership boundary. If mentor execution becomes active-active in the future, the tracker must move behind a shared claim/lease before that rollout; this change does not claim cross-machine safety for a topology that is not currently enabled.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 8. Rollback cost
|
|
111
|
+
|
|
112
|
+
Reverting the code restores version-1 behavior. Older code reads the version-2 file as unsupported and starts fresh, so rollback removes the brake until the prior version is restored. No database migration or user-data conversion is required. The file contains only machine-local mentor retry metadata and can be regenerated.
|
|
113
|
+
|
|
114
|
+
The additive `deliveryReason` field disappears on rollback. Existing consumers already tolerate its absence.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Class-Closure Declaration
|
|
119
|
+
|
|
120
|
+
**Defect class:** `unbounded-self-action`
|
|
121
|
+
**Closure:** `guard`
|
|
122
|
+
|
|
123
|
+
The guard is the durable pre-transport reservation plus the three-attempt content-key breaker. Unit tests prove the first send, cap, independent novel content, restart persistence, one-shot escalation, normalization, and capacity bound. The production-wiring E2E test drives three refused transport attempts, requires the fourth identical attempt to be suppressed, and requires a new message to retain an independent attempt.
|
|
124
|
+
|
|
125
|
+
Steady state for one unanswered content key is therefore: at most three outbound attempts, one exhaustion signal, then permanent suppression in valid durable state. The escalation path cannot recursively send another mentor prompt.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Evidence pointers
|
|
130
|
+
|
|
131
|
+
- Refusal-first: four new tracker tests failed against unmodified source with `reserveSend is not a function`; ten pre-existing tracker tests remained green.
|
|
132
|
+
- Focused proof after implementation: 77/77 across tracker, tick, runner, route integration, production lifecycle, and mentor config hot-read coverage.
|
|
133
|
+
- Static validation: `npm run build` and `npm run lint` pass.
|
|
134
|
+
- No route, config, transport identity, LLM breaker, cadence, budget, or agenda change.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Causal autopsy
|
|
139
|
+
|
|
140
|
+
**Origin:** latent.
|
|
141
|
+
|
|
142
|
+
The correlation-scoped anti-ping-pong guard was correct within one reply window but treated timeout expiry as the end of all retry history. That latent reset became an unbounded loop when Telegram visibly accepted bot-authored posts that the mentee bot could not ingest. Every correlation expired normally, so the system repeatedly regenerated and resent identical content while believing each attempt was new.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Side-effects review — `GET /completion-claim/stats`
|
|
2
|
+
|
|
3
|
+
**Change:** adds a read-only counters endpoint for the completion-claim verifier, and corrects
|
|
4
|
+
the `rollout-evidence-ref` in `docs/specs/claim-verification-sentinel.md` from
|
|
5
|
+
`/completion-claim-verification/stats` (a prefix that does not exist) to `/completion-claim/stats`.
|
|
6
|
+
|
|
7
|
+
**Discovered:** 2026-07-27, while checking — before proposing a new feature — whether a
|
|
8
|
+
claim-review capability already existed. It did, and it could not graduate.
|
|
9
|
+
|
|
10
|
+
## The condition
|
|
11
|
+
|
|
12
|
+
`docs/specs/claim-verification-sentinel.md` is `status: approved`, `rollout-disposition: active`,
|
|
13
|
+
`rollout-evidence-type: endpoint`, with a graduation metric of
|
|
14
|
+
`classified-completion-claims >= 1`. Verified on `main`:
|
|
15
|
+
|
|
16
|
+
- `CompletionClaimVerifier` exists and **is** constructed (`src/server/AgentServer.ts:2572`).
|
|
17
|
+
- `monitoring.completionClaimVerification` config is present with `dryRun: true`.
|
|
18
|
+
- `POST /completion-claim/observe` and `GET /completion-claim/audit` are live —
|
|
19
|
+
`/completion-claim/audit` returns 200 and was **actively recording** (records dated
|
|
20
|
+
`2026-07-27T17:46:20.971Z`, `dryRun: true`, verdicts such as `uncorroborated-unknown`).
|
|
21
|
+
- `CompletionClaimVerifier.stats()` is implemented (`src/monitoring/CompletionClaimVerifier.ts:231`)
|
|
22
|
+
and **called by no route**.
|
|
23
|
+
- `/completion-claim/stats` → 404. `/completion-claim-verification/stats` (the spec's ref) → 404.
|
|
24
|
+
|
|
25
|
+
So the feature runs, observes, and records — while the evidence its own spec nominates for
|
|
26
|
+
graduation is unreadable. It cannot progress, and nothing surfaces that it is stuck.
|
|
27
|
+
|
|
28
|
+
**Correction of record:** an earlier filing of this said "the route does not exist anywhere."
|
|
29
|
+
That was wrong — the observe and audit routes exist under a different prefix. The accurate
|
|
30
|
+
finding is narrower: no *stats* surface exists at any prefix, and the spec's ref points at a
|
|
31
|
+
prefix that never did. Recorded rather than quietly amended.
|
|
32
|
+
|
|
33
|
+
## 1. Over-block — what legitimate input does this reject?
|
|
34
|
+
|
|
35
|
+
None. New read-only route; rejects nothing that previously worked. It 503s when the verifier is
|
|
36
|
+
absent, which is the same contract the sibling audit route already uses — deliberately matched so
|
|
37
|
+
"feature off" reads identically across both surfaces rather than one 404ing and one 503ing.
|
|
38
|
+
|
|
39
|
+
## 2. Under-block — what does this still miss?
|
|
40
|
+
|
|
41
|
+
- **It does not make the feature graduate.** It makes graduation *evaluable*. Whether the
|
|
42
|
+
criterion is met is a separate, later, evidence-driven decision that belongs to the operator.
|
|
43
|
+
- **Local scope only.** The audit route already owns the pool projection; adding a second
|
|
44
|
+
cross-machine fan-out for the same underlying data would duplicate a surface rather than serve
|
|
45
|
+
one. A pool view of counters is a follow-up if it is ever wanted, not a gap this change creates.
|
|
46
|
+
- **It does not audit the wider class.** This is one instance of "a spec nominates rollout
|
|
47
|
+
evidence that was never built". Whether other `rollout-evidence-ref` values resolve is an
|
|
48
|
+
open question this change does not answer. <!-- tracked: ACT-1394 -->
|
|
49
|
+
|
|
50
|
+
## 3. Level-of-abstraction fit
|
|
51
|
+
|
|
52
|
+
Correct layer, and deliberately the *thinnest* one. `stats()` already existed with the right
|
|
53
|
+
shape; this exposes it and adds nothing else. The alternative — computing counters in the route —
|
|
54
|
+
would have created a second source of truth for numbers the verifier already maintains.
|
|
55
|
+
|
|
56
|
+
The spec-ref correction belongs in the same change because a stats route at a path the spec does
|
|
57
|
+
not name would leave the rollout check just as unevaluable as before, only less obviously.
|
|
58
|
+
|
|
59
|
+
## 4. Signal vs authority compliance
|
|
60
|
+
|
|
61
|
+
Pure signal, and structurally incapable of being anything else: a GET that reads a counters
|
|
62
|
+
snapshot. It gates nothing, blocks nothing, and mutates nothing. `stats()` returns a deep copy
|
|
63
|
+
(`JSON.parse(JSON.stringify(...))`), so a caller cannot reach through the response into live
|
|
64
|
+
counters. A test asserts the route touches no mutation entry point — `recordDisposition` and
|
|
65
|
+
`recordCanaryDrift` throw if called during the request.
|
|
66
|
+
|
|
67
|
+
## 5. Interactions
|
|
68
|
+
|
|
69
|
+
- **Sibling route.** Shares the 503-when-absent contract with `/completion-claim/audit`; no
|
|
70
|
+
shared state, no ordering dependency, and reading stats does not disturb the audit ring.
|
|
71
|
+
- **Rollout tracking.** The corrected `rollout-evidence-ref` is what the rollout machinery reads.
|
|
72
|
+
Before this change it pointed at a 404, so any automated evidence check would have failed or
|
|
73
|
+
silently found nothing; after it, the check resolves.
|
|
74
|
+
- **Metric naming.** The response carries both `stats.classifiedTurns` (the internal field) and
|
|
75
|
+
`classified-completion-claims` (the spec's name) so a rollout check does not need to know the
|
|
76
|
+
internal field name. Duplication is deliberate and one-directional — the spec name mirrors the
|
|
77
|
+
counter, never the reverse.
|
|
78
|
+
|
|
79
|
+
## 6. External surfaces
|
|
80
|
+
|
|
81
|
+
One new authenticated GET. No wire-format change to any existing route, no schema, no migration,
|
|
82
|
+
no config, no persisted state. The only non-route change is a single frontmatter line in a spec.
|
|
83
|
+
|
|
84
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
85
|
+
|
|
86
|
+
**Machine-local by design, and correctly so — no durable state is introduced.** The counters
|
|
87
|
+
describe what *this* server process has observed; they are in-memory, per-process, and reset on
|
|
88
|
+
restart, which is a property of the existing verifier and not something this change alters. There
|
|
89
|
+
is nothing here to replicate: a peer's counters are a different fact about a different process,
|
|
90
|
+
not a stale copy of the same fact. `scope: 'local'` is stated explicitly in the response so a
|
|
91
|
+
reader never mistakes it for a pool figure, and the pool projection continues to live on the audit
|
|
92
|
+
route where the underlying records are durable.
|
|
93
|
+
|
|
94
|
+
## 8. Rollback cost
|
|
95
|
+
|
|
96
|
+
Delete the route and revert one frontmatter line. Nothing persists, nothing migrates, no consumer
|
|
97
|
+
exists yet that could break. The feature returns to exactly its present state: running, observing,
|
|
98
|
+
and unable to graduate.
|
|
99
|
+
|
|
100
|
+
## Evidence
|
|
101
|
+
|
|
102
|
+
Tests verified to fail against unmodified source before the fix was applied:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
# route reverted
|
|
106
|
+
Tests 5 failed (5)
|
|
107
|
+
✗ 503s when the verifier is absent
|
|
108
|
+
✗ returns the raw counters when present
|
|
109
|
+
✗ surfaces the spec's graduation metric name
|
|
110
|
+
✗ reports zero honestly rather than omitting the metric
|
|
111
|
+
✗ is read-only
|
|
112
|
+
|
|
113
|
+
# route applied
|
|
114
|
+
Tests 5 passed (5)
|
|
115
|
+
tsc --noEmit — clean
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The "reports zero honestly" case is the one worth keeping: a dark feature that has classified
|
|
119
|
+
nothing must read as `0`, never as an absent field, because absent is indistinguishable from
|
|
120
|
+
"the endpoint does not work" — which is precisely the condition this route exists to end.
|