ADR-0010: Asynchrony through the event loop and coroutines
- Status: Accepted
- Date: 2026-06-24
- Deciders: jka
- Decision: D10
Context
Several operations take time: streaming model output, running git or other commands, and waiting on the network. Tcl is single-threaded by default, and the UI must never freeze while such work runs.
Decision
Long operations use Tcl's event loop: fileevent for channel readiness and
coroutines (Tcl 8.6+) to keep sequential logic readable. rio does not use threads.
Examples in the code base: the agent loop runs each turn in a coroutine that
yields at the approval gate and while waiting for a provider (ADR-0026,
ADR-0104); command execution for the agent is asynchronous with a timer-driven
timeout (ADR-0083); the GUI reads the core channel with a fileevent reader
(ADR-0030).
Consequences
- Streaming code reads as a straight sequence rather than as callbacks.
- Anything that blocks the interpreter (a synchronous
exec, a synchronous HTTP fetch) blocks every client of that core. Such calls must stay short or be made asynchronous; the agent's command tool had to become asynchronous for exactly this reason. - Nested event loops need care.
vwaitinside afileeventhandler, or starting a core call from inside another call's round trip, causes re-entrancy bugs; several later decisions defer work to idle or timer callbacks for this reason.