fix: multiplex cron delivery routes through correct profile bot - #80876
Open
xbrxr03 wants to merge 5 commits into
Open
fix: multiplex cron delivery routes through correct profile bot#80876xbrxr03 wants to merge 5 commits into
xbrxr03 wants to merge 5 commits into
Conversation
When multiplex_profiles is enabled, all cron job deliveries route through
the default profile's Telegram adapter regardless of which profile owns the
cron job. This causes SCOUT's hourly reports to appear in JARVIS's chat,
CHASE's EOD briefs to leak into the wrong DM, etc.
Root cause: resolve_delivery_transport() in _deliver_result() receives only
the default profile's dict. The per-profile adapters
(_profile_adapters on GatewayRunner) are never passed through the cron
delivery chain, so every delivery resolves to the main profile's bot.
Fix:
- gateway/run.py: pass runner._profile_adapters to the cron scheduler
- cron/scheduler_provider.py: thread profile_adapters through start() and
_start_multiplex() to cron_tick()
- cron/scheduler.py:
- Add profile_adapters param to tick(), run_one_job(), _deliver_result()
- Stamp each job with _profile_home (the profile's HERMES_HOME) in tick()
before dispatching to ThreadPoolExecutor, since ContextVars don't
propagate to worker threads in Python 3.11
- In _deliver_result(), when profile_adapters is provided, match the
job's _profile_home to the correct profile's adapter and construct a
DeliveryTransport that routes through that profile's bot
Verified: agent.log confirms 'Job X: using profile Y adapter for telegram
delivery' for chase and outreach crons. Messages now appear in the correct
profile's Telegram chat.
Collaborator
When multiplex_profiles is enabled, all cron job deliveries route through
the default profile's Telegram adapter regardless of which profile owns the
cron job. This causes SCOUT's hourly reports to appear in JARVIS's chat,
CHASE's EOD briefs to leak into the wrong DM, etc.
Root cause: resolve_delivery_transport() in _deliver_result() receives only
the default profile's dict. The per-profile adapters
(_profile_adapters on GatewayRunner) are never passed through the cron
delivery chain, so every delivery resolves to the main profile's bot.
Fix:
- gateway/run.py: pass runner._profile_adapters to the cron scheduler
- cron/scheduler_provider.py: thread profile_adapters through start() and
_start_multiplex() to cron_tick()
- cron/scheduler.py:
- Add profile_adapters param to tick(), run_one_job(), _deliver_result()
- Stamp each job with _profile_home (the profile's HERMES_HOME) in tick()
before dispatching to ThreadPoolExecutor, since ContextVars don't
propagate to worker threads in Python 3.11
- In _deliver_result(), when profile_adapters is provided, match the
job's _profile_home to the correct profile's adapter and construct a
DeliveryTransport that routes through that profile's bot
Verified: agent.log confirms 'Job X: using profile Y adapter for telegram
delivery' for chase and outreach crons. Messages now appear in the correct
profile's Telegram chat.
Previous patch set transport and pconfig but left runtime_adapter and the adapters dict pointing to the default profile. DeliveryRouter uses self.adapters to find the platform bot, so it still sent through JARVIS's bot. Now swaps adapters to the profile's dict and sets runtime_adapter to the profile's adapter.
14 tasks
xbrxr03
pushed a commit
to xbrxr03/hermes-agent
that referenced
this pull request
Aug 15, 2026
Two bugs fixed: 1. _build_process_event_source: extract profile from session_key so delegation completions and watch patterns from non-default profiles (CONTENT, CHASE, etc.) carry source.profile for correct adapter routing. Previously, background-process SessionSource objects had no profile, causing _adapter_for_source to fall back to the default adapter. 2. _inject_watch_notification + _run_process_watcher: replace self.adapters iteration with _adapter_for_source(source) for profile-aware adapter resolution. Without this, delegation completions from secondary profiles always route through the default (JARVIS) bot. Same root cause as the cron delivery bug (PR NousResearch#80876): multiplex profiles all share self.adapters, so any code path that iterates self.adapters instead of using _adapter_for_source sends through the wrong bot.
Two bugs fixed: 1. _build_process_event_source: extract profile from session_key so delegation completions and watch patterns from non-default profiles (CONTENT, CHASE, etc.) carry source.profile for correct adapter routing. Previously, background-process SessionSource objects had no profile, causing _adapter_for_source to fall back to the default adapter. 2. _inject_watch_notification + _run_process_watcher: replace self.adapters iteration with _adapter_for_source(source) for profile-aware adapter resolution. Without this, delegation completions from secondary profiles always route through the default (JARVIS) bot. Same root cause as the cron delivery bug (PR NousResearch#80876): multiplex profiles all share self.adapters, so any code path that iterates self.adapters instead of using _adapter_for_source sends through the wrong bot.
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.
Bug
When
multiplex_profilesis enabled, all cron job deliveries route through the default profile's Telegram adapter regardless of which profile owns the cron job. This causes cross-profile message leakage — e.g., SCOUT's hourly reports appearing in JARVIS's chat, CHASE's EOD briefs leaking into the wrong DM, etc.Root Cause
resolve_delivery_transport()in_deliver_result()receives only the default profile'sadaptersdict. The per-profile adapters (_profile_adaptersonGatewayRunner) are never passed through the cron delivery chain, so every delivery resolves to the main profile's bot.Fix
Three-file change threading
profile_adaptersthrough the cron delivery chain:gateway/run.py: Passrunner._profile_adaptersto the cron scheduler at startup.cron/scheduler_provider.py: Threadprofile_adaptersthroughstart(),_start_multiplex(), and the per-profilecron_tick()calls.cron/scheduler.py:profile_adaptersparam totick(),run_one_job(), and_deliver_result()._profile_home(the profile'sHERMES_HOME) intick()before dispatching toThreadPoolExecutor. This is critical because Python 3.11'sThreadPoolExecutordoes not propagateContextVaroverrides to worker threads — so_get_hermes_home()inside_deliver_result()would return the default home, not the profile's._deliver_result(), whenprofile_adaptersis provided, match the job's_profile_hometo the correct profile's adapter and construct aDeliveryTransportthat routes through that profile's bot.Verification
Messages now appear in the correct profile's Telegram chat.
Why _profile_home instead of ContextVar?
The multiplex cron tick sets
HERMES_HOME_OVERRIDEviaset_hermes_home_override()before callingcron_tick(), and resets it in afinallyblock. Butcron_tick()dispatches jobs to aThreadPoolExecutor. Python 3.11 does not propagateContextVarvalues toThreadPoolExecutorworkers (fixed in 3.12+), so_get_hermes_home()inside_deliver_result()would always return the default home. Stamping_profile_homeon the job dict in the main thread (where the override is active) before dispatch solves this reliably across Python versions.