# V16.11 — Advisor Lifecycle, Event-First Answers, Warm Reuse, Honest Latency

> Release focus: cut DeepSeek Web **wall-clock latency** by replacing answer
> **polling** with an **event-first** channel, and make the advisor lifecycle a
> **single owner** instead of three that could drift.

## Why this release exists

V16.9/V16.10 shipped the pieces — `advisor-session-manager.mjs` (a conversation
lifecycle owner), `advisor-answer-observer.mjs` (an answer stability owner) and a
request/response browser transport — but deliberately did **not** wire them into
production. The controller kept owning the browser worker lifecycle inline
(`ACTIVE_WEB_WORKERS`), the conversation inline, and the answer wait as a 500 ms
poll loop in `deepseek-web-adapter.mjs`.

That left three problems:

1. **Dual ownership.** The worker lifecycle was owned both inline and (unused) by
   the session manager. Two owners of one lifecycle drift.
2. **Polling tax.** Every answer waited on a full poll round-trip (clamped
   400–750 ms). The page could already be stable while Node slept.
3. **Invisible cleanup.** A Windows browser process or profile lock could leak
   with nothing proving it was gone.

V16.11 fixes all three, without weakening a single prior contract.

## What changed

### 1. One owner: the Advisor Session Manager

`lib/advisor-session-manager.mjs` is now the **single production owner** of all
three advisor lifecycles:

| Lifecycle | Owner API |
|---|---|
| BrowserWorker | `acquireWorker()` / `releaseWorkerLease()` / `healthCheck()` / `recycleIfNeeded()` |
| Conversation | `openConversation()` / `reuseConversation()` / `closeConversation()` |
| AdvisorRun | `beginAdvisorRun()` / `endAdvisorRun()` |

It keeps the **byte-stable** V16.9 policy id
(`ADVISOR_SESSION_MANAGER_POLICY = "advisor-session-manager-v16-9"`) and the
`single-writer` invariant, and evolves via
`ADVISOR_SESSION_MANAGER_SCHEMA_VERSION = 2` +
`ADVISOR_SESSION_MANAGER_IMPLEMENTATION = "advisor-session-manager-v16-11"`. The
V16.9 contract test still passes unchanged.

Epochs are decided by the pure `lib/advisor-lifecycle-v16-11.mjs`
(`workerEpoch` / `profileEpoch` / `conversationEpoch` / `runGeneration`). A
worker recycle bumps `workerEpoch` **and** `conversationEpoch` and clears the
conversation id; a run generation is untouched by a recycle.

### 2. Event-first answers, bounded poll fallback

`lib/browser-transport-v16-11.mjs` (protocol v2) adds a typed
`request` / `response` / `event` envelope **alongside** the legacy V1 messages,
so an old worker still works. `lib/advisor-event-bridge-v16-11.mjs` is the single
place that decides, per consult, which channel drives the observation:

- **event-first** when the worker advertises `eventChannel` — a push replaces a
  poll round-trip;
- **bounded poll fallback** when there is no event channel, or the channel goes
  silent past `eventSilenceMs`.

The bridge reports which channel produced the accepted answer
(`event` / `poll` / `event-then-poll` / `none`) so a measurement can never blur
the two.

### 3. Bounded recovery + duplicate-submit prevention

`lib/advisor-recovery-v16-11.mjs` classifies a failure and returns a plan bounded
by `RECOVERY_MAX_TOTAL_ATTEMPTS = 3` and `RECOVERY_MAX_PER_KIND = 2`, fail-closed
on an unknown kind. `createSubmitGuard` is a single-flight, epoch-scoped
idempotency gate: re-claiming an in-flight or completed submit is refused
(`submit-in-flight`), so a recovery loop can never double-submit.

### 4. Proven Windows cleanup

`lib/windows-resource-hygiene-v16-11.mjs` runs an **ordered** teardown (kill the
process tree **before** file removal, then release locks) and returns a receipt
(`HYGIENE_VERDICT.COMPLETE` / `PARTIAL` / `FAILED` / `NOTHING_TO_DO`), so cleanup
is proven instead of assumed.

### 5. Honest latency metrics

`lib/advisor-latency-metrics-v16-11.mjs` never averages across `workerState`
(cold/warm) or `channel` (event/poll). A cell with no MEASURED sample is
`NOT_MEASURED`; a cold→warm speedup is only computed when **both** sides have a
MEASURED sample. A deterministic (simulated) run reports `SIMULATED_ONLY` and
never claims a live-provider win.

### 6. Production wiring

`lib/advisor-runtime-v16-11.mjs` is the single per-run composition. It owns ONE
session manager and threads the event bridge, recovery and metrics into it. The
controller (`pi/extensions/ues.ts`) hydrates it lazily via the
`ADVISOR_LIFECYCLE` stack; `ACTIVE_WEB_WORKERS` becomes a **process registry**
(handles only) and `ACTIVE_ADVISOR_RUNTIMES` remembers the per-run owner. The
browser process is injected as `acquireWorker` / `releaseWorker` / `healthCheck`
callbacks, so the lifecycle is deterministically testable while the process stays
owned by the extension.

## Non-goals / explicit limits

- No live authenticated DeepSeek profile is assumed. The deterministic bench
  (`npm run bench:v16.11`) reports `SIMULATED_ONLY`; a live cold/warm latency
  claim requires an operator-run authenticated profile and is **NOT_MEASURED**
  here.
- `providerTokens` is always `NOT_MEASURED`: this release measures wall-clock
  structure, not provider token usage.
- Pi remains the sole executor; DeepSeek remains an **untrusted advisor**. The
  local verifier is still the correctness authority.

## Verification

- `npm run integrity` — V16.8/V16.9/V16.10/V16.11 source contracts all enforced.
- `npm run eval:v16.11` — the V16.11 lifecycle/transport/recovery/hygiene suites.
- `npm run bench:v16.11` — the deterministic cold/warm + event/poll benchmark.
- `npm run release:verify` — the full gate (ci + every prior eval + V16.11).
