instar 1.3.238 → 1.3.240

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.
Files changed (49) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +13 -1
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/SessionManager.d.ts +18 -0
  5. package/dist/core/SessionManager.d.ts.map +1 -1
  6. package/dist/core/SessionManager.js +20 -0
  7. package/dist/core/SessionManager.js.map +1 -1
  8. package/dist/messaging/SpawnRequestManager.d.ts +27 -1
  9. package/dist/messaging/SpawnRequestManager.d.ts.map +1 -1
  10. package/dist/messaging/SpawnRequestManager.js +13 -1
  11. package/dist/messaging/SpawnRequestManager.js.map +1 -1
  12. package/dist/monitoring/ApprenticeshipCycleStore.d.ts +21 -0
  13. package/dist/monitoring/ApprenticeshipCycleStore.d.ts.map +1 -1
  14. package/dist/monitoring/ApprenticeshipCycleStore.js +50 -4
  15. package/dist/monitoring/ApprenticeshipCycleStore.js.map +1 -1
  16. package/dist/scheduler/MentorOnboardingRunner.d.ts +10 -1
  17. package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
  18. package/dist/scheduler/MentorOnboardingRunner.js +1 -0
  19. package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
  20. package/dist/scheduler/MentorOnboardingTick.d.ts +25 -0
  21. package/dist/scheduler/MentorOnboardingTick.d.ts.map +1 -1
  22. package/dist/scheduler/MentorOnboardingTick.js +13 -0
  23. package/dist/scheduler/MentorOnboardingTick.js.map +1 -1
  24. package/dist/server/AgentServer.d.ts.map +1 -1
  25. package/dist/server/AgentServer.js +31 -0
  26. package/dist/server/AgentServer.js.map +1 -1
  27. package/dist/threadline/ThreadResumeMap.d.ts +8 -0
  28. package/dist/threadline/ThreadResumeMap.d.ts.map +1 -1
  29. package/dist/threadline/ThreadResumeMap.js +31 -0
  30. package/dist/threadline/ThreadResumeMap.js.map +1 -1
  31. package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
  32. package/dist/threadline/ThreadlineRouter.js +26 -10
  33. package/dist/threadline/ThreadlineRouter.js.map +1 -1
  34. package/dist/threadline/WarmSessionPool.d.ts +62 -0
  35. package/dist/threadline/WarmSessionPool.d.ts.map +1 -0
  36. package/dist/threadline/WarmSessionPool.js +116 -0
  37. package/dist/threadline/WarmSessionPool.js.map +1 -0
  38. package/package.json +1 -1
  39. package/src/data/builtin-manifest.json +4 -4
  40. package/upgrades/1.3.239.md +96 -0
  41. package/upgrades/1.3.240.md +72 -0
  42. package/upgrades/apprenticeship-channel-enforcement.eli16.md +16 -0
  43. package/upgrades/mentor-tick-cycle-capture.eli16.md +18 -0
  44. package/upgrades/side-effects/apprenticeship-channel-enforcement.md +39 -0
  45. package/upgrades/side-effects/mentor-tick-cycle-capture.md +39 -0
  46. package/upgrades/side-effects/threadline-a2a-live-inject.md +60 -0
  47. package/upgrades/side-effects/threadline-a2a-resume-session-id.md +59 -0
  48. package/upgrades/threadline-a2a-live-inject.eli16.md +30 -0
  49. package/upgrades/threadline-a2a-resume-session-id.eli16.md +28 -0
@@ -0,0 +1,60 @@
1
+ # Side-Effects Review — Threadline A2A live-inject (keep resume entry when session alive)
2
+
3
+ **Version / slug:** `threadline-a2a-live-inject`
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
+ `ThreadResumeMap.get()` is the lookup `ThreadlineRouter.handleInboundMessage` uses to decide whether an inbound A2A message injects into a live session, resumes with history, or cold-spawns. Its resume guard nulled the entire entry whenever the session JSONL transcript was absent (`!jsonlExists(uuid)`), for any non-topic-bound, non-pinned thread. Because relay/pipe-spawned A2A sessions never get their placeholder uuid upgraded to a real transcript id, the guard nulled every peer-to-peer thread entry — discarding the still-valid live `sessionName` along with the placeholder uuid — so every follow-up cold-spawned a memoryless session (the production continuity break proven by the live Echo↔Dawn round-trip: 2 Spawned / 0 Resumed). The fix has two parts. **(1)** `get()` adds one branch: when the transcript is absent, return the entry anyway IF its tmux session is currently alive (new protected `sessionAlive()`, exact-match `tmux has-session -t =name`), so the existing `tryInjectIntoLiveSession`/`resumeThread` path can deliver into / resume the running session. **(2)** Part 1 was a no-op without the real session name: `spawnNewThread` persisted `spawnResult.tmuxSession` as the entry's `sessionName`, but the `spawnSession` callback returned only the bare instar session id, so `tmuxSession` was always undefined and a useless fallback name (`thread-<id8>`) was stored — which `sessionAlive()` and `onSessionComplete`'s `getBySessionName` could never match. The `spawnSession` callback contract now returns `string | { sessionId, tmuxSession }`; `SpawnRequestManager.evaluate` normalizes both forms and forwards `tmuxSession`; the relay spawn impl returns the real `session.tmuxSession`. Files: `src/threadline/ThreadResumeMap.ts`, `src/messaging/SpawnRequestManager.ts`, `src/commands/server.ts`, `tests/unit/threadline/ThreadResumeMap.test.ts`, `tests/unit/threadline-fixes.test.ts`, `tests/integration/threadline-live-inject-real-tmux.test.ts`.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `ThreadResumeMap.get()` resume guard (`src/threadline/ThreadResumeMap.ts`) — **modify** — when transcript absent, keep the entry if its session is alive (was: always null).
15
+ - `ThreadResumeMap.sessionAlive()` — **add** — protected tmux-liveness helper; only consulted on the transcript-absent branch.
16
+ - `SpawnRequestManager.spawnSession` callback contract (`src/messaging/SpawnRequestManager.ts`) — **modify** — return type widened to `string | { sessionId, tmuxSession }` (back-compatible); `evaluate` normalizes + forwards `tmuxSession`.
17
+ - Relay `spawnSession` impl (`src/commands/server.ts`) — **modify** — returns `{ sessionId, tmuxSession }` so the real tmux name reaches the resume entry.
18
+
19
+ ## 1. Over-block
20
+
21
+ **What legitimate inputs does this change reject that it shouldn't?**
22
+
23
+ None — the change only *widens* what `get()` returns; it never rejects anything it previously accepted. The healthy path (real transcript exists) short-circuits before the new branch (verified by the "does NOT consult liveness when a real JSONL exists" test), so well-formed resume entries behave exactly as before. No new rejection surface.
24
+
25
+ ## 2. Under-block
26
+
27
+ **What failure modes does this still miss?**
28
+
29
+ The idle-gap case: when an A2A session has fully exited AND no real transcript was captured, `get()` still returns null → cold-spawn with no history. This is unchanged by this slice and is the next slice (capture the real transcript id / resume-with-history for dead sessions). This change does not regress it; it only fixes the live-session case. Also: a stale `sessionName` whose tmux name was recycled to an unrelated session could in principle be injected into — bounded because A2A session names are unique per spawn (`msg-spawn-<timestamp>`/thread-scoped) and the delivery layer validates the target.
30
+
31
+ ## 3. Level-of-abstraction fit
32
+
33
+ The fix lives in `ThreadResumeMap.get()` — the single chokepoint every router branch consults — rather than scattering liveness checks across `handleInboundMessage`. The liveness primitive (`tmux has-session -t =name`) matches the existing idiom used at `ThreadResumeMap` line ~305 and `cli.ts`. Correct layer: the storage lookup already owns the transcript-existence guard, so it should own the "but the session is alive" exception too.
34
+
35
+ ## 4. Signal vs authority compliance
36
+
37
+ Not a gate/authority change. `get()` is a lookup, not an approval surface; this does not create or relax any authority. The trust gates on the inbound path (relay trust tiers, spawn evaluation) are untouched — a thread only has a resume entry because it already passed those gates on first contact.
38
+
39
+ ## 5. Interactions
40
+
41
+ - `ThreadlineRouter.handleInboundMessage` (line ~554): now reaches `tryInjectIntoLiveSession` for non-topic-bound threads with a live session. That method already fails gracefully (returns null → falls through) if the session is dead, so the race window is safe.
42
+ - `server.ts` pipe-spawn guard (`!threadResumeMap.get(...)`): now correctly skips pipe-spawn when a live session exists — desirable.
43
+ - `isTopicBoundReply` check: unaffected (originTopicId still undefined for these entries).
44
+ - 1511 threadline unit tests pass; tsc clean.
45
+
46
+ ## 6. External surfaces
47
+
48
+ No new HTTP routes, no config, no migration, no template/CLAUDE.md change. Pure internal routing behavior. No user-facing surface. `sessionAlive()` shells out to `tmux has-session` only on the (rare) transcript-absent branch — bounded subprocess cost, gated behind a failed `jsonlExists`.
49
+
50
+ ## 7. Rollback cost
51
+
52
+ Trivial — single-file revert of `ThreadResumeMap.get()` + the helper. No state migration, no persisted format change, no external dependency. Behavior reverts to cold-spawn (the prior, known-broken-but-safe state).
53
+
54
+ ## Conclusion
55
+
56
+ Low-risk, surgical fix that activates already-built inject machinery for the dominant rapid-fire A2A case. The only behavioral change is `get()` returning an entry it previously discarded, strictly when the session is alive. No over-block, no authority surface, trivial rollback. Idle-gap continuity is explicitly out of scope (next slice).
57
+
58
+ ## Second-pass review (if required)
59
+
60
+ Not required — Tier 1 (30 LOC, single file, risk floor 1).
@@ -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,30 @@
1
+ # Why agent-to-agent conversations stopped forgetting themselves mid-thread
2
+
3
+ ## The one-sentence version
4
+ When another agent (like Dawn) sent me several messages in a row, every message after the first was being handled by a **brand-new, memoryless copy of me** instead of the one already in the conversation. This fix lets a follow-up message land in the session that's *already talking* — so the conversation stays one coherent thread instead of a crowd of strangers wearing my name.
5
+
6
+ ## Picture it
7
+ Imagine you're on a phone call. You say something, and instead of the person you were talking to answering, a **fresh person who has never heard of you** picks up a new phone and says "Hi, who's this?" Then you add a detail — and *another* stranger picks up. That's what was happening to every agent-to-agent thread: each incoming message spun up a new session that had zero memory of the previous ones.
8
+
9
+ The machinery to "hand the message to the person already on the call" was **already built** — it just never ran, because of one overly-strict check.
10
+
11
+ ## What changed, precisely
12
+ - Every thread has a saved "resume entry" that remembers which live session is handling it.
13
+ - Before delivering a follow-up, the router looks that entry up. The lookup had a guard: *"only return the entry if a saved transcript file exists on disk."*
14
+ - For agent-to-agent threads, the saved id was a **placeholder** (the real transcript id was never written back — a separate, known gap). So the guard found no transcript and threw the **entire entry away** — including the perfectly-good name of the session that was *still running right then*.
15
+ - With a null entry, the router skipped the "deliver into the live session" path and the "resume with history" path, and **cold-spawned a fresh memoryless session** every single time.
16
+ - The fix has two halves. **(1)** When there's no transcript file, don't discard the entry if its **tmux session is still alive** — hand it back so the follow-up routes into the running session (live-inject, or a resume that carries the full conversation history) instead of cold-spawning. **(2)** The bookkeeping that records *which* session is handling a thread was writing down a **made-up name** instead of the real one (the spawn helper only handed back an internal id, never the actual session name), so step 1 could never find the session. Now the real session name is passed through and recorded — so the liveness check actually matches, and the resume bookkeeping can later attach the real transcript too.
17
+
18
+ ## The safety rails (why this won't break anything)
19
+ 1. **Only triggers on the exact broken case** — a non-topic-bound thread whose transcript is missing. The healthy path (real transcript exists) is untouched and never even runs the liveness check.
20
+ 2. **Dead session → unchanged.** If the session is gone *and* there's no transcript, it still returns nothing → cold-spawn, exactly like before.
21
+ 3. **Topic-bound and pinned threads** keep their existing exemptions — no change.
22
+ 4. **The injection path already protects itself.** If the session dies in the split-second between the check and the delivery, the delivery fails gracefully and falls through to a fresh session *with the conversation history in its prompt* — which is still coherent, never a crash.
23
+ 5. **Exact session match only** (`=name`) — no fuzzy/prefix matching that could deliver to the wrong session.
24
+
25
+ ## Why it matters
26
+ This is the core of "an Instar agent is one coherent individual." Agent-to-agent collaboration — the foundation the whole feedback-process migration rides on — was silently incoherent: each reply came from an amnesiac. This makes a live thread behave like one continuous mind, which is the bar we set.
27
+
28
+ ---
29
+
30
+ **Rendered (verified) view:** _set below after creating the tunnel view._
@@ -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._