# Routing

Routing lets the LLM decide what happens after a task completes. There are two routing mechanisms: **conditional** (`router`) and **unconditional** (`send_to`).

## Choosing Between `depends_on`, `router`, and `send_to`

These three mechanisms look similar but solve different problems. Pick the wrong one and you'll either lose context, fire work that shouldn't run, or hang the mission.

**Default to `depends_on`. Build a static DAG whenever you can.**

A `depends_on` edge tells the runner "this task waits for those tasks, then runs." The runner knows about it upfront from the topological sort, schedules it automatically, and gives it full ancestor context. This is the right tool for the vast majority of task relationships — sequential pipelines, fan-in from multiple predecessors, anything where every listed predecessor is expected to run.

Reach for `send_to` or `router` only when a task genuinely should *not* always run — i.e. when the set of downstream tasks depends on what happened at runtime.

| You want to... | Use |
|----------------|-----|
| Run task B after task A always completes | `depends_on = [tasks.A]` on B |
| Run task C after A and B both complete (fan-in, both always run) | `depends_on = [tasks.A, tasks.B]` on C |
| Pick one of several branches based on LLM judgment | `router` on the source |
| Fan out — source always triggers multiple parallel branches | `send_to` on the source |
| Multiple conditional sources converge on one shared handler (first-one-wins fan-in) | `send_to` (or `router`) from each source, pointing at the shared task |
| Chain a sequence where every step always runs | `depends_on` all the way — not `send_to` |

### Common mistake: using `send_to` for a static sequence

```hcl
# ❌ Wrong — send_to used for a linear pipeline that always runs end-to-end
task "fetch" {
  objective = "Fetch data"
  send_to   = [tasks.process]
}

task "process" {
  objective = "Process it"
  send_to   = [tasks.publish]
}

task "publish" {
  objective = "Publish result"
}
```

This works, but it throws away the benefits of the static DAG. `process` and `publish` become dynamically activated — they can't be referenced by `depends_on` from anywhere else, they're invisible to the topological sort, and the runner has no upfront picture of the mission. Prefer:

```hcl
# ✅ Right — static DAG, every task is schedulable and discoverable
task "fetch" {
  objective = "Fetch data"
}

task "process" {
  objective  = "Process it"
  depends_on = [tasks.fetch]
}

task "publish" {
  objective  = "Publish result"
  depends_on = [tasks.process]
}
```

Rule of thumb: if every branch would always run, it belongs in `depends_on`. `send_to` and `router` are for runtime-shaped graphs — fan-out that's conceptually "trigger these in parallel," or fan-in where *which* predecessor fires is decided at runtime.

## Conditional Routing (`router`)

A `router` block presents route options to the commander after the task finishes. The commander evaluates the conditions and picks one — or none.

```hcl
task "classify" {
  objective = "Classify the incoming request"

  router {
    route {
      target    = tasks.handle_billing
      condition = "The request is about billing or payments"
    }
    route {
      target    = tasks.handle_support
      condition = "The request is a technical support issue"
    }
    route {
      target    = tasks.handle_general
      condition = "The request doesn't fit other categories"
    }
  }
}

task "handle_billing"  { objective = "Process billing request" }
task "handle_support"  { objective = "Handle the support ticket" }
task "handle_general"  { objective = "Handle general inquiry" }
```

Only the chosen branch executes. If the commander picks "none", no branch activates and the mission completes.

### How It Works

Route options are injected as a system prompt when the commander starts, so it knows the available routes upfront. When calling `task_complete`, the commander includes a `route` parameter alongside the `summary`:

1. Commander sees route options in its system prompt (injected automatically from the `router` config)
2. Commander does its work, delegating to agents as needed
3. Commander calls `task_complete` with `summary` and `route` — choosing the route whose condition best matches the results, or `none` if no route applies
4. The chosen route's target task is activated; unchosen branches never execute

This happens in a single tool call — no extra turns needed for route selection.

### Chained Routers

Route targets can themselves have routers. This creates natural decision chains:

```hcl
task "classify" {
  objective = "Classify the request type"
  router {
    route {
      target    = tasks.handle_billing
      condition = "Billing related"
    }
  }
}

task "handle_billing" {
  objective = "Determine the billing action"
  router {
    route {
      target    = tasks.process_refund
      condition = "Customer wants a refund"
    }
    route {
      target    = tasks.correct_invoice
      condition = "Invoice needs correction"
    }
  }
}

task "process_refund" {
  objective = "Process the refund"
}

task "correct_invoice" {
  objective = "Fix the invoice"
}
```

## Unconditional Routing (`send_to`)

`send_to` activates target tasks immediately when the source completes. No LLM decision — all targets fire.

```hcl
task "fetch" {
  objective = "Fetch data from the API"
  send_to   = [tasks.process_a, tasks.process_b]
}

task "process_a" { objective = "Process path A" }
task "process_b" { objective = "Process path B" }
```

Use `send_to` when:

1. **Fan-out** — one task triggers multiple parallel branches that each do distinct work. (If they do the same work across items, use an [iterator](/missions/iteration) instead.)
2. **Conditional fan-in onto a shared handler** — a router (or chain of routers) has several branches that all need to end at the same downstream task. Only one branch actually runs, so the shared task can't use `depends_on` (it would hang waiting on the branches that were never activated). Instead each branch `send_to`s the shared task; whichever branch runs activates it.

```hcl
# A router picks one of three handlers; every handler ends with the same notify step.
task "classify" {
  objective = "Classify the incoming ticket"
  router {
    route {
      target    = tasks.handle_billing
      condition = "Billing issue"
    }
    route {
      target    = tasks.handle_bug
      condition = "Technical bug"
    }
    route {
      target    = tasks.handle_general
      condition = "General inquiry"
    }
  }
}

task "handle_billing" {
  objective = "Resolve billing issue"
  send_to   = [tasks.notify]
}

task "handle_bug" {
  objective = "File a bug report"
  send_to   = [tasks.notify]
}

task "handle_general" {
  objective = "Answer the inquiry"
  send_to   = [tasks.notify]
}

task "notify" {
  objective = "Notify the customer that the ticket was handled"
}
```

`notify` can't be `depends_on = [handle_billing, handle_bug, handle_general]` — that would wait for all three, but the router only activates one. `send_to` from each branch is the right shape: whichever handler fires pushes into `notify`.

**Don't** use `send_to` as a substitute for `depends_on` in a linear pipeline where every step always runs — that makes the graph dynamic for no reason and prevents downstream tasks from waiting on those steps. See [Choosing Between...](#choosing-between-depends_on-router-and-send_to).

## Cross-Mission Routing

Route targets can reference other missions using `missions.name` instead of `tasks.name`. When a mission route is chosen, the current mission completes and the target mission launches as a new instance.

```hcl
task "handle_complaint" {
  objective = "Draft a response to the complaint"

  router {
    route {
      target    = missions.escalation_mission
      condition = "The complaint is severe and needs escalation"
    }
    route {
      target    = tasks.close_ticket
      condition = "The complaint can be resolved without escalation"
    }
  }
}
```

When the commander selects a mission route, it also provides any required inputs for the target mission. The `task_complete` tool presents the target mission's input requirements and the commander fills them in.

A mission can route to itself — this creates a new instance, not a loop. This is useful for retry or restart patterns.

### Full Example

```hcl
mission "escalation_mission" {
  directive = "Handle an escalated complaint"

  commander {
    model = models.anthropic.claude_haiku_4_5
  }
  agents = [agents.assistant]

  input "complaint_summary" {
    type        = "string"
    description = "Summary of the original complaint"
  }

  input "severity" {
    type        = "string"
    description = "Severity level"
    default     = "high"
  }

  task "triage" {
    objective = <<-EOT
      Perform high-priority triage for the escalated complaint.
      Complaint: "${inputs.complaint_summary}"
      Severity: "${inputs.severity}"
    EOT
  }
}

mission "support_triage" {
  directive = "Triage incoming customer messages"

  commander {
    model = models.anthropic.claude_haiku_4_5
  }
  agents = [agents.assistant]

  input "customer_message" {
    type        = "string"
    description = "The customer's message"
  }

  task "analyze" {
    objective = "Analyze the customer message: ${inputs.customer_message}"

    router {
      route {
        target    = tasks.handle_complaint
        condition = "The customer is unhappy"
      }
    }
  }

  task "handle_complaint" {
    objective = "Draft a response to the complaint"

    router {
      route {
        target    = missions.escalation_mission
        condition = "The complaint mentions legal action or cancellation"
      }
      route {
        target    = tasks.close_ticket
        condition = "The complaint can be resolved directly"
      }
    }
  }

  task "close_ticket" {
    objective = "Close the support ticket with a summary"
  }
}
```

## Static vs Dynamic Tasks

Tasks are either **statically scheduled** (part of the initial topological sort) or **dynamically activated** (only run when a `router` or `send_to` fires).

A dynamically activated task is the root of its own sub-DAG. These two worlds do not mix:

- **Dynamic targets cannot have `depends_on`.** They start when activated, not when some other set of tasks completes.
- **No task can `depends_on` a dynamic target.** If a dynamic target is never activated, the dependent task would hang forever.
- **`router` and `send_to` are mutually exclusive.** A task pushes work either conditionally or unconditionally, not both.

### First-One-Wins

A dynamic target can be referenced by multiple routers or `send_to` sources. The first activation wins — once a task starts, subsequent activations are silently ignored. Tasks run at most once.

### Ancestor Context

When a dynamically activated task starts, the runner treats the activating task as its parent for context purposes. The routed-to task gets access to the full ancestry of the task that activated it — the same depth of context as if it had `depends_on`.

## Validation Rules

| Rule | Description |
|------|-------------|
| No cycles | Combined `depends_on` + router + `send_to` edges must be acyclic |
| Dynamic targets cannot have `depends_on` | Tasks reachable via router or `send_to` cannot also declare dependencies |
| No task can `depends_on` a dynamic target | Static tasks cannot wait on dynamically activated tasks |
| `router` and `send_to` mutually exclusive | A task can have one or the other, not both |
| No self-routing / self-send | A task cannot target itself |
| No duplicate targets | Each target can appear at most once within a router or `send_to` |
| Targets must exist | Task targets must reference existing tasks; mission targets must reference existing missions |
| Parallel iterators cannot have a router | Sequential iterators can — the route is evaluated after the final iteration |
| At least one startable task | Every mission must have at least one task with no dependencies that isn't router-only |

## Iterator Interaction

- **Parallel iterators** cannot have a `router` (each iteration is independent — there's no single decision point)
- **Sequential iterators** can have a `router` — the route is evaluated after the final iteration completes
- Both parallel and sequential iterators can use `send_to` — targets activate after iteration completes

## Persistence & Resume

Route decisions are persisted to the database. On resume:

1. Route decisions are loaded to reconstruct which task activated which
2. Incomplete dynamic targets are re-queued for execution
3. The activating task's commander is restored so the dynamic target can query it for context

## See Also

- [Tasks](/missions/tasks) - Task configuration and dependencies
- [Internal Tools](/missions/internal-tools) - `task_complete` routing parameters
- [Missions Overview](/missions/overview) - Mission structure and execution
