Skip to main content

Module effect

Module effect 

Source
Expand description

The effect runner: dispatches Cmd values into tokio tasks.

There are exactly two places in the codebase that spawn a tokio task: this module and tests. Everywhere else asks the reducer to return a Cmd, and the runner handles it. That centralization is what makes structured concurrency per turn actually work — nothing can accidentally spawn a detached task that outlives the turn it was started for.

Architecture:

  main loop ── reducer ── Cmd ── dispatch ── EffectRunner
                                                ├── TurnScope(turn A) ── JoinSet
                                                ├── TurnScope(turn B) ── JoinSet
                                                └── detached effects (Save, Exit, …)
                                                      ↓
                                             Msg via mpsc::Sender<Msg>
                                                      ↓
                                                main loop (next iteration)

The runner dispatches every Cmd variant to a real handler — model streaming (CallModelModelProvider::chat), tool execution (ExecuteToolToolExecutor::execute), persistence (SaveConversation, LoadConversation, PersistLastModel, PersistReasoningFor), MCP lifecycle (InitMcpServers, StopMcpServer), local side-effects (WriteImageToTemp, OpenInSystem, PullOllamaModel, SetTerminalTitle). Cancellation flows through Cmd::CancelScope(TurnId) → the scope’s CancellationToken.

Structs§

EffectRunner
The runner. One instance per process, constructed by app::run and consumed when the main loop exits.
TurnScope
One turn’s cancellable scope. Construct once per SubmitPrompt; abandon (drop) at the end of the turn.

Constants§

MSG_CHANNEL_CAPACITY
Bounded channel capacity for the effect → reducer stream. 512 is generous — a single streaming chunk fits comfortably, and the main loop drains at ~60 Hz so backlog rarely grows. Bigger wastes RAM; smaller introduces spurious backpressure on bursty tool output.

Type Aliases§

MsgSender
Single channel back to the reducer. EffectRunner holds the sender; every spawned task clones this so it can emit Msg as work progresses. Bounded capacity applies natural backpressure — if the main loop can’t keep up, the provider’s streaming send .awaits and the whole pipeline throttles.