feat: /btw now answers side questions with conversation context; /background renamed to /bg

/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).
This commit is contained in:
Teknium
2026-08-29 07:14:17 -07:00
parent b6bd681e89
commit 74a95a3ddf
63 changed files with 966 additions and 169 deletions
+21 -19
View File
@@ -6396,7 +6396,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
except Exception:
pass
# Count live /background tasks. The dict entry is removed in the
# Count live /bg tasks. The dict entry is removed in the
# task thread's finally block, so len() reflects truly-running tasks.
# len() on a CPython dict is atomic; safe to read without a lock.
try:
@@ -11947,20 +11947,20 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
def _should_handle_background_command_inline(
self, text: str, has_images: bool = False
) -> bool:
"""Return True when /background should be dispatched while the agent runs.
"""Return True when /bg or /btw should be dispatched while the agent runs.
Same queue problem /steer had. ``/background`` (``/bg``, ``/btw``)
exists to start independent work *without* waiting for the current
turn, but a slash command typed while the agent is busy goes into
``_pending_input``, and ``process_loop`` is blocked inside
``self.chat()`` for the whole run. The background task therefore only
starts once the foreground turn has finished, which is the one moment
it was not needed.
Same queue problem /steer had. ``/bg`` exists to start independent
work *without* waiting for the current turn, and ``/btw`` exists to
answer a side question about the in-flight conversation, but a slash
command typed while the agent is busy goes into ``_pending_input``,
and ``process_loop`` is blocked inside ``self.chat()`` for the whole
run. The side task would therefore only start once the foreground
turn has finished, which is the one moment it was not needed.
The command's own ``CommandDef`` already declares
Both commands' ``CommandDef`` entries already declare
``busy_policy="dispatch"``; the gateway honours that, the classic CLI
never consulted it. Dispatching inline on the UI thread starts the
background session immediately and leaves the foreground turn running
side session immediately and leaves the foreground turn running
untouched: no interrupt, no steer.
"""
if not text or has_images or not _looks_like_slash_command(text):
@@ -11971,7 +11971,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
from hermes_cli.commands import resolve_command
base = text.split(None, 1)[0].lower().lstrip('/')
cmd = resolve_command(base)
return bool(cmd and cmd.name == "background")
return bool(cmd and cmd.name in ("bg", "btw"))
except Exception:
return False
@@ -12575,8 +12575,10 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
self._handle_agents_command()
elif canonical == "journey":
self._handle_journey_command(cmd_original)
elif canonical == "background":
elif canonical == "bg":
self._handle_background_command(cmd_original)
elif canonical == "btw":
self._handle_btw_command(cmd_original)
elif canonical == "queue":
# Extract prompt after "/queue " or "/q "
parts = cmd_original.split(None, 1)
@@ -18213,12 +18215,12 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin):
event.app.invalidate()
return
# Same treatment for /background (/bg, /btw) while the agent is
# running. Queuing it defeats the entire point of the command:
# process_loop is blocked inside self.chat(), so the background
# task would only start once the foreground turn it was meant to
# run alongside has already finished (#75221). The foreground
# turn is left alone: no interrupt, no steer.
# Same treatment for /bg and /btw while the agent is
# running. Queuing them defeats the entire point of the
# commands: process_loop is blocked inside self.chat(), so the
# side task would only start once the foreground turn it was
# meant to run alongside has already finished (#75221). The
# foreground turn is left alone: no interrupt, no steer.
if self._should_handle_background_command_inline(
text, has_images=has_images
):