diff --git a/gateway/run.py b/gateway/run.py index 04c80279c8..b5d49dbcb3 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -21397,6 +21397,12 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew if not sid: return + # Warm the SessionDB cache off-loop. A cold cache at the turn + # boundary stalls the loop for the init duration and can drop + # the tick-completion write (the /goal continuation seam, one + # sibling over). + await self._warm_goals_session_db("loop completion") + mgr = LoopManager(session_id=sid) state = mgr.state if state is None or not state.awaiting_response: @@ -21435,6 +21441,11 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew list_active_loops, ) + # Warm the cache off-loop once per scan. The scan reads + # every persisted loop, so a cold cache runs the state.db + # init on the loop thread before the first read. + await self._warm_goals_session_db("loop wakeup") + now = time.time() for sid, state in list_active_loops(): if state.awaiting_response or now < state.next_due_at: diff --git a/gateway/slash_commands.py b/gateway/slash_commands.py index d66162fb60..78c276dcfc 100644 --- a/gateway/slash_commands.py +++ b/gateway/slash_commands.py @@ -3038,6 +3038,10 @@ class GatewaySlashCommandsMixin: except Exception as exc: logger.debug("loop manager unavailable: %s", exc) return None, None + # Warm the SessionDB cache off-loop. A cold cache drops the first + # /loop write while the reply claims the loop was set (same class + # as the /goal false-ack fix). + await self._warm_goals_session_db("loop manager") try: session_entry = await self.async_session_store.get_or_create_session(event.source) except Exception: diff --git a/hermes_cli/loops.py b/hermes_cli/loops.py index 87e07a6842..7368d85b2e 100644 --- a/hermes_cli/loops.py +++ b/hermes_cli/loops.py @@ -415,6 +415,12 @@ def save_loop(session_id: str, state: LoopState) -> None: return db = _get_session_db() if db is None: + logger.warning( + "LoopManager: loop for %s not persisted — session DB " + "unavailable (bootstrap window exceeded, in-memory state " + "still active)", + session_id, + ) return try: db.set_meta(_meta_key(session_id), state.to_json())