Skip to content

Add a /btw side question command - #1555

Open
yablokolabs wants to merge 2 commits into
CodebuffAI:mainfrom
yablokolabs:feat/btw-side-question
Open

yablokolabs wants to merge 2 commits into
CodebuffAI:mainfrom
yablokolabs:feat/btw-side-question

Conversation

@yablokolabs

Copy link
Copy Markdown

What

/btw <question> answers a question about the current session without touching it. One model call, no tools, the answer rendered inline, and the run state dropped.

Why

A quick question in the middle of a task ("why is this flag off?", "what does this function do?") used to go through the normal turn path, so the question and the answer stayed in the conversation for every later turn to carry. Claude Code's /btw does the opposite: fork the context, answer once, throw the fork away. This is the same idea on the SDK every other turn already runs on.

How

  • cli/src/commands/btw.ts builds a tool-less agent definition (toolNames: []) on the model the current freebuff selection runs, and calls client.run with previousRun set to the session's last settled run state and maxAgentSteps: 1. The returned run state is discarded, so the session and the transcript stay as they were.
  • Catalog selections run a server-side handle that only the catalog root admits, so the side agent names the compiled model id underneath the row. Everything else already is a plain model id.
  • cli/src/commands/command-registry.ts registers the command freebuff-only, next to /model and /reasoning, because the side agent is built from the freebuff catalog. A missing question prints usage. No run yet explains that a message comes first. Failures print inline instead of throwing out of the input handler.
  • cli/src/data/slash-commands.ts adds it to the freebuff menu.
  • BYOK: the handler resolves the selected connection the way a normal send does, so a side question runs on the user's provider when they picked one.

Tests

bun test --preload ./cli/test/setup-agents-artifact.ts cli/src/commands/__tests__/btw.test.ts (4 passing): one call with no tools and one step, previousRun passed through, run errors surfaced, an empty answer rejected, and no client asking for /login.

freebuff-command-aliases.test.ts now also asserts /btw exists in Freebuff mode and is absent in Codebuff mode.

Limits

  • I could not run this against the live API: this checkout has no credentials. The call is the same client.run every turn uses, with previousRun and an inline agent.
  • Tools are withheld through the agent definition, not through a hard deny in the runtime. The instructions also tell the model it has none.
  • A question asked while a turn is running forks the last settled state, so it does not see that turn's newest messages.

Asking something small mid-task used to put the question and answer in the
conversation for every later turn to carry. `/btw` forks the session's last
settled run, answers in one tool-less model call, and drops the result, so the
transcript stays the work itself.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@codebuff-team

Copy link
Copy Markdown
Contributor

Nice shape for a feature PR: clear scope, a dedicated btw.ts module with an injectable getClient for testing, and a real test file (btw.test.ts) covering the no-tools/one-step contract, error surfacing, empty-answer rejection, and the no-login case. The freebuff-only gating in command-registry.ts and slash-commands.ts is consistent with how /reasoning is already gated, and the aliases test was updated to assert the gating both ways - that's the right instinct for a feature like this.

Concerns that would need resolving before this is portable as-is:

  1. sideQuestionAgent() in btw.ts reaches into getFreebuffModelDirectory() and compiledFreebuffModelIdOfRow, i.e. it depends on the freebuff catalog internals (freebuff-catalog-compiled-model, freebuff-catalog-store). That's exactly the kind of private-tree-coupled code the maintainers will want to look at closely, since this mirror squashes from a private source and those modules may have moved or been renamed since this export.
  2. The command silently uses useChatStore.getState().runState as previousRun - worth confirming this is actually always "the last settled run," per the PR description's own caveat that a question asked mid-turn forks stale state. That's an acknowledged limitation, not a blocker, but it should be called out loudly in a code comment at the call site, not just the PR body.
  3. costMode: 'free' is hardcoded in askSideQuestion - is that intentional regardless of the user's actual selected model/plan? Worth a comment or confirmation.
  4. No test exercises the BYOK resolution path (resolveByokConnection) wired up in command-registry.ts, despite the PR body calling it out as a deliberate feature.

Given the PR touches freebuff-specific catalog/BYOK surface that's adjacent to (if not inside) paths the mirror restricts, and the size/complexity here, this needs a maintainer who knows the private catalog internals to validate before it's portable. Solid work, just needs that review pass.

@codebuff-team codebuff-team added bot:triaged Classified by the community triage bot pr:needs-work Right idea, not mergeable as written labels Oct 26, 2026
The side question hardcoded costMode 'free', so a user who picked their own
provider still sent the fork through the free-mode admission path a normal send
avoids. Match the send path, spell out the mid-turn run-state staleness where
the state is read, and cover the BYOK branch in the test.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
@yablokolabs

yablokolabs commented Oct 26, 2026 •

Copy link
Copy Markdown
Author

@codebuff-team thanks for the review, Follow-up on the four points, pushed as 4845ff6.

Catalog internals. compiledFreebuffModelIdOfRow comes from common/src/util/freebuff-catalog-compiled-model in this mirror, not the private tree. The same row lookup and compiled-id call already run in cli/src/utils/freebuff-catalog-agent.ts, cli/src/utils/freebuff-agent-selection.ts and cli/src/components/freebuff-model-selector.tsx. If those move upstream, /btw breaks alongside them, so this file adds no new exposure.

Stale runState. Confirmed, not assumed: the chat store is written once per turn after the run settles (setRunState in cli/src/hooks/use-send-message.ts:988; status-bar.tsx:138 relies on the same fact). A mid-turn /btw therefore forks the session as of the previous settle. The comment at the call site now says that, next to where the state is read.

costMode. Not intentional, and a real bug. A normal send uses costMode: byok ? 'normal' : 'free' (cli/src/hooks/use-send-message.ts:814) so BYOK never enters the free-mode admission or budget path. /btw hardcoded 'free' even while passing a BYOK connection. It now matches the send path.

BYOK test. Added: the new case asserts the connection reaches getClient and that costMode is 'normal', and the existing case pins 'free' for non-BYOK.

Verification: btw.test.ts 5 pass, freebuff-command-aliases.test.ts 2 pass. CLI typecheck reports 20 errors before and after this change (stash-compared), all in packages/agent-runtime and sdk from an @ai-sdk provider type mismatch; none in cli. Still no live API run from this checkout, and tool withholding remains toolNames: [] plus prompt, not a runtime deny.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:triaged Classified by the community triage bot pr:needs-work Right idea, not mergeable as written

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants