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, RefreshInstructions), MCP lifecycle (InitMcpServers, StopMcpServer), local side-effects (WriteImageToTemp, OpenInSystem, PullOllamaModel, SetTerminalTitle, DismissStatusAfter). 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§

DEFAULT_MAX_ATTEMPTS
Total attempts (initial + retries). 3 attempts means up to 2 retries on top of the first request, costing at most ~1.5s of extra latency on the worst path (500ms + 1000ms backoff).
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.

Functions§

retry_transient_http
Retry a closure whose output is Result<reqwest::Response> whenever the response was a transient upstream failure (5xx / 429 / connection failed). Returns the first non-transient response, or the last result after attempts are exhausted.

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.