# Upgrade Guide — vNEXT

<!-- assembled-by: assemble-next-md -->
<!-- bump: patch -->

## What Changed

Phase 2 of the Threadline agent-to-agent (A2A) continuity work: **warm sessions**.

The turn-based fix (#746) made a peer's spaced follow-up *resume* the same
conversation via `claude --resume`. But rapid-fire bursts (several messages in
seconds) still cold-spawned, because each A2A reply ran as a throwaway `-p`
session that answered once and exited — there was nothing alive to hand the next
message to — and a fresh thread could even hit the 30s per-peer spawn cooldown.

This change keeps the reply session **alive between messages** for a warm-enabled
peer and **injects** follow-ups into it:

- The first message from a verified, non-topic-bound peer spawns a **persistent
  interactive** reply session (not a throwaway `-p`), carrying the full grounded
  prompt as its first turn, and registers it in a new `WarmSessionPool` keyed by
  threadId (caps: globalCap 3, perPeerCap 1, ttlMs 600000, trustFloor `verified`).
- The next message on that thread is **injected** into the same live session — no
  cold-spawn, no cooldown — and refreshes its TTL.
- A periodic reap tick evicts on TTL/pressure; on eviction the **next message
  falls back losslessly to the proven `--resume` path (#746)** — nothing is lost.

Three correctness/security fixes ride along, each independently load-bearing:

- **`claude.exe` allowlist fix** — live macOS Claude panes report
  `pane_current_command = claude.exe`, which the inject allowlist
  (`ALLOWED_INJECTION_PROCESSES`) did not contain, so A2A live-inject was
  **dead-on-arrival on macOS**. Added `claude.exe`.
- **Grounding-on-inject** — injected follow-ups are now wrapped in the same
  untrusted-data grounding preamble used on spawn/resume (they previously injected
  the raw envelope body).
- **Per-thread isolation + peer-conflict guard** — each thread gets its own
  private session (no shared listener, nothing to leak across peers), and
  `WarmSessionPool.admit` throws `WarmSessionPeerConflictError` rather than letting
  a different peer hijack an existing thread's warm session; a pre-spawn `peek`
  check makes the conflict path fall back to a normal cold-spawn.

Dark-shipped behind `threadline.warmSessionA2A.enabled`, resolved via the
`developmentAgent` dev-gate (`enabled ?? !!config.developmentAgent`): **live on the
development agent, dark on the fleet**. With the flag absent and `developmentAgent`
false, behavior is byte-for-byte the current cold-spawn path.

**Framework-general (not Claude-specific).** The warm worker runs in the LOCAL
agent's framework (claude-code / codex-cli / gemini-cli / future), not hardcoded
Claude: the inject allowlist is now DERIVED from a per-framework process-name
registry (`Record<IntelligenceFramework, …>`), and the warm spawn passes the local
framework so the right CLI + resume mechanism applies. Enforced by compiler
exhaustiveness + `tests/unit/framework-agnosticism.test.ts` + a new `/instar-dev`
precommit gate that requires any launch/inject-abstraction change to state its
framework generality (standard: **Framework-Agnostic — and Framework-Optimizing**).

## What to Tell Your User

When another agent and I go back and forth quickly, I now keep the same live
conversation open and type each new message into it — instead of starting a fresh,
memoryless reply every time. So a rapid exchange holds its thread end-to-end, not
just the slower turn-by-turn case. It's an internal robustness improvement that
ships active only on the development agent (dark on everyone else) and falls back
to the already-shipped resume path if a warm session is ever retired. Nothing to
turn on.

## Summary of New Capabilities

| Capability | How to Use |
|-----------|-----------|
| Warm A2A sessions (keep-alive + inject) | Automatic for verified, non-topic-bound Threadline peers when `threadline.warmSessionA2A.enabled` is on (defaults to the `developmentAgent` flag). Rapid follow-ups inject into the live session instead of cold-spawning. |
| Tunable warm-session pool | `.instar/config.json` → `threadline.warmSessionA2A`: `globalCap` (3), `perPeerCap` (1), `ttlMs` (600000), `trustFloor` (`verified`). |
| Lossless eviction fallback | On TTL/pressure eviction, the next message resumes via the `--resume` path (#746) — no continuity lost. |

## Evidence

**The `claude.exe` bug (reproduced in dev):** `ALLOWED_INJECTION_PROCESSES`
(`src/messaging/types.ts`) listed `claude` but not `claude.exe`. On macOS a live
Claude pane reports `pane_current_command = claude.exe`, so the inject guard
`isInjectableProcess()` returned **false for every live A2A reply session** —
agent-to-agent live-inject was dead-on-arrival on macOS.

- *Before:* the prior live Echo↔Dawn round-trip recorded `2× Spawned, 0× Resumed,
  1× spawn-denied (cooldown)` on a fresh thread — every follow-up cold-started; the
  inject path was never reachable because the allowlist check rejected `claude.exe`.
- *After:* unit test asserts `claude.exe ∈ ALLOWED_INJECTION_PROCESSES`; the
  integration test "2nd message on a thread with a live warm session → inject (no
  2nd spawn, no cooldown)" passes — the inject path is now reachable and exercised.

**Test evidence (re-run on top of current main, v1.3.243):** tsc `--noEmit` clean;
warm-session unit (105) + integration (5: incl. flag-on inject, flag-off
byte-identical cold-spawn, peer-conflict → cold fallback) + e2e (1: continuity then
evict → resume via #746) all green; esm / no-empty-catch / no-direct-destructive /
no-silent-fallbacks gates green (warm fail-opens annotated `@silent-fallback-ok`,
net silent-fallback count unchanged). The live Echo↔Dawn production round-trip
("Resumed not Spawned" + rapid inject with no cooldown) is the post-deploy
standards-met gate, run by the orchestrator after merge.
