Skip to content

fix: multiplex cron delivery routes through correct profile bot - #80876

Open
xbrxr03 wants to merge 5 commits into
NousResearch:mainfrom
xbrxr03:fix/multiplex-cron-delivery-routing
Open

fix: multiplex cron delivery routes through correct profile bot#80876
xbrxr03 wants to merge 5 commits into
NousResearch:mainfrom
xbrxr03:fix/multiplex-cron-delivery-routing

Conversation

@xbrxr03

@xbrxr03 xbrxr03 commented Aug 7, 2026

Copy link
Copy Markdown

Bug

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 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's adapters 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

Three-file change threading profile_adapters through the cron delivery chain:

  1. gateway/run.py: Pass runner._profile_adapters to the cron scheduler at startup.

  2. cron/scheduler_provider.py: Thread profile_adapters through start(), _start_multiplex(), and the per-profile cron_tick() calls.

  3. cron/scheduler.py:

    • Add profile_adapters param to tick(), run_one_job(), and _deliver_result().
    • Stamp each job with _profile_home (the profile's HERMES_HOME) in tick() before dispatching to ThreadPoolExecutor. This is critical because Python 3.11's ThreadPoolExecutor does not propagate ContextVar overrides to worker threads — so _get_hermes_home() inside _deliver_result() would return the default home, not the profile's.
    • 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.

Verification

2026-08-05 07:21:58 INFO cron.scheduler: Job c6c2af16728f: using profile outreach adapter for telegram delivery
2026-08-05 12:00:30 INFO cron.scheduler: Job 0a6b7a0c6c34: using profile outreach adapter for telegram delivery
2026-08-05 22:02:01 INFO cron.scheduler: Job e8c05d2b5e45: using profile chase adapter for telegram delivery

Messages now appear in the correct profile's Telegram chat.

Why _profile_home instead of ContextVar?

The multiplex cron tick sets HERMES_HOME_OVERRIDE via set_hermes_home_override() before calling cron_tick(), and resets it in a finally block. But cron_tick() dispatches jobs to a ThreadPoolExecutor. Python 3.11 does not propagate ContextVar values to ThreadPoolExecutor workers (fixed in 3.12+), so _get_hermes_home() inside _deliver_result() would always return the default home. Stamping _profile_home on the job dict in the main thread (where the override is active) before dispatch solves this reliably across Python versions.

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.
@alt-glitch alt-glitch added type/bug Something isn't working comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery area/profiles Multi-profile isolation, HERMES_HOME scoping P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages duplicate This issue or pull request already exists labels Aug 7, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #73363. Both patches correct multiplex cron delivery that otherwise uses the default profile bot. #73363 is the earlier focused implementation and includes regression coverage for connected, empty, and absent secondary adapter maps.

AJH763 added 3 commits August 8, 2026 00:57
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.
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants