instar 1.3.237 → 1.3.239

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 (58) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +41 -3
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +8 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/types.d.ts +37 -0
  8. package/dist/core/types.d.ts.map +1 -1
  9. package/dist/core/types.js.map +1 -1
  10. package/dist/messaging/SpawnRequestManager.d.ts +4 -1
  11. package/dist/messaging/SpawnRequestManager.d.ts.map +1 -1
  12. package/dist/messaging/SpawnRequestManager.js +8 -1
  13. package/dist/messaging/SpawnRequestManager.js.map +1 -1
  14. package/dist/monitoring/GeminiLoopRunner.d.ts +93 -0
  15. package/dist/monitoring/GeminiLoopRunner.d.ts.map +1 -0
  16. package/dist/monitoring/GeminiLoopRunner.js +134 -0
  17. package/dist/monitoring/GeminiLoopRunner.js.map +1 -0
  18. package/dist/monitoring/geminiLoopProduction.d.ts +62 -0
  19. package/dist/monitoring/geminiLoopProduction.d.ts.map +1 -0
  20. package/dist/monitoring/geminiLoopProduction.js +124 -0
  21. package/dist/monitoring/geminiLoopProduction.js.map +1 -0
  22. package/dist/scheduler/MentorOnboardingRunner.d.ts +10 -1
  23. package/dist/scheduler/MentorOnboardingRunner.d.ts.map +1 -1
  24. package/dist/scheduler/MentorOnboardingRunner.js +1 -0
  25. package/dist/scheduler/MentorOnboardingRunner.js.map +1 -1
  26. package/dist/scheduler/MentorOnboardingTick.d.ts +25 -0
  27. package/dist/scheduler/MentorOnboardingTick.d.ts.map +1 -1
  28. package/dist/scheduler/MentorOnboardingTick.js +13 -0
  29. package/dist/scheduler/MentorOnboardingTick.js.map +1 -1
  30. package/dist/server/AgentServer.d.ts +8 -0
  31. package/dist/server/AgentServer.d.ts.map +1 -1
  32. package/dist/server/AgentServer.js +84 -0
  33. package/dist/server/AgentServer.js.map +1 -1
  34. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  35. package/dist/server/CapabilityIndex.js +1 -0
  36. package/dist/server/CapabilityIndex.js.map +1 -1
  37. package/dist/server/routes.d.ts +3 -0
  38. package/dist/server/routes.d.ts.map +1 -1
  39. package/dist/server/routes.js +45 -0
  40. package/dist/server/routes.js.map +1 -1
  41. package/dist/threadline/ThreadResumeMap.d.ts +8 -0
  42. package/dist/threadline/ThreadResumeMap.d.ts.map +1 -1
  43. package/dist/threadline/ThreadResumeMap.js +31 -0
  44. package/dist/threadline/ThreadResumeMap.js.map +1 -1
  45. package/dist/threadline/WarmSessionPool.d.ts +62 -0
  46. package/dist/threadline/WarmSessionPool.d.ts.map +1 -0
  47. package/dist/threadline/WarmSessionPool.js +116 -0
  48. package/dist/threadline/WarmSessionPool.js.map +1 -0
  49. package/package.json +1 -1
  50. package/src/data/builtin-manifest.json +47 -47
  51. package/upgrades/1.3.238.md +68 -0
  52. package/upgrades/1.3.239.md +96 -0
  53. package/upgrades/mentor-tick-cycle-capture.eli16.md +18 -0
  54. package/upgrades/side-effects/gemini-loop-driver-wiring.md +59 -0
  55. package/upgrades/side-effects/mentor-tick-cycle-capture.md +39 -0
  56. package/upgrades/side-effects/parallel-activity-coherence.md +9 -0
  57. package/upgrades/side-effects/threadline-a2a-live-inject.md +60 -0
  58. package/upgrades/threadline-a2a-live-inject.eli16.md +30 -0
@@ -0,0 +1,39 @@
1
+ # Side-Effects Review — Mentor tick keystone cycle capture
2
+
3
+ **Slug:** `mentor-tick-cycle-capture` · **Date:** `2026-06-04` · **Author:** `echo`
4
+ **Second-pass reviewer:** `not required` (additive, opt-in, no-op by default)
5
+
6
+ ## Summary
7
+
8
+ `runMentorTick` now (after the existing per-finding ledger capture) records a
9
+ `mentor-mentee-differential` apprenticeship CYCLE via an injected, optional
10
+ `recordCycle`. The host (`AgentServer.buildMentorRunner`) wires it to the
11
+ ApprenticeshipCycleStore ONLY when `mentor.apprenticeshipInstanceId` is set, so it
12
+ is a no-op by default. The mentee's transcript is the cycle's menteeOutput; the
13
+ forensics findings (mapped to titles) are the differential.
14
+
15
+ ## Decision-point inventory
16
+
17
+ One new branch: record a cycle iff `recordCycle` is wired AND the transcript is
18
+ non-empty. Both sides covered (records on a real tick; skips on empty transcript;
19
+ no-op when unwired).
20
+
21
+ ## 1. Over-block
22
+
23
+ Rejects nothing real. The transcript guard skips an empty/failed Stage A (not a
24
+ cycle) — intended. Without `apprenticeshipInstanceId`, no cycle is recorded
25
+ (back-compat); the existing finding-capture path is unchanged.
26
+
27
+ ## 2. Under-block
28
+
29
+ It records ALL of a tick's findings as the differential without de-duping against
30
+ prior cycles (acceptable — each tick is a distinct cycle). The cycle's `task` is a
31
+ generic per-tick descriptor, not the full Stage-A prompt (kept terse on purpose).
32
+ cycleNumber is computed as max-existing+1 (single-writer mentor tick, no race).
33
+
34
+ ## 3. Level-of-abstraction fit
35
+
36
+ Right layer: the pure tick gets an injected callback (no store dependency in the
37
+ pure core); the store wiring + instance resolution live in `buildMentorRunner`
38
+ beside the existing `capture` wiring, reading the cycle store lazily to dodge the
39
+ boot-order (buildMentorRunner runs before the store is constructed).
@@ -40,6 +40,15 @@ ParallelWorkSentinel is Phase B (ships dark), a separate PR.
40
40
  gates/mutates). 6 tests. The cadence + lease-gating + server wiring + config (ships dark) +
41
41
  the nudge→Telegram/sentinel-events routing are the remaining wiring on this branch.
42
42
 
43
+ ## Phase B wiring (the on-switch)
44
+ - `src/config/ConfigDefaults.ts` — `monitoring.parallelWorkSentinel: { enabled: false }` — ships
45
+ DARK (false-positive nudge worse than silence; graduate after proven quiet). Add-missing-only
46
+ via applyDefaults ⇒ free migration; never a destructive sub-flag default.
47
+ - `src/core/types.ts` — `MonitoringConfig.parallelWorkSentinel?: { enabled, cadenceMinutes? }`.
48
+ - `src/server/AgentServer.ts` — when enabled, construct `ParallelWorkSentinel` over the index in
49
+ its OWN try/catch (cascade isolation), tick on an unref'd cadence (default 15min), audit each
50
+ transition to `logs/sentinel-events.jsonl`; cleared on `stop()`. Signal-only.
51
+
43
52
  ## Decision-point inventory
44
53
  - `ParallelActivityIndex` read aggregation — **add** — pure read over existing state; no
45
54
  block/allow surface, no mutation.
@@ -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,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._