# Upgrade Guide — vNEXT

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

## What Changed

Second wedge-signature family + the API fresh-respawn lever, from the
2026-06-05 EXO 3.0 incident. A session whose transcript accumulated literal
red-team test payloads (generated during MTP security-harness work) got EVERY
reply rejected by the API's Usage Policy classifier — `API Error: … appears to
violate our Usage Policy` on each turn, because every turn re-sends the full
conversation. Same permanent-death shape as the thinking-block 400 (still
emitting output, so the silence + socket sentinels miss it), but the
ContextWedgeSentinel only knew the thinking-block signature, so it watched the
session die for an hour and said nothing. Recovery required hand-editing
`topic-resume-map.json` because `/sessions/refresh` did not forward `fresh`.

Now: `classifyWedgeTail()` recognizes both families and tags every sentinel
event with `kind` (`thinking-block-400` | `aup-rejection`) in the audit trail
and escalation wording. The AUP family carries an extra discriminator — the
signature must appear on MORE THAN ONE line (the loop always repeats; a benign
one-off rejection must not cost the session its conversation). And
`POST /sessions/refresh` accepts `fresh: true`, reaching the same
SessionRefresh fresh-mode the sentinel uses internally.

Audit fix #6 under "No Unbounded Loops" (P19): the SessionRouter's per-peer
breaker hook (`markOwnerSuspect`) — which fires on delivery-retry exhaustion —
was never wired in production, and `isMachineAlive` reads only capacity
heartbeats, so every session owned by a slow-but-heartbeating machine re-paid
the full ~4.5s retry tax per message. Now `OwnerSuspectBreaker` (pure core
class, per-peer half-open windows with ABSOLUTE per-episode TTL, composing
`FailureEpisodeLatch` for one-log/one-signal episode accounting) is wired into
`markOwnerSuspect` + composed into `isMachineAlive` + filtered into placement
candidates (with an all-suspect unfiltered fallback). A new router dep
`onOwnerResponsive` closes the window on ANY delivery ack. Also fixed: the
router's per-session `chains` map leaked one settled entry per session-ever-
routed (now bounded by in-flight sessions). The adversarial reviewer
reproduced and fixed a forever-suspect bug in the first draft (re-marks
extended the TTL → a recovered busy peer stayed written off indefinitely —
the absolute TTL is their fix, regression-tested). Suspect-window message
POLICY (re-place fast vs hold-for-stability) is deliberately unchanged —
operator decision, options sent separately.

## What to Tell Your User

If a conversation ever dies the way the EXO session did — every reply
instantly refused by the AI provider because of what's accumulated in the
transcript — I now notice it, can recover it automatically with a fresh
restart (when auto-recovery is enabled), and explain what happened instead of
leaving you staring at "delivered" receipts with no answers.

If you run me on more than one machine: when one machine starts failing to
receive its conversations' messages, the router now learns it once (instead of
re-discovering it ~4.5 seconds at a time, per conversation, per message),
routes around it, re-checks every 30 seconds, and picks the machine back up
the moment it answers again. Pinned conversations stay put through brief
blips. If a machine stays unreachable for 10+ minutes you get one note in the
health log.

## Summary of New Capabilities

- ContextWedgeSentinel detects the AUP-rejection loop (second signature
  family) with a repetition discriminator against benign one-off rejections;
  audit events + escalations carry the wedge kind. No configuration change —
  rides the existing `monitoring.contextWedgeSentinel` settings.
- `POST /sessions/refresh` accepts `fresh: true` — kill + respawn WITHOUT
  `--resume` (clears the topic's resume UUID), the recovery lever for any
  poisoned transcript.
- CLAUDE.md template + migration: existing agents learn about the second
  family, the API lever, and the prevention rule (adversarial payloads live in
  files on disk, never pasted into conversation transcripts).

- Per-machine delivery circuit breaker (30s half-open windows, instant
  recovery on any successful delivery, sustained-failure health-log signal).
  No configuration needed.

## Evidence

Live incident recovery (2026-06-05, topic 19437): manual kill + resume-map
clear + fresh spawn verified end-to-end — fresh session answered immediately
where the wedged one had failed every turn for ~1h. Unit: 32 sentinel tests
(incl. both sides of the one-off-vs-loop boundary, kind tagging, escalation
wording per family) + 8 route tests (fresh validation, forwarding, 202 shape)
+ 3 migrator tests (fresh install carries the note; existing-section patch;
idempotency). Integration + e2e wedge suites green; tsc clean; preflight PASS.

CMT-1109 audit, grounded to three concrete findings (unwired hook;
heartbeat-only aliveness; no-op queue dep). Adversarial second-pass: probe-1
OBJECT confirmed by reproduction (forever-suspect under steady traffic) and
fixed in-review with a regression test; probes 2–5 (swap-count invariance,
pin behavior through suspect windows, all-suspect fallback can't wedge,
stale-ack semantics, chains-cleanup serialization race) traced clean. Tests:
40 green across the breaker unit suite (incl. the P19 sustained-suspicion
bound and the end-to-end zero-retry-tax short-circuit), SessionRouter (23),
dispatch integration (3), and the session-pool deliverMessage e2e (4); tsc
clean.
