Skip to content

feat: add ephemeral /btw command for side questions - #3504

Closed
areu01or00 wants to merge 1 commit into
NousResearch:mainfrom
areu01or00:feat/btw-ephemeral-side-questions
Closed

feat: add ephemeral /btw command for side questions#3504
areu01or00 wants to merge 1 commit into
NousResearch:mainfrom
areu01or00:feat/btw-ephemeral-side-questions

Conversation

@areu01or00

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds an ephemeral /btw side-question command to Hermes.

/btw lets users ask a quick follow-up without interrupting the main session. It snapshots the current session context, answers with a separate no-tools helper agent, and does not persist the /btw exchange back into the main session
history.

This is implemented for both the interactive CLI and the messaging gateway.

Related Issue

No tracked issue.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • Added /btw to the central slash-command registry
  • Implemented CLI /btw as an ephemeral side-question flow that runs in parallel without interrupting the main session
  • Added a persist_session switch to AIAgent so ephemeral helper flows can avoid writing session logs and DB history
  • Implemented gateway /btw support for same-chat/thread replies
  • Added a per-session gateway guard so only one /btw runs at a time per chat/session
  • Updated slash-command reference docs
  • Added CLI and gateway tests for /btw

How to Test

  1. Run the targeted test suites:
    python -m pytest tests/test_cli_btw_command.py
    python -m pytest tests/gateway/test_btw_command.py
    python -m pytest tests/hermes_cli/test_commands.py
  2. In the CLI, start a task and run /btw .
    Verify the /btw reply appears separately and does not interrupt the main task.
  3. In a messaging chat/thread, send /btw .
    Verify the reply appears in the same chat/thread, does not interrupt the main run, and does not persist into main session history.

Checklist

Code

Documentation & Housekeeping

Screenshots / Logs

Targeted test results:

  • tests/test_cli_btw_command.py: passed
  • tests/gateway/test_btw_command.py: passed
  • tests/hermes_cli/test_commands.py: passed

@ticketclosed-wontfix

Copy link
Copy Markdown
Contributor

Reviewing this because I genuinely want this feature — ephemeral side questions is something I've wanted for a while. So this is coming from a place of "let's get this mergeable."

The ephemeral isolation design is solid — session_db=None, skip_memory=True, persist_session=False is the right way to keep /btw out of the main session. The per-session guard with add_done_callback cleanup is clean too. Good stuff.

Some issues worth addressing though:

Critical

gateway/run.py ~3884 — self._reasoning_config = reasoning_config inside _run_btw_task writes to the shared GatewayRunner singleton. This runs concurrently with the main session agent, so you can stomp the reasoning config mid-turn. Either pass it as a parameter to _resolve_turn_agent_config or make the btw path fully self-contained.

High

  • gateway/run.py ~3897 — list(getattr(running_agent, "_session_messages", []) or []) reads from a list the main agent is actively mutating from a thread pool executor. list() on a concurrently-modified list can produce a partial copy or raise RuntimeError. Safer to just fall back to session_store.load_transcript when an agent is active.

  • Both CLI and gateway pass the full unbounded conversation history to the btw agent. On long sessions this will blow through token limits for what's supposed to be a quick side question. Worth truncating to the last ~30 messages.

  • gateway/run.py ~3910 — **turn_route["runtime"] unpacking can collide with the explicit kwargs below it (e.g. reasoning_config, session_id). The CLI version correctly uses .get() for each key — mirror that pattern here.

  • tests/gateway/test_btw_command.py:116-130fake_loop.run_in_executor is mocked to return the result directly, so run_sync() never executes and the AIAgent patch is dead code. This test doesn't actually verify the agent gets the right params or that run_conversation is called.

Medium

  • asyncio.get_event_loop() at line ~3930 — deprecated in 3.10+, will raise in some 3.12+ contexts. Should be asyncio.get_running_loop() (there's already a correct usage elsewhere in this file).

  • max_iterations=8 (gateway) / max(1, min(self.max_turns, 8)) (CLI) — a no-tools agent completes in exactly 1 LLM call. Setting this to 1 avoids wasting tokens on retry loops that can't accomplish anything.

  • enabled_toolsets=["__btw_no_tools__"] works by accident — unknown toolset names silently resolve to no tools. If toolset resolution ever starts validating names, this breaks. enabled_toolsets=[] would be a more explicit contract.

  • TOCTOU on self._app in the background thread (cli.py ~4174, ~4198) — app can become None between the check and the call if the user exits. Capture to a local first.

  • When adapter is None in _run_btw_task, it logs a warning and returns silently. The user gets no feedback. Better to check adapter presence in _handle_btw_command and return an error message before spawning the task.

Nit

import time as _tmod appears twice inside run_btw() (success and error branches). Just import it once at the top of the function.


Good enough to ship after addressing the critical and high items. The rest can be follow-ups.

teknium1 added a commit that referenced this pull request Mar 31, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR #3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #4161 — reimplemented cleanly on current main. Your design was the right approach (snapshot history, no-tools agent, persist_session flag). Key changes: fixed enabled_toolsets=[] to actually mean 'no tools' instead of the btw_no_tools hack, added skip_context_files, and cleaned up the gateway code. Thanks @areu01or00!

@teknium1 teknium1 closed this Mar 31, 2026
teknium1 added a commit that referenced this pull request Mar 31, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR #3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
angelburgosrosado pushed a commit to angelburgosrosado/hermes-agent that referenced this pull request Apr 27, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR NousResearch#3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
angelburgosrosado pushed a commit to angelburgosrosado/hermes-agent that referenced this pull request Apr 28, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR NousResearch#3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR NousResearch#3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR NousResearch#3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
waefrebeorn pushed a commit to waefrebeorn/slermes that referenced this pull request Jul 2, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR NousResearch#3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
nepenth pushed a commit to nepenth/hermes-agent that referenced this pull request Aug 7, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR NousResearch#3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
jh1nresh pushed a commit to jh1nresh/hermes-agent that referenced this pull request Aug 26, 2026
Adds /btw <question> — ask a quick follow-up using the current
session context without interrupting the main conversation.

- Snapshots conversation history, answers with a no-tools agent
- Response is not persisted to session history or DB
- Runs in a background thread (CLI) / async task (gateway)
- Per-session guard prevents concurrent /btw in gateway

Implementation:
- model_tools.py: enabled_toolsets=[] now correctly means "no tools"
  (was falsy, fell through to default "all tools")
- run_agent.py: persist_session=False gates _persist_session()
- cli.py: _handle_btw_command (background thread, Rich panel output)
- gateway/run.py: _handle_btw_command + _run_btw_task (async task)
- hermes_cli/commands.py: CommandDef for "btw"

Inspired by PR NousResearch#3504 by areu01or00, reimplemented cleanly on current
main with the enabled_toolsets=[] fix and without the __btw_no_tools__
hack.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants