# 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

| 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)

```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.
