Skip to content

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.

At each node in the modality tree, the orchestrator resolves three levels in strict sequence:

  1. Metadata — if the modality’s metadata is pending, run a metadata subagent
  2. Node — if the modality itself is pending (properties, placements), run a node subagent
  3. 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.

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
}
}

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.

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.

pub async fn generate_lesson(session_id: &str, config: OpenAiConfig) {
let session = get_lesson_plan_session(session_id);
session.orchestrate("".to_string()).await;
}

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.

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 --> Done

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:

  1. Parent phase — run the parent-level subagent to create structure (slides, sections, placements)
  2. Child phasereconcile_children() to create Slot::Hot entries, then generate each child

Children are currently generated sequentially. Parallel child generation is planned future work.

  • AI TraitsPending, Describe, Agent used by the orchestrator
  • Subagents — the agents the orchestrator spawns at each level
  • Bridge — how intent variants become tools for subagents