diff --git a/agent/relay_runtime.py b/agent/relay_runtime.py index 2b49725cbcf..895308b322d 100644 --- a/agent/relay_runtime.py +++ b/agent/relay_runtime.py @@ -201,14 +201,15 @@ class RelayRuntime: raise RuntimeError("Hermes Relay session is closing") if session.context is None or session.handle is None: raise RuntimeError("Hermes Relay session context is unavailable") + context = session.context.copy() - def invoke() -> Any: - self.relay.get_scope_stack() - return callback(*args, **kwargs) + def invoke() -> Any: + self.relay.get_scope_stack() + return callback(*args, **kwargs) - # A copy permits a helper called by an existing Relay callback to - # re-enter the same logical session without re-entering Context. - return session.context.copy().run(invoke) + # A copy permits a helper called by an existing Relay callback to + # re-enter the same logical session without re-entering Context. + return context.run(invoke) async def run_in_session_async( self, diff --git a/tests/hermes_cli/test_relay_shared_metrics_runtime.py b/tests/hermes_cli/test_relay_shared_metrics_runtime.py index 7b39d0973c6..1ef5a18dd88 100644 --- a/tests/hermes_cli/test_relay_shared_metrics_runtime.py +++ b/tests/hermes_cli/test_relay_shared_metrics_runtime.py @@ -1182,6 +1182,34 @@ def test_async_session_runner_awaits_inside_saved_relay_context(direct_runtime): assert result == session.handle +def test_sync_session_runner_releases_lock_before_callback(direct_runtime): + runtime = relay_runtime.get_runtime() + assert runtime is not None + session = runtime.ensure_session({"session_id": "sync-session"}) + assert session is not None + acquired = threading.Event() + contender = None + + def probe() -> Any: + nonlocal contender + + def acquire_session_lock() -> None: + with session.lock: + acquired.set() + + contender = threading.Thread(target=acquire_session_lock) + contender.start() + assert acquired.wait(timeout=1) + return direct_runtime._scope.get() + + result = runtime.run_in_session(session, probe) + assert contender is not None + contender.join(timeout=1) + + assert result == session.handle + assert contender.is_alive() is False + + def test_active_turn_requires_matching_session_and_profile( direct_runtime, tmp_path,