quick-window-91705
05/21/2026, 6:25 PMquick-window-91705
05/21/2026, 6:27 PM<pre-call-shared-util>
<sync/async call e.g acompletion()>
<post-call-shared-util>quick-window-91705
05/21/2026, 6:29 PMcalm-artist-7206
05/21/2026, 7:19 PMcalm-artist-7206
05/21/2026, 7:20 PMmodern-fireman-99755
05/21/2026, 11:07 PMmodern-fireman-99755
05/22/2026, 8:08 AM| Dimension | A. Sans-IO (generator+driver) | B. Async-canonical + sync shim | C. Extract pure helpers |
|------------------------|----------------------------------|----------------------------------|--------------------------------|
| What varies | The I/O transport | The execution model | Nothing β shared pure helper |
| Already in repo? | Yes β RemoteWorkspaceMixin | Yes β AsyncExecutor (ACP/MCP) | Yes β RetryMixin, ParallelExec |
| Lines eliminated | ~65 (LLM transport + MCP) | ~700 (LLM, Agent, Convo, Cond.) | ~140 (dispatch, title, utils) |
| Subclass back-compat | Safe β internal only | Risky β needs __init_subclass__ | Safe β internal only |
| Perf impact | None | One thread-hop per top-level run | None |
| pyright friendliness | Awkward (tagged-union yields) | Clean | Cleanest |
| Cancellation / Ctrl-C | Trivial β driver controls it | Needs portal-cancel plumbing | Trivial |
| Stack traces | Slightly worse | Worse (portal thread frames) | Identical to today |
| Testability | Best β drive with fake transport | Same as today | Same as today |
| Where it shines | LLM transport, HTTP, MCP | Big duplicated control loops | Small pairs (one-line diff) |
| Where it's wrong | Non-I/O orchestration | Tiny methods; nested event loops | Bodies that aren't ~1 line off |
| Effort to land | Medium (1 contained PR) | High (3 PRs + shim) | Low (mechanical) |
| Reversibility | High | Medium (public API shift) | High |
| Risk | Low | Mediumβhigh | Very low |bland-bear-604
05/22/2026, 8:43 AM_run_loop() generator owns all the control flow (status transitions, stuck detection, iteration cap, error handling) and yields once at the agent step. run() feeds it agent.step(), arun() feeds it await agent.astep(). Elegant, and the codebase already has this pattern in RemoteWorkspaceMixin. -78 net lines.
The generator is great specifically for run/arun because there's exactly one yield point β the agent step. All the hairy loop logic lives in one place. But it doesn't generalize well to functions with multiple async call sites (like the LLM layer where you have prep β transport call β process response β telemetry).
So I'd say: generator for the conversation run loop (#3354), shared utils for everything else (#3353). The two PRs don't conflict.
One nit on #3353: 12 new internal helpers in the LLM class is a lot of names to hold in your head. The pattern is right, but some of those could probably be grouped or simplified. πΎ