From 9a62bc0c9b8629c7819dc89af36d742005d0edcc Mon Sep 17 00:00:00 2001 From: mika <211269698+mikamikasuki@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:36:33 -0700 Subject: [PATCH 1/4] fix(chat): start return pump after transports Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com> --- loopx/capabilities/manager_context/roundtrip.py | 3 ++- loopx/chat_server.py | 5 ++++- loopx/extensions/lark/manager_returns.py | 5 +++-- tests/test_chat_transport_composition.py | 13 +++++++++++++ tests/test_manager_context_roundtrip.py | 10 ++++++++++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/loopx/capabilities/manager_context/roundtrip.py b/loopx/capabilities/manager_context/roundtrip.py index e4cb10af09..2b66f39593 100644 --- a/loopx/capabilities/manager_context/roundtrip.py +++ b/loopx/capabilities/manager_context/roundtrip.py @@ -1370,4 +1370,5 @@ def run(self): def close(self): self.stop.set() - self.thread.join(timeout=3) + if self.thread.ident is not None: + self.thread.join(timeout=3) diff --git a/loopx/chat_server.py b/loopx/chat_server.py index 7fef1f807d..4d89b8fd7f 100644 --- a/loopx/chat_server.py +++ b/loopx/chat_server.py @@ -1690,7 +1690,9 @@ def _lark_snapshot(): ) server.lark_goal_topic_runtime.start() from .extensions.lark.manager_returns import start_return_service - server.manager_return_service = start_return_service(server, server.runtime_controller.coordination_runtime_root) + server.manager_return_service = start_return_service( + server, server.runtime_controller.coordination_runtime_root, start_service=False + ) from .chat_loopx_mode import DelegationWakeService def _wake_goal_context(session): @@ -1717,6 +1719,7 @@ def _wake_goal_context(session): try: if external_conversation_factories: server.conversation_transports.start(server) + server.manager_return_service.start() server.serve_forever() except KeyboardInterrupt: print("Stopping LoopX Chat", flush=True) diff --git a/loopx/extensions/lark/manager_returns.py b/loopx/extensions/lark/manager_returns.py index 2f37bcb7aa..67869cc7eb 100644 --- a/loopx/extensions/lark/manager_returns.py +++ b/loopx/extensions/lark/manager_returns.py @@ -320,7 +320,7 @@ def verify(self, route: dict[str, Any], session: dict[str, Any], turn: dict[str, ) -def start_return_service(server: Any, runtime_root: Path) -> Any: +def start_return_service(server: Any, runtime_root: Path, *, start_service: bool = True) -> Any: """Compose the return pump at the existing Chat/Lark service boundary.""" from ...capabilities.manager_context.roundtrip import ReturnService @@ -333,5 +333,6 @@ def start_return_service(server: Any, runtime_root: Path) -> Any: runtime_root, server.registry_path, server.chat_store, transport ) transport.cancelled = service.stop.is_set - service.start() + if start_service: + service.start() return service diff --git a/tests/test_chat_transport_composition.py b/tests/test_chat_transport_composition.py index 665c094bcc..31fbd4082b 100644 --- a/tests/test_chat_transport_composition.py +++ b/tests/test_chat_transport_composition.py @@ -17,6 +17,7 @@ def __init__(self, ref): self.transport_ref = ref self.calls = [] self.available = True + self.started = False def observe(self): self.calls.append("observe") @@ -33,6 +34,7 @@ def verify(self, route, session, turn, text, attempt): return {"reply_verified": False, "verification_performed": True} def start(self, server): + self.started = True self.calls.append(("start", server)) def close(self): @@ -184,6 +186,16 @@ def test_real_chat_entrypoint_composes_one_store_controller_and_return_service(t entered = threading.Event() captured = [] external = Transport("external-owner") + return_service_start_observations = [] + if installed: + from loopx.capabilities.manager_context import roundtrip + original_return_service_start = roundtrip.ReturnService.start + + def record_return_service_start(service): + return_service_start_observations.append(external.started) + original_return_service_start(service) + + monkeypatch.setattr(roundtrip.ReturnService, "start", record_return_service_start) class Server(chat.ChatHTTPServer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -204,6 +216,7 @@ def __init__(self, *args, **kwargs): if installed: assert server.manager_return_service.args[3] is server.conversation_transports assert server.conversation_transports.bindings is server.runtime_controller.project_contexts.conversation_bindings + assert return_service_start_observations == [True] else: from loopx.extensions.lark.manager_returns import LarkManagerReturnTransport assert not hasattr(server, "conversation_transports") diff --git a/tests/test_manager_context_roundtrip.py b/tests/test_manager_context_roundtrip.py index 4fcd509d11..5483fcd96d 100644 --- a/tests/test_manager_context_roundtrip.py +++ b/tests/test_manager_context_roundtrip.py @@ -855,6 +855,16 @@ def transport(*_): assert observations == [] # Legacy delivery is outside the exact-source telemetry contract. +def test_return_service_can_close_before_start(flow): + root, registry, store, _ = flow + service = ReturnService(root, registry, store, lambda *_: {"reply_verified": True}) + + service.close() + + assert service.stop.is_set() + assert not service.thread.is_alive() + + @pytest.mark.parametrize("project", [False, True], ids=["steward", "project"]) def test_registration_revocation_blocks_return_without_retargeting(flow, project): root, registry, store, create = flow From c80b3cfb4c95ce01a87d71cc632846c028e73ddf Mon Sep 17 00:00:00 2001 From: mika <211269698+mikamikasuki@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:00:56 -0700 Subject: [PATCH 2/4] fix(chat): type return service lifecycle Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com> --- loopx/capabilities/manager_context/roundtrip.py | 4 ++-- loopx/chat_server.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/loopx/capabilities/manager_context/roundtrip.py b/loopx/capabilities/manager_context/roundtrip.py index 2b66f39593..58d28c8e14 100644 --- a/loopx/capabilities/manager_context/roundtrip.py +++ b/loopx/capabilities/manager_context/roundtrip.py @@ -1355,7 +1355,7 @@ def __init__(self, root, registry, store, external_sender): target=self.run, daemon=True, name="loopx-manager-returns" ) - def start(self): + def start(self) -> None: self.thread.start() def run(self): @@ -1368,7 +1368,7 @@ def run(self): ) self.stop.wait(3) - def close(self): + def close(self) -> None: self.stop.set() if self.thread.ident is not None: self.thread.join(timeout=3) diff --git a/loopx/chat_server.py b/loopx/chat_server.py index 4d89b8fd7f..9bfeb40d8d 100644 --- a/loopx/chat_server.py +++ b/loopx/chat_server.py @@ -52,7 +52,10 @@ from .extensions.lark.conversation_identity import observe_lark_conversation_identity from .extensions.lark.private_conversations import LarkPrivateConversations from .chat_loopx_mode import handle_loopx_request -from .capabilities.manager_context.roundtrip import project_chat_session_snapshot +from .capabilities.manager_context.roundtrip import ( + ReturnService, + project_chat_session_snapshot, +) from .control_plane.goals.active_state_metadata import active_state_section_text from .control_plane.coordination.local_authority import LocalCoordinationAuthorityUnavailable from .control_plane.status.ssh_host_catalog import ( @@ -420,6 +423,7 @@ class ChatHTTPServer(ThreadingHTTPServer): action_store: ChatActionStore action_service: ChatActionService runtime_controller: ChatRuntimeController + manager_return_service: ReturnService lark_runner: CommandRunner lark_cli_resolution: LarkCliResolution lark_app_setup_manager: LarkAppSetupManager From 23cae923ecf14cce246b497d9a617bc7b4e497ff Mon Sep 17 00:00:00 2001 From: mika <211269698+mikamikasuki@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:36:00 -0700 Subject: [PATCH 3/4] chore(semantics): refresh Chat I/O line census Regenerate the existing project-registry I/O manifest after the Chat startup typing change moved four load_registry call sites. Keep the recorded kinds and owners unchanged. Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com> --- loopx/semantics/project_registry_io_manifest_v1.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/loopx/semantics/project_registry_io_manifest_v1.json b/loopx/semantics/project_registry_io_manifest_v1.json index a66ddbac18..f0cae2cb5f 100644 --- a/loopx/semantics/project_registry_io_manifest_v1.json +++ b/loopx/semantics/project_registry_io_manifest_v1.json @@ -495,7 +495,7 @@ }, { "site": "loopx/chat_server.py::.ChatRequestHandler._goal_channel_extension_ready::codec_read:load_registry#1", - "line": 1009, + "line": 1013, "column": 24, "kind": "codec_read", "api": "load_registry", @@ -503,7 +503,7 @@ }, { "site": "loopx/chat_server.py::.ChatRequestHandler._registry_and_goal::codec_read:load_registry#1", - "line": 536, + "line": 540, "column": 20, "kind": "codec_read", "api": "load_registry", @@ -511,7 +511,7 @@ }, { "site": "loopx/chat_server.py::.serve_chat::codec_read:load_registry#1", - "line": 1588, + "line": 1592, "column": 16, "kind": "codec_read", "api": "load_registry", @@ -519,7 +519,7 @@ }, { "site": "loopx/chat_server.py::.serve_chat._wake_goal_context::codec_read:load_registry#1", - "line": 1697, + "line": 1701, "column": 20, "kind": "codec_read", "api": "load_registry", From 0e6a4c87e1dc9a7a2c15cf3d4c9c19300b5fa87c Mon Sep 17 00:00:00 2001 From: mika <211269698+mikamikasuki@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:00:38 -0700 Subject: [PATCH 4/4] chore(semantics): refresh Chat I/O line census Signed-off-by: mika <211269698+mikamikasuki@users.noreply.github.com> --- loopx/semantics/project_registry_io_manifest_v1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loopx/semantics/project_registry_io_manifest_v1.json b/loopx/semantics/project_registry_io_manifest_v1.json index f0cae2cb5f..0fac76cd91 100644 --- a/loopx/semantics/project_registry_io_manifest_v1.json +++ b/loopx/semantics/project_registry_io_manifest_v1.json @@ -519,7 +519,7 @@ }, { "site": "loopx/chat_server.py::.serve_chat._wake_goal_context::codec_read:load_registry#1", - "line": 1701, + "line": 1703, "column": 20, "kind": "codec_read", "api": "load_registry",