Skip to content

fix(cli): dispatch /agents inline while agent is running (#32477) - #32541

Closed
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/cli-agents-inline-busy-path-32477
Closed

fix(cli): dispatch /agents inline while agent is running (#32477)#32541
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/cli-agents-inline-busy-path-32477

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

What does this PR do?

Typing /agents (or its /tasks alias) during an active agent turn in the classic CLI does nothing — the user reports "the agents appear to be delegating properly, but attempting to monitor with /tasks or /agents does nothing". The slash command flows through _pending_input, which process_loop only drains after self.chat() returns; until then the queued command sits silently behind the running delegation chain. From the user's perspective the keystroke is lost during exactly the window the command exists for.

Mirror the inline-dispatch pattern PR #25011 introduced for /steer: detect /agents on the UI thread inside handle_enter and call process_command directly. _handle_agents_command is read-only (touches process_registry plus a couple of CLI attrs) and _cprint already routes thread-unsafe output through prompt_toolkit's run_in_terminal, so UI-thread dispatch is safe.

Mirrors _should_handle_steer_command_inline / _should_handle_model_command_inline precedence: detector → inline process_command on the UI thread when _agent_running is True; idle path falls through to the normal _pending_inputprocess_loop flow. Resolves /tasks via resolve_command so the alias dispatches identically.

Related Issue

Fixes #32477

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

  • cli.py — add _should_handle_agents_command_inline (parallel to _should_handle_steer_command_inline) and dispatch inline from handle_enter when the detector fires.
  • tests/cli/test_cli_agents_busy_path.py — new regression suite mirroring test_cli_steer_busy_path.py: detector returns True for /agents and /tasks while running, False when idle / non-slash / images attached / other slash commands; process_command routes to _handle_agents_command without enqueueing on _pending_input.

How to Test

  1. hermes chat with a delegating profile.
  2. Start a task that delegates to a subagent.
  3. While the agent is mid-run, type /agents (or /tasks). The active-agents + running-processes summary should print above the prompt immediately instead of disappearing into the queue.
  4. uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/cli/test_cli_agents_busy_path.py tests/cli/test_cli_steer_busy_path.py -v — 15 passes.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run focused tests for the touched code and all pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15.x

Documentation & Housekeeping

  • I've updated relevant documentation (README, `docs/`, docstrings) — or N/A
  • I've updated `cli-config.yaml.example` if I added/changed config keys — or N/A
  • I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

Audited siblings: scanned cli.py for other slash commands wired through process_command that are likely to be typed while the agent is running and are read-only / thread-safe (/status, /profile, /help, /commands, /tools, /toolsets, /whoami). They share the same queue-deadlock symptom but each has different UI/console assumptions, and none was named in the bug report. Intentionally left out of this PR's scope to keep the diff minimal — happy to widen if preferred, or to generalize via a safe_inline_when_busy flag on CommandDef.

Copilot AI review requested due to automatic review settings May 26, 2026 10:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds inline (busy-path) dispatch for the classic CLI /agents command (and /tasks alias) so it runs immediately during an active agent loop instead of being queued behind process_loop and effectively doing nothing until the run completes.

Changes:

  • Add _should_handle_agents_command_inline() to detect when /agents or /tasks should bypass the queue while the agent is running
  • Update handle_enter() to print and dispatch /agents inline when busy
  • Add regression tests validating detector behavior and that /agents//tasks do not enqueue when process_command() is invoked

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
tests/cli/test_cli_agents_busy_path.py Adds regression tests for inline detection/dispatch for /agents and /tasks while busy
cli.py Implements the inline detector and triggers inline dispatch from the UI thread in handle_enter()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cli.py
@@ -7792,6 +7792,30 @@ def _should_handle_steer_command_inline(self, text: str, has_images: bool = Fals
except Exception:
return False

def _should_handle_agents_command_inline(self, text: str, has_images: bool = False) -> bool:
Comment thread cli.py
Comment on lines +7807 to +7817
if not text or has_images or not _looks_like_slash_command(text):
return False
if not getattr(self, "_agent_running", False):
return False
try:
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 == "agents")
except Exception:
return False
Comment thread cli.py
Comment on lines +7811 to +7814
try:
from hermes_cli.commands import resolve_command
base = text.split(None, 1)[0].lower().lstrip('/')
cmd = resolve_command(base)
Comment thread cli.py Outdated
# makes it useless: the user sees "nothing happens" until the
# whole delegation chain finishes. See #32477.
if self._should_handle_agents_command_inline(text, has_images=has_images):
_cprint(f"\n⚙️ {text}")
Comment on lines +113 to +138
"""When the detector fires, /agents dispatch through process_command must
reach _handle_agents_command directly rather than being queued."""

def test_process_command_routes_to_agents_handler(self):
"""With _agent_running=True, /agents calls _handle_agents_command and
does NOT enqueue onto _pending_input."""
cli = _make_cli()
cli._agent_running = True
cli._pending_input = MagicMock()
cli._handle_agents_command = MagicMock()

cli.process_command("/agents")

cli._handle_agents_command.assert_called_once()
cli._pending_input.put.assert_not_called()

def test_tasks_alias_routes_to_agents_handler(self):
"""/tasks resolves to canonical 'agents' and dispatches the same."""
cli = _make_cli()
cli._agent_running = True
cli._pending_input = MagicMock()
cli._handle_agents_command = MagicMock()

cli.process_command("/tasks")

cli._handle_agents_command.assert_called_once()
Comment on lines +113 to +138
"""When the detector fires, /agents dispatch through process_command must
reach _handle_agents_command directly rather than being queued."""

def test_process_command_routes_to_agents_handler(self):
"""With _agent_running=True, /agents calls _handle_agents_command and
does NOT enqueue onto _pending_input."""
cli = _make_cli()
cli._agent_running = True
cli._pending_input = MagicMock()
cli._handle_agents_command = MagicMock()

cli.process_command("/agents")

cli._handle_agents_command.assert_called_once()
cli._pending_input.put.assert_not_called()

def test_tasks_alias_routes_to_agents_handler(self):
"""/tasks resolves to canonical 'agents' and dispatches the same."""
cli = _make_cli()
cli._agent_running = True
cli._pending_input = MagicMock()
cli._handle_agents_command = MagicMock()

cli.process_command("/tasks")

cli._handle_agents_command.assert_called_once()
@alt-glitch alt-glitch added P2 Medium — degraded but workaround exists type/bug Something isn't working comp/cli CLI entry point, hermes_cli/, setup wizard labels May 26, 2026
@briandevans

Copy link
Copy Markdown
Contributor Author

@copilot Addressed in commit 3b5eb124d:

  • /agents foo detection — detector now requires the input to be a bare command with no trailing tokens (len(text.split()) != 1 short-circuits). Inputs like /agents foo / /tasks something now fall through to the normal dispatch where _handle_agents_command can surface a usage error rather than silently dropping the argument on the busy path. Added test_ignores_agents_with_trailing_args covering this.
  • ⚙️ {text} echo — removed. The busy-path block now mirrors the existing /steer block exactly (silent inline dispatch); _handle_agents_command's own _cprint output already lands cleanly via run_in_terminal. Avoids the screen-reader / terminal-font concern.
  • resolve_command hot-path import — fair point on overhead, but my detector intentionally mirrors the existing _should_handle_steer_command_inline and _should_handle_model_command_inline which both use the same inside-method import. Lifting just this one to module scope would diverge from the in-file convention; a cleaner fix is a follow-up that hoists the import for all three detectors. Happy to do that as a separate PR if preferred.
  • Tests vs handle_enter — the suite intentionally mirrors test_cli_steer_busy_path.py: detector unit tests + process_command routing tests, no direct handle_enter integration test. The full handle_enter chain depends on the prompt_toolkit event loop / key-binding fixtures that the existing busy-path suites don't mock, so adding it here would be a precedent break. Detector + process_command coverage is what the established pattern provides.

The CI failure on test (3) is a known baseline flake — slice 3 reports FATAL: exception not rethrown from tests/acp/test_server.py after the 78-test session passes cleanly (PyO3 runtime cleanup artifact, unrelated to this PR's touched files).

…h#32477)

Typing /agents (or its /tasks alias) during an active agent turn does
nothing in classic CLI: the slash command goes through _pending_input,
which process_loop only drains after self.chat() returns. By the time
the queued command is pulled, the user has been staring at a quiet
screen for the entire delegation chain — defeating the introspection
command's only purpose.

Mirror the inline-dispatch pattern PR NousResearch#25011 introduced for /steer:
when the detector sees /agents while the agent is running, call
process_command directly on the UI thread. _handle_agents_command
is read-only (reads process_registry + a couple of CLI attrs) and
_cprint already routes thread-unsafe output through
prompt_toolkit's run_in_terminal, so UI-thread dispatch is safe.

Fixes NousResearch#32477
Address Copilot inline review on NousResearch#32541:

* Reject `/agents foo` / `/tasks something` on the busy path — `/agents`
  takes no args, so trailing tokens must fall through to the normal
  dispatch where `_handle_agents_command` can surface a usage error
  rather than silently dropping the argument.
* Drop the `⚙️  {text}` echo from the busy-path block.  Mirrors the
  existing `/steer` block which dispatches silently and lets
  `_handle_agents_command`'s own output land via `_cprint` /
  `run_in_terminal`.  Avoids the screen-reader / terminal-font concern
  Copilot flagged.

Adds a `test_ignores_agents_with_trailing_args` case covering the new
detector constraint.
@briandevans
briandevans force-pushed the fix/cli-agents-inline-busy-path-32477 branch from 3b5eb12 to b9d2f4a Compare May 28, 2026 17:09
@briandevans

Copy link
Copy Markdown
Contributor Author

Closing to focus the queue on security/file-safety work where civilian merges are landing. Happy to reopen if maintainers want this picked up.

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

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: /tasks and /agents do nothing

3 participants