# car-workflow
Declarative multi-stage workflow orchestration for [Common Agent Runtime](https://github.com/Parslee-ai/car).
## What it does
Composes `car-multi` agent coordination patterns and `car-engine` action proposals into a named, conditional, compensable stage graph.
## Key types
| `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)
```json
{
"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::Precondition`s) 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.
```json
{ "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.
```json
{ "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.