From e8b7ce8c19d24db1b3a31609d81d2d2dd419122c Mon Sep 17 00:00:00 2001 From: Burgunthy Date: Sun, 28 Jun 2026 05:51:49 +0900 Subject: [PATCH] fix(session): persist profile_name and route batch key by profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two follow-ups observed after deploying profile routing: 1. sessions.profile_name was NULL even when the agent ran inside the routed profile scope. _insert_session_row never wrote it, get_or_create_session / reset_session never passed it through, and the agent-side _ensure_db_session fallback had no way to read it. - Declare profile_name TEXT in SCHEMA_SQL so _reconcile_columns auto-adds it on existing DBs. - _insert_session_row takes profile_name and writes it. - SessionStore passes source.profile (or old_entry.origin.profile on reset) into db_create_kwargs. - _ensure_db_session reads the active profile via get_active_profile_name() inside _profile_runtime_scope. 2. DiscordAdapter._text_batch_key called build_session_key without profile=, so the batch key always landed in agent:main even when the routed profile differed — diverging from the agent session key namespace (agent:crypto-trader, agent:ai-expert, ...). Pass event.source.profile through so both namespaces agree. Live verification (jth-server-2, 2026-06-28): a test message in a routed #coin thread produced agent:crypto-trader:discord:thread:... in the batch log and profile_name=crypto-trader in the sessions row. Default-routed chat still produced agent:main / NULL. Co-Authored-By: Claude Opus 4.7 --- gateway/session.py | 2 ++ hermes_state.py | 10 +++++++--- plugins/platforms/discord/adapter.py | 10 +++++++++- run_agent.py | 8 ++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/gateway/session.py b/gateway/session.py index 962d186c3513..c5356f088bfd 100644 --- a/gateway/session.py +++ b/gateway/session.py @@ -1993,6 +1993,7 @@ class SessionStore: "chat_id": source.chat_id, "chat_type": source.chat_type, "thread_id": source.thread_id, + "profile_name": source.profile, } if _needs_save: @@ -2273,6 +2274,7 @@ class SessionStore: "chat_id": old_entry.origin.chat_id if old_entry.origin else None, "chat_type": old_entry.origin.chat_type if old_entry.origin else None, "thread_id": old_entry.origin.thread_id if old_entry.origin else None, + "profile_name": old_entry.origin.profile if old_entry.origin else None, } if self._db and db_end_session_id: diff --git a/hermes_state.py b/hermes_state.py index eafefc2ea976..34923ebc4f23 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -791,6 +791,7 @@ CREATE TABLE IF NOT EXISTS sessions ( compression_failure_cooldown_until REAL, compression_failure_error TEXT, compression_fallback_streak INTEGER NOT NULL DEFAULT 0, + profile_name TEXT, rewind_count INTEGER NOT NULL DEFAULT 0, archived INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (parent_session_id) REFERENCES sessions(id) @@ -1759,6 +1760,7 @@ class SessionDB: thread_id: str = None, parent_session_id: str = None, cwd: str = None, + profile_name: str = None, ) -> None: """Insert a session row, enriching NULL metadata on conflict. @@ -1782,9 +1784,9 @@ class SessionDB: conn.execute( """INSERT INTO sessions ( id, source, user_id, session_key, chat_id, chat_type, thread_id, - model, model_config, system_prompt, parent_session_id, cwd, started_at + model, model_config, system_prompt, parent_session_id, cwd, profile_name, started_at ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET model = COALESCE(sessions.model, excluded.model), model_config = COALESCE(sessions.model_config, excluded.model_config), @@ -1794,7 +1796,8 @@ class SessionDB: chat_type = COALESCE(sessions.chat_type, excluded.chat_type), thread_id = COALESCE(sessions.thread_id, excluded.thread_id), parent_session_id = COALESCE(sessions.parent_session_id, excluded.parent_session_id), - cwd = COALESCE(sessions.cwd, excluded.cwd)""", + cwd = COALESCE(sessions.cwd, excluded.cwd), + profile_name = COALESCE(sessions.profile_name, excluded.profile_name)""", ( session_id, source, @@ -1808,6 +1811,7 @@ class SessionDB: system_prompt, parent_session_id, cwd, + profile_name, time.time(), ), ) diff --git a/plugins/platforms/discord/adapter.py b/plugins/platforms/discord/adapter.py index b216ef29d8ea..a97aef067710 100644 --- a/plugins/platforms/discord/adapter.py +++ b/plugins/platforms/discord/adapter.py @@ -6612,12 +6612,20 @@ class DiscordAdapter(BasePlatformAdapter): # ------------------------------------------------------------------ def _text_batch_key(self, event: MessageEvent) -> str: - """Session-scoped key for text message batching.""" + """Session-scoped key for text message batching. + + Passes ``event.source.profile`` through so routed messages batch + under the same namespace the agent run will use (e.g. + ``agent:crypto-trader`` instead of ``agent:main``). Without this, + the batch key would always land in ``agent:main`` even when the + routed profile differs. + """ from gateway.session import build_session_key return build_session_key( event.source, group_sessions_per_user=self.config.extra.get("group_sessions_per_user", True), thread_sessions_per_user=self.config.extra.get("thread_sessions_per_user", False), + profile=event.source.profile, ) def _enqueue_text_event(self, event: MessageEvent) -> None: diff --git a/run_agent.py b/run_agent.py index ecb6dce613f4..bcacec3909b6 100644 --- a/run_agent.py +++ b/run_agent.py @@ -599,6 +599,13 @@ class AIAgent: return source = _session_source_for_agent(self.platform) try: + try: + from hermes_cli.profiles import get_active_profile_name + _profile_for_session = get_active_profile_name() + if _profile_for_session == "default": + _profile_for_session = None + except Exception: + _profile_for_session = None self._session_db.create_session( session_id=self.session_id, source=source, @@ -608,6 +615,7 @@ class AIAgent: user_id=None, parent_session_id=self._parent_session_id, cwd=_launch_cwd_for_session(source), + profile_name=_profile_for_session, ) self._session_db_created = True except Exception as e: