12. Deep Dive — The /btw Command
/btw is one of the more clever features in Claude Code. It lets the user ask a quick side question without interrupting what the agent is doing. It is a useful study in how the codebase reuses primitives to build new user-facing features at low cost.
Shape
- Location:
/workspaces/src/commands/btw/ - Command type:
'local-jsx'(/workspaces/src/commands/btw/index.ts:4). It renders a React/Ink overlay rather than feeding a prompt into the main model. - Description: "Ask a quick side question without interrupting the main conversation" (
commands/btw/index.ts:6-7). - Argument hint:
<question>(commands/btw/index.ts:9). - Availability: No feature flag, no
isEnabled(), not inINTERNAL_ONLY_COMMANDS. Always on for everyone (commands.ts:263). - Usage tracking: Each invocation increments
btwUseCountin global config (btw.tsx:237-240).
The flow
When the user types /btw <question>:
- The slash parser routes to
commands/btw/btw.tsx:229-236:
export async function call(
onDone: LocalJSXCommandOnDone,
context: ProcessUserInputContext,
args: string,
): Promise<React.ReactNode> {
const question = args?.trim()
if (!question) {
onDone('Usage: /btw <your question>', { display: 'system' })
return null
}-
The main TUI renders a
<BtwSideQuestion>React component (btw.tsx:36-181) as an overlay: spinner while fetching, scrollable markdown when the response arrives, dismiss with Space/Enter/Escape. -
Meanwhile the main agent loop keeps running in the background — it is not aborted, not paused.
-
A separate, ephemeral forked agent is spawned via
runForkedAgent()inutils/forkedAgent.ts. This is the same primitive used by memory extraction and the fork-subagent path.
The cached-param fork
The forked agent reuses the parent's cached prompt prefix. buildCacheSafeParams() (btw.tsx:208-227) builds a CacheSafeParams from the main thread's last request:
systemPrompt— cached system prompt bytes from the parent's last request.userContext— env/workspace state dict.systemContext— OS/system context appended.toolUseContext— current tool availability and thinking config.forkContextMessages— conversation messages after the compact boundary, with any in-progress assistant messages stripped (stripInProgressAssistantMessage()atsideQuestion.ts:201-206).
Because the cached-safe params match the parent byte-for-byte up to the boundary, the prompt cache hits and the side question is cheap.
The prompt the model actually sees
This is the most interesting bit. The user's question is wrapped in a <system-reminder> (sideQuestion.ts:61-78):
<system-reminder>This is a side question from the user. You must answer this question directly in a single response.
IMPORTANT CONTEXT:
- You are a separate, lightweight agent spawned to answer this one question
- The main agent is NOT interrupted - it continues working independently in the background
- You share the conversation context but are a completely separate instance
- Do NOT reference being interrupted or what you were "previously doing" - that framing is incorrect
CRITICAL CONSTRAINTS:
- You have NO tools available - you cannot read files, run commands, search, or take any actions
- This is a one-off response - there will be no follow-up turns
- You can ONLY provide information based on what you already know from the conversation context
- NEVER say things like "Let me try...", "I'll now...", "Let me check...", or promise to take any action
- If you don't know the answer, say so - do not offer to look it up or investigate
Simply answer the question with the information you have.</system-reminder>
${question}
Two things stand out:
- The clarification about identity. The model is told explicitly that it is a separate instance answering in parallel, so it doesn't produce weird "I was just doing X but let me pause" text.
- The anti-promise guardrail. "NEVER say things like 'Let me try...', 'Let me check...'" — because this agent has no tools and one turn, any future-tense promise is a lie.
Tool access
Zero. canUseTool returns { behavior: 'deny', reason: 'side_question' } for every tool (sideQuestion.ts:86-89). If the model tries a tool anyway, the call is caught and replaced with an error.
Budget
maxTurns: 1— one response, no loop.skipCacheWrite: true— does not pollute the parent's prompt cache with post-boundary content.- Thinking config is inherited from the parent (
sideQuestion.ts:82-84) so the prefix hash matches.
Rendering
The result is extracted from assistant content blocks via extractSideQuestionResponse() (sideQuestion.ts:125-155). The <BtwSideQuestion> component renders markdown with scroll keys, dismiss keys, and a character counter. It's purely client-side — the result never enters the main conversation history.
What this demonstrates
- Forked agents with cache-safe params are a reusable primitive. The same mechanism powers
/btw, memory extraction, and fork subagents — only the wrapping prompt and tool policy change. - One-off read-only agents are safe to expose to users without permission dialogs: no tools = no side effects.
- Anti-pattern guardrails matter. The explicit "do not reference being interrupted" language exists because LLMs have a strong pull to narrate their situation; the prompt pushes back.
- Priority-independent responsiveness. Because
/btwis just a client-side React overlay plus a fork, the main agent never stops; the user can fire off a quick clarification while the main agent is still doing real work.