Skip to content

feat(plugins): add context-aware turn controllers and busy-safe commands - #63208

Open
0error0warning wants to merge 3 commits into
NousResearch:mainfrom
0error0warning:feature/supergoal-plugin-abi-upstream
Open

feat(plugins): add context-aware turn controllers and busy-safe commands#63208
0error0warning wants to merge 3 commits into
NousResearch:mainfrom
0error0warning:feature/supergoal-plugin-abi-upstream

Conversation

@0error0warning

Copy link
Copy Markdown

Summary

Adds generic plugin ABI needed by standalone long-running controllers, without any Supergoal-specific code:

  • context-aware slash commands with CommandContext.session_id and enqueue_followup
  • async-safe plugin command dispatch
  • post-turn TurnControlContext / TurnDirective controllers with dedupe and monotonic state versions
  • on_session_rotate hook for compression continuity
  • busy-safe plugin control subcommands that never interrupt an active agent
  • CLI, Gateway, TUI, and compression integration

Design

The core remains product-name-agnostic. A standalone plugin owns state, policy, evidence, and command semantics; Hermes only provides generic lifecycle and queue primitives.

Verification

  • tests/hermes_cli/test_plugin_turn_control_abi.py
  • tests/gateway/test_plugin_turn_control_abi.py
  • tests/hermes_cli/test_plugins.py
  • 133 focused Core tests passed on current main
  • external standalone consumer suite: 57 tests passed against this branch
  • py_compile and git diff --check passed

No new model tools or user-facing environment variables are added.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/plugins Plugin system and bundled plugins sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 12, 2026

@teknium1 teknium1 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.

Thanks for extracting this as a generic plugin ABI rather than coupling core to a consumer.

Problems

  • Busy-safe gateway commands are unreachable in real traffic. The new runner-side branch in gateway/run.py:9242 is behind BasePlatformAdapter.handle_message(): an active session first checks only should_bypass_active_session() and otherwise queues the event (gateway/platforms/base.py:4675-4826). The added test calls runner._handle_message() directly, so it bypasses that production guard.
  • The public ABI is undocumented. website/docs/developer-guide/plugins/index.md:788 still publishes the old register_command signature and has no contract for CommandContext, turn directives, or controller lifecycle behavior.

Suggested changes

  • Extend the adapter-level busy routing to recognize and directly dispatch declared safe plugin controls, then add an end-to-end active-adapter regression test.
  • Update the plugin developer guide with the new API and surface-specific follow-up semantics.

Automated hermes-sweeper review.

Comment thread gateway/run.py
if _denied is not None:
return _denied

# Plugin commands are absent from the built-in registry. Resolve

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.

This branch is not reached for an actual busy adapter session: BasePlatformAdapter.handle_message() queues events unless the command is in its static should_bypass_active_session() set (gateway/platforms/base.py:4675-4826). Route declared busy-safe plugin commands through that adapter-level bypass path and add a test entering handle_message() with an active guard.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 15, 2026
@redbean808909

Copy link
Copy Markdown

Thanks for extracting this as a generic plugin ABI. I have a second concrete consumer for this work and wanted to suggest a possible convergence with #65188.

Concrete consumer: a source-first supervisor

I have a working downstream implementation of a prospective /spec plugin. It accepts one or more specification documents and creates a durable ledger mapping each source requirement to:

  • its implementation status;
  • implementation or verification evidence;
  • the next required action; and
  • exact source references.

After every completed agent turn, the supervisor audits the ledger and source coverage. It requests another turn while repository-completable requirements remain, pauses on explicit blockers or sustained lack of progress, and completes only after the evidence and coverage gates pass.

It also provides /spec status|pause|resume|stop|clear. The plugin would own all source parsing, ledger policy, persistence, state fencing, and command semantics. I am not proposing any /spec-specific behavior in Hermes core.

This differs from a general long-running goal controller because completion is measured against exact source-document requirements and evidence, rather than only a free-form mission description. It gives this ABI another real consumer without coupling the ABI to either product.

Possible convergence with #65188

#65188 already establishes useful session-identity plumbing:

  • the current, rotatable session_id;
  • the stable gateway_session_key;
  • signature-aware context forwarding;
  • async/awaitable command compatibility; and
  • propagation across the current CLI, gateway, TUI, and hook paths.

Would it make sense to treat #65188 as the identity layer and this PR as the control layer?

In that arrangement, this PR would remain responsible for:

  • capability-bearing command context;
  • safe follow-up enqueueing;
  • post-turn controllers and directives;
  • busy-safe control subcommands; and
  • session-rotation notification.

I think the two identities should remain explicit in both CommandContext and TurnControlContext:

@dataclass(frozen=True)
class CommandContext:
    surface: str
    session_id: str
    gateway_session_key: str
    platform: str
    source: Any | None
    task_id: str
    metadata: Mapping[str, Any]
    enqueue_followup: Callable[[str], Awaitable[bool]]

session_id, gateway_session_key, and task_id have different lifetimes:

  • session_id identifies the current physical transcript and may rotate.
  • gateway_session_key identifies the stable routed conversation.
  • task_id should identify a particular active execution or cancellation scope.

In particular, task_id should not double as the stable conversation key. Controller dedupe and state-version tracking should use:

stable_identity = context.gateway_session_key or context.session_id

One command-dispatch convention

The current PR and #65188 propose different context-aware handler conventions. One possible reconciliation would be to preserve #65188's raw_args-first, signature-aware dispatcher and add the typed context as an optional keyword:

def handle_super(
    raw_args: str,
    *,
    command_context: CommandContext,
):
    ...

Internally:

call_plugin_command_handler(
    handler,
    raw_args,
    session_id=context.session_id,
    gateway_session_key=context.gateway_session_key,
    command_context=context,
)

This would support a compatible progression:

handler(raw_args)
handler(raw_args, *, session_id="", gateway_session_key="")
handler(raw_args, *, command_context=...)

It would avoid maintaining both signature-aware keyword forwarding and a separate context_aware=True positional calling convention. Existing handlers remain unchanged, while richer plugins can request the capability-bearing object.

Production-path considerations

The current review correctly notes that busy-safe gateway dispatch must happen before BasePlatformAdapter queues an event for an active session. For this consumer, /spec status, /spec pause, and /spec stop must remain reachable while the worker is active, while /spec start or replacement operations should still be rejected.

I suggest preserving normal slash-command authorization and adding an E2E test that enters through the real adapter path with an active session. A test that calls runner._handle_message() directly will not cover the production guard.

It would also help to define follow-up semantics explicitly for each surface. In particular, the current TUI context supplies an enqueue_followup implementation that always returns False; that should either gain a native transport or be documented as unsupported rather than presenting apparent parity.

Proposed initial acceptance cases

My downstream implementation can contribute reduced tests for the following generic contracts:

  1. A legacy handler(raw_args) remains byte-for-byte compatible.
  2. A rich handler receives distinct live and stable session identities.
  3. A follow-up captured for an old physical session is rejected after rotation.
  4. Busy-safe status/pause/stop reaches the handler through the real adapter and authorization path.
  5. A non-safe plugin subcommand is rejected while busy without becoming user text.
  6. At most one automatic follow-up is accepted after a turn.
  7. Duplicate or stale controller directives are ignored using stable identity and monotonic state versions.
  8. Session rotation preserves the plugin's association with the logical conversation.

Cold-start recovery and delayed session wakeup do not need to be part of this PR. Those appear better aligned with #64229 and #65448. ACP parity could likewise be a focused follow-up once the core contract is settled.

Would maintainers be open to treating #65188 as the identity foundation and revising or splitting this PR so that it supplies the command capabilities and turn-control layer on top? I would be happy to provide a reduced source-supervisor fixture and production-path regression tests as an external acceptance consumer.

I am also willing to implement this convergence if the direction is acceptable. Depending on contributor and maintainer preference, I can provide commits for this branch or submit a focused successor rebased on #65188. The implementation would be limited to the generic command-context, turn-controller, stable-identity, and production busy-routing contracts described above, with the source supervisor remaining an external acceptance consumer. I would preserve attribution to both existing contributions and avoid opening another overlapping ABI proposal.

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

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants