refactor(agent): call the eight RPC methods instead of posting events (CHOO-1436) - #368
Draft
amaudruz wants to merge 1 commit into
Draft
refactor(agent): call the eight RPC methods instead of posting events (CHOO-1436)#368amaudruz wants to merge 1 commit into
amaudruz wants to merge 1 commit into
Conversation
… (CHOO-1436) Eight methods used the message bus as an RPC channel: register a future keyed on a request id, post a `com.switch.*` event into a room, block on `asyncio.wait_for`, and have a sync callback elsewhere complete the future. **Every one of them was switch-core talking to itself.** The responder was never out of process — for the two pre-invocation mediation calls the resource manager resolved the shared tracker in-process without even sending a reply, and for the other six a Switch-owned puppet received the event and answered. The homeserver was a loopback with a ten-second timeout bolted to it. What each actually was, once the round trip is removed: - `pre_tool_call` / `pre_llm_request` — one query against what the agent has attached. Now `MediationService`, which is also the first test coverage this logic has ever had; six cases, including that another agent's tool of the same name does not count. - `post_tool_result` / `post_llm_response` — **nothing.** Each posted an event carrying the literal string `"ok"` and read that same string back off the wire as its verdict. They are kept as the hook points they are meant to be, and as the membership check a caller is entitled to fail on, but the tautology is gone. Note their verdict vocabulary differs from the pre-invocation pair — `ok`/`blocked`/`redacted`, not `proceed`/`blocked` — which the round trip made easy to miss. - The four resource ones — `resource_service` calls. The gateway already called that service directly, so this is the existing shape, not a new one. Removing the hop removes several things that only existed to serve it: - Both trackers, which were the same forty lines twice over, differing in the future's value type and one log string. - `ResourceManagerClient` entirely. Once its six handlers go there is nothing left: it was a service wearing a Matrix client, and its only use of the room id was to map it back to the Switch room id the caller started with. It stops being a system client provisioned into every room. - Twelve event types, their models, their dispatch entries and their no-op base handlers — which empties the RPC bucket in `recorded_types.py`. - The sender-identity dance. Two of the eight sent as the resource manager rather than the agent, and it looked like routing. It was not: nothing dispatches on sender. It was a workaround for `_should_ignore` dropping a client's own events, so an agent sending its own response would have deadlocked until the timeout. Behaviour is preserved deliberately, including the parts that are not obviously right. A timeout used to surface as HTTP 404; there is no timeout now, and a real failure propagates with its type and traceback rather than being stringified into a `ValueError` — which is what the error-handling rules here ask for anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 6 of the Tuwunel-replacement plan. Stacked on #367. Net -763 lines (+340 / -1103).
Eight methods used the message bus as an RPC channel: register a future keyed on a request id, post a
com.switch.*event into a room, block onasyncio.wait_for, and have a sync callback elsewhere complete the future.The finding that made this small
Every one of them was switch-core talking to itself.
The responder was never out of process. For the two pre-invocation mediation calls the resource manager resolved the shared tracker in-process without even sending a reply; for the other six a Switch-owned puppet received the event and answered. The homeserver was a loopback with a ten-second timeout bolted to it.
What each one actually was
pre_tool_call/pre_llm_request— one query against what the agent has attached. NowMediationService, which is also the first test coverage this logic has ever had. Six cases, including that another agent's tool of the same name does not count.post_tool_result/post_llm_response— nothing. Each posted an event carrying the literal string"ok"and then read that same string back off the wire as its verdict. Kept as the hook points they are meant to be, and as the membership check a caller is entitled to fail on, but the tautology is gone.Worth flagging: their verdict vocabulary differs from the pre-invocation pair —
ok/blocked/redacted, notproceed/blocked. The round trip made that easy to miss, and it is enforced by the response schema.The four resource ones —
resource_servicecalls. The gateway already called that service directly (gateway/documents.py,rooms_yaml.py), so this is the existing shape rather than a new one.What goes with the hop
ResourceManagerCliententirely. Once its six handlers go there is nothing left — it was a service wearing a Matrix client, and its only use of the room id was to map it back to the Switch room id the caller started with. It stops being a system client provisioned into every room.recorded_types.py._should_ignoredropping a client's own events: an agent sending its own response would have deadlocked until the timeout.Behaviour changes, such as they are
Preserved deliberately, including the parts that are not obviously right —
post_*still returnsokunconditionally rather than being given a meaning it never had.Two genuine differences:
status="error"field, shipped over the wire and re-raised as aValueError. That is what this repo's error-handling rules ask for anyway.Why this matters for step 5
Finishing step 5 is blocked on the event types the message log deliberately does not keep — a dispatcher driven by
messagescannot carry them, soAgentClientkeeps its Matrix sync loop and the puppet model cannot be deleted. This removes twelve of them. What remains on the bus after this: commands, five task types, two telemetry types, and runtime state.Testing
2360 pass, ruff and mypy clean. The map that preceded this found no test anywhere exercising a request/response round trip and none for either tracker; the mediation logic now has six.
Not run against the real Matrix stack, in common with the rest of the stack.
🤖 Generated with Claude Code