Skip to content

refactor: agent-driven handover, drop gateway-side classifier - #1

Merged
keiravoss94 merged 1 commit into
mainfrom
chore/agent-driven-handover
Apr 27, 2026
Merged

keiravoss94 merged 1 commit into
mainfrom
chore/agent-driven-handover

Conversation

@keiravoss94

@keiravoss94 keiravoss94 commented Apr 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Consolidates handover activation onto the agent-callable trigger_handover tool and removes the gateway's own pre-dispatch classification paths (phrase match + auxiliary LLM classifier). The pre-dispatch hook now only enforces an already active handover (silent ingest + owner /takeback) and the listen-only flow.

Motivation

  • The auxiliary LLM classifier ran one extra completion call per non-phrase DM, with no conversation context. The main agent's normal turn already has full context, so classifying twice was wasteful and worse-informed.
  • Phrase triggers were unreliable in the multi-language deployments these profiles serve (zh / ms / en mixed in a single chat).
  • The agent's own judgement plus a clear tool description is good enough; profile-specific scope rules belong in AGENTS.md, not in plugin config.

Removed

  • LLMClassifierConfig, HandoverTriggers config dataclasses
  • llm_classifier_says_handover + _extract_completion_text
  • matches_phrases
  • Phrase + classifier branches in rules/handover.py
  • Related tests
  • Dead config.example.yaml / README.md / after-install.md references to triggers: / phrases / llm_classifier

Kept

  • trigger_handover tool (the state.handovers.activate path is unchanged)
  • state.py, notify.py, transcript_utils.py
  • rules/listen_only.py (untouched)
  • Owner /takeback exit handling in rules/handover.py

Version bump

  • plugin.yaml: 0.1.0 → 0.2.0. (Not bumping pyproject.toml; no version pinned there.)

Consumer impact

Path-installed / symlinked profiles need a gateway restart to pick up the new code:

hermes gateway restart -p <profile>

Profiles are not required to change config.yaml; any leftover triggers: block under plugins.gateway-policy.handover is silently ignored by the new loader.

Test plan

  • pytest -x passes (22/22)
  • Smoke: hermes gateway restart -p basketball-jersey-demo, tail gateway logs, confirm gateway-policy loaded: ... handover=True, tool=True and no errors.

Summary by CodeRabbit

  • Breaking Changes

    • Handover activation is now agent-driven exclusively via the trigger_handover tool; gateway-side phrase and LLM classifier triggers have been removed.
  • Configuration Updates

    • Removed phrase-based and LLM classifier trigger settings from handover configuration.
    • Replaced generic notification template with separate notify_on_activate and notify_on_exit templates with updated placeholders.
  • Chores

    • Version bumped to 0.2.0.

Removes the LLM classifier and phrase trigger paths from the
pre_gateway_dispatch hook. Activation is now exclusively the
trigger_handover tool's responsibility, called by the main agent
with full conversation context. The plugin's pre-dispatch hook
retains silent ingest for active-handover sessions and listens
for the owner /takeback command.

Motivation:
- LLM classifier added one aux-LLM call per non-phrase DM, blind
  to conversation context. The main agent's normal turn already
  has full context; classifying twice was wasteful.
- Phrase triggers were unreliable across the multi-language
  deployments these profiles serve (zh / ms / en mixed).

Removed: LLMClassifierConfig, HandoverTriggers,
llm_classifier_says_handover, _extract_completion_text,
matches_phrases, the phrase + classifier branches in
rules/handover.py, related tests, and dead config.example /
README references. plugin.yaml bumped to 0.2.0.

Kept: trigger_handover tool (state.activate path unchanged),
state.py, rules/listen_only.py, notify.py, /takeback exit
handling.
@keiravoss94
keiravoss94 merged commit be99c6e into main Apr 27, 2026
1 check was pending
@coderabbitai

coderabbitai Bot commented Apr 27, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1680879f-40e3-4f85-aa93-46e9045082b4

📥 Commits

Reviewing files that changed from the base of the PR and between 7591320 and d371f2a.

📒 Files selected for processing (10)
  • README.md
  • __init__.py
  • after-install.md
  • config.example.yaml
  • config.py
  • plugin.yaml
  • rules/handover.py
  • tests/conftest.py
  • tests/test_rules.py
  • triggers.py

Walkthrough

The handover mechanism is refactored to shift activation responsibility from the gateway to the agent. Gateway-side phrase matching and LLM classifier triggers are removed entirely. Activation now occurs exclusively through the agent-callable trigger_handover tool, while the gateway rule enforces already-active handovers via owner commands.

Changes

Cohort / File(s) Summary
Documentation
README.md, after-install.md, __init__.py
Updated docstrings and guides to reflect agent-driven activation model, removing references to gateway-side phrase triggers and optional LLM classifier.
Configuration Schema
config.example.yaml, config.py, plugin.yaml
Removed triggers.phrases and triggers.llm_classifier config blocks; replaced single notify_template with notify_on_activate and notify_on_exit templates; bumped plugin version to 0.2.0.
Core Logic
rules/handover.py, triggers.py
Deleted _activate function and all phrase/LLM trigger paths from handover rule; removed matches_phrases() and llm_classifier_says_handover() helper functions from triggers module; rule now guards on state.handovers.is_active() before processing.
Tests
tests/conftest.py, tests/test_rules.py
Updated test fixture to remove HandoverTriggers import; rewrote handover rule tests to pre-activate state and verify no-op behavior when inactive; added coverage for owner /takeback exit command.

Sequence Diagram(s)

sequenceDiagram
    participant Agent
    participant Gateway as Gateway<br/>(handover rule)
    participant State as State<br/>(handovers)
    participant Owner
    participant Customer

    rect rgba(200, 150, 100, 0.5)
    Note over Agent,State: Activation (Agent-Driven)
    Agent->>State: trigger_handover(chat_id)
    State->>State: Set handover active
    end

    rect rgba(100, 150, 200, 0.5)
    Note over Gateway,Owner: Gateway Enforcement
    Customer->>Gateway: Send message
    Gateway->>State: Check is_active()?
    State-->>Gateway: true
    Gateway->>Gateway: Silent ingest<br/>(no transcript to chat)
    Gateway->>Owner: Notify owner
    Owner->>Customer: Send /takeback
    Owner->>Gateway: /takeback command
    Gateway->>State: Deactivate handover
    Gateway-->>Customer: handover_exit
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~40 minutes

Poem

🐰 Whiskers twitched with delight,
Gateway triggers? No more!
Agent holds the handover light,
Activation by tool, not lore. ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/agent-driven-handover

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

1 participant