Command
Command<I, F> is the universal envelope through which all state mutations flow. Every dispatch() call receives a Command, and every async effect completion returns one.
Definition
Section titled “Definition”pub enum Command<I, F = ()> { Intent(I), Feedback(F),}The two type parameters are:
I— the intent type. Externally-initiated mutations. Session dispatchesCommand::Intent(i)only.F— the feedback type (defaults to()). Internal async results such as effect completions, streaming tokens, or export results. Never exposed externally.
Intent vs Feedback
Section titled “Intent vs Feedback”| Intent | Feedback | |
|---|---|---|
| Origin | External caller (Dart, AI tool, test) | Internal async effect (fx.spawn) |
| Entry point | session.dispatch(Command::Intent(i)) | sender.send(Command::Feedback(fb)) |
| Visibility | Public API surface | Internal to the reduce cycle |
| Timing | Immediate — reduced in the current dispatch() | Deferred — arrives on cmd_rx, drained at the top of the next dispatch() |
flowchart LR A[Dart / AI / Test] -->|"Command::Intent(i)"| B[dispatch] B --> C[reduce] C -->|"fx.spawn"| D[async effect] D -->|"sender.send(Command::Feedback(fb))"| E[cmd_rx] E -->|next dispatch| BNo per-modality command enums
Section titled “No per-modality command enums”Previously, each modality defined its own command enum (WhiteboardCommand, WorksheetCommand, LessonPlanCommand). These are deleted. Command<Self::Intent, Self::Feedback> replaces them all.
The intent and feedback types are associated types on the Reducer trait:
trait Reducer: Send + Sync + 'static { type Intent: Send + 'static; type Feedback: Send + 'static; // ... fn reduce( cmd: Command<Self::Intent, Self::Feedback>, state: &mut Self::State, fx: &mut Effects<Self::Services, Command<Self::Intent, Self::Feedback>>, );}For a modality like Whiteboard, the concrete command type is Command<WhiteboardIntent, WhiteboardFeedback>. For a Session<Whiteboard, SessionIntent<WhiteboardIntent>>, the command type is Command<SessionIntent<WhiteboardIntent>, SessionFeedback>.
Usage in Effects
Section titled “Usage in Effects”Both Effects::send and Effects::spawn work with the full Command<I, F> type:
// Synchronous -- reduced in the same dispatch() callfx.send(Command::Intent(SomeIntent::DoSomething));
// Async -- feedback arrives on the next dispatch()fx.spawn(|svc, sender| { let result = svc.backend.load("id"); sender.send(Command::Feedback(SomeFeedback::Loaded(result)));});