Orchestration and Workflow Control

Orchestration is the control plane for agent work: what runs in what order, what happens on failure, when to stop, and when a human must intervene. Without it, you have a clever model in a room with no fire exits.

Intuition

Think of an airport ground crew, not a free-jazz solo. Flights (steps) have sequence, gates (dependencies), go-arounds (retries), diversions (fallbacks), and weather holds (approvals). Production agents need the same discipline — especially when tools flake and models waffle.

How it works

Core controls

Example

Primary search API fails twice → wait with backoff → failover to backup index → continue summarization → if backup also fails, return “degraded: cached summary” instead of hanging.

flowchart TD S[Start workflow] --> A[Step A] A -->|ok| B[Step B] A -->|error| R{Retries left?} R -->|yes| A R -->|no| F[Fallback] F --> B B --> C{Needs HITL?} C -->|yes| H[Human] C -->|no| D[Done] H --> D

Why it matters in production

Orchestration styles

Style Pros Cons
Fixed DAG / state machine Debuggable, testable Less flexible
LLM chooses next step Flexible Needs hard caps & validators
Mixed Playbook + LLM fillers Best default for many teams

Prefer fixed skeletons for money and compliance paths; allow freer planning only inside sandboxes.

In code

A minimal orchestrator with retries, timeout budget, and fallback.

import time
from dataclasses import dataclass

@dataclass
class StepResult:
    ok: bool
    value: str
    error: str | None = None

def flaky_search(attempt: int) -> StepResult:
    if attempt < 2:
        return StepResult(False, "", "503")
    return StepResult(True, "docs://primary")

def backup_search() -> StepResult:
    return StepResult(True, "docs://backup")

def with_retries(fn, retries=2, backoff=0.01) -> StepResult:
    last = StepResult(False, "", "not_started")
    for i in range(retries + 1):
        last = fn(i)
        if last.ok:
            return last
        time.sleep(backoff * (2 ** i))
    return last

def run_workflow(deadline: float) -> str:
    if time.time() > deadline:
        return "aborted:timeout"
    search = with_retries(flaky_search)
    if not search.ok:
        search = backup_search()
    if not search.ok:
        return "degraded:no_index"
    # summarize would go here
    return f"ok:{search.value}"

print(run_workflow(deadline=time.time() + 5))

Real systems use durable workflows (queues, step functions) so a process crash mid-run can resume safely.

What goes wrong

Putting it into practice

Write an error budget for the workflow: max retries, max fallback rate, max HITL rate, max dollars per run. Alert when any budget burns faster than expected. Chaos-test one dependency a week in staging (force 503s) and confirm users see a controlled degraded answer, not a hang.

Prefer durable state machines for anything that touches money or accounts. In-memory loops die with the process and leave half-applied side effects. Export a timeline view in your admin tool: step, attempt, latency, outcome — the same view on-call will need during an incident.

Observability hooks

Emit one structured event per state transition: {run_id, step, attempt, outcome, latency_ms, cost_usd}. From those events you can build retry heatmaps and find steps that always fall back. Orchestration without observability is superstition — you will tweak prompts for outages that were really timeout misconfigs. Give on-call a single run-id search that reconstructs the whole path.

Compensating actions

When a mid-pipeline write succeeds and a later step fails, you may need a compensating action (cancel reservation, delete draft ticket) rather than a naive retry from zero. Encode compensations next to the step definition. Orchestration that only knows “retry” will leave orphaned side effects that support teams clean up by hand — expensive and error-prone.

SLOs for agent runs

Pick service-level objectives the way you would for an API: success rate, p95 run duration, fallback rate, and dollars per successful run. Page on burn rate, not on single failures. Orchestration tuning then becomes ordinary reliability work — increase timeout here, cut a redundant step there — instead of prompt folklore. Publish the SLOs next to the workflow diagram so product and on-call share one definition of “healthy.”

One-line summary

Orchestrate agent workflows with explicit sequences, budgets, retries, fallbacks, and approval hooks so failures become controlled degradations instead of runaway chaos.

Key terms