feat(gateway): route inbound messages via routes table + GatewayRunner profile binding (3/6)#37497
Open
davidgut1982 wants to merge 7 commits into
Open
feat(gateway): route inbound messages via routes table + GatewayRunner profile binding (3/6)#37497davidgut1982 wants to merge 7 commits into
davidgut1982 wants to merge 7 commits into
Conversation
This was referenced Jun 2, 2026
c589883 to
99f6de7
Compare
Introduce AgentProfile dataclass and a ContextVar (_current_agent_profile) that lets path getters (get_hermes_home, get_skills_dir, get_memory_dir) resolve to the active agent's home directory under asyncio. - agent/profile.py: AgentProfile, use_profile() context manager, load_agent_registry() from GatewayConfig - hermes_constants.py: get_hermes_home() reads ContextVar before env fallback - tests/agent/test_profile_contextvar.py: ContextVar isolation under asyncio.gather, nested contexts, registry loading Single-agent installs see zero change — no profile bound means fallback to HERMES_HOME env var as before.
Add agent_id field to SessionSource and SessionEntry, prefix session keys with agent:<id>: in build_session_key. Default "main" preserves every historical key string for single-agent installs. - gateway/session.py: SessionSource.agent_id, SessionEntry.agent_id, build_session_key prefixing - hermes_state.py: sessions table migration (agent_id TEXT DEFAULT 'main'), new idx_sessions_agent index - tests/gateway/test_session.py: build_session_key prefixing for all chat_type × agent_id combinations - tests/*/test_session_boundary_hooks.py: hook payload agent_id kwarg
… hook
Add declarative routing (routes: match → agent) and a select_agent plugin
hook. _attach_agent_id injects the resolved agent_id into event.source
before build_session_key. Seven platform adapters get pre-injection for
batching paths; the rest inherit it from base.py.
- gateway/agent_routing.py: resolve_agent_id(), _route_matches()
- gateway/config.py: agents, routes, default_agent schema
- gateway/platforms/base.py: _attach_agent_id(), set_routing_context()
- gateway/platforms/{telegram,discord,slack,matrix,feishu,wecom,yuanbao}.py:
pre-batch injection
- hermes_cli/plugins.py: select_agent hook registration
- tests/gateway/test_agent_routing.py: declared-order matching, hook chain,
default fallback, profile isolation
…s agent_id to hooks
GatewayRunner loads the agent registry at init and wraps every inbound
message in use_profile(). AIAgent accepts an optional profile= kwarg.
All invoke_hook call sites gain agent_id= kwarg. _handle_message is
split into _handle_message (ContextVar plumbing) + _handle_message_inner
(legacy logic) so tests that grep the source body continue to work.
- gateway/run.py: registry loading, use_profile() wrapping, hook kwargs
- run_agent.py: AIAgent(profile=), profile-aware model/toolset resolution
- model_tools.py, tools/{approval,terminal,delegate}.py: hook agent_id
- cli.py, tui_gateway/server.py: session boundary hook agent_id
- tests/gateway/test_profile_overrides.py: per-agent model/toolset overrides
- tests/test_model_tools.py: hook payload verification
- tests/gateway/test_{update,title,reasoning}_command.py: adapt to
_handle_message split
The MGA series added _attach_agent_id() into the handle_message hot path (base.py:handle_message). It reads three instance attributes set only in BasePlatformAdapter.__init__: _default_agent_id, _gateway_routes, and _gateway_ref. Any adapter constructed without running __init__ (or a future partial-construction path) crashes with AttributeError on _default_agent_id, which is read outside the existing try/except. Because this runs on every inbound message, a missing attribute would crash real message processing, not just tests. Harden the method to read all three via getattr() with safe defaults so a partially-constructed adapter degrades to the "main" agent instead of raising. Also fix the _make_adapter() test helper, which deliberately bypasses __init__ via object.__new__() and hand-sets attributes: it predated the MGA routing attributes and never set them. Add the three so the mock faithfully mirrors a real __init__-constructed adapter. Fixes 10 regressions in tests/gateway/test_active_session_text_merge.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
987095f to
8793b95
Compare
DeliveryRouter.__init__ was missing the `registry` keyword argument that gateway/run.py:1868 passes as `DeliveryRouter(self.config, registry=self._agent_registry)`. This caused a TypeError on startup failing pytest shards 2/3/4/6 on PR NousResearch#37497 (mga-3). Also restores: - `agent_id` field on DeliveryTarget dataclass - `agent_id=origin.agent_id` propagation in DeliveryTarget.parse() - `_profile`/`use_profile` profile-binding block in deliver() Authoritative reference: feat/mga-5-cron-api-propagation:gateway/delivery.py Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Empty commit to fire push event so the fix(gateway) delivery.py commit (6fbb441) is picked up by NousResearch CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Route inbound messages via a
routestable plus aselect_agenthook, and haveGatewayRunnerload the agent registry, bind the activeAgentProfile, and propagateagent_idto hooks. Adds per-agent profile runtime overrides (model/provider/base_url/api_key_env).Why
This is the core of single-gateway/multi-agent: an inbound message must be mapped to an agent, that agent's profile bound for the turn, and its model/provider overrides applied over gateway defaults.
How to test
Platforms tested
Linux (CT 133 / Proxmox LXC)
Part 3 of 6 in the multi-agent gateway decomposition (replaces #34741). Depends on: #37496
Note on conflict resolution: two cherry-picks.
gateway/config.pywas an additive conflict (keptmain'sfilter_silence_narrationloader alongside the newagents/routes/default_agentloaders).gateway/run.pyconflicted withmain's #35314 empty-model "last-known-good" safety net; resolved by applying_apply_profile_runtime_overrides()first, then the safety net as the final guard, so an empty model after profile resolution is still recovered.