Orchestrator
The orchestrator is deterministic code on Session, not an agent. It controls execution order, gates on Pending::is_pending(), and builds context for subagents at each tree level.
Execution order
Section titled “Execution order”At each node in the modality tree, the orchestrator resolves three levels in strict sequence:
- Metadata — if the modality’s metadata is pending, run a metadata subagent
- Node — if the modality itself is pending (properties, placements), run a node subagent
- Children — recurse into child modalities in parallel
flowchart TD Start([orchestrate]) --> MetaCheck{metadata\nis_pending?} MetaCheck -->|yes| MetaAgent[run metadata\nsubagent] MetaCheck -->|no| NodeCheck MetaAgent --> NodeCheck{node\nis_pending?} NodeCheck -->|yes| NodeAgent[run node\nsubagent] NodeCheck -->|no| Children NodeAgent --> Children[recurse into\nchildren] Children --> C1[child 1\norchestrate] Children --> C2[child 2\norchestrate] Children --> CN[child N\norchestrate]If nothing is pending at a level, the subagent for that level is skipped, but recursion into children continues. This allows partial generation — a lesson plan with some slides already filled will only generate the missing ones.
Context flow
Section titled “Context flow”Downward via describe() chain
Section titled “Downward via describe() chain”Each level adds its own Describe output to the ancestor context string. Children receive lineage context like “I’m a whiteboard inside slide 3 of a photosynthesis lesson” without any type needing to know about its parents.
impl<I: ReduceIntent<Whiteboard>> Session<Whiteboard, I> { async fn orchestrate(&self, ancestor_context: String) { let context = format!("{}\n{}", ancestor_context, self.describe()); // 1. Metadata subagent if pending // 2. Node subagent if pending // 3. Children in parallel, each recurses }}Property bindings
Section titled “Property bindings”Props flow via PropertyBinding::Template on placements. Parent properties and metadata bound to child properties are resolved before the child runs. The child sees bound values as its own Property values — no need to pass parent props in context since they are already there via bindings.
After resolution, PropertyBinding is irrelevant — it is plumbing for how a value got there.
No ancestor history
Section titled “No ancestor history”Children do not get parent conversation history. They receive a curated task prompt built by the orchestrator. This follows the subagent design principles: task-specific prompts, not shared state.
Entry points
Section titled “Entry points”Full generation from root
Section titled “Full generation from root”pub async fn generate_lesson(session_id: &str, config: OpenAiConfig) { let session = get_lesson_plan_session(session_id); session.orchestrate("".to_string()).await;}Mid-tree generation
Section titled “Mid-tree generation”When a user says “fill this slide”, generation starts at that node:
pub async fn generate_from(session_id: &str, placement_id: &str, config: OpenAiConfig) { let session = get_lesson_plan_session(session_id); let child_session = session.get_child(placement_id); let context = session.ancestor_context_for(placement_id); child_session.orchestrate(context).await;}The orchestrator builds the ancestor context string from the root down to the starting node, then begins the normal metadata-node-children sequence from there.
Full orchestration flow
Section titled “Full orchestration flow”flowchart TD Entry([generate_lesson / generate_from]) --> BuildCtx[build ancestor\ncontext string] BuildCtx --> LP[LessonPlan orchestrate]
LP --> LPMeta{LP metadata\npending?} LPMeta -->|yes| LPMetaAgent[metadata subagent:\nset topic, grade, etc.] LPMeta -->|no| LPNode LPMetaAgent --> LPNode{LP node\npending?} LPNode -->|yes| LPNodeAgent[node subagent:\nadd slides, sections] LPNode -->|no| LPChildren LPNodeAgent --> Reconcile[reconcile_children:\ncreate Slots for\nnew placements] Reconcile --> LPChildren
LPChildren --> WB1[Whiteboard 1\norchestrate] LPChildren --> WB2[Whiteboard 2\norchestrate] LPChildren --> WBN[Whiteboard N\norchestrate]
WB1 --> WBMeta{WB metadata\npending?} WBMeta -->|yes| WBMetaAgent[metadata subagent:\nset canvas size, etc.] WBMeta -->|no| WBNode WBMetaAgent --> WBNode{WB node\npending?} WBNode -->|yes| WBNodeAgent[node subagent:\nadd components,\nset properties] WBNode -->|no| Done([done]) WBNodeAgent --> DoneImplementation
Section titled “Implementation”The orchestrator dispatches through the same Session::dispatch() path as interactive editing. All mutations go through reduce -> recompile -> commit -> emit, maintaining consistency. The bridge adapts CommandTool intent variants into rig tools that the subagent can call.
For parent modalities (LessonPlan), generation has two phases:
- Parent phase — run the parent-level subagent to create structure (slides, sections, placements)
- Child phase —
reconcile_children()to createSlot::Hotentries, then generate each child
Children are currently generated sequentially. Parallel child generation is planned future work.