Commit Graph

2 Commits

Author SHA1 Message Date
teknium1 90a65d6e38 fix(gateway): gateway-stop teardown fires memory-provider lifecycle hooks under the owning profile
Under `gateway.multiplex_profiles: true` every gateway-hosted session that ended
inside the gateway process logged `Memory provider 'openviking' on_session_end
failed: get_secret('OPENVIKING_API_KEY') called with no profile secret scope
active` and skipped its end-of-session commit. The provider lifecycle hooks
(`flush_pending` -> `on_session_end` -> provider teardown -> `close`) read
credentials and home at call time; the stop-time finalize pass and the
idle-cache sweep run on the main loop outside any adapter handler, so
`_run_in_executor_with_context` copied an EMPTY scope into the worker.

Route `_cleanup_agent_resources_off_loop` and `_finalize_session_off_loop`
through `_run_release_in_profile_scope` (the seam cache eviction already uses):
a scoped caller keeps its scope; an unscoped caller passes the session key and
the owner's profile scope is entered from it. The stop path passes the key for
both active and idle-cached agents. Cron's post-run cleanup thread is the
sibling seam and is fixed by the cherry-picked #110634.

Live repro (fake OpenViking recording the X-API-Key header, alpha profile,
multiplex on): base - WARNING + traceback, no commit; head - no warning,
`POST /api/v1/sessions/<sid>/commit` arrives with alpha's key.

Fixes #110622.
2026-09-14 16:13:51 -07:00
Teknium 763b10c320 fix(gateway): run session-finalize plugin hooks off-loop and bounded
Session-finalize hooks ran synchronously on the gateway event loop from
three call sites (shutdown drain, session-expiry watcher, /new reset).
A plugin hook doing heavy blocking work froze the whole loop: adapter
heartbeats stopped, the drain machinery could not run, and systemd
eventually SIGKILLed the process mid-export. Observed live on a
multi-day 4.7G session where the nemo_relay observability plugin
serialized a full-session ATIF trace inside on_session_finalize.

Changes:
- gateway/run.py: new GatewayRunner._finalize_session_off_loop()
  dispatches hermes_cli.lifecycle.finalize_session via the gateway
  executor under asyncio.wait_for (10s budget), mirroring
  _cleanup_agent_resources_off_loop (#53175). Shutdown finalize and
  the session-expiry watcher now use it.
- gateway/slash_commands.py: /new reset path uses the same helper.
- plugins/observability/nemo_relay: ATIF export is now bounded
  (HERMES_NEMO_RELAY_ATIF_EXPORT_TIMEOUT_S, default 30s) and skipped
  entirely for sessions whose Relay scope operations already errored
  (their exporter state is unreliable and the export can be
  pathologically slow).
- tests/gateway/test_finalize_session_off_loop.py: regression tests
  proving the loop stays live under a wedged hook and the budget is
  enforced.
2026-08-15 14:20:27 -07:00