/btw and ctx.side_question tore their cache-parity fork down with
shutdown_memory_provider() + close(). The fork shares the live session's
id, and CLI/gateway key a session's terminal sandbox, browser and file
read stamps by that id (run_conversation(task_id=session_id)), so
close() -> _close_task_resources(session_id) -> cleanup_vm/cleanup_browser
tore down the parent session's live tool state after every side question.
Background review already releases only clients for exactly this reason
(_release_fork_clients). Lift that into one shared teardown,
background_review.release_cache_parity_fork(parent, fork, task=...):
attribute usage to the parent under `task`, then release_clients(). Both
one-shot fork callers use it; /btw usage is now recorded under its own
task name, side_question, instead of background_review.
Tests: a parent-session terminal env survives a plugin side question and
a real /btw fork (both red before); the side request runs under the
turn's profile and secret scope when redeemed from a fresh thread.
GW-15 counts /btw as owner work, but its _spawn passed no holder, so
stop_authority_work could not reach its fork while join_authority_work
waited for it. A slow side question (the one-shot fallback allows 180 s)
held retirement for the whole settle window, then the profile kept its
reservation ("workers did not stop; ownership retained").
answer_side_question now publishes its cache-parity fork through
on_fork, and /btw passes a holder (_SideQuestionStop) that interrupts it.
Once stopped, the one-shot fallback is skipped; one already in flight is
an uncancellable, read-only aux call, so a stopped /btw with no fork left
stops counting as owner work and publishes no late answer.
Review finding by @kshitijk4poor on #140967
Refs #134889 (GW-15)
Refs #140719
The initial /btw implementation (#97937) answered from a rendered
plain-text transcript digest — truncated context, cold-written tokens on
every question. Teknium's call: reuse the self-improvement review fork
instead, which keeps the entire prompt cache stable for the fork and
gives it the complete conversation for very cheap.
- agent/background_review.py: extract the review-fork construction into
build_cache_parity_fork() — same runtime/credentials as the parent,
byte-identical system prompt / tools[] / reasoning config on the
same-model path, shared session_id for prefix warmth, full persistence
detachment (no state.db writes, no rotation, no external memory,
in-place-only compaction). The review thread now calls the helper;
behavior unchanged (full review test suite green).
- agent/side_question.py: /btw prefers the fork when a live parent
AIAgent exists — replays the untruncated snapshot as warm cache reads,
denies every tool at dispatch via an empty thread whitelist (tools[]
stays byte-identical for cache parity), attributes usage to the parent,
and trims a mid-turn snapshot tail so role alternation holds. The
one-shot digest remains as fallback (no live agent = cold cache anyway,
and any fork failure degrades gracefully).
- CLI passes self.agent, TUI passes the session agent, gateway looks up
the chat's cached agent (parity with how turns reuse it).
Live-verified: /btw on the worktree runs the fork path (agent.log shows
the side question as a forked conversation turn on the parent session_id
with the full history replayed), answers correctly from context.
/bg (formerly /background, which is retired) keeps the existing semantics:
spawn a fresh, independent agent session in the background.
/btw is now its own command matching the convention other harnesses use:
ask a quick side question ABOUT the current conversation without
interrupting it. A one-shot auxiliary LLM call (main model by default,
overridable via auxiliary.side_question.* in config.yaml) answers from a
read-only transcript snapshot — the live session's history, role
alternation, and prompt cache are untouched, and the current turn keeps
running.
Surfaces wired: CLI (inline mid-run dispatch), gateway (all messengers,
busy-dispatch table + idle dispatch, i18n across all 17 locales), TUI
(prompt.btw RPC + btw.complete event), Discord native slash, relay
command manifest, desktop exec routing, docs (EN + zh-Hans).