Aleph
Architecture

Dispatcher

Orchestrator-level flow dispatch — FlowSpec resolution, sandbox provisioning, HarnessRunner spawn, and 7-step lifecycle.

Dispatcher

The Dispatcher is the Orchestrator — the seam between the Gateway's chat ingress and the harness that drives each run. It does not model multi-step DAG plans: that earlier TaskGraph concept was retired with the Phase 6/7 harness migration. Today's orchestrator's job is to take a FlowRequest from any channel, resolve the right FlowSpec + sandbox + harness, and spawn the HarnessRunner task that produces a FlowOutcome.

Location: src/orchestrator/dispatch.rs

Note: There is no src/dispatcher/ directory. The historical Dispatcher module — ToolRegistry, RiskEvaluator, ToolIndex, HydrationResult — was dissolved during the Phase 6/7 harness migration. Tool aggregation now lives in src/tools/registry.rs, the executor lives in src/executor/, and tool-call guardrails live in src/tools/scoped/.


Topology

Gateway (chat ingress, protocol adapters)
   │ FlowRequest { agent_id, input, tool_service, trace_sink, … }

Orchestrator (resolves AgentDef + FlowSpec, builds HarnessDeps, dispatches)
   │ HarnessRunner::run

AgentHarness (Think → Act loop, verifiers, compaction)
   │ uses
   ├── SessionService  (append-only event log per session)
   ├── ToolService     (tool catalog + execution)
   ├── Sandbox         (exec environment, capability ledger)
   └── AiProvider      (LLM)


FlowOutcome → Gateway renders response

All agent execution — including subagent spawning — routes through Orchestrator → Harness. There is no separate dispatcher inside the harness; the Think phase parses the model's tool calls and the Act phase executes them.


Orchestrator State

pub struct Orchestrator {
    pub flow_registry: Arc<FlowRegistry>,
    pub routing_overrides: Arc<RoutingOverrides>,
    pub default_routing: Arc<HashMap<AgentId, FlowId>>,
    pub session_service: Arc<dyn SessionService>,
    pub sandbox_factory: SandboxFactory,
    pub harness: Arc<dyn HarnessRunner>,
    pub subagent_routing: Option<ProviderChain>,
    pub agent_registry: Option<Arc<AgentRegistry>>,
    active_sessions: Arc<Mutex<HashSet<String>>>,
}

#[async_trait]
pub trait HarnessRunner: Send + Sync {
    async fn run(
        &self,
        session_key: String,
        spec: FlowSpec,
        input: FlowInput,
        sandbox: Arc<dyn Sandbox>,
        event_tx: broadcast::Sender<FlowStreamEvent>,
        cancel: CancellationToken,
        tool_service: Option<Arc<dyn ToolService>>,
        trace_sink: Option<Arc<dyn TraceSink>>,
        interaction_manifest: Option<InteractionManifest>,
        workspace_override: Option<PathBuf>,
        max_iterations_override: Option<u32>,
        transient_context: Option<String>,
        think_level: Option<ThinkLevel>,
        envelope: TurnEnvelope,
        model_directive: Option<SessionModelPref>,
    ) -> Result<FlowOutcome, FlowError>;
}

Orchestrator::with_subagent_routing and with_agent_registry add the boot-time provider chain and shared agent registry so spawned subagents see user-defined agents and the failover chain.


Seven-Step Dispatch

Orchestrator::dispatch is a fixed sequence (design §6). Each step has one job; failure short-circuits the call.

#StepSource
1Resolve FlowSpec from explicit flow_id or route-by-agentflow_registry.resolve / resolve_flow_id
2Enforce depth guard (MAX_FLOW_DEPTH)resolver::depth_guard
3Defer agent lookup to HarnessRunner (it owns AgentRegistry)
4Resolve session key + acquire per-session lockresolver::resolve_session + active_sessions
5Defer brain pick to HarnessRunner (ProviderRegistry lives there)
6Provision sandbox (per-request sandbox_override short-circuits the factory)sandbox_factory
7Spawn harness task; plumb event/completion/cancel back to callertokio::spawn(harness.run(...))

The spawn also re-establishes current_project_root and current_agent_id task-locals inside the new task — tokio::spawn does not inherit task-locals, and per-project memory seams (note_manage, retrieval assembler, session compaction) plus agent-scoped skill discovery (~/.aleph/agents/<id>/skills) both read these ambient values. The harness receives workspace_override explicitly for CWD + prompt context, but the task-locals must be re-set so cross-session tools see the right scope.


FlowHandle

pub struct FlowHandle {
    pub session_key: String,
    pub events: broadcast::Receiver<FlowStreamEvent>,
    pub completion: oneshot::Receiver<Result<FlowOutcome, FlowError>>,
    pub cancel: CancellationToken,
}

FlowStreamEvent carries live deltas (Delta, Reasoning, ToolCallStart, ToolCallDone { duration_ms }, ContextGauge, SafetyBlock) and the terminal Complete(FlowOutcome). cancel propagates a CancellationToken into the harness task; cancellation surfaces as FlowError::Cancelled and TerminateReason::Cancelled.


FlowOutcome

pub struct FlowOutcome {
    pub final_text: String,
    pub iterations: u32,
    pub tool_calls_made: u32,
    pub total_tokens: u32,
    pub hit_limit: bool,                  // Back-compat alias for TerminateReason
    pub terminate_reason: TerminateReason,
    pub duration_ms: u64,
    pub token_breakdown: TokenBreakdown,
    pub tool_timeline: Vec<ToolInvocation>,
    pub estimated_cost: Option<CostEstimate>,
    pub context_tokens: u32,
    pub context_window: u32,
    pub serving_model: Option<String>,
    pub serving_provider: Option<String>,
}

TerminateReason distinguishes every cap site the harness can exit on: Completed, HitMaxIterations, ContextBudgetExhausted, StallTimeout, TurnTimeout { phase }, ConsecutiveFailureCap, VerifierVeto, EmptyResponseExhausted, StopHookHalt, MaxOutputTokensExhausted, ReactiveCompactExhausted, Cancelled, and the BudgetExhaustedPartialResult { reason, partial_summary } escalation when a budget cap fires after useful partial text was emitted (policy-gated in escalate_partial_result).

The DiminishingReturns variant is retained for back-compat only — the diminishing-returns hard stop was removed under R10.


Act-Stage Parallel Groups

Act (inside AgentHarness::act) partitions tool calls by resource claim and dispatches intra-group concurrently / inter-group serially. Tools declare one of three claim kinds:

ClaimMeaning
SharedNo conflict with any other claim
Exclusive { Global }Mutex over the entire execution
Exclusive { Paths }Mutex over a specific set of filesystem paths

Serial mode only falls back on single call, parallelism disabled, duplicate call, or no parallel group. Two calls admitted as disjoint on the model's original args stay disjoint; any guardrail rewrite (PII mask) serializes the batch.

Cross-batch dedup: a duplicate (name, args) within or across batches reuses the first result.


See Also

  • Harness — Think→Act loop + HarnessRunner::run implementation
  • Tool Architecture — Tool catalog + ToolService contract
  • Teams — Team dispatcher fuses the loop-graph governance node with multi-agent dispatch

On this page