From 5a7548f5e51eb2397ffa8dbddb6a27f4279a7a7f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 17:07:15 -0400 Subject: [PATCH 1/3] Check the selected route at each ACP prompt --- src/toad/acp/agent_process.py | 7 ++++--- src/toad/comms_root.py | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/toad/acp/agent_process.py b/src/toad/acp/agent_process.py index e1ce7831a..abd478d60 100644 --- a/src/toad/acp/agent_process.py +++ b/src/toad/acp/agent_process.py @@ -5,7 +5,6 @@ import json import os from collections.abc import Callable -from contextlib import ExitStack from contextvars import ContextVar from pathlib import Path from typing import Optional @@ -19,6 +18,7 @@ join_retirement, ) from agent_comms.launch_root import LaunchRoot +from agent_comms.errors import RouteChangedError from agent_comms.route_selection import RouteSelection from pydantic import JsonValue @@ -70,8 +70,7 @@ async def start(self, agent, processes: tuple[AgentProcess, ...]=()): env = os.environ.copy() cwd = str(agent.project_root_path.resolve()) selection = RouteSelection.capture(agent.controller.transcripts.access.launch_root.path) - with ExitStack() as acquisition: - acquisition.enter_context(selection.route.hold_client_route()) + with selection.route.hold_client_route(): if not agent.session.accepts_updates: return self for connection in dict.fromkeys((self, *processes)): @@ -95,6 +94,8 @@ async def start(self, agent, processes: tuple[AgentProcess, ...]=()): async def prompt(self, agent, content, metadata: dict[str, JsonValue], require: Callable[[], None]): """The agent-comms-acp child writes the prompt under Core's maintenance gate.""" + if RouteSelection.capture() != self.route_selection: + raise RouteChangedError(RouteChangedError.reader_sentence) require() return await self.rpc.prompt(agent.session_id, content, **metadata) diff --git a/src/toad/comms_root.py b/src/toad/comms_root.py index 75198f65a..fdf00ee28 100644 --- a/src/toad/comms_root.py +++ b/src/toad/comms_root.py @@ -14,7 +14,6 @@ import asyncio import threading from collections.abc import Callable, Mapping -from contextlib import ExitStack from dataclasses import dataclass, field from functools import partial from pathlib import Path @@ -372,8 +371,7 @@ def require(self, selected: RouteSelection) -> Observing: def open_selected(self, selected: RouteSelection) -> Observing: def attach() -> Observing: previous = self.observation - with ExitStack() as acquisition: - acquisition.enter_context(selected.route.hold_client_route()) + with selected.route.hold_client_route(): service = Comms(selected.route) if service.root.resolve() != selected.root or RouteSelection.capture() != selected: raise RouteChangedError("Comms route changed while opening the service") From e7b2148b014f409d42a0582ce0b4ee9d91052438 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 17:22:11 -0400 Subject: [PATCH 2/3] Sort the ACP route error import --- src/toad/acp/agent_process.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toad/acp/agent_process.py b/src/toad/acp/agent_process.py index abd478d60..d50455689 100644 --- a/src/toad/acp/agent_process.py +++ b/src/toad/acp/agent_process.py @@ -17,8 +17,8 @@ StreamingChildStdio, join_retirement, ) -from agent_comms.launch_root import LaunchRoot from agent_comms.errors import RouteChangedError +from agent_comms.launch_root import LaunchRoot from agent_comms.route_selection import RouteSelection from pydantic import JsonValue From 12e6c7f1e6191d95f46c0df0964621d3c0a0d9de Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Fri, 23 Oct 2026 17:24:20 -0400 Subject: [PATCH 3/3] Use the coordination owner to check prompt routes --- src/toad/acp/agent_process.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/toad/acp/agent_process.py b/src/toad/acp/agent_process.py index d50455689..b985f69b4 100644 --- a/src/toad/acp/agent_process.py +++ b/src/toad/acp/agent_process.py @@ -6,6 +6,7 @@ import os from collections.abc import Callable from contextvars import ContextVar +from functools import partial from pathlib import Path from typing import Optional @@ -17,7 +18,6 @@ StreamingChildStdio, join_retirement, ) -from agent_comms.errors import RouteChangedError from agent_comms.launch_root import LaunchRoot from agent_comms.route_selection import RouteSelection from pydantic import JsonValue @@ -94,8 +94,8 @@ async def start(self, agent, processes: tuple[AgentProcess, ...]=()): async def prompt(self, agent, content, metadata: dict[str, JsonValue], require: Callable[[], None]): """The agent-comms-acp child writes the prompt under Core's maintenance gate.""" - if RouteSelection.capture() != self.route_selection: - raise RouteChangedError(RouteChangedError.reader_sentence) + access = agent.controller.transcripts.access + await access.preparation.run_thread(partial(access.require, self.route_selection)) require() return await self.rpc.prompt(agent.session_id, content, **metadata)