The Director is Ryvos's highest-level orchestration system. Given a goal, it uses the LLM to generate a multi-step execution plan as a directed acyclic graph (DAG), executes each node, evaluates results, and evolves the plan if needed.
When to Use the Director
The Director is enabled by default for goal-driven runs. Use it when:
- The task has multiple distinct steps that depend on each other
- You need evaluation against specific success criteria
- The task might need re-planning if initial approaches fail
- You want automated quality assessment
# Director is used automatically with --director flag
ryvos run --director "migrate the database from PostgreSQL to SQLite"
# Or with a goal file
ryvos run --goal migrate.toml "start the migration"OODA Loop
The Director follows an Observe-Orient-Decide-Act loop:
┌─────────────────────────────────────────────┐
│ Director OODA Loop │
│ │
│ 1. OBSERVE: Parse the goal and constraints │
│ 2. ORIENT: Generate execution graph (DAG) │
│ 3. DECIDE: Select entry node │
│ 4. ACT: Execute graph node-by-node │
│ 5. EVALUATE: Check results against goal │
│ 6. EVOLVE: If failed, diagnose and re-plan │
│ │
│ Repeat up to max_evolution_cycles │
└─────────────────────────────────────────────┘
Goal Definition
Goals are structured definitions with weighted success criteria:
# migrate.toml
[goal]
description = "Migrate user data from PostgreSQL to SQLite"
success_threshold = 0.9
[[goal.criteria]]
id = "schema_created"
type = "llm_judge"
weight = 0.3
description = "SQLite schema matches PostgreSQL schema"
prompt = "Does the SQLite database have equivalent tables and columns?"
[[goal.criteria]]
id = "data_migrated"
type = "llm_judge"
weight = 0.5
description = "All user records are migrated correctly"
prompt = "Are all records present in SQLite with correct data?"
[[goal.criteria]]
id = "tests_pass"
type = "output_contains"
weight = 0.2
description = "Migration tests pass"
value = "test result: ok"
[[goal.constraints]]
category = "time"
description = "Complete within 10 minutes"
hard = false
[[goal.constraints]]
category = "safety"
description = "Do not drop the PostgreSQL tables"
hard = trueCriterion Types
| Type | Description |
|---|---|
output_contains | Final output contains a specific string |
output_equals | Final output exactly matches a string |
llm_judge | LLM evaluates the output against a custom prompt |
custom | Named custom evaluation function |
Criteria are weighted. The overall score is the weighted sum of individual scores, and the goal passes if it exceeds success_threshold.
Constraints
| Category | Description |
|---|---|
time | Time limits |
cost | Token/dollar limits |
safety | Actions to avoid |
scope | Boundaries of the task |
quality | Output quality requirements |
Hard constraints cause immediate failure if violated. Soft constraints are reported but do not stop execution.
DAG Graph Execution
The Director asks the LLM to generate a DAG of execution nodes:
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Analyze │────>│ Create │────>│ Migrate │
│ PostgreSQL │ │ SQLite │ │ Data │
│ Schema │ │ Schema │ │ │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
┌──────┴───────┐
│ Verify │
│ Migration │
└──────────────┘
Node Structure
Each node is an independent agent run:
struct Node {
id: String, // Unique identifier
agent_prompt: String, // What this node should do
goal: Option<Goal>, // Optional per-node goal
max_turns: usize, // Turn limit for this node
system_prompt: Option<String>, // Override system prompt
}Edge Conditions
Edges between nodes have conditions that control execution flow:
| Condition | Description |
|---|---|
Always | Always follow this edge |
OnSuccess | Follow only if the source node succeeded |
OnFailure | Follow only if the source node failed |
Conditional | Follow if a specific condition is met in the handoff context |
LlmDecide | Ask the LLM whether to follow this edge |
Handoff Context
Nodes share data through a HandoffContext (a HashMap<String, Value>). When a node completes, it can write results that downstream nodes read:
Node A writes: { "schema": "CREATE TABLE users (...)" }
↓
Node B reads: handoff["schema"] to create the SQLite tables
Graph Executor
The GraphExecutor walks the DAG:
- Start at the entry node
- Execute the node as a full agent run
- Evaluate edge conditions from the completed node
- Follow matching edges to the next node(s)
- Repeat until no more nodes or all paths complete
- Collect results from all terminal nodes
GraphExecutor::execute(
nodes,
edges,
entry_id,
runtime,
session_id,
).await -> ExecutionResultEvolution Cycles
If the goal is not met after the first graph execution, the Director enters an evolution cycle:
- Diagnose — Analyze what went wrong using semantic failure detection
- Re-plan — Ask the LLM to generate an improved graph
- Execute — Run the new graph
- Evaluate — Check against the goal again
This repeats up to max_evolution_cycles (default: 3).
Semantic Failure Types
The Director classifies failures into five categories:
| Failure Type | Description |
|---|---|
LogicContradiction | The plan contradicted itself |
VelocityDrift | Progress stalled or went in the wrong direction |
ConstraintViolation | A hard constraint was broken |
FailureAccumulation | Too many individual node failures |
QualityDeficiency | Output quality does not meet criteria |
Each failure type generates targeted re-planning instructions. For example, a VelocityDrift failure prompts the LLM to try a fundamentally different approach rather than tweaking the same plan.
Events
The Director publishes events on the EventBus throughout execution:
| Event | When |
|---|---|
GraphGenerated | LLM produces the initial DAG |
NodeComplete | A single node finishes execution |
GoalEvaluated | Goal evaluation completed (score + pass/fail) |
EvolutionTriggered | Starting a new evolution cycle |
SemanticFailureCaptured | A failure type was diagnosed |
DecisionMade | Routing decision at a conditional edge |
Multi-Agent Orchestrator
For tasks that benefit from specialized agents, the MultiAgentOrchestrator provides routing:
┌─────────────────────────────────────────┐
│ Orchestrator │
│ │
│ Agent A: code specialist │
│ Agent B: database specialist │
│ Agent C: testing specialist │
│ │
│ Task → match_score() → best agent │
└─────────────────────────────────────────┘
Dispatch Modes
| Mode | Behavior |
|---|---|
Parallel | Send task to multiple agents, merge results |
Relay | Pass through agents sequentially, each refining |
Broadcast | Send to all agents, collect all responses |
Agent Capabilities
Each agent in the orchestrator declares:
- Available tools (subset of the full registry)
- Specializations (keywords for routing)
- Security policy overrides (e.g., higher trust for code agent)
Configuration
[agent.director]
enabled = true # Enable Director for goal-driven runs
max_evolution_cycles = 3 # Max re-planning attemptsGoals can be passed via the CLI:
# Inline
ryvos run --director "build a REST API for the user model"
# From file
ryvos run --goal task.toml "execute the plan"Next Steps
- Agent Loop — The ReAct execution engine
- Guardian Watchdog — Monitoring during execution
- Failure Journal — Learning from execution failures