car-workflow 0.21.0

Declarative multi-stage workflow orchestration for Common Agent Runtime
Documentation

car-workflow

Declarative multi-stage workflow orchestration for Common Agent Runtime.

What it does

Composes car-multi agent coordination patterns and car-engine action proposals into a named, conditional, compensable stage graph.

Key types

Type Purpose
Workflow top-level definition: stages + conditional edges (+ optional pinned goal)
Stage / StageStep what each step does — pattern, proposal, sub_workflow, approval, loop_until, for_each
PatternKind which car-multi pattern a pattern step runs — incl. adversarial_review (fresh-context verification) and tournament (pairwise ranking)
Edge conditional transition between stages (reuses car_ir::Precondition)
CompensationHandler saga-style rollback per stage
WorkflowEngine executes the workflow graph
verify_workflow static analysis before execution

Example (JSON definition)

{
  "id": "review-deploy",
  "name": "Review and Deploy",
  "start": "review",
  "stages": [
    { "id": "review", "name": "Code Review",
      "step": { "type": "pattern", ... } },
    { "id": "deploy", "name": "Deploy",
      "step": { "type": "proposal", ... } }
  ],
  "edges": [
    { "from": "review", "to": "deploy",
      "conditions": [{"key": "stage.review.succeeded", "operator": "eq", "value": true}] }
  ]
}

Dynamic stage steps

Beyond the fixed pattern/proposal/sub_workflow/approval steps, two steps keep the graph declarative and statically verifiable while letting control flow depend on runtime state:

  • loop_until — repeat an inner body step until a state predicate holds (AND of car_ir::Preconditions) or max_iterations is reached. The body's produced state plus stage.<id>.answer / stage.<id>.iteration are visible to the until check. An empty until runs exactly max_iterations times.

    { "type": "loop_until", "max_iterations": 5,
      "until": [{"key": "done", "operator": "eq", "value": true}],
      "body": { "type": "proposal", "proposal": { ... } } }
    
  • for_each — fan an inner body out over a JSON array resolved at runtime from state key items_from (e.g. produced by an earlier stage), with optional bounded concurrency (max_concurrent). Before each run, {{item}} and {{index}} are substituted into every string in the body (pattern tasks, agent prompts, proposal parameter values). Per-item answers land at foreach.<id>.<index>.{item,answer} and each body's own state deltas at foreach.<id>.<index>.state.<key> (namespaced per item so concurrent bodies don't clobber); the count at foreach.<id>.count. A missing/non-array items_from is a no-op.

    { "type": "for_each", "items_from": "files", "max_concurrent": 4,
      "body": { "type": "pattern", "pattern": "swarm_parallel",
                "task": "Review {{item}}", "agents": [ ... ] } }
    

Both reject an approval gate as their body (no pause/resume inside a loop or fan-out), and verify_workflow recurses into bodies to validate them.

Failure-mode defenses

Three structural guards against the classic long-run failure modes:

  • Goal drift — set the workflow's optional goal; it's pinned into state as goal and re-anchored into every agent step's task ("Overall goal: … / Current step: …"), so a many-stage run can't lose the original objective.
  • Self-preferential bias / premature "done" — the adversarial_review pattern kind runs a fresh reviewer (no author context) against acceptance criteria over a review_key from state, exposing the verdict as the typed stage.<id>.review_passed (bool) to branch on (and a PASS/FAIL answer summary). It fails closed when there's no work to review. Use it as a completion gate before declaring a workflow done.
  • Comparative selection — the tournament pattern kind ranks competing candidates by pairwise judging when "best of N" matters more than consensus.

Prefix-cache re-run

WorkflowEngine::run_cached(workflow, &prior_result) re-runs a workflow while replaying every stage that already succeeded from the prior result (instantly, no agent/proposal call) and running the first incomplete stage — and everything after it — live, seeded with the prior final_state. Use it to recover a run that failed partway, or to continue after fixing the cause, without re-paying for completed work. (Distinct from resume, which continues an approval pause.)

Where it fits

Surfaced via the WebSocket workflow.run and workflow.verify methods. Use car-multi directly for ad-hoc coordination patterns; reach for car-workflow when you want a named, persistable, compensable definition with conditional flow control.