Skip to content
Automation
Skill

/cao-workflow

Author and run CAO Python workflow scripts — multi-step, parameterized, fan-out

From plugin
cli-agent-orchestrator
1.5k20 skills1 MCP

Claude Code

$ npx -y skills add awslabs/cli-agent-orchestrator --skill cao-workflow --agent claude-code

SKILL.md

cao-workflow.SKILL.md
name: cao-workflow
description: Author and run CAO Python workflow scripts — multi-step, parameterized, fan-out
  orchestrations executed by `cao workflow run`. Use when the user wants a repeatable multi-step
  job (e.g. data analysis over many files, a review pipeline, a parameterized batch). Authoring
  ends at a validated script file; running it is a separate, user-approved step.

CAO Workflows

A CAO workflow is a **Python script** you write, validate, and — only after asking the user — run through `cao workflow run`. Each script drives one or more agent *steps* through CAO's shared substrate, so you can fan work out across agents, collect their results, and resume a run that was interrupted.

> Your job as an author ends at a **validated script file on disk**. Authoring does NOT run the > workflow. Never claim a workflow ran, or will run, when all you did was write it. Running is a > separate step the user must approve (see Lifecycle step c).

When to use

Reach for this skill when the user asks to **build or run a multi-step or parameterized workflow** — for example:

  • "Analyze every file in `reports/` and summarize the findings."
  • "Run a review pipeline: implement, then review, then verify."
  • "Do the same batch job but with a different input directory each time."

If the work is a single one-off agent call, you don't need a workflow. Workflows earn their keep when there are multiple steps, fan-out, parameterization, or a need to resume.

The script API

Author scripts import from the `cao_workflow` package. This package runs **only in the script subprocess** and imports nothing from `cli_agent_orchestrator.*` — it talks to CAO over HTTP. Its public surface:

  • `step(provider, agent, prompt, *, recovery, step_id=None, timeout=None, **opts) -> StepHandle` —

run one agent step and **declare** what re-running it would mean. `recovery` is keyword-only with no default, so omitting it is a `TypeError` at the call. See "Declaring a recovery policy" below before you pick a value.

  • `run_step(provider, agent, prompt, *, step_id=None, timeout=None, **opts) -> StepHandle` —

the same call, **declaring no policy**. That is the only difference between the two. A `recovery=` passed to `run_step` lands in `**opts`; the server validates it, the shim does not — see below.

  • `StepHandle` has **five** fields: `.step_id`, `.terminal_id`, `.output`, `.status`, and

`.replayed`. **`.replayed` qualifies `.terminal_id`.** When it is `True` the server returned a stored result and ran nothing, and `.terminal_id` is the ORIGINAL id — it names a terminal that **no longer exists**. That flag is the only thing standing between you and reading, writing to, or waiting on a dead id, so check it before you touch `.terminal_id`.

  • `get_inputs() -> dict` — the run's resolved inputs (see Parameterized workflows). Returns

`{}` when nothing was declared; never raises on absence.

  • `emit_output(value)` — print the run-level `CAO_WORKFLOW_OUTPUT:` sentinel (the run's return).
  • `ShimError` (and `ShimIdentityError`, `ShimTransportError`, `ShimHTTPError`) — the failure

hierarchy `step` and `run_step` raise. Failures surface **unchanged** — the shim never retries. A structured HTTP error with a non-empty string `detail.kind` prints as `run-step returned HTTP <status> (<kind>): <message>` (without the message suffix when absent); an unstructured error keeps the original `run-step returned HTTP <status>` text.

Declaring a recovery policy

`recovery=` is **the author's claim about the step, and nothing more.** CAO has no mechanism to prove what a step does to the outside world, so it cannot and does not verify the claim. A recovery policy **DECLARES what re-running this step would mean; it never grants permission.**

The three values, all of which are statements you are making, not protections you are getting:

| Value | What you are asserting | | --- | --- | | `"idempotent"` | re-running this step has the same effect as running it once | | `"reconcile"` | re-running it needs a reconciliation step first (**deferred** — today CAO treats it exactly like `idempotent`) | | `"manual"` | do not decide this one without me — halt and ask |

**`"idempotent"` grants nothing and protects nothing.** It does not make a step safe to re-run; it tells the resume gate that *you* believe it already is — and wherever the gate would otherwise stop and ask a human, it re-executes the step on your word instead. Declare it on a step that charges a card, sends mail, or files a ticket and CAO will charge the card again, exactly as instructed. If you cannot show the step is safe to repeat, `"manual"` is the honest declaration.

Omitting a policy is a **fourth, distinct state** — it is never silently read as `"manual"`. Use `run_step` for it deliberately: an undeclared step still replays (replay executes nothing), but where the alternative is re-execution it halts for a human.

**`recovery=` on `run_step` is checked late, not never.** `run_step` has no `recovery` parameter, so the value rides `**opts` to the server, which stores it, lets the resume gate honour it, and **rejects an unknown value with a `422`** — the route types that field as the closed policy enum. What `run_step` lacks is `step()`'s client-side check, which refuses a bad value *before any HTTP attempt*; on `run_step` a typo instead fails that step mid-run. Neither surface has its value checked by `validate` (the linter sees the keyword, not its contents), which is why `validate` reports the `run_step` form as `unenforced-recovery-policy`. Use `step()` to declare, and `run_step` only to declare nothing.

Lifecycle

The whole sequence, in order: **describe → author → validate → present plan → approve → run → observe**. **No step may be skipped** — validate is mandatory, approval is a human act, and you must ask before running.

**Python is the format.** You are never asked to choose between YAML and Python, and you must never ask the us

Read more
@awslabs
Ships withcli-agent-orchestrator

CLI Agent Orchestrator (CAO) coordinates multiple AI coding CLIs so a supervisor can delegate work to specialist agents in parallel or sequence. 📚 Documentation — guides, reference, and two interactive courses.

1.5k20 skills1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
1.5k
Star history of awslabs/cli-agent-orchestrator on GitHub, a chart by star-history.com

Other skills on cli-agent-orchestrator.

More skills from other plugins.

agent-builder

agent-builder

Load immediately after an Agent intent, before the first agent_builder_* call. Governs how you create, configure, test, and…

n8n2w209k
@n8n-io@n8n-ioInstall Skill
browser-use

browser-use

Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work. Also covers…

@browser-use@browser-useInstall Skill
add-anydoc

add-anydoc

Add local office-document-to-Markdown conversion to NanoClaw agent containers with the pinned Firecrawl AnyDoc CLI. Use when…

@nanocoai@nanocoaiInstall Skill

Questions about cao-workflow.

Which coding agents can use cao-workflow?
cao-workflow installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.