instar 1.3.239 → 1.3.241

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,92 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Enforces the dogfooded-channel standard (APPRENTICESHIP-PROGRAM-PROJECT-DESIGN §4a) in
9
+ code. Every `ApprenticeshipCycleStore` record now carries a `channel`
10
+ (`telegram-playwright` | `threadline-backup` | `direct-shortcut` | `unknown`), with an
11
+ idempotent migration for existing DBs. `roleCoverage` now counts a
12
+ `mentor-mentee-differential` cycle toward the keystone axis **only** when it did NOT run
13
+ through a `direct-shortcut` — a shortcut cycle is still recorded (for honesty, surfaced via
14
+ the new `shortcutDifferentialCount`) but can never make the keystone look healthy. Unset /
15
+ pre-field channels are grandfathered as `unknown` and still count, so an already-earned
16
+ keystone is never retroactively un-fired.
17
+
18
+ Adds a structural dedup at the Telegram→session delivery chokepoint
19
+ (`SessionManager.injectTelegramMessage`). A given Telegram `messageId` now reaches a
20
+ session at most once: the method keeps a small per-`(session, messageId)` ledger (pruned on
21
+ a 10-minute window) and, on a repeat, suppresses the re-injection and logs it instead of
22
+ delivering the same user message again. `messageId` is threaded in from both delivery paths
23
+ — the `/internal/telegram-forward` route (lifeline → server) and the in-process
24
+ `telegram→session` path. Callers that carry no positive `messageId` are unaffected (no
25
+ dedup), preserving back-compat.
26
+
27
+ This closes an observed bug where a single user message was forwarded to a codex session
28
+ five times over ~50s (upstream re-forward while the session was starting), making the agent
29
+ queue and re-process the same task repeatedly. The suppression is logged so the upstream
30
+ over-forward stays visible for a separate root-cause.
31
+
32
+ Path 1 of the Threadline A2A continuity fix. Headless one-shot `claude -p` reply
33
+ sessions never report their session id, so the resume entry kept a placeholder
34
+ uuid and every spaced follow-up cold-spawned (a live Echo↔Dawn round-trip proved
35
+ Resumed=0 even after the prior foundation shipped).
36
+
37
+ Fix: `ThreadlineRouter.spawnNewThread` now mints a uuid and launches the reply
38
+ session with `claude --session-id <uuid>` (so its transcript is created at exactly
39
+ `<uuid>.jsonl`), persisting that uuid as the thread's resume handle.
40
+ `resumeThread` launches the follow-up with `claude --resume <uuid>` — reloading
41
+ the full prior transcript — and sends only the new message + grounding (the
42
+ transcript already holds the history, so re-injecting it is dropped). The
43
+ `sessionId`/`resumeSessionId` options are threaded through
44
+ `SessionManager.spawnSession` (spliced as `--session-id`/`--resume` into the
45
+ claude-code headless argv before `-p`, mutually exclusive, gated on framework) and
46
+ `SpawnRequestManager`. Additive + gated: every non-A2A spawn is unchanged when the
47
+ options are absent; the stale-uuid resume-crash guard falls back to a fresh spawn.
48
+
49
+ ## What to Tell Your User
50
+
51
+ Nothing user-facing changes. Internally, the apprenticeship now records which channel each
52
+ mentor↔mentee cycle ran through and structurally refuses to let a CLI/API shortcut count as
53
+ real keystone progress — so "is the real mentorship happening?" can't be faked.
54
+
55
+ Nothing user-facing changes in how you talk to your agent. Under the hood, if a single
56
+ message you send ever got relayed to the agent more than once (a rare hiccup when the agent
57
+ was just starting up), the agent now notices and acts on it only once — so it won't
58
+ double-work a request or burn extra model usage repeating itself. A note is written to the
59
+ log each time a duplicate is caught, so the underlying relay glitch can still be chased down.
60
+
61
+ When another agent and I have a back-and-forth, I now stay in the same
62
+ conversation across messages instead of forgetting between each one — so the
63
+ realistic turn-based exchange (which the upcoming agent-to-agent feedback work
64
+ relies on) holds its thread. It's an internal robustness fix; nothing to turn on.
65
+ The rapid-fire 3-messages-in-10-seconds case is smoothed by a separate follow-up.
66
+
67
+ ## Summary of New Capabilities
68
+
69
+ - **Turn-based agent-to-agent continuity:** a peer agent's follow-up message now resumes the SAME conversation (full prior context) instead of cold-spawning a memoryless reply session.
70
+
71
+ ## Evidence
72
+
73
+ Verified on this machine, before/after.
74
+
75
+ **Mechanism (directly verified, 2026-06-04):** `claude --session-id
76
+ a6b1c2d3-…-0002 -p "remember BANANA77"` created the transcript at exactly
77
+ `<that-uuid>.jsonl` (29 lines). `claude --resume <that-uuid> -p "what was the
78
+ secret word?"` grew the SAME transcript to 55 lines and the planted word
79
+ `BANANA77` appears in the resumed transcript (6×) — i.e. `--resume` reloaded the
80
+ full prior conversation. So set-then-resume by a known id gives real continuity.
81
+
82
+ **Before (live A6 round-trip, `echo-dawn-A6-verify-…`, server on v1.3.239):**
83
+ 2× `[relay] Spawned session`, 0× `Resumed`; `onSessionComplete` fired + matched
84
+ the thread ("demoted 1 thread(s)") but `claudeSessionId` POST events = 0, so the
85
+ placeholder uuid was never upgraded → `jsonlExists` failed → cold-spawn.
86
+
87
+ **After (unit):** spawning with `sessionId` emits `--session-id <uuid>` before
88
+ `-p`; with `resumeSessionId` emits `--resume <uuid>`; neither when absent.
89
+ `spawnNewThread` persists the minted uuid as the entry uuid (not a placeholder);
90
+ `resumeThread` forwards `resumeSessionId = entry.uuid`. tsc clean; 1548
91
+ threadline/fixes/continuity tests green. The live Dawn round-trip showing
92
+ `Resumed` (not `Spawned`) runs post-deploy.
@@ -0,0 +1,16 @@
1
+ # Enforcing the dogfooded channel — explained simply
2
+
3
+ The apprenticeship rule (just documented) is: a mentor must drive its mentee through
4
+ the REAL Telegram UX (via Playwright), because experiencing the user experience IS the
5
+ test. A shortcut — calling the mentee's CLI directly — gets an answer but skips the
6
+ thing we're actually testing.
7
+
8
+ A doc alone is a wish. This makes it a guarantee: every recorded apprenticeship cycle
9
+ now writes down WHICH channel it actually ran through. And the keystone check — the one
10
+ that tells us the real mentor↔mentee work is happening — only counts a cycle if it ran
11
+ through the real channel (Telegram-Playwright, or the backup). A shortcut cycle is still
12
+ saved (so we're honest it happened), but it does NOT light up the keystone. So a shortcut
13
+ can never make the program look healthier than it is.
14
+
15
+ Older cycles (recorded before this field existed) are grandfathered in — they still
16
+ count, so this never retroactively "un-fires" a keystone we already earned.
@@ -0,0 +1,39 @@
1
+ # Side-Effects Review — Apprenticeship dogfooded-channel enforcement
2
+
3
+ **Slug:** `apprenticeship-channel-enforcement` · **Date:** `2026-06-04` · **Author:** `echo`
4
+ **Second-pass reviewer:** `not required` (additive field + a counting gate; grandfathered)
5
+
6
+ ## Summary
7
+
8
+ Adds a `channel` field to ApprenticeshipCycleStore records (`telegram-playwright` |
9
+ `threadline-backup` | `direct-shortcut` | `unknown`), an idempotent ALTER-TABLE migration
10
+ for existing DBs (default `unknown`), and a `roleCoverage` gate: a
11
+ `mentor-mentee-differential` cycle on `direct-shortcut` is recorded but does NOT count
12
+ toward the keystone axis (surfaced via a new `shortcutDifferentialCount`). Enforces the
13
+ §4a dogfooded-channel standard in code.
14
+
15
+ ## Decision-point inventory
16
+
17
+ One new branch in `roleCoverage`: a mentor-mentee-differential cycle is skipped from axis
18
+ tallying iff `channel === 'direct-shortcut'`. Covered both-sides (dogfooded fires; shortcut
19
+ doesn't + increments the counter; grandfathered/backup fire).
20
+
21
+ ## 1. Over-block
22
+
23
+ Rejects nothing. Garbage/unset channels normalize to `unknown`, which COUNTS (grandfathered)
24
+ — so it never retroactively un-fires an earned keystone (cycle #4, recorded pre-field, holds).
25
+ Only an EXPLICIT `direct-shortcut` is gated, and even then the cycle is still stored + listed.
26
+
27
+ ## 2. Under-block
28
+
29
+ The mentor tick (#743) records cycles without setting `channel` → they default to `unknown`
30
+ → count. That is intentionally grandfathered for now; aligning the automated tick to set
31
+ `direct-shortcut` (or to drive via Playwright, the real fix) is the follow-on once Codey's
32
+ Playwright driving capability exists. The gate is per-axis-tally; it does not retro-rewrite
33
+ stored rows.
34
+
35
+ ## 3. Level-of-abstraction fit
36
+
37
+ Right layer: the enforcement lives in the cycle store's `roleCoverage` (the single place the
38
+ keystone is computed), reusing the existing axis-tally loop. No route/server change — the
39
+ existing `GET /apprenticeship/instances/:id/role-coverage` surfaces the new field for free.
@@ -0,0 +1,69 @@
1
+ # Side-Effects Review — Telegram delivery-chokepoint dedup
2
+
3
+ **Version / slug:** `telegram-delivery-dedup`
4
+ **Date:** `2026-06-03`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ `SessionManager.injectTelegramMessage` gains an optional trailing `messageId` param and a
11
+ structural dedup: a `(tmuxSession, messageId)` ledger (pruned on a 10-minute window) ensures
12
+ a given Telegram message reaches a session at most once. A repeat is suppressed (returns
13
+ `true` — already delivered) and logged via `console.warn`. `messageId` is threaded from the
14
+ two delivery callers: the `/internal/telegram-forward` route and the in-process
15
+ `telegram→session` path (derived from `pipeline.id` = `tg-{messageId}`).
16
+
17
+ ## Decision-point inventory
18
+
19
+ One decision: "have I already delivered this `(session, messageId)` within the window?"
20
+ → suppress + log, else record + deliver. Gated on `messageId` being a positive number.
21
+
22
+ ## 1. Over-block
23
+
24
+ **What legitimate inputs does this reject?** Only a re-delivery of the SAME Telegram
25
+ `messageId` to the SAME session within 10 minutes — which is, by definition, a duplicate of
26
+ a message already delivered (Telegram `message_id` is unique per chat; a genuine re-send is a
27
+ NEW id). The first delivery always lands. Distinct messages (distinct ids), messages to
28
+ different sessions, and any caller that supplies no positive `messageId` (e.g. `0`/undefined)
29
+ are never deduped. So no legitimate distinct user message is dropped.
30
+
31
+ ## 2. Under-block
32
+
33
+ **What does this still miss?** It does not fix the *upstream* cause of the over-forward
34
+ (lifeline re-forward / PendingRelayStore re-drive / sentinel pause+resume) — it is a
35
+ chokepoint backstop. The suppression `console.warn` is deliberately retained so the upstream
36
+ multiplicity remains observable for a follow-up root-cause. It also does not dedupe non-
37
+ Telegram delivery paths (WhatsApp/iMessage inject have their own methods, out of scope).
38
+
39
+ ## 3. Level-of-abstraction fit
40
+
41
+ **Right layer?** Yes. `injectTelegramMessage` is the single server→session delivery
42
+ chokepoint that writes the `/tmp/instar-telegram` file and does the tmux inject; every
43
+ Telegram delivery path funnels through it. Guarding there means the dedup holds regardless of
44
+ which upstream component over-forwards (defense-in-depth, Structure > Willpower). The two
45
+ callers only thread the id through.
46
+
47
+ ## 4. Signal vs authority compliance
48
+
49
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
50
+
51
+ The dedup is an authority (it suppresses a delivery), but a tightly-scoped and conservative
52
+ one: it acts only on an exact `(session, messageId)` duplicate within a short window, and
53
+ returns success (the message WAS delivered on the first call), so callers see no failure. The
54
+ `console.warn` preserves the signal of the upstream over-forward for diagnosis. No user
55
+ message is lost.
56
+
57
+ ## 5. Reversibility / blast radius
58
+
59
+ Bounded. The ledger is an in-memory `Map` pruned on a 10-minute window (naturally bounded by
60
+ message volume). No persistence, no schema, no config, no migration. If the dedup ever
61
+ misbehaved, the change is a few lines in one method plus two one-line caller edits; reverting
62
+ is trivial. The only behavioral change is "the exact same Telegram message is not injected
63
+ into the same session twice within 10 minutes."
64
+
65
+ ## 6. Test coverage
66
+
67
+ `tests/unit/session-telegram-inject.test.ts` — both sides of the boundary with realistic
68
+ input: repeat id → suppressed (one file); distinct id → delivered (two files); no id →
69
+ no dedup (two files); id `0` → no dedup; same id to two sessions → both deliver.
@@ -0,0 +1,59 @@
1
+ # Side-Effects Review — Threadline A2A resume via deterministic session-id
2
+
3
+ **Version / slug:** `threadline-a2a-resume-session-id`
4
+ **Date:** `2026-06-04`
5
+ **Author:** `Echo (instar dev agent)`
6
+ **Second-pass reviewer:** `not required (Tier 1)`
7
+
8
+ ## Summary of the change
9
+
10
+ Path 1 of the A2A continuity fix. Headless `claude -p` reply sessions never reported their session id, so `onSessionComplete` left the resume entry's uuid as a placeholder and resume-after-exit cold-spawned. Fix: when `ThreadlineRouter.spawnNewThread` spawns the first reply session it mints a uuid and passes it as `claude --session-id <uuid>` (so the transcript is created at exactly `<uuid>.jsonl`), and persists that uuid as the resume entry. `resumeThread` spawns the follow-up with `claude --resume <uuid>` (reloading the full transcript) and a minimal prompt (new message + grounding only — the transcript already holds history). Threaded through: `SessionManager.spawnSession` (new `sessionId?`/`resumeSessionId?` options → `--session-id`/`--resume` spliced into the claude-code headless argv before `-p`, mutually exclusive, gated on framework), `SpawnRequestManager` (forwards both from `SpawnRequest` → callback), the relay `spawnSession` callback in `server.ts`. Files: `src/core/SessionManager.ts`, `src/messaging/SpawnRequestManager.ts`, `src/commands/server.ts`, `src/threadline/ThreadlineRouter.ts` + tests.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `SessionManager.spawnSession` headless argv (`src/core/SessionManager.ts`) — **modify (additive)** — splice `--session-id`/`--resume` when the new options are set; no change when absent.
15
+ - `ThreadlineRouter.spawnNewThread` resume-entry uuid (`src/threadline/ThreadlineRouter.ts`) — **modify** — save the real minted uuid instead of the placeholder.
16
+ - `ThreadlineRouter.resumeThread` prompt + spawn (`src/threadline/ThreadlineRouter.ts`) — **modify** — pass `resumeSessionId` + minimal prompt (drop re-injected history).
17
+ - `SpawnRequest`/`SpawnRequestManager.evaluate` forwarding — **modify (additive)** — carry `sessionId`/`resumeSessionId`.
18
+
19
+ ## 1. Over-block
20
+
21
+ **What legitimate inputs does this change reject that it shouldn't?**
22
+
23
+ None — no allow/block surface. The flags only set/resume the conversation id. No message is rejected. When the new options are absent (every non-A2A spawn), behavior is unchanged. "No block/allow surface — over-block not applicable."
24
+
25
+ ## 2. Under-block
26
+
27
+ **What failure modes does this still miss?**
28
+
29
+ Rapid-fire (multiple messages while the reply session is still running): `--resume` on a still-busy session, or a fresh spawn hitting the 30s cooldown, is unchanged by this slice — that is the warm-session follow-up. Also: a `--resume` against a transcript that was deleted/rotated falls back to a fresh spawn (handled by the existing stale-uuid guard), losing that thread's history — acceptable + lossless beyond history.
30
+
31
+ ## 3. Level-of-abstraction fit
32
+
33
+ The uuid is minted + owned by `ThreadlineRouter` (which owns the resume entry) and threaded down to the spawn primitive — the right layering. `SessionManager` only learns "set/resume this id," mirroring its existing `--resume` (triage) and `extraClaudeFlags` patterns.
34
+
35
+ ## 4. Signal vs authority compliance
36
+
37
+ Not a gate/authority change. No approval surface added or relaxed. Trust gates on the inbound path are untouched.
38
+
39
+ ## 5. Interactions
40
+
41
+ - Builds on shipped slices 1+2 (router finds the live session + real tmux name). With this, `get()`'s `jsonlExists(uuid)` now passes legitimately (the transcript exists at the minted id) → resume path fires for spaced follow-ups.
42
+ - The relay `spawnSession` callback keeps `codexAllowMcpTools: true` + all MCP/permission flags → the A2A worker still replies via `threadline_send`.
43
+ - 1548 threadline/fixes/continuity unit tests pass; tsc clean. One obsolete test (`includes thread history in resume prompt`) replaced — it asserted the now-removed history-re-injection behavior.
44
+
45
+ ## 6. External surfaces
46
+
47
+ No new HTTP routes, no config defaults, no template/CLAUDE.md change. Pure internal session-lifecycle behavior. Migration-parity: nothing to migrate (no config/hook/template surface).
48
+
49
+ ## 7. Rollback cost
50
+
51
+ Low — revert the four source files. No persisted-format change (the resume entry already had a `uuid` field; it now holds a real id instead of a placeholder — old placeholder entries simply fail `jsonlExists` and cold-spawn as before). No external dependency.
52
+
53
+ ## Conclusion
54
+
55
+ Low-risk, additive, deterministic fix that completes the realistic turn-based A2A continuity case on top of the shipped foundation. The mechanism (`--session-id` set + `--resume` reload) was empirically verified before coding. Rapid-fire smoothness is explicitly out of scope.
56
+
57
+ ## Second-pass review (if required)
58
+
59
+ Not required — Tier 1 (additive, gated, ~single-concern).
@@ -0,0 +1,28 @@
1
+ # Why agent-to-agent conversations can now actually remember
2
+
3
+ ## The one-sentence version
4
+ When another agent sends me several messages over a conversation, each of my replies used to run as a throwaway that finished and forgot everything — so the next message met a stranger. Now I give each reply session a **known name tag** when it starts, write that tag down, and on the next message I **re-open the exact same conversation by its tag** — so I pick up right where we left off, with the full history.
5
+
6
+ ## What was broken (proven live)
7
+ A real round-trip with Dawn showed every follow-up still cold-spawned a memoryless session. The deep reason: each reply ran as a headless one-shot Claude that **never reports its own session id back to me**, so when it finished I had no handle to reconnect to. My earlier fix made the router *try* to reconnect, but there was nothing to reconnect to.
8
+
9
+ ## The fix (deterministic, no guessing)
10
+ Claude lets you (a) **set** a session's id when you launch it (`--session-id <uuid>`) and (b) **re-open** a session by that id later (`--resume <uuid>`). I verified both directly: setting an id creates the transcript under that exact id, and resuming it reloads the whole prior conversation (I planted a secret word in turn 1 and the resumed session recalled it).
11
+
12
+ So now:
13
+ - When a peer's first message spawns a reply session, I **mint a fresh id and pass it as `--session-id`**, then store that id as the thread's resume handle.
14
+ - When the next message arrives, I spawn with **`--resume <that id>`** so the session reloads the full prior transcript and continues — and I send it only the new message (the transcript already holds the history, so re-pasting it would just duplicate it).
15
+
16
+ ## The safety rails
17
+ 1. **Additive + off by default.** The new options do nothing unless set. Every other kind of session (jobs, your topic sessions, Codex) is byte-for-byte unchanged.
18
+ 2. **Claude-code only, and the two flags are mutually exclusive** — a launch either sets a new id or resumes one, never both.
19
+ 3. **A stale id can't wedge me.** If a transcript no longer exists, the existing resume-crash guard falls back to a fresh spawn instead of hanging.
20
+ 4. **The reply path is untouched.** Setting/resuming the conversation id doesn't change tools or permissions, so the agent still replies normally over the secure channel.
21
+ 5. **Built on the shipped foundation** (the router already finds the live session + carries the real session name); this adds the missing piece — the real, resumable id.
22
+
23
+ ## What it does NOT yet fix
24
+ The *rapid-fire* case (three messages in ten seconds, while the reply is still being written) still isn't smooth — that needs the bigger persistent-session work. This fixes the realistic **turn-based** conversation (you reply, I reply, you reply a minute later), which is exactly what the agent-to-agent feedback migration needs.
25
+
26
+ ---
27
+
28
+ **Rendered (verified) view:** _set below after creating the tunnel view._