Orchestrator¶
The orchestrator is the central routing layer that receives all inbound user messages and tasks, classifies intent, and dispatches work to the appropriate agent, skill, or subsystem.
Realizes:
spec_v3.md §7.1,spec_v3.md §23.2
Overview¶
The orchestrator (src/donna/orchestrator/) sits between the user-facing channels (Discord, dashboard, SMS) and the agent/skill execution layer. The live entry point is DiscordIntentDispatcher, which routes free-text messages and determines whether a user message is a task, an automation request, a chat interaction, or something novel that requires Claude escalation.
Dormancy note (v3.1):
AgentDispatcher— the task-level dispatcher through the PM Agent hierarchy — is built and unit-tested but not constructed in production (it appears nowhere outside its own docstring). The live task path is:DiscordIntentDispatcher→ChallengerAgent→ (on escalation)ClaudeNoveltyJudge, with time-bound placement handled by the event-drivenAutoScheduler(a notification-subsystem component, not an agent). The PM/Prep/Scheduler agent pipeline and the agent-layerToolRegistryare dormant; treat the rows below describing them as design targets, not live behavior. Seespec_v3.md §7.2(v3.1 status note) and the Sub-Agent System critique design doc.
The orchestrator enforces a strict principle from the spec: models propose, the orchestrator validates and executes. No agent or skill calls tools directly — and as of v3.1 the agent-layer ToolRegistry.execute requires task_type+agent_name so the allowlist check can no longer be bypassed by omitting them (principle #6). For Discord messages, it first runs the Challenger Agent for capability matching, then the Claude Novelty Judge for unmatched patterns, and builds automation drafts with cadence-policy awareness.
The InputParser handles the specific case of natural language task capture: rendering the parse template, calling the model, validating the output schema, applying learned preferences, and running deduplication.
Key Concepts¶
| Concept | Description |
|---|---|
| AgentDispatcher | Routes tasks through PM assessment, then to the recommended execution agent. Manages agent timeouts and the skill shadow path. |
| DiscordIntentDispatcher | Routes free-text Discord messages to task creation, automation drafting, chat, or Claude escalation based on Challenger Agent matching. |
| InputParser | Parses natural language into structured TaskParseResult via template rendering, model call, schema validation, preference application, and deduplication. |
| AgentActivityListener | Protocol for receiving agent lifecycle events (start, complete, failure). Used by the notification subsystem to track work in progress. |
| DispatchResult | Return type from DiscordIntentDispatcher.dispatch(): indicates the routing outcome (task_created, automation_confirmation_needed, clarification_posted, chat, no_action). |
| DraftAutomation | Proposed automation built from the Challenger's extraction, before user confirmation. Carries schedule, alert conditions, cadence policy adjustments. |
| PendingDraft | Multi-turn conversation state for messages that need clarification. Stored in a PendingDraftRegistry keyed by thread/DM. |
| Skill Shadow | Phase 1 skill system integration. The dispatcher runs the skill path in parallel for logging without affecting the user-facing response. |
Architecture¶
flowchart TD
subgraph Inbound["Inbound Channels"]
D[Discord Message]
DB[Dashboard Chat]
SMS[SMS/Voice]
end
subgraph Discord["Discord Intent Dispatch"]
D --> DID[DiscordIntentDispatcher.dispatch]
DID --> PD{Pending Draft?}
PD -->|Yes| RES[Resume: merge context, re-parse]
PD -->|No| CH[ChallengerAgent.match_and_extract]
CH -->|ready + task| CT[Create Task]
CH -->|ready + automation| AD[Build Automation Draft]
CH -->|ready + chat| CHAT[Route to Chat]
CH -->|needs_input| CL[Store PendingDraft, clarify]
CH -->|escalate_to_claude| NJ[ClaudeNoveltyJudge.evaluate]
NJ -->|task| CT2[Create Task from Verdict]
NJ -->|automation| AD2[Build Draft from Verdict]
NJ -->|clarify| CL2[Store PendingDraft]
NJ -->|chat| CHAT
end
subgraph TaskDispatch["Task Dispatch"]
CT --> IP[InputParser.parse]
IP --> AGD[AgentDispatcher.dispatch]
AGD --> PM[PM Agent Assessment]
PM -->|needs_input| WI[Task → waiting_input]
PM -->|complete| EX{Recommended Agent}
EX --> SCHED[Scheduler Agent]
EX --> PREP[Prep Agent]
EX --> OTHER[Other Agent]
end
subgraph Shadow["Skill Shadow (Phase 1)"]
AGD -.->|Background| SS[ChallengerAgent.match_and_extract]
SS -.-> SE[SkillExecutor.execute]
SE -.-> LOG[Log result only]
end
AgentDispatcher Flow¶
-
Build context. Creates an
AgentContextwith the model router, database, user ID, project root, and tool registry. -
Skill shadow (background). If
skill_routing_enabledis true, the dispatcher runs the Challenger Agent against the task title. On a match, it looks up the corresponding skill and version from theSkillDatabase, executes viaSkillExecutor, and logs the result. This path never affects the user-facing response -- it produces observability data for evaluating skill readiness. -
PM assessment. The PM Agent evaluates task completeness. If the task needs more information, it returns
needs_inputwith questions. If assessment fails, the error propagates. -
Challenger assessment. If a Challenger Agent is registered, it runs after PM to evaluate the task against the capability registry.
-
Execution dispatch. The PM's recommended agent (typically
scheduler) is resolved from the agents dict. If unavailable, the dispatcher falls back to the scheduler. The chosen agent executes with a configured timeout viaasyncio.wait_for. -
Activity notification. On completion or failure, the
AgentActivityListeneris notified so downstream systems (notifications, dashboard) can update.
DiscordIntentDispatcher Flow¶
The Discord dispatcher handles the nuanced classification of free-text messages:
-
Thread resume. Checks if the message is in a thread with a
PendingDraft. If so, merges the reply with the existing partial context and re-runs the Challenger. -
Challenger matching. The Challenger Agent classifies intent (
task/automation/chat/question) and extracts structured inputs. Returns a confidence level and match status. -
Needs-input path. For ambiguous or incomplete messages, the dispatcher creates a
PendingDraftand returns a clarifying question. The next message in the same thread resumes this draft. -
Escalation path. When the Challenger cannot match a capability (
escalate_to_claude), the Claude Novelty Judge evaluates whether this represents a genuinely new pattern. If it is a skill candidate, the reasoning is recorded for the nightlySkillCandidateDetector. If not, aclaude_native_registeredfingerprint is written so the detector skips this pattern. -
Automation drafting. Automation-intent messages produce a
DraftAutomationwith the target schedule, alert conditions, and cadence-policy-adjusted active schedule. TheCadencePolicyclamps the schedule based on the matched capability's lifecycle state (e.g.,claude_nativecapabilities run at lower frequency thantrustedskills).
InputParser Pipeline¶
The InputParser is a standalone pipeline for the specific parse_task task type:
- Load and render the prompt template with current date/time and user input.
- Call
ModelRouter.complete()withtask_type="parse_task". - Validate the response against the
parse_taskJSON schema. - Apply learned preferences via
PreferenceApplier(post-parse, pre-database). - Run deduplication check via
Deduplicator(raisesDuplicateDetectedErroron match). - Return a typed
TaskParseResultwith title, description, domain, priority, deadline, estimated duration, tags, and confidence score.
Configuration¶
The orchestrator itself has minimal config -- it relies on the configurations of the subsystems it orchestrates:
- Agent definitions:
config/agents.yaml-- agent names, timeout seconds, model assignments. - Task types:
config/task_types.yaml-- prompt templates, output schemas, model routing, tool dependencies. Themanual_escalationblock per task type controls which escalation modes are available. - Capabilities:
config/capabilities.yaml-- capability registry for the Challenger Agent. - Skills:
config/skills.yaml--enabledflag controls whether the skill shadow path runs. - Prompt templates:
prompts/parse_task.md-- Jinja2 template for task parsing. - Output schemas:
schemas/task_parse_output_v2.json-- JSON Schema for parse result validation.
API¶
| Class / Function | Module | Description |
|---|---|---|
AgentDispatcher |
dispatcher.py |
dispatch(task, user_id) -- PM assessment + execution agent routing. Constructor takes skill_executor, skill_database, skill_routing_enabled for Phase 1 shadow. |
AgentActivityListener |
dispatcher.py |
Protocol: on_agent_start(), on_agent_complete(), on_agent_failure(). |
DiscordIntentDispatcher |
discord_intent_dispatcher.py |
dispatch(msg) -- returns DispatchResult. Constructor takes ChallengerAgent, ClaudeNoveltyJudge, PendingDraftRegistry, CadencePolicy. |
DispatchResult |
discord_intent_dispatcher.py |
Dataclass: kind, task_id, draft_automation, clarifying_question. |
DraftAutomation |
discord_intent_dispatcher.py |
Dataclass: capability_name, inputs, schedule_cron, alert_conditions, target_cadence_cron, active_cadence_cron, skill_candidate, notification_channels. |
InputParser |
input_parser.py |
parse(raw_text, user_id, channel) -- returns TaskParseResult. |
TaskParseResult |
input_parser.py |
Frozen dataclass: title, description, domain, priority, deadline, estimated_duration, tags, confidence, etc. |
See Also¶
- Domain: Agents -- PM Agent, Challenger Agent, execution agents
- Domain: Skill System -- skill executor, capability matching, shadow evaluation
- Domain: Task Management -- task schema, state machine, deduplication
- Domain: Cost & Escalation -- budget enforcement that intersects with dispatch
- Domain: Chat -- chat routing for
intent_kind: chatmessages