instar 1.3.245 → 1.3.246
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +108 -2
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +13 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/SessionManager.d.ts +3 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +3 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/frameworkInjectionProcesses.d.ts +41 -0
- package/dist/core/frameworkInjectionProcesses.d.ts.map +1 -0
- package/dist/core/frameworkInjectionProcesses.js +53 -0
- package/dist/core/frameworkInjectionProcesses.js.map +1 -0
- package/dist/core/frameworkSessionLaunch.d.ts +9 -0
- package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
- package/dist/core/frameworkSessionLaunch.js +7 -0
- package/dist/core/frameworkSessionLaunch.js.map +1 -1
- package/dist/core/types.d.ts +22 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/SpawnRequestManager.d.ts +13 -0
- package/dist/messaging/SpawnRequestManager.d.ts.map +1 -1
- package/dist/messaging/SpawnRequestManager.js +3 -0
- package/dist/messaging/SpawnRequestManager.js.map +1 -1
- package/dist/messaging/types.d.ts.map +1 -1
- package/dist/messaging/types.js +8 -1
- package/dist/messaging/types.js.map +1 -1
- package/dist/threadline/ThreadlineRouter.d.ts +55 -1
- package/dist/threadline/ThreadlineRouter.d.ts.map +1 -1
- package/dist/threadline/ThreadlineRouter.js +246 -6
- package/dist/threadline/ThreadlineRouter.js.map +1 -1
- package/dist/threadline/WarmSessionPool.d.ts +22 -0
- package/dist/threadline/WarmSessionPool.d.ts.map +1 -1
- package/dist/threadline/WarmSessionPool.js +38 -0
- package/dist/threadline/WarmSessionPool.js.map +1 -1
- package/package.json +1 -1
- package/scripts/instar-dev-precommit.js +42 -0
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/1.3.246.md +97 -0
- package/upgrades/side-effects/threadline-warm-session-a2a.md +146 -0
- package/upgrades/threadline-warm-session-a2a.eli16.md +63 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-04T18:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-04T18:56:49.668Z",
|
|
5
|
+
"instarVersion": "1.3.246",
|
|
6
6
|
"entryCount": 198,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -1506,7 +1506,7 @@
|
|
|
1506
1506
|
"type": "subsystem",
|
|
1507
1507
|
"domain": "sessions",
|
|
1508
1508
|
"sourcePath": "src/core/SessionManager.ts",
|
|
1509
|
-
"contentHash": "
|
|
1509
|
+
"contentHash": "4d50cc3ebd4b430069d5957fbed44bdadd487c17386b680f0cafe66a33c381e2",
|
|
1510
1510
|
"since": "2025-01-01"
|
|
1511
1511
|
},
|
|
1512
1512
|
"subsystem:auto-updater": {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Phase 2 of the Threadline agent-to-agent (A2A) continuity work: **warm sessions**.
|
|
9
|
+
|
|
10
|
+
The turn-based fix (#746) made a peer's spaced follow-up *resume* the same
|
|
11
|
+
conversation via `claude --resume`. But rapid-fire bursts (several messages in
|
|
12
|
+
seconds) still cold-spawned, because each A2A reply ran as a throwaway `-p`
|
|
13
|
+
session that answered once and exited — there was nothing alive to hand the next
|
|
14
|
+
message to — and a fresh thread could even hit the 30s per-peer spawn cooldown.
|
|
15
|
+
|
|
16
|
+
This change keeps the reply session **alive between messages** for a warm-enabled
|
|
17
|
+
peer and **injects** follow-ups into it:
|
|
18
|
+
|
|
19
|
+
- The first message from a verified, non-topic-bound peer spawns a **persistent
|
|
20
|
+
interactive** reply session (not a throwaway `-p`), carrying the full grounded
|
|
21
|
+
prompt as its first turn, and registers it in a new `WarmSessionPool` keyed by
|
|
22
|
+
threadId (caps: globalCap 3, perPeerCap 1, ttlMs 600000, trustFloor `verified`).
|
|
23
|
+
- The next message on that thread is **injected** into the same live session — no
|
|
24
|
+
cold-spawn, no cooldown — and refreshes its TTL.
|
|
25
|
+
- A periodic reap tick evicts on TTL/pressure; on eviction the **next message
|
|
26
|
+
falls back losslessly to the proven `--resume` path (#746)** — nothing is lost.
|
|
27
|
+
|
|
28
|
+
Three correctness/security fixes ride along, each independently load-bearing:
|
|
29
|
+
|
|
30
|
+
- **`claude.exe` allowlist fix** — live macOS Claude panes report
|
|
31
|
+
`pane_current_command = claude.exe`, which the inject allowlist
|
|
32
|
+
(`ALLOWED_INJECTION_PROCESSES`) did not contain, so A2A live-inject was
|
|
33
|
+
**dead-on-arrival on macOS**. Added `claude.exe`.
|
|
34
|
+
- **Grounding-on-inject** — injected follow-ups are now wrapped in the same
|
|
35
|
+
untrusted-data grounding preamble used on spawn/resume (they previously injected
|
|
36
|
+
the raw envelope body).
|
|
37
|
+
- **Per-thread isolation + peer-conflict guard** — each thread gets its own
|
|
38
|
+
private session (no shared listener, nothing to leak across peers), and
|
|
39
|
+
`WarmSessionPool.admit` throws `WarmSessionPeerConflictError` rather than letting
|
|
40
|
+
a different peer hijack an existing thread's warm session; a pre-spawn `peek`
|
|
41
|
+
check makes the conflict path fall back to a normal cold-spawn.
|
|
42
|
+
|
|
43
|
+
Dark-shipped behind `threadline.warmSessionA2A.enabled`, resolved via the
|
|
44
|
+
`developmentAgent` dev-gate (`enabled ?? !!config.developmentAgent`): **live on the
|
|
45
|
+
development agent, dark on the fleet**. With the flag absent and `developmentAgent`
|
|
46
|
+
false, behavior is byte-for-byte the current cold-spawn path.
|
|
47
|
+
|
|
48
|
+
**Framework-general (not Claude-specific).** The warm worker runs in the LOCAL
|
|
49
|
+
agent's framework (claude-code / codex-cli / gemini-cli / future), not hardcoded
|
|
50
|
+
Claude: the inject allowlist is now DERIVED from a per-framework process-name
|
|
51
|
+
registry (`Record<IntelligenceFramework, …>`), and the warm spawn passes the local
|
|
52
|
+
framework so the right CLI + resume mechanism applies. Enforced by compiler
|
|
53
|
+
exhaustiveness + `tests/unit/framework-agnosticism.test.ts` + a new `/instar-dev`
|
|
54
|
+
precommit gate that requires any launch/inject-abstraction change to state its
|
|
55
|
+
framework generality (standard: **Framework-Agnostic — and Framework-Optimizing**).
|
|
56
|
+
|
|
57
|
+
## What to Tell Your User
|
|
58
|
+
|
|
59
|
+
When another agent and I go back and forth quickly, I now keep the same live
|
|
60
|
+
conversation open and type each new message into it — instead of starting a fresh,
|
|
61
|
+
memoryless reply every time. So a rapid exchange holds its thread end-to-end, not
|
|
62
|
+
just the slower turn-by-turn case. It's an internal robustness improvement that
|
|
63
|
+
ships active only on the development agent (dark on everyone else) and falls back
|
|
64
|
+
to the already-shipped resume path if a warm session is ever retired. Nothing to
|
|
65
|
+
turn on.
|
|
66
|
+
|
|
67
|
+
## Summary of New Capabilities
|
|
68
|
+
|
|
69
|
+
| Capability | How to Use |
|
|
70
|
+
|-----------|-----------|
|
|
71
|
+
| 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. |
|
|
72
|
+
| Tunable warm-session pool | `.instar/config.json` → `threadline.warmSessionA2A`: `globalCap` (3), `perPeerCap` (1), `ttlMs` (600000), `trustFloor` (`verified`). |
|
|
73
|
+
| Lossless eviction fallback | On TTL/pressure eviction, the next message resumes via the `--resume` path (#746) — no continuity lost. |
|
|
74
|
+
|
|
75
|
+
## Evidence
|
|
76
|
+
|
|
77
|
+
**The `claude.exe` bug (reproduced in dev):** `ALLOWED_INJECTION_PROCESSES`
|
|
78
|
+
(`src/messaging/types.ts`) listed `claude` but not `claude.exe`. On macOS a live
|
|
79
|
+
Claude pane reports `pane_current_command = claude.exe`, so the inject guard
|
|
80
|
+
`isInjectableProcess()` returned **false for every live A2A reply session** —
|
|
81
|
+
agent-to-agent live-inject was dead-on-arrival on macOS.
|
|
82
|
+
|
|
83
|
+
- *Before:* the prior live Echo↔Dawn round-trip recorded `2× Spawned, 0× Resumed,
|
|
84
|
+
1× spawn-denied (cooldown)` on a fresh thread — every follow-up cold-started; the
|
|
85
|
+
inject path was never reachable because the allowlist check rejected `claude.exe`.
|
|
86
|
+
- *After:* unit test asserts `claude.exe ∈ ALLOWED_INJECTION_PROCESSES`; the
|
|
87
|
+
integration test "2nd message on a thread with a live warm session → inject (no
|
|
88
|
+
2nd spawn, no cooldown)" passes — the inject path is now reachable and exercised.
|
|
89
|
+
|
|
90
|
+
**Test evidence (re-run on top of current main, v1.3.243):** tsc `--noEmit` clean;
|
|
91
|
+
warm-session unit (105) + integration (5: incl. flag-on inject, flag-off
|
|
92
|
+
byte-identical cold-spawn, peer-conflict → cold fallback) + e2e (1: continuity then
|
|
93
|
+
evict → resume via #746) all green; esm / no-empty-catch / no-direct-destructive /
|
|
94
|
+
no-silent-fallbacks gates green (warm fail-opens annotated `@silent-fallback-ok`,
|
|
95
|
+
net silent-fallback count unchanged). The live Echo↔Dawn production round-trip
|
|
96
|
+
("Resumed not Spawned" + rapid inject with no cooldown) is the post-deploy
|
|
97
|
+
standards-met gate, run by the orchestrator after merge.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Side-Effects Review — Threadline Warm-Session A2A Integration
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `threadline-warm-session-a2a`
|
|
4
|
+
**Date:** `2026-06-04`
|
|
5
|
+
**Author:** `Echo (instar dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `not required (Tier 1 — additive + dark-gated)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Phase 2 of A2A continuity: keep a verified peer's reply session **alive** between
|
|
11
|
+
messages and **inject** follow-ups, instead of cold-spawning each time. The first
|
|
12
|
+
message spawns a persistent **interactive** session (not throwaway `-p`), registered
|
|
13
|
+
in a new `WarmSessionPool` keyed by threadId; subsequent messages inject into the
|
|
14
|
+
live session (no spawn, no 30s cooldown); a reap tick evicts on TTL/pressure; on
|
|
15
|
+
eviction the next message falls back losslessly to the shipped `--resume` path
|
|
16
|
+
(#746). Three correctness/security fixes ride along: `claude.exe` added to the
|
|
17
|
+
inject allowlist (live macOS Claude panes report `claude.exe`, so live-inject was
|
|
18
|
+
dead-on-arrival); grounding-on-inject (wrap injected body in the untrusted-data
|
|
19
|
+
preamble); per-thread isolation + `WarmSessionPeerConflictError` guard.
|
|
20
|
+
|
|
21
|
+
Dark-shipped behind `threadline.warmSessionA2A.enabled ?? !!config.developmentAgent`.
|
|
22
|
+
Files: `src/messaging/types.ts`, `src/threadline/WarmSessionPool.ts`,
|
|
23
|
+
`src/threadline/ThreadlineRouter.ts`, `src/commands/server.ts`,
|
|
24
|
+
`src/config/ConfigDefaults.ts`, `src/core/types.ts`,
|
|
25
|
+
`src/messaging/SpawnRequestManager.ts`, `src/core/SessionManager.ts`,
|
|
26
|
+
`src/core/frameworkSessionLaunch.ts` + unit/integration/e2e tests.
|
|
27
|
+
|
|
28
|
+
## Decision-point inventory
|
|
29
|
+
|
|
30
|
+
- `ALLOWED_INJECTION_PROCESSES` (`src/messaging/types.ts`) — **modify (additive)** — add `claude.exe`. Widens which live panes accept an injected message; only matters for the A2A inject path (Telegram uses a different non-allowlisted path).
|
|
31
|
+
- `WarmSessionPool.admit` (`src/threadline/WarmSessionPool.ts`) — **new** — throws `WarmSessionPeerConflictError` if a different peer owns the thread; caps/TTL/LRU eviction.
|
|
32
|
+
- `ThreadlineRouter.tryInjectIntoLiveSession` (`src/threadline/ThreadlineRouter.ts`) — **modify** — wrap injected body in grounding preamble; `pool.touch` on success.
|
|
33
|
+
- `ThreadlineRouter.handleInboundMessage` new-spawn path — **modify (gated)** — when `preferWarmSession`, request an interactive keep-alive spawn + `pool.admit`; pre-spawn `peek` conflict → cold-spawn fallback.
|
|
34
|
+
- `server.ts` relay decision + `spawnSession` callback + reap tick — **modify (gated)** — resolve `warmEnabled`, construct pool (null when off), branch the spawn callback on `opts.interactive`, periodic `reapExpired` + `killWarmSessionByName`, `.unref()`, cleared on shutdown.
|
|
35
|
+
- `ConfigDefaults` / `ThreadlineConfig` — **new (additive)** — `warmSessionA2A` caps with no `enabled` (resolved via dev-gate).
|
|
36
|
+
- `SessionManager.spawnInteractiveSession` + `frameworkSessionLaunch` — **modify (additive)** — new `sessionId?` option → `--session-id` on the interactive claude launch (mutually exclusive with `--resume`).
|
|
37
|
+
|
|
38
|
+
## 1. Over-block
|
|
39
|
+
|
|
40
|
+
The new `WarmSessionPeerConflictError` could *withhold* a warm session from a
|
|
41
|
+
legitimate same-thread message if peerId were computed inconsistently. Mitigation:
|
|
42
|
+
peerId is the same stable sender fingerprint used for the thread's trust/ownership
|
|
43
|
+
checks; same-peer refresh is explicitly tested as allowed. On any conflict the
|
|
44
|
+
message is **not dropped** — it falls back to a normal cold-spawn. So the worst
|
|
45
|
+
"over-block" outcome is "no warm speedup," never "message rejected." The trust
|
|
46
|
+
floor (`verified`) gates *warmth*, not *delivery*: a below-floor peer still gets the
|
|
47
|
+
unchanged cold-spawn reply.
|
|
48
|
+
|
|
49
|
+
## 2. Under-block
|
|
50
|
+
|
|
51
|
+
`claude.exe` widens the inject allowlist — could an unintended process now be
|
|
52
|
+
injected into? No: inject is keyed on the pool's own `sessionName` (a session this
|
|
53
|
+
agent spawned + admitted), not a free scan of panes; the allowlist is a
|
|
54
|
+
defense-in-depth process-name check on top of an already-owned session handle. The
|
|
55
|
+
grounding-on-inject wrap closes the prior gap where a peer's raw body reached the
|
|
56
|
+
session without untrusted-data framing. Residual: a malicious *verified* peer can
|
|
57
|
+
still keep one warm session pinned (bounded by perPeerCap 1 + ttl 600s + globalCap
|
|
58
|
+
3) — a resource bound, not a content-leak (per-thread isolation means nothing to
|
|
59
|
+
leak across peers).
|
|
60
|
+
|
|
61
|
+
## 3. Level-of-abstraction fit
|
|
62
|
+
|
|
63
|
+
`WarmSessionPool` is a pure registry (no I/O); the router owns the
|
|
64
|
+
spawn/admit/inject/touch decisions; the server owns process construction + the kill
|
|
65
|
+
primitive (`killWarmSessionByName` resolves tmux-name → session-id → kill, because
|
|
66
|
+
`killSession` keys by session id). The interactive `sessionId` option lives at the
|
|
67
|
+
launch builder, mirroring the existing `--resume` plumbing. Correct layering — the
|
|
68
|
+
pool never touches tmux, the router never builds argv.
|
|
69
|
+
|
|
70
|
+
## 4. Signal vs authority compliance
|
|
71
|
+
|
|
72
|
+
The trust floor is a **resource** control (who may keep a session warm), not a new
|
|
73
|
+
authority/approval surface. No gate is relaxed. Inbound trust gates are untouched.
|
|
74
|
+
Trust comparison uses an explicit `TRUST_ORDER` index (never string `>=`, which
|
|
75
|
+
would make `'verified' >= 'trusted'` true alphabetically) — guarded by a test.
|
|
76
|
+
|
|
77
|
+
## 5. Interactions
|
|
78
|
+
|
|
79
|
+
- Builds on #746: on eviction, `get()` + `jsonlExists(uuid)` resume the thread via
|
|
80
|
+
`--resume` — the e2e test forces an evict and asserts the next message resumes
|
|
81
|
+
(entry intact). `onSessionComplete` demote/persist is unchanged.
|
|
82
|
+
- The interactive worker keeps project MCP (does **not** set `disableProjectMcp`),
|
|
83
|
+
matching the `-p` path, so `threadline_send` is available — the warm worker can
|
|
84
|
+
actually reply.
|
|
85
|
+
- Re-verified after rebasing onto current main (v1.3.243, which added #747 Telegram
|
|
86
|
+
delivery dedupe + #748 duplicate-response guard): tsc clean; warm unit (105) +
|
|
87
|
+
integration (5) + e2e (1) + esm/empty-catch/no-direct-destructive/no-silent-
|
|
88
|
+
fallbacks gates all green. No-silent-fallbacks net count unchanged (warm
|
|
89
|
+
fail-opens annotated `@silent-fallback-ok`).
|
|
90
|
+
|
|
91
|
+
## 6. External surfaces
|
|
92
|
+
|
|
93
|
+
New config block `threadline.warmSessionA2A` (additive; `applyDefaults` deep-merges
|
|
94
|
+
nested objects — verified by test, no `PostUpdateMigrator` patch needed). No new
|
|
95
|
+
HTTP route. CLAUDE.md/template: warm sessions are internal robustness — no
|
|
96
|
+
agent-facing capability surface added. Migration-parity: config is additive +
|
|
97
|
+
existence-checked; dark by default on the fleet (developmentAgent gate).
|
|
98
|
+
|
|
99
|
+
## 7. Rollback cost
|
|
100
|
+
|
|
101
|
+
Low. Flag-off (or `developmentAgent` false) is byte-for-byte the current cold-spawn
|
|
102
|
+
path — proven by flag-off tests. Full revert = the listed source files; no persisted
|
|
103
|
+
format change (the pool is in-memory only). `claude.exe` allowlist + grounding-on-
|
|
104
|
+
inject are independently safe to keep even if the warm path is reverted.
|
|
105
|
+
|
|
106
|
+
## Framework generality
|
|
107
|
+
|
|
108
|
+
Per the **Framework-Agnostic — and Framework-Optimizing** standard, the warm path
|
|
109
|
+
must work for EVERY agentic framework (claude-code / codex-cli / gemini-cli /
|
|
110
|
+
future), not just Claude. Two parts were initially Claude-leaning and are now
|
|
111
|
+
routed through the framework abstraction:
|
|
112
|
+
|
|
113
|
+
- **Inject allowlist.** `ALLOWED_INJECTION_PROCESSES` was a hardcoded shells +
|
|
114
|
+
`claude`/`claude.exe` list — a non-Claude warm worker's pane process (`codex`,
|
|
115
|
+
`gemini`) would be refused. It is now DERIVED: `shells ∪ FRAMEWORK_INJECTION_
|
|
116
|
+
PROCESS_NAMES` (new `src/core/frameworkInjectionProcesses.ts`), a
|
|
117
|
+
`Record<IntelligenceFramework, …>` so the compiler forces an entry per framework.
|
|
118
|
+
- **Warm spawn framework.** The interactive keep-alive spawn now passes
|
|
119
|
+
`framework: _defaultFramework` (the local agent's framework) to
|
|
120
|
+
`spawnInteractiveSession`, so a codex/gemini agent's warm worker launches in its
|
|
121
|
+
own framework with its own MCP/permission flags. The deterministic `sessionId`
|
|
122
|
+
is mapped per-framework by `frameworkSessionLaunch` (claude pins `--session-id`;
|
|
123
|
+
codex/gemini ignore it and resume by their own mechanism) — not a Claude
|
|
124
|
+
assumption, it's the abstraction's job.
|
|
125
|
+
|
|
126
|
+
**Enforced (structure, not willpower):** (1) compiler exhaustiveness on the two
|
|
127
|
+
`Record<IntelligenceFramework, …>` maps; (2) `tests/unit/framework-agnosticism.test.ts`
|
|
128
|
+
fails if any framework's injection-process entry is empty, a launch builder is
|
|
129
|
+
missing, or the allowlist drifts from `shells ∪ registry`; (3) the `/instar-dev`
|
|
130
|
+
precommit gate (`assertFrameworkGenerality`) requires this very section for any
|
|
131
|
+
change to the launch/inject abstraction surface.
|
|
132
|
+
|
|
133
|
+
## Conclusion
|
|
134
|
+
|
|
135
|
+
Low-risk, additive, dark-gated. Completes the rapid-fire A2A continuity case on top
|
|
136
|
+
of the shipped turn-based foundation, with per-thread isolation removing the shared-
|
|
137
|
+
listener leak concern entirely. Both sides of every boundary are tested (warm-on
|
|
138
|
+
inject vs flag-off cold-spawn; same-peer refresh vs peer-conflict fallback; evict →
|
|
139
|
+
resume). Live Echo↔Dawn production round-trip is the orchestrator's standards-met
|
|
140
|
+
gate (post-deploy).
|
|
141
|
+
|
|
142
|
+
## Second-pass review (if required)
|
|
143
|
+
|
|
144
|
+
Not required — Tier 1 (additive, dark-gated, single subsystem). The subsystem-scale
|
|
145
|
+
shape (a new pool + lifecycle wiring) was handled via spec-converge + an ELI16
|
|
146
|
+
review surfaced to the operator before merge, per the A4 ceremony.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Warm sessions: how rapid agent-to-agent threads stop cold-starting
|
|
2
|
+
|
|
3
|
+
## The problem in one breath
|
|
4
|
+
When another agent and I have a back-and-forth, each of my replies used to run as
|
|
5
|
+
a **throwaway**: it answered once and exited. So when a second message arrived a
|
|
6
|
+
moment later, there was nothing alive to hand it to — I'd cold-start a brand-new,
|
|
7
|
+
memoryless reply. The turn-based fix I already shipped (#746) handles the *slow*
|
|
8
|
+
case by re-opening the same conversation with `--resume`. But *rapid-fire* (a few
|
|
9
|
+
messages in seconds) still cold-started, and on a fresh thread could even hit a
|
|
10
|
+
30-second spawn cooldown.
|
|
11
|
+
|
|
12
|
+
## A real bug I found while grounding this
|
|
13
|
+
While reading the code I found that even when a reply session *was* briefly alive,
|
|
14
|
+
my "type the next message into it" path was being **refused every single time**.
|
|
15
|
+
The safety check only allows a short list of program names, and it lists `claude`
|
|
16
|
+
— but on this Mac the live program actually shows up as **`claude.exe`**. So the
|
|
17
|
+
check never matched, and agent-to-agent live-typing has been **dead on arrival on
|
|
18
|
+
macOS**. One-line fix: allow `claude.exe`. (This is exactly why "ground before you
|
|
19
|
+
assert" matters — the feature looked wired but wasn't.)
|
|
20
|
+
|
|
21
|
+
## The fix (reuses what's already shipped)
|
|
22
|
+
Keep the reply session **alive** between messages — exactly like my normal chat
|
|
23
|
+
sessions with you, which stay open and get messages typed into them:
|
|
24
|
+
- The first message from a peer spawns a **persistent** reply session (not a
|
|
25
|
+
throwaway), tracked in a small pool keyed by the thread.
|
|
26
|
+
- The next message is **typed into that same live session** (now that the
|
|
27
|
+
`claude.exe` fix lets it through) — no cold-start, no cooldown.
|
|
28
|
+
- The pool has tight caps (3 sessions total, 1 per peer) and a 10-minute timeout
|
|
29
|
+
so a chatty peer can't pin a pile of live sessions; when one is evicted, the
|
|
30
|
+
**next message just falls back to the proven `--resume` path** — nothing lost.
|
|
31
|
+
|
|
32
|
+
## The nice surprise: the security question dissolved
|
|
33
|
+
An earlier draft worried about letting "verified" peers into a **single shared**
|
|
34
|
+
listener — one peer's content could mix into a session handling others. The new
|
|
35
|
+
design gives **each conversation its own private session**, so there's nothing
|
|
36
|
+
shared to leak. The trust gate stops being a secrecy control and becomes a simple
|
|
37
|
+
"who's allowed to keep a session warm" resource control. Injected follow-ups also
|
|
38
|
+
now get the same untrusted-data grounding wrapper that spawns/resumes already use.
|
|
39
|
+
|
|
40
|
+
## Works for every framework — not just Claude (your note)
|
|
41
|
+
|
|
42
|
+
The first cut leaned Claude-specific in two spots, and both are now routed through
|
|
43
|
+
the framework abstraction so a Codex or Gemini agent gets the same warm sessions:
|
|
44
|
+
- The "is this process safe to type into?" allowlist used to be hardcoded to
|
|
45
|
+
`claude`/`claude.exe`. It's now **derived** from a per-framework registry
|
|
46
|
+
(`claude → claude/claude.exe`, `codex → codex`, `gemini → gemini`), typed so the
|
|
47
|
+
compiler refuses to add a framework without giving it a process name.
|
|
48
|
+
- The warm worker now launches in **the local agent's framework**, not always
|
|
49
|
+
Claude — so on a Codex agent it spins up a Codex session, with Codex's own
|
|
50
|
+
resume mechanism.
|
|
51
|
+
|
|
52
|
+
And — the part you actually asked for — this is enforced by the **review process**,
|
|
53
|
+
not by remembering: a CI test fails if any framework lacks coverage or the allowlist
|
|
54
|
+
drifts, and the `/instar-dev` precommit gate makes any change to the launch/inject
|
|
55
|
+
layer state, in writing, whether it works for Codex and Gemini. So "works for all
|
|
56
|
+
frameworks" is now a gate, not a good intention.
|
|
57
|
+
|
|
58
|
+
## How it ships
|
|
59
|
+
Dark on the fleet, **live only on the development agent**, behind a flag, with the
|
|
60
|
+
proven resume path as the fallback if a warm session is ever evicted. With the flag
|
|
61
|
+
off, behavior is byte-for-byte the current cold-spawn path. Full unit + integration
|
|
62
|
+
+ e2e tests cover both sides: warm inject when enabled, byte-identical cold-spawn
|
|
63
|
+
when disabled, peer-conflict → cold fallback, and evict → resume continuity.
|