返回 Skills 目錄
getcargohq/cargo-skills包含需要注意的行為

SKILL DETAIL

cargo-orchestration

getcargohq/cargo-skills/cargo-orchestration

The cargo-orchestration skill enables you to make Cargo actually run something, or show what it would run, using the Cargo CLI. You can execute a single connector action, run a multi-step workflow, trigger a batch across a whole segment or model, message an AI agent, build or edit a node graph, draw a workflow, tool, or play as a diagram, and query the runtime tables (runs, batches, spans, records) with SQL. The skill provides command references, examples, and best practices, including sampling before batch operations, using `action execute` instead of `node execute` for general execution, and visualizing workflows with the `node diagram` command. It also includes references for resource discovery, polling async operations, troubleshooting, and response shapes.

安裝量 · 135查看來源

Installation

npx skills add https://github.com/getcargohq/cargo-skills --skill cargo-orchestration

技能檔案

SKILL.md

最近同步 · 2026年8月29日

references/examples/actions.md
# Action examples

## What is an action?

An **action** is a single operation you can execute without building a workflow. Use `action execute` for one record, or `action execute-batch` for multiple records.

Actions come in four kinds:

| Kind        | What it does                         | Required fields                                |
| ----------- | ------------------------------------ | ---------------------------------------------- |
| `tool`      | Run an orchestration tool            | `toolUuid` or `templateSlug` or `releaseUuid`  |
| `connector` | Call a third-party service           | `integrationSlug` + `actionSlug`               |
| `agent`     | Invoke an AI agent                   | `agentUuid` or `templateSlug` or `releaseUuid` |
| `native`    | Run a built-in platform action       | `actionSlug`                                   |

`config` is where a **node** keeps its configuration; a top-level action has none — its inputs go in `--data` (single) or `--records` (batch). Omit the key on `execute` / `execute-batch`: that is the shape `action list` returns, and `"config": {}` is merely tolerated there.

**`get-output-schema` is the exception — it still requires `config`.** Hand it the action object from `action list` unchanged and it fails `400 — expected record, received undefined` at `action.config`; add `"config": {}` for that command only (the examples below do). Nodes, alert `--actions`, play `healthAlertActions`, and agent / MCP-server `--actions` require it as well.

> **When to use actions vs workflows:** Actions are for running a **single operation** without building a workflow graph. If you need to **chain multiple operations** together (enrichment → scoring → CRM push), use `run create --nodes` or `batch create --nodes` instead. See `tools.md` for workflow examples.

---

## Find an action — `action list`

Free: no run, no credits. Searches the integration catalog, Cargo native actions, this workspace's tools, and its agents in one call.

```bash
cargo-ai orchestration action list enrich company
cargo-ai orchestration action list --kind tool
cargo-ai orchestration action list send --kind connector --integration-slug slack
cargo-ai orchestration action list verify email --limit 5
```

| Flag | Meaning |
| --- | --- |
| `[query...]` | Space-separated keywords. **All** terms must match (AND), against action slug, name, description, and integration. Omit to browse. |
| `--kind` | One of `connector`, `native`, `tool`, `agent`. `tool` and `agent` need a signed-in workspace. |
| `--integration-slug` | Restrict connector results to one integration. |
| `--limit` | Default 20, max 50. |

Response:

```json
{
  "query": "enrich company",
  "totalMatches": 37,
  "results": [
    {
      "name": "Enrich company",
      "description": "Return firmographics for a domain…",
      "score": 12,
      "action": {
        "kind": "connector",
        "integrationSlug": "cargo",
        "actionSlug": "enrichCompany",
        "connectorUuid": "<uuid>"
      },
      "connectors": [{ "uuid": "<uuid>", "slug": "cargo", "name": "Cargo" }],
      "credits": [{ "...": "cost table for this action" }],
      "autocompletes": [{ "slug": "<slug>", "params": { "...": "..." } }]
    }
  ]
}
```

Notes worth knowing:

- **`results[].action` is the payload** — pass it verbatim to `execute`, `execute-batch`, or `get-output-schema`. `connectorUuid` is resolved to the integration's default connector (or the first one) and sits **at the top level of the action, never inside `config`**.
- **`credits`** is the action's cost table when it bills — the cheapest pre-flight cost check there is. Cross-check a GTM provider's playbook (`../../../cargo-gtm/provider-playbooks/<slug>.md`) before fanning out.
- **`autocompletes`** flags config fields that need a picked id (HubSpot object type, Slack channel, Metabase question). Resolve those to concrete values before running — over MCP that is the `autocomplete_action` tool; over the CLI, use the integration's own list actions.
- Ranking: action slug/name > integration > description. `score` is comparable within one response only.
- Structural native nodes (`start`, `end`, `branch`, `delay`, `filter`, `group`, `split`, `switch`, `note`) are excluded — they belong in a node graph, not in `action execute`. See `nodes.md`.
- `unknown command` means the CLI predates `action list` — refresh it (`npm install -g @cargo-ai/cli@…`).

---

## Execute one action on one record

```bash
# Tool action
cargo-ai orchestration action execute \
  --action '{"kind":"tool","toolUuid":"<tool-uuid>"}' \
  --data '{"domain":"acme.com"}'

# Connector action
cargo-ai orchestration action execute \
  --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany"}' \
  --data '{"domain":"acme.com"}'

# Agent action
cargo-ai orchestration action execute \
  --action '{"kind":"agent","agentUuid":"<agent-uuid>"}' \
  --data '{"company":"Acme Corp"}'
```

Returns a `run` object. Poll with `run get <uuid>` until terminal, or pass `--wait-until-finished`:

```bash
cargo-ai orchestration action execute \
  --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany"}' \
  --data '{"domain":"acme.com"}' \
  --wait-until-finished
```

Custom polling interval (default 5000ms):

```bash
cargo-ai orchestration action execute \
  --action '{"kind":"tool","toolUuid":"<tool-uuid>"}' \
  --data '{"domain":"acme.com"}' \
  --wait-until-finished --polling-interval 2000
```

### Response

```json
{
  "run": {
    "uuid": "run-uuid",
    "status": "pending",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

With `--wait-until-finished`, the response contains the terminal run state:

```json
{
  "run": {
    "uuid": "run-uuid",
    "status": "success",
    "createdAt": "2025-01-15T10:00:00Z",
    "finishedAt": "2025-01-15T10:00:05Z"
  }
}
```

**Status values:** `pending`, `running`, `success`, `error`, `cancelled`.

---

## Execute one action on many records

```bash
cargo-ai orchestration action execute-batch \
  --action '{"kind":"tool","toolUuid":"<tool-uuid>"}' \
  --records '[{"domain":"acme.com"},{"domain":"globex.com"},{"domain":"initech.com"}]'
```

Returns a `batch` object. Poll with `batch get <uuid>` until terminal, or pass `--wait-until-finished`:

```bash
cargo-ai orchestration action execute-batch \
  --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany"}' \
  --records '[{"domain":"acme.com"},{"domain":"globex.com"}]' \
  --wait-until-finished
```

### Webhook notification

Get notified when the batch completes instead of polling:

```bash
cargo-ai orchestration action execute-batch \
  --action '{"kind":"tool","toolUuid":"<tool-uuid>"}' \
  --records '[{"domain":"acme.com"},{"domain":"globex.com"}]' \
  --webhook-url "https://hooks.example.com/done" \
  --webhook-secret "my-secret"
```

### Response

```json
{
  "batch": {
    "uuid": "batch-uuid",
    "status": "pending",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

With `--wait-until-finished`:

```json
{
  "batch": {
    "uuid": "batch-uuid",
    "status": "success",
    "runsCount": 3,
    "executedRunsCount": 3,
    "failedRunsCount": 0,
    "creditsUsedCount": 3,
    "createdAt": "2025-01-15T10:00:00Z",
    "finishedAt": "2025-01-15T10:00:15Z"
  }
}
```

---

## Retry configuration

Add a `retry` object to the action for automatic retries on transient failures:

```bash
cargo-ai orchestration action execute \
  --action '{
    "kind":"connector",
    "integrationSlug":"clearbit",
    "actionSlug":"enrichCompany",
    "retry":{"maximumAttempts":3,"initialInterval":1000,"backoffCoefficient":2}
  }' \
  --data '{"domain":"acme.com"}' \
  --wait-until-finished
```

---

## Discovering action parameters

To find the right values for each action kind:

```bash
# Tool actions — find toolUuid
cargo-ai orchestration tool list
# → Extract .tools[].uuid

# Connector actions — find integrationSlug + actionSlug
cargo-ai connection integration list
cargo-ai connection integration get <slug>
# → Extract actions from the integration

# Agent actions — find agentUuid
cargo-ai ai agent list
# → Extract .agents[].uuid

# Connector actions — find connectorUuid (optional, for authenticated connectors)
cargo-ai connection connector list
# → Extract .connectors[].uuid
```

---

## Resolve an action's output schema

**Never guess what an action outputs.** There are two free ways to discover what an action **produces** — no run, no credits.

### 1. Connector actions: read `output.schema` from the integration catalog

`integration get <slug>` (and `integration list`) return each action's output schema inline, next to its input schema:

```bash
cargo-ai connection integration get waterfall
# → .integration.actions.verifyEmail.config.schema   — input (what you pass)
# → .integration.actions.verifyEmail.output.schema   — output (what it emits)
```

**Not every action declares an output schema** — e.g. `waterfall.verifyEmail`, `clearbit.enrichCompany`, and most `hubspot` record actions do, while `waterfall.detectJobChange`, `waterfall.searchProspects`, and `salesNavigator.searchAccounts` don't (no `output` key). When it's absent, the only way to see the real shape is `runContext` from an actual run.

### 2. Any action kind: `action get-output-schema`

For non-connector kinds (`tool`, `agent`, `native`) — or when you already have the action object in hand — resolve the same schema without touching the catalog:

```bash
cargo-ai orchestration action get-output-schema \
  --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany","config":{}}'
```

It accepts the same `--action` object as `action execute`, so it works for every kind:

```bash
# Tool action — resolves the tool workflow's output-node schema
cargo-ai orchestration action get-output-schema \
  --action '{"kind":"tool","toolUuid":"<tool-uuid>","config":{}}'

# Agent action — resolves the deployed release's output schema
cargo-ai orchestration action get-output-schema \
  --action '{"kind":"agent","agentUuid":"<agent-uuid>","config":{}}'

# Native action
cargo-ai orchestration action get-output-schema \
  --action '{"kind":"native","actionSlug":"<slug>","config":{}}'
```

### Response

The JSON Schema sits under a top-level **`schema`** key (not returned bare), and for connector actions it is exactly the catalog's `output.schema` — e.g. `waterfall` / `verifyEmail` resolves to:

```json
{
  "schema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "email": { "type": "string" },
      "domain": { "type": "string" },
      "email_status": { "type": "string" },
      "smtp_provider": { "type": "string" },
      "mx_records": { "type": "array", "items": { "type": "string" } }
    }
  }
}
```

An `agent` action without a structured `output.jsonSchema` resolves to `{"schema":{"type":"object","properties":{"answer":{"type":"string"}}}}` — the free-text answer envelope. This is the authoritative confirmation that downstream references must go through `.answer` (`{{nodes.<slug>.answer}}`, or `{{nodes.<slug>.answer.<field>}}` for structured agents).

Two distinct failure modes, both non-zero exit with `status: 404`:

- `"Action not found."` — the `actionSlug` / `toolUuid` / `agentUuid` doesn't exist. Slugs are exact and case-sensitive (`enrichCompany`, not `company_enrich`); list them via `integration get <slug>` → `.integration.actions` keys.
- `"Action has no output schema."` — the action exists but declares no output schema (its catalog entry has no `output` key). Fall back to running it once and reading `runContext.<nodeSlug>` from `run get`.

### Why it's useful

- **Wire a node graph correctly the first time.** Know which fields exist before referencing them downstream as `{{nodes.<slug>.<field>}}` — avoids the silent-`undefined` footgun (see `../node-selection.md`).
- **Know an agent's output envelope** (`.answer` vs structured fields) before writing branch/filter expressions against it.
- **Map onto storage columns** ahead of a batch, without a throwaway run to inspect the output.

---

## End-to-end: enrich a company with a connector action

```bash
# 1. Find the integration and action
cargo-ai connection integration get clearbit
# → Find actionSlug: "enrichCompany" (slugs are exact — keys of .integration.actions)

# 2. Execute
cargo-ai orchestration action execute \
  --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany"}' \
  --data '{"domain":"acme.com"}' \
  --wait-until-finished
# → Done. Check run.status for success/error.
```

## End-to-end: run a tool action on multiple leads

```bash
# 1. Find the tool
cargo-ai orchestration tool list
# → Find "Lead Enrichment", extract uuid

# 2. Execute batch
cargo-ai orchestration action execute-batch \
  --action '{"kind":"tool","toolUuid":"<tool-uuid>"}' \
  --records '[
    {"email":"[email protected]","company":"Acme"},
    {"email":"[email protected]","company":"Globex"},
    {"email":"[email protected]","company":"Initech"}
  ]' \
  --wait-until-finished
# → Check batch.status, batch.failedRunsCount
```
references/examples/agents.md
# AI agent examples

## Basic chat: ask a question and get a response

```bash
# 1. Find the right agent by name
cargo-ai ai agent list
# → Match by name, extract agent uuid

# 2. Create a chat session
cargo-ai ai chat create \
  --trigger '{"type":"draft"}' \
  --agent-uuid <agent-uuid> \
  --name "Quick question"
# → Extract chat.uuid

# 3. Send a message
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"What is Acme Corp'\''s employee count?"}]'
```

Message create response:

```json
{
  "userMessage": { "uuid": "user-msg-uuid", "status": "success" },
  "assistantMessage": {
    "uuid": "assistant-msg-uuid",
    "status": "pending",
    "parts": []
  }
}
```

```bash
# 4. Poll for the response (repeat every 2s)
cargo-ai ai message get <assistant-msg-uuid>
```

Poll until `status` is `success` or `error`:

```json
{
  "message": {
    "uuid": "assistant-msg-uuid",
    "status": "success",
    "parts": [
      { "type": "text", "text": "Acme Corp has approximately 500 employees..." }
    ],
    "errorMessage": null
  }
}
```

Status values: `pending` → `generating` → `success` or `error`. On `error`, read `.message.errorMessage`.

## Multi-turn conversation

```bash
# 1. Create a chat
cargo-ai ai chat create \
  --trigger '{"type":"draft"}' \
  --agent-uuid <agent-uuid> \
  --name "Lead research"
# → Extract chat.uuid

# 2. First message
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Find the VP of Sales at Acme Corp"}]'
# → Poll assistantMessage.uuid until success

# 3. Follow-up in the same chat (agent remembers context)
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Now find their email address"}]'
# → Poll the new assistantMessage.uuid

# 4. Another follow-up
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Draft a cold outreach email to them"}]'
# → Poll again
```

## Reuse an existing chat session

```bash
# 1. List existing chats for an agent
cargo-ai ai chat list --agent-uuid <agent-uuid> --limit 10
# → Find a chat by name or pick the most recent one

# 2. Send a message in the existing chat
cargo-ai ai message create \
  --chat-uuid <existing-chat-uuid> \
  --parts '[{"type":"text","text":"Any updates on the Acme deal?"}]'
# → Poll for response
```

## Send a message with actions

Give the agent access to specific actions for enrichment, CRM actions, etc.

```bash
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Enrich this lead and add to Salesforce"}]' \
  --actions '[{"slug":"clearbit","kind":"tool","toolUuid": "<tool-uuid>","config":{}},{"slug":"salesforce","kind":"tool","config":{}}]'
# → The agent can use these actions during its response
```

## Send a message with model resources

Give the agent access to a data model to query.

```bash
# 1. Find the model UUID
cargo-ai storage model list

# 2. Send message with the model as a resource
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Find all companies in France with more than 100 employees"}]' \
  --resources '[{"slug":"companies","kind":"model","integrationSlug":"salesforce","modelUuid":"<model-uuid>"}]'
```

## Use a specific language model and temperature

```bash
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Write a creative subject line for this campaign"}]' \
  --language-model-slug gpt-4o \
  --temperature 0.9
```

Lower temperature (0.0–0.3) for factual/structured tasks, higher (0.7–1.0) for creative tasks.

## Send a message with actions, resources, and custom model

Full example combining all options.

```bash
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Research Acme Corp, enrich their data, and update our CRM"}]' \
  --actions '[{"slug":"clearbit","kind":"tool","config":{}},{"slug":"salesforce","kind":"tool","config":{}}]' \
  --resources '[{"slug":"companies","kind":"model","integrationSlug":"salesforce","modelUuid":"<model-uuid>"}]' \
  --language-model-slug gpt-4o \
  --temperature 0.3 \
  --max-steps 10
```

## List messages in a chat

```bash
cargo-ai ai message list --chat-uuid <chat-uuid> --limit 20
# → Returns all messages in order (both user and assistant)
```

## Check all chats for an agent

```bash
# All chats
cargo-ai ai chat list --agent-uuid <agent-uuid>

# With pagination
cargo-ai ai chat list --agent-uuid <agent-uuid> --limit 5 --offset 0
```

## End-to-end: use an AI template to create an agent and run a research task

This example uses an AI template to bootstrap a lead researcher agent, then sends it a research task.

```bash
# Step 1 — Browse AI templates
cargo-ai ai template list
# → Find slug: "lead-researcher"
#   languageModelSlug: "gpt-4o", temperature: 0.3

# Step 2 — Create an agent
cargo-ai ai agent create \
  --name "Lead Researcher" \
  --icon-color purple --icon-face 🔍
# → Extract agent.uuid (e.g. "agent-abc")

# Step 3 — Configure the draft release with template settings
cargo-ai ai release update-draft --agent-uuid agent-abc \
  --system-prompt "You are a research assistant. Given a company domain and a contact name, find their role, LinkedIn profile URL, and email address. Return structured JSON with keys: role, linkedin_url, email." \
  --language-model-slug gpt-4o \
  --temperature 0.3

# Step 4 — Attach a knowledge file (optional — ICP criteria, product info, etc.)
cargo-ai content file upload --file ./icp-criteria.pdf
# → Extract file.uuid

# Step 5 — Give the agent access to actions (optional — connectors as actions)
cargo-ai orchestration tool list
# → Find a "Find Email" tool, extract uuid

# Step 6 — Create a chat session
cargo-ai ai chat create \
  --trigger '{"type":"draft"}' \
  --agent-uuid agent-abc \
  --name "Lead research — Acme Corp"
# → Extract chat.uuid

# Step 7 — Send the research request
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Research the VP of Sales at acme.com. Find their name, LinkedIn URL, and email address."}]' \
  --actions '[{"slug":"find_email","kind":"tool","toolUuid":"<email-finder-tool-uuid>","config":{}}]' \
  --max-steps 10
# → Extract assistantMessage.uuid

# Step 8 — Poll for the response (every 2s)
cargo-ai ai message get <assistant-msg-uuid>
# → Done when message.status is "success" (read .parts) or "error" (read .errorMessage)

# Step 9 — Follow up in the same chat
cargo-ai ai message create \
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Now draft a personalised cold outreach email to this person."}]'
# → Poll again
```
references/examples/plays.md
# Play examples

## What is a play?

A **play** is a segment-driven automation. It is linked to a specific model and segment, and runs its workflow automatically when records in that segment change (are added, updated, or removed). Plays are the reactive side of Cargo — "when this data changes, do that."

Key properties of a play:

- **`name`** — human-readable name (workflows themselves don't have names)
- **`workflowUuid`** — the underlying workflow that executes
- **`modelUuid`** — the data model the play operates on
- **`segmentUuid`** — the segment that triggers runs
- **`changeKinds`** — which segment changes trigger a run (`added`, `updated`, `removed`)
- **`schedule`** — optional cron schedule for periodic re-evaluation
- **`isEnabled`** — whether the play is active

## List all plays

```bash
cargo-ai orchestration play list
```

Response:

```json
{
  "plays": [
    {
      "uuid": "play-uuid",
      "name": "Enrich new companies",
      "workflowUuid": "workflow-uuid",
      "modelUuid": "model-uuid",
      "segmentUuid": "segment-uuid",
      "changeKinds": ["added", "updated"],
      "isEnabled": true,
      "schedule": null,
      "description": "Enriches companies when they enter the segment"
    }
  ]
}
```

## Find a play's workflow UUID

Plays have names — workflows don't. Use the play to find the right workflow and model.

```bash
# 1. Find the play
cargo-ai orchestration play list
# → Extract play.workflowUuid and play.modelUuid

# 2. Create a batch over the play's model (empty filter = all rows)
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"<play.modelUuid>","filter":{"conjonction":"and","groups":[]}}'

# 3. Poll until done
cargo-ai orchestration batch get <batch-uuid>

# Or block until finished — returns the final batch result without a separate poll step
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"<play.modelUuid>","filter":{"conjonction":"and","groups":[]}}' \
  --wait-until-finished
```

An empty filter (`{"conjonction":"and","groups":[]}`) enrols every row in the model;
add conditions to narrow it — see `references/filter-syntax.md` for the full shape.

> **Never pass `play.segmentUuid` to `{"kind":"segment"}`.** That UUID points at
> the play's internally generated segment, whose record count is never
> populated — the batch is rejected (`segmentLinkedToPlay`, or `noRecords` on
> older backends) no matter how many rows the model holds. `{"kind":"segment"}`
> is only for standalone segments from `segmentation segment list`.

## Update a play's workflow

To change what a play does, update its draft release and deploy it. The draft release holds the unpublished node graph for the workflow.

> **Looking for inspiration?** Before designing a node graph from scratch, check `cargo-ai orchestration template list` for pre-built patterns (lead scoring, enrichment pipelines, CRM syncs). Use `cargo-ai orchestration template get <slug>` to copy a ready-made node graph and adapt it instead of starting from zero. Templates tagged `"kind":"play"` are designed for segment-driven automations.

```bash
# Step 1 — Find the play and its workflowUuid
cargo-ai orchestration play list
# → Find "Enrich new companies", extract play.workflowUuid

# Step 2 — Get the current draft release (contains the current node graph)
cargo-ai orchestration draft-release get --workflow-uuid <play.workflowUuid>
# → Copy the "nodes" array and make your changes

# Step 3 — Update the draft release with your new nodes
cargo-ai orchestration draft-release update \
  --workflow-uuid <play.workflowUuid> \
  --nodes '[...your updated node graph...]'

# Step 4 — Validate the updated nodes before deploying
cargo-ai orchestration node validate --nodes '[...your updated node graph...]'
# → { "outcome": "valid" }

# Step 5 — Deploy the draft release
cargo-ai orchestration draft-release deploy \
  --workflow-uuid <play.workflowUuid> \
  --nodes '[...your updated node graph...]' \
  --form-fields 'null' \
  --description "Your release description"
```

> **Do not skip validation.** Deploying an invalid node graph will cause runs to fail. Always run `node validate` before `draft-release deploy`.

> **Do not pass `--version` to `draft-release deploy`.** The deploy-specific `--version` flag is shadowed by the global `--version` flag — passing it causes the command to print the CLI version (e.g. `1.0.11`) and exit 0 **without deploying**. Omit it and let the server auto-assign (first deploy → `1.0.0`, then `1.0.1`, etc.). Always confirm the deploy worked with `release get-deployed --workflow-uuid <uuid>` — the response should show `status: "deployed"`, not `draft`.

---

## Run a play's workflow on specific records

> **`run create` is not compatible with play workflows** — it will return
> `playNotCompatible`. Always use `batch create` for plays.
>
> Allowed batch data kinds for plays: `segment`, `change`, `filter`, `recordIds`.

### By filter (query the model)

```bash
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"<play.modelUuid>","filter":{"field":"domain","operator":"is","value":"acme.com"},"limit":10}'
```

### By record IDs

```bash
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"recordIds","modelUuid":"<play.modelUuid>","ids":["record-id-1","record-id-2"]}'
```

## Monitor a play's runs

```bash
# List recent runs
cargo-ai orchestration run list \
  --workflow-uuid <play.workflowUuid> \
  --limit 20

# Count errors
cargo-ai orchestration run count \
  --workflow-uuid <play.workflowUuid> \
  --statuses error

# List running batches
cargo-ai orchestration batch list \
  --workflow-uuids <play.workflowUuid> \
  --statuses running
```

## Cancel runs and batches

```bash
# Cancel specific runs
cargo-ai orchestration run cancel \
  --workflow-uuid <play.workflowUuid> \
  --uuids <run-uuid-1>,<run-uuid-2>

# Cancel a batch
cargo-ai orchestration batch cancel <batch-uuid>
```

## End-to-end: use a template to run a play

This example takes a "lead-scoring" play template, fills in its placeholders, validates the node graph, and runs it against the play's segment.

```bash
# Step 1 — List available play templates
cargo-ai orchestration template list
# → Find slug: "lead-scoring", kind: "play"

# Step 2 — Get the template's node graph
cargo-ai orchestration template get lead-scoring
# → Copy the "nodes" array. It will contain __REPLACE_WITH_*__ placeholders.

# Step 3 — Discover what you need to fill in
cargo-ai connection connector list
# → Find your connector UUIDs (e.g. a Clearbit connector)
cargo-ai ai agent list
# → Find agentUuid if the template uses an agent node

# Step 4 — Validate the node graph after filling in placeholders
cargo-ai orchestration node validate --nodes '[
  {
    "uuid": "77777777-7777-4777-a777-777777777777", "slug": "start", "kind": "native", "actionSlug": "start",
    "config": {}, "childrenUuids": ["88888888-8888-4888-a888-888888888888"], "fallbackOnFailure": false,
    "position": {"x": 0, "y": 0}
  },
  {
    "uuid": "88888888-8888-4888-a888-888888888888", "slug": "score", "kind": "native", "actionSlug": "agent",
    "config": {
      "prompt": {
        "kind": "templateExpression",
        "expression": "Score this lead from 1-10 based on ICP fit. Company: {{nodes.start.company}}, Domain: {{nodes.start.domain}}, Employee count: {{nodes.start.employee_count}}. Return score and reasoning.",
        "instructTo": "none",
        "fromRecipe": false
      },
      "advancedSettings": {
        "connectorUuid": "<openai-connector-uuid>",
        "languageModelSlug": "gpt-4.1-mini",
        "temperature": 0.1
      }
    },
    "childrenUuids": ["99999999-9999-4999-a999-999999999999"], "fallbackOnFailure": false,
    "position": {"x": 0, "y": 166}
  },
  {
    "uuid": "99999999-9999-4999-a999-999999999999", "slug": "end", "kind": "native", "actionSlug": "end",
    "config": {
      "variables": [
        {"name": "score", "type": "number", "value": {"kind": "templateExpression", "expression": "{{nodes.score.score}}", "instructTo": "none", "fromRecipe": false}},
        {"name": "reasoning", "type": "string", "value": {"kind": "templateExpression", "expression": "{{nodes.score.reasoning}}", "instructTo": "none", "fromRecipe": false}}
      ]
    },
    "childrenUuids": [], "fallbackOnFailure": false,
    "position": {"x": 0, "y": 332}
  }
]'
# → { "outcome": "valid" }

# Step 5 — Find the play's workflowUuid and modelUuid
cargo-ai orchestration play list
# → Find "Lead Scoring", extract workflowUuid and modelUuid

# Step 6 — Run the template nodes against the play's model (empty filter = all rows)
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"<play.modelUuid>","filter":{"conjonction":"and","groups":[]}}' \
  --nodes '[...validated nodes from step 4...]'
# → Extract batch.uuid

# Step 7 — Poll until finished (every 5s)
cargo-ai orchestration batch get <batch-uuid>
# → Done when .status is "success", "error", or "cancelled"

# Alternative to steps 6+7 — block until finished in one command
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"<play.modelUuid>","filter":{"conjonction":"and","groups":[]}}' \
  --nodes '[...validated nodes from step 4...]' \
  --wait-until-finished
```
references/examples/queries.md
# Orchestration query examples

Run SQL against orchestration runtime tables — `runs`, `batches`, `spans`, `records` — with `cargo-ai orchestration query execute`. Use this for ad-hoc analytics on workflow execution (error rates, throughput, slowest nodes, per-node failure breakdowns) without the workflow-scoped filters of `run get-metrics` / `run count`.

The backing store is ClickHouse; queries are read-only and exit non-zero with `{"errorMessage": "..."}` on error.

> For SQL against workspace storage (Companies, Contacts, Deals…), use `cargo-ai storage query execute "<sql>"` — documented in the `cargo-storage` skill (`references/examples/queries.md`).

## Basic query flow

```bash
cargo-ai orchestration query execute \
  "SELECT count() FROM runs WHERE status = 'error'"
```

Success response:

```json
{
  "rows": [{ "count()": 42 }]
}
```

## Tables

Tables are referenced **without** a schema prefix. The query engine scopes every read to your workspace automatically.

| Table     | Use it for                                                                 |
| --------- | -------------------------------------------------------------------------- |
| `runs`    | Per-record workflow executions (status, timing, executions array, batch)   |
| `batches` | Batch-level rows: counts (`runs_count`, `failed_runs_count`), credit usage |
| `spans`   | Flattened per-node execution rows (one row per node execution)             |
| `records` | Materialized view over `runs` keyed by record id                           |

Common columns: `workspace_uuid`, `workflow_uuid`, `batch_uuid`, `release_uuid`, `status`, `created_at`, `updated_at`, `finished_at`, `credits_used_count`. See the migration files in `apps/backend/src/domains/orchestration/migrations/` for the full schema.

## Example queries

```bash
# Error rate across the whole workspace
cargo-ai orchestration query execute \
  "SELECT countIf(status='error') / count() AS error_rate FROM runs WHERE created_at > now() - INTERVAL 1 DAY"

# Errors per workflow over the last week
cargo-ai orchestration query execute \
  "SELECT workflow_uuid, count() AS errors FROM runs WHERE status='error' AND created_at > now() - INTERVAL 7 DAY GROUP BY workflow_uuid ORDER BY errors DESC"

# Batch status breakdown
cargo-ai orchestration query execute \
  "SELECT status, count() FROM batches GROUP BY status"

# Slowest node executions in the last hour
cargo-ai orchestration query execute \
  "SELECT node_slug, node_kind, dateDiff('second', execution_started_at, execution_finished_at) AS duration_s
   FROM spans
   WHERE execution_finished_at > now() - INTERVAL 1 HOUR
   ORDER BY duration_s DESC
   LIMIT 20"

# Per-node failure counts
cargo-ai orchestration query execute \
  "SELECT node_slug, count() AS failures
   FROM spans
   WHERE execution_status='error' AND execution_started_at > now() - INTERVAL 1 DAY
   GROUP BY node_slug
   ORDER BY failures DESC"

# Credit spend by workflow this month
cargo-ai orchestration query execute \
  "SELECT workflow_uuid, sum(credits_used_count) AS credits
   FROM batches
   WHERE created_at >= toStartOfMonth(now())
   GROUP BY workflow_uuid
   ORDER BY credits DESC"
```

## Common table expressions

```bash
cargo-ai orchestration query execute \
  "WITH recent AS (SELECT * FROM runs WHERE created_at > now() - INTERVAL 1 DAY)
   SELECT status, count() FROM recent GROUP BY status"
```

## Limits and restrictions

Orchestration queries run as a read-only ClickHouse user with per-query caps:

| Limit                | Value      |
| -------------------- | ---------- |
| `max_execution_time` | 30s        |
| `max_result_rows`    | 10 000     |
| `max_rows_to_read`   | 10 000 000 |
| `max_columns_to_read`| 50         |
| `max_subquery_depth` | 5          |

DDL, introspection functions, table functions (`merge`, `cluster`, `remote`, `url`, `s3`, `file`, …), dictionary accessors, and the query cache are all denied. Wrap heavy aggregations in time filters (`created_at > now() - INTERVAL N DAY`) to stay under the row-scan cap.

## Error handling

```json
{ "errorMessage": "Code: 158. Memory limit exceeded ..." }
```

Common causes:
- Scanned too many rows → narrow the time window with a `created_at`/`execution_started_at` predicate
- Forbidden function (e.g. `system.tables`, `cluster()`, `url()`) → use only `SELECT` against the four tables above
- Too many result rows → add a `LIMIT` or aggregate before returning
references/examples/segments.md
# Segment data examples

**Remember:** `segment fetch` and `segment download` require `--model-uuid`, not `--segment-uuid`. Get the `modelUuid` from `segment list`.

**Remember:** filter JSON uses `conjonction` (not `conjunction`).

## Fetch all records (no filter)

```bash
# 1. Find the model UUID
cargo-ai segmentation segment list
# → Extract modelUuid from the segment you want

# 2. Fetch with empty filter
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --fetching-limit 100 --fetching-offset 0
```

Response:

```json
{
  "records": [
    { "_id": "rec-1", "name": "Acme Corp", "domain": "acme.com", "employee_count": 500 },
    { "_id": "rec-2", "name": "Globex", "domain": "globex.com", "employee_count": 1200 }
  ],
  "count": 2,
  "columns": [
    { "slug": "_id", "type": "string", "label": "ID", "modelUuid": "model-uuid" },
    { "slug": "name", "type": "string", "label": "Company Name", "modelUuid": "model-uuid" }
  ]
}
```

## Fetch with pagination

```bash
# Page 1
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --fetching-limit 50 --fetching-offset 0

# Page 2
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --fetching-limit 50 --fetching-offset 50

# Page 3
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --fetching-limit 50 --fetching-offset 100
```

## Fetch with sorting

```bash
# Sort by creation date (newest first)
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --sort '[{"columnSlug":"created_at","kind":"desc"}]' \
  --fetching-limit 100

# Sort by employee count (highest first)
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --sort '[{"columnSlug":"employee_count","kind":"desc"}]' \
  --fetching-limit 50
```

## Filter by string column

```bash
# Companies in the US
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "string", "columnSlug": "country", "operator": "is", "values": ["US"]}
      ]
    }]
  }' \
  --fetching-limit 100

# Companies whose name contains "tech"
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "string", "columnSlug": "name", "operator": "contains", "values": "tech"}
      ]
    }]
  }' \
  --fetching-limit 100
```

## Filter by number column

```bash
# Companies with 100+ employees
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "number", "columnSlug": "employee_count", "operator": "greaterThan", "value": 100}
      ]
    }]
  }' \
  --fetching-limit 100

# Companies with 50–200 employees
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "number", "columnSlug": "employee_count", "operator": "between", "firstValue": 50, "lastValue": 200}
      ]
    }]
  }' \
  --fetching-limit 100
```

## Filter by date column

```bash
# Created after a specific date
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "date", "columnSlug": "created_at", "operator": "greaterThan", "value": "2025-01-01"}
      ]
    }]
  }' \
  --fetching-limit 100

# Created in a date range
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "date", "columnSlug": "created_at", "operator": "between", "firstValue": "2025-01-01", "lastValue": "2025-03-31"}
      ]
    }]
  }' \
  --fetching-limit 100
```

## Filter by boolean column

```bash
# Only customers
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "boolean", "columnSlug": "is_customer", "operator": "isTrue"}
      ]
    }]
  }' \
  --fetching-limit 100
```

## Combine multiple conditions (AND)

```bash
# US companies with 100+ employees, created after 2025-01-01
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "string", "columnSlug": "country", "operator": "is", "values": ["US"]},
        {"kind": "number", "columnSlug": "employee_count", "operator": "greaterThan", "value": 100},
        {"kind": "date", "columnSlug": "created_at", "operator": "greaterThan", "value": "2025-01-01"}
      ]
    }]
  }' \
  --sort '[{"columnSlug":"employee_count","kind":"desc"}]' \
  --fetching-limit 50
```

## Sort by multiple columns

```bash
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --sort '[{"columnSlug":"country","kind":"asc"},{"columnSlug":"employee_count","kind":"desc"}]' \
  --fetching-limit 100
```

## OR logic across groups

```bash
# Companies in the US OR companies with 500+ employees
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "or",
    "groups": [
      {
        "conjonction": "and",
        "conditions": [
          {"kind": "string", "columnSlug": "country", "operator": "is", "values": ["US"]}
        ]
      },
      {
        "conjonction": "and",
        "conditions": [
          {"kind": "number", "columnSlug": "employee_count", "operator": "greaterThan", "value": 500}
        ]
      }
    ]
  }' \
  --fetching-limit 100
```

## Filter for non-null values

```bash
# Only records with an email
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "string", "columnSlug": "email", "operator": "isNotNull"}
      ]
    }]
  }' \
  --fetching-limit 100
```

## Filter records NOT enrolled in a workflow

Find records that have never been processed by a specific play or tool. First get the `workflowUuid` from `play list` or `tool list`.

```bash
# 1. Find the workflow UUID from the play
cargo-ai orchestration play list
# → Extract play.workflowUuid

# 2. Fetch records that have never entered this workflow
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {
          "kind": "enrollment",
          "workflowUuid": "<workflow-uuid>",
          "activityKind": "workflowEntered",
          "frequency": {"operator": "not"},
          "period": {"operator": "moreThan", "value": 0, "unit": "day"}
        }
      ]
    }]
  }' \
  --fetching-limit 100
```

## Filter records enrolled in a workflow in the last 30 days

```bash
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {
          "kind": "enrollment",
          "workflowUuid": "<workflow-uuid>",
          "activityKind": "workflowEntered",
          "frequency": {"operator": "moreThan", "value": 0},
          "period": {"operator": "lessThan", "value": 30, "unit": "day"}
        }
      ]
    }]
  }' \
  --fetching-limit 100
```

## Combine enrollment with other conditions

US companies not yet enrolled in the enrichment workflow:

```bash
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{
    "conjonction": "and",
    "groups": [{
      "conjonction": "and",
      "conditions": [
        {"kind": "string", "columnSlug": "country", "operator": "is", "values": ["US"]},
        {
          "kind": "enrollment",
          "workflowUuid": "<workflow-uuid>",
          "activityKind": "workflowEntered",
          "frequency": {"operator": "not"},
          "period": {"operator": "moreThan", "value": 0, "unit": "day"}
        }
      ]
    }]
  }' \
  --fetching-limit 100
```

## Fetch with enrichment and sync

Enrichment triggers any connected enrichment tools on the records. Sync writes the results back to the model.

```bash
cargo-ai segmentation segment fetch \
  --model-uuid <model-uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --fetching-limit 50 \
  --enrich --sync
```

## Discover column slugs before filtering

```bash
# List models to see all columns with their slugs and types
cargo-ai storage model list
# → models[].columns[].slug — use these in filter conditions
# → models[].columns[].type — use to pick the right condition kind:
#     "string" → kind "string"
#     "number" → kind "number"
#     "date" → kind "date"
#     "boolean" → kind "boolean"
#     "object" → kind "object"
#     "array" → kind "array"
```
references/examples/templates.md
# Orchestration templates

## What is a template?

A **template** is a pre-built workflow blueprint — a ready-to-use node graph that captures common automation patterns (enrichment pipelines, CRM syncs, AI research flows, lead scoring). Templates serve two purposes:

1. **Design-time inspiration** — when building or updating a tool or play, browse templates to find one close to your use case, then copy its node graph into your draft release as a starting point instead of designing from scratch.
2. **Runtime shortcut** — plug a template's nodes directly into `run create` or `batch create` via the `--nodes` flag without modifying the tool's stored definition.

Templates are read-only. You discover them by slug, inspect their node graph and expected input schema, then either adapt the graph for a draft release or pass it directly as `--nodes` when creating a run or batch.

## List all templates

```bash
cargo-ai orchestration template list
```

Response:

```json
{
  "templates": [
    {
      "slug": "company-enrichment",
      "name": "Company Enrichment",
      "description": "Enrich a company record with firmographic data from Clearbit",
      "kind": "tool"
    },
    {
      "slug": "lead-scoring",
      "name": "Lead Scoring",
      "description": "Score inbound leads based on ICP fit",
      "kind": "play"
    }
  ]
}
```

Key fields:

- **`slug`** — identifier used to fetch the template
- **`name`** — human-readable name
- **`kind`** — `"tool"` (on-demand) or `"play"` (segment-driven)

## Get a template by slug

```bash
cargo-ai orchestration template get <slug>
```

Example:

```bash
cargo-ai orchestration template get company-enrichment
```

Response:

```json
{
  "template": {
    "slug": "company-enrichment",
    "name": "Company Enrichment",
    "description": "Enrich a company record with firmographic data from Clearbit",
    "kind": "tool",
    "nodes": [
      {
        "uuid": "44444444-4444-4444-a444-444444444444",
        "slug": "start",
        "kind": "native",
        "actionSlug": "start",
        "config": {},
        "childrenUuids": ["55555555-5555-4555-a555-555555555555"],
        "fallbackOnFailure": false,
        "position": { "x": 0, "y": 0 }
      },
      {
        "uuid": "55555555-5555-4555-a555-555555555555",
        "slug": "enrich_company",
        "kind": "connector",
        "integrationSlug": "clearbit",
        "actionSlug": "enrichCompanyFromDomain",
        "connectorUuid": "__REPLACE_WITH_CONNECTOR_UUID__",
        "config": {
          "domain": {
            "kind": "templateExpression",
            "expression": "{{nodes.start.domain}}",
            "instructTo": "none",
            "fromRecipe": false
          }
        },
        "childrenUuids": ["66666666-6666-4666-a666-666666666666"],
        "fallbackOnFailure": false,
        "position": { "x": 0, "y": 166 }
      },
      {
        "uuid": "66666666-6666-4666-a666-666666666666",
        "slug": "end",
        "kind": "native",
        "actionSlug": "end",
        "config": {
          "variables": [
            {
              "name": "company_name",
              "type": "string",
              "value": {
                "kind": "templateExpression",
                "expression": "{{nodes.enrich_company.name}}",
                "instructTo": "none",
                "fromRecipe": false
              }
            }
          ]
        },
        "childrenUuids": [],
        "fallbackOnFailure": false,
        "position": { "x": 0, "y": 332 }
      }
    ]
  }
}
```

## Use a template as inspiration when building a tool or play

When creating or redesigning a tool or play, start with a template rather than building nodes from scratch. Copy the template's node graph into the draft release, replace any placeholders, then deploy.

```bash
# 1. Find a template that matches your use case
cargo-ai orchestration template list
# → Find "company-enrichment" (kind: "tool") or "lead-scoring" (kind: "play")

# 2. Inspect the node graph — understand the structure and spot placeholders
cargo-ai orchestration template get company-enrichment

# 3. Fill in placeholders (connectorUuid, agentUuid, etc.) and validate
cargo-ai orchestration node validate --nodes '[...modified nodes...]'
# → { "outcome": "valid" }

# 4. Find your tool's workflowUuid
cargo-ai orchestration tool list
# → Extract tool.workflowUuid

# 5. Save the adapted nodes to the draft release
cargo-ai orchestration draft-release update \
  --workflow-uuid <tool.workflowUuid> \
  --nodes '[...validated nodes...]'

# 6. Deploy the draft release
cargo-ai orchestration draft-release deploy \
  --workflow-uuid <tool.workflowUuid> \
  --nodes '[...validated nodes...]' \
  --form-fields 'null' \
  --description "Based on company-enrichment template"
```

> For plays, the same pattern applies — just use `play list` and replace `run create` with `batch create` in any test steps.

## Use a template to run a tool

The standard pattern:

1. List templates to find the right slug
2. Get the template to inspect its nodes
3. Replace any `__REPLACE_WITH_*__` placeholders in the node graph
4. Validate the nodes before running
5. Run against a tool workflow

```bash
# 1. Find the template
cargo-ai orchestration template list
# → Find "company-enrichment"

# 2. Get the node graph
cargo-ai orchestration template get company-enrichment
# → Copy the "nodes" array, replace connectorUuid placeholders

# 3. Find your connector UUID
cargo-ai connection connector list
# → Find your Clearbit connector, extract its uuid

# 4. Validate the modified nodes
cargo-ai orchestration node validate --nodes '[...modified nodes...]'
# → { "outcome": "valid" }

# 5. Find the tool
cargo-ai orchestration tool list
# → Find your tool, extract workflowUuid

# 6. Run
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"domain":"acme.com"}' \
  --nodes '[...validated nodes...]'
# → Poll with: cargo-ai orchestration run get <run-uuid>
```

## Use a template to run a play

For `kind: "play"` templates, use `batch create` instead of `run create`:

```bash
# 1. Get the template
cargo-ai orchestration template get lead-scoring

# 2. Replace placeholders, validate
cargo-ai orchestration node validate --nodes '[...]'

# 3. Find the play's workflowUuid and modelUuid
cargo-ai orchestration play list

# 4. Batch run over the play's model (empty filter = all rows)
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"<play.modelUuid>","filter":{"conjonction":"and","groups":[]}}' \
  --nodes '[...validated nodes...]'
# → Poll with: cargo-ai orchestration batch get <batch-uuid>
```

## Placeholder convention

Template node graphs use `__REPLACE_WITH_*__` strings to mark values that must be substituted before use:

| Placeholder                       | Replace with                                                    |
| --------------------------------- | --------------------------------------------------------------- |
| `__REPLACE_WITH_CONNECTOR_UUID__` | UUID from `cargo-ai connection connector list`                  |
| `__REPLACE_WITH_TOOL_UUID__`      | UUID from `cargo-ai orchestration tool list`                    |
| `__REPLACE_WITH_AGENT_UUID__`     | UUID from `cargo-ai ai agent list`                              |
| `__REPLACE_WITH_MODEL_UUID__`     | UUID from `cargo-ai storage model list`                         |

Always run `node validate` after substitution to confirm there are no structural errors.
references/examples/tools.md
# Tool examples

## What is a tool?

A **tool** is an on-demand workflow. Unlike plays (which react to segment changes), tools are triggered manually, via API, or on a cron schedule. Tools are the proactive side of Cargo — "run this workflow right now on this data."

Key properties of a tool:

- **`name`** — human-readable name (workflows themselves don't have names)
- **`workflowUuid`** — the underlying workflow that executes
- **`description`** — what the tool does
- **`creditsCost`** — estimated credit cost per run
- **`triggers`** — optional cron triggers for scheduled execution
- **`isReadOnly`** — whether the tool can be modified

## List all tools

```bash
cargo-ai orchestration tool list
```

Response:

```json
{
  "tools": [
    {
      "uuid": "tool-uuid",
      "name": "Company Enrichment",
      "workflowUuid": "workflow-uuid",
      "description": "Enriches a company record with firmographic data",
      "creditsCost": { "kind": "minMax" },
      "triggers": [],
      "isReadOnly": false
    }
  ]
}
```

## Find a tool's workflow UUID

Tools have names — workflows don't. Use the tool to find the right workflow.

```bash
# 1. List tools, find by name
cargo-ai orchestration tool list
# → Find "Company Enrichment", extract tool.workflowUuid

# 2. Use the workflowUuid for run/batch commands
cargo-ai orchestration run create \
  --workflow-uuid <workflow-uuid-from-tool> \
  --data '{"company":"Acme Corp","domain":"acme.com"}'
```

## Update a tool's workflow

To change what a tool does, update its draft release and deploy it. The draft release holds the unpublished node graph for the workflow.

> **Looking for inspiration?** Before designing a node graph from scratch, check `cargo-ai orchestration template list` for pre-built patterns (enrichment pipelines, CRM syncs, AI research flows). Use `cargo-ai orchestration template get <slug>` to copy a ready-made node graph and adapt it instead of starting from zero. Templates tagged `"kind":"tool"` are designed for on-demand workflows.

```bash
# Step 1 — Find the tool and its workflowUuid
cargo-ai orchestration tool list
# → Find "Company Enrichment", extract tool.workflowUuid

# Step 2 — Get the current draft release (contains the current node graph)
cargo-ai orchestration draft-release get --workflow-uuid <tool.workflowUuid>
# → Copy the "nodes" array and make your changes

# Step 3 — Update the draft release with your new nodes
cargo-ai orchestration draft-release update \
  --workflow-uuid <tool.workflowUuid> \
  --nodes '[...your updated node graph...]'

# Step 4 — Validate the updated nodes before deploying
cargo-ai orchestration node validate --nodes '[...your updated node graph...]'
# → { "outcome": "valid" }

# Step 5 — Deploy the draft release
cargo-ai orchestration draft-release deploy \
  --workflow-uuid <tool.workflowUuid> \
  --nodes '[...your updated node graph...]' \
  --form-fields 'null' \
  --description "Your release description"
```

> **Do not skip validation.** Deploying an invalid node graph will cause runs to fail. Always run `node validate` before `draft-release deploy`.

---

## Run a tool on a single record

The most common use case — run an existing tool workflow on one record. Tools support both `run create` (single record) and `batch create` (multiple records). Allowed batch data kinds for tools: `file`, `records`.

```bash
# 1. Find the tool
cargo-ai orchestration tool list
# → Extract tool.workflowUuid

# 2. Run with inline record data
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"company":"Acme Corp","domain":"acme.com","employee_count":500}'

# Or block until finished — returns the final run result without a separate poll step
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"company":"Acme Corp","domain":"acme.com","employee_count":500}' \
  --wait-until-finished
```

Run create response:

```json
{
  "run": {
    "uuid": "run-uuid",
    "workflowUuid": "...",
    "status": "pending",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

```bash
# 3. Poll run status every 2s
cargo-ai orchestration run get <run-uuid>
```

Poll until `status` is `success`, `error`, or `cancelled`:

```json
{
  "run": {
    "uuid": "run-uuid",
    "status": "success",
    "createdAt": "...",
    "finishedAt": "2025-01-15T10:00:05Z"
  }
}
```

## Upload a CSV file

Before running a tool on records from a file, you must upload the CSV first. The upload returns the `s3Filename` needed by batch commands.

```bash
cargo-ai workspaceManagement file upload --file ./my-companies.csv
```

Response:

```json
{
  "s3Filename": "abc123-my-companies.csv",
  "contentType": "text/csv",
  "name": "my-companies.csv"
}
```

You can also inspect which columns the file contains:

```bash
cargo-ai workspaceManagement file list-columns --s3-filename abc123-my-companies.csv
```

## Run a tool on records from a file

```bash
# 1. Find the tool
cargo-ai orchestration tool list
# → Extract tool.workflowUuid

# 2. Upload the CSV
cargo-ai workspaceManagement file upload --file ./my-companies.csv
# → Extract s3Filename from the response


# 3. Create the batch
cargo-ai orchestration batch create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"kind":"file","s3Filename":"<s3Filename>"}'
# → Extract batch.uuid

# 4. Poll until finished (repeat every 5s)
cargo-ai orchestration batch get <batch-uuid>
# → Done when .status is "success", "error", or "cancelled"
# → Extract batch.releaseUuid

# Or skip polling — block until finished and get the final batch result in one step
cargo-ai orchestration batch create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"kind":"file","s3Filename":"<s3Filename>"}' \
  --wait-until-finished
# → Returns the final batch result directly

# 5. Download results
cargo-ai orchestration batch download \
  --uuid <batch-uuid> \
  --output-node-slug end
```

## Monitor a tool's runs

```bash
# List recent runs
cargo-ai orchestration run list \
  --workflow-uuid <tool.workflowUuid> \
  --limit 20

# Running and pending runs
cargo-ai orchestration run list \
  --workflow-uuid <tool.workflowUuid> \
  --statuses running,pending

# Error runs
cargo-ai orchestration run list \
  --workflow-uuid <tool.workflowUuid> \
  --statuses error \
  --limit 10

# Count errors
cargo-ai orchestration run count \
  --workflow-uuid <tool.workflowUuid> \
  --statuses error
```

## Monitor running batches

```bash
# List all running batches for the tool
cargo-ai orchestration batch list \
  --workflow-uuids <tool.workflowUuid> \
  --statuses running

# Check a specific batch
cargo-ai orchestration batch get <batch-uuid>
```

## Cancel runs and batches

```bash
# Cancel specific runs
cargo-ai orchestration run cancel \
  --workflow-uuid <tool.workflowUuid> \
  --uuids <run-uuid-1>,<run-uuid-2>

# Cancel a batch (stops all remaining runs)
cargo-ai orchestration batch cancel <batch-uuid>
```

## Run with custom nodes (ad-hoc workflow)

The `--nodes` flag lets you run a custom node graph at runtime without modifying the tool's published definition. When using `--nodes`, you do not need to pass `--workflow-uuid` — the nodes define the entire workflow inline. Every graph needs a `start` node and an `end` node, linked via `childrenUuids`.

> See `../nodes.md` for the full node creation guide — node kinds, native actions, config expressions, routing, and more examples.

Minimal example — start, enrich via connector, output:

```bash
cargo-ai orchestration run create \
  --data '{"domain":"acme.com"}' \
  --nodes '[
    {
      "uuid":"11111111-1111-4111-a111-111111111111","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["22222222-2222-4222-a222-222222222222"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"22222222-2222-4222-a222-222222222222","slug":"enrich_company","kind":"connector",
      "integrationSlug":"clearbit","actionSlug":"enrichCompanyFromDomain",
      "connectorUuid":"<connector-uuid>",
      "config":{
        "domain":{"kind":"templateExpression","expression":"{{nodes.start.domain}}","instructTo":"none","fromRecipe":false}
      },
      "childrenUuids":["33333333-3333-4333-a333-333333333333"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"33333333-3333-4333-a333-333333333333","slug":"end","kind":"native","actionSlug":"end",
      "config":{
        "variables":[
          {"name":"company_name","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.enrich_company.name}}","instructTo":"none","fromRecipe":false}}
        ]
      },
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":332}
    }
  ]'
```

Validate before running:

```bash
cargo-ai orchestration node validate --nodes '[...]'
# → { "outcome": "valid" } or { "outcome": "notValid", "invalidNodes": [...] }
```

Custom node runs are polled the same way as regular runs:

```bash
cargo-ai orchestration run get <run-uuid>
```

### Common errors

| Error                         | Cause                                                | Fix                              |
| ----------------------------- | ---------------------------------------------------- | -------------------------------- |
| `startNodeNotFound`           | No node with `slug:"start"` and `actionSlug:"start"` | Add the required start node      |
| `invalidReleaseOrCustomNodes` | Both `--release-uuid` and `--nodes` provided         | Use one or the other, not both   |
| `nodesNotFound`               | `childrenUuids` references a UUID not in the array   | Verify all UUID cross-references |

## Run a tool multiple times on different records

```bash
# Run on first record
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"company":"Acme Corp","domain":"acme.com"}'

# Run on second record
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"company":"Globex Inc","domain":"globex.com"}'

# Poll each run separately
cargo-ai orchestration run get <run-uuid-1>
cargo-ai orchestration run get <run-uuid-2>
```

## End-to-end: use a template to run a tool

This example takes a "company-enrichment" tool template, fills in its connector placeholder, validates, and runs it on a single record.

```bash
# Step 1 — List available tool templates
cargo-ai orchestration template list
# → Find slug: "company-enrichment", kind: "tool"

# Step 2 — Get the template's node graph
cargo-ai orchestration template get company-enrichment
# → Copy the "nodes" array. It contains __REPLACE_WITH_CONNECTOR_UUID__ placeholders.

# Step 3 — Find your connector UUID
cargo-ai connection connector list
# → Find your Clearbit connector, extract uuid (e.g. "abc-123")

# Step 4 — Fill in placeholders and validate
cargo-ai orchestration node validate --nodes '[
  {
    "uuid": "44444444-4444-4444-a444-444444444444", "slug": "start", "kind": "native", "actionSlug": "start",
    "config": {}, "childrenUuids": ["55555555-5555-4555-a555-555555555555"], "fallbackOnFailure": false,
    "position": {"x": 0, "y": 0}
  },
  {
    "uuid": "55555555-5555-4555-a555-555555555555", "slug": "enrich_company", "kind": "connector",
    "integrationSlug": "clearbit", "actionSlug": "enrichCompanyFromDomain",
    "connectorUuid": "abc-123",
    "config": {
      "domain": {
        "kind": "templateExpression",
        "expression": "{{nodes.start.domain}}",
        "instructTo": "none",
        "fromRecipe": false
      }
    },
    "childrenUuids": ["66666666-6666-4666-a666-666666666666"], "fallbackOnFailure": false,
    "position": {"x": 0, "y": 166}
  },
  {
    "uuid": "66666666-6666-4666-a666-666666666666", "slug": "end", "kind": "native", "actionSlug": "end",
    "config": {
      "variables": [
        {"name": "company_name", "type": "string", "value": {"kind": "templateExpression", "expression": "{{nodes.enrich_company.name}}", "instructTo": "none", "fromRecipe": false}},
        {"name": "industry", "type": "string", "value": {"kind": "templateExpression", "expression": "{{nodes.enrich_company.category.industry}}", "instructTo": "none", "fromRecipe": false}},
        {"name": "employees", "type": "string", "value": {"kind": "templateExpression", "expression": "{{nodes.enrich_company.metrics.employeesRange}}", "instructTo": "none", "fromRecipe": false}}
      ]
    },
    "childrenUuids": [], "fallbackOnFailure": false,
    "position": {"x": 0, "y": 332}
  }
]'
# → { "outcome": "valid" }

# Step 5 — (Optional) Preview expression resolution without side effects
cargo-ai orchestration node compute \
  --node '{"uuid":"55555555-5555-4555-a555-555555555555","slug":"enrich_company","kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompanyFromDomain","connectorUuid":"abc-123","config":{"domain":{"kind":"templateExpression","expression":"{{nodes.start.domain}}","instructTo":"none","fromRecipe":false}},"childrenUuids":["66666666-6666-4666-a666-666666666666"],"fallbackOnFailure":false,"position":{"x":0,"y":166}}' \
  --context '{"nodes":{"start":{"domain":"acme.com"}}}'
# → Shows resolved config: { "domain": "acme.com" }

# Step 6 — Find the tool's workflowUuid
cargo-ai orchestration tool list
# → Find "Company Enrichment", extract workflowUuid

# Step 7 — Run
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"domain":"acme.com"}' \
  --nodes '[...validated nodes from step 4...]'
# → Extract run.uuid

# Step 8 — Poll until done (every 2s)
cargo-ai orchestration run get <run-uuid>
# → Done when status is "success", "error", or "cancelled"
```
references/filter-syntax.md
# Filter syntax

Complete reference for building segment filter conditions in the Cargo CLI.

> **CRITICAL — common silent failure:**
> Every filter object uses the key `conjonction` — **not** `conjunction`.
> This is intentional (French spelling). A typo here does **not** throw an error — it simply returns no records.
> Double-check this spelling every time you write a filter. Search for `conjunction` in your JSON before running.

## Structure

A filter has two levels of nesting: top-level groups joined by a conjunction, and each group contains conditions joined by their own conjunction.

```json
{
  "conjonction": "and",
  "groups": [
    {
      "conjonction": "and",
      "conditions": [
        { "kind": "string", "columnSlug": "domain", "operator": "contains", "values": "acme" }
      ]
    }
  ]
}
```

- Top-level `conjonction`: `"and"` or `"or"` — joins the groups
- Group-level `conjonction`: `"and"` or `"or"` — joins the conditions within a group
- Empty filter (all records): `{"conjonction":"and","groups":[]}`

## Condition kinds and operators

### string

```json
{ "kind": "string", "columnSlug": "name", "operator": "is", "values": ["Acme Corp"] }
{ "kind": "string", "columnSlug": "name", "operator": "isNot", "values": ["Test"] }
{ "kind": "string", "columnSlug": "name", "operator": "contains", "values": "acme" }
{ "kind": "string", "columnSlug": "name", "operator": "doesNotContain", "values": "test" }
{ "kind": "string", "columnSlug": "name", "operator": "startsWith", "values": "A" }
{ "kind": "string", "columnSlug": "name", "operator": "endsWith", "values": "Corp" }
{ "kind": "string", "columnSlug": "name", "operator": "isNull" }
{ "kind": "string", "columnSlug": "name", "operator": "isNotNull" }
{ "kind": "string", "columnSlug": "name", "operator": "isEmpty" }
{ "kind": "string", "columnSlug": "name", "operator": "isNotEmpty" }
```

Operators with values: `is`, `isNot`, `contains`, `doesNotContain`, `startsWith`, `endsWith`.
`values` can be a string or an array of strings.

Operators without values: `isNull`, `isNotNull`, `isEmpty`, `isNotEmpty`.

### number

```json
{ "kind": "number", "columnSlug": "employee_count", "operator": "is", "value": 500 }
{ "kind": "number", "columnSlug": "employee_count", "operator": "isNot", "value": 0 }
{ "kind": "number", "columnSlug": "employee_count", "operator": "greaterThan", "value": 100 }
{ "kind": "number", "columnSlug": "employee_count", "operator": "lowerThan", "value": 1000 }
{ "kind": "number", "columnSlug": "employee_count", "operator": "between", "firstValue": 100, "lastValue": 500 }
{ "kind": "number", "columnSlug": "employee_count", "operator": "isNull" }
{ "kind": "number", "columnSlug": "employee_count", "operator": "isNotNull" }
```

Note: single-value operators use `value` (not `values`). `between` uses `firstValue` and `lastValue`.

### date

```json
{ "kind": "date", "columnSlug": "created_at", "operator": "is", "value": "2025-01-15" }
{ "kind": "date", "columnSlug": "created_at", "operator": "isNot", "value": "2025-01-15" }
{ "kind": "date", "columnSlug": "created_at", "operator": "greaterThan", "value": "2025-01-01" }
{ "kind": "date", "columnSlug": "created_at", "operator": "lowerThan", "value": "2025-06-01" }
{ "kind": "date", "columnSlug": "created_at", "operator": "between", "firstValue": "2025-01-01", "lastValue": "2025-06-30" }
{ "kind": "date", "columnSlug": "created_at", "operator": "isNull" }
{ "kind": "date", "columnSlug": "created_at", "operator": "isNotNull" }
```

Same structure as `number` but `value`/`firstValue`/`lastValue` are ISO date strings.

### boolean

```json
{ "kind": "boolean", "columnSlug": "is_customer", "operator": "isTrue" }
{ "kind": "boolean", "columnSlug": "is_customer", "operator": "isFalse" }
{ "kind": "boolean", "columnSlug": "is_customer", "operator": "isNull" }
{ "kind": "boolean", "columnSlug": "is_customer", "operator": "isNotNull" }
```

### object / array

```json
{ "kind": "object", "columnSlug": "metadata", "operator": "isNull" }
{ "kind": "object", "columnSlug": "metadata", "operator": "isNotNull" }
{ "kind": "object", "columnSlug": "metadata", "operator": "matchConditions" }
```

For `matchConditions`, nest `objectProperty` conditions inside.

### objectProperty

Used to filter on nested properties within object or array columns:

```json
{
  "kind": "objectProperty",
  "columnSlug": "metadata",
  "propertyName": "industry",
  "operator": "is",
  "value": "SaaS"
}
```

Supports: `is`, `isNot`, `contains`, `doesNotContain`, `startsWith`, `endsWith`, `greaterThan`, `lowerThan`, `between`, `isNull`, `isNotNull`, `isEmpty`, `isNotEmpty`.

For `between`: use `value` and `otherValue`.

### segment

Filter records that belong (or don't belong) to another segment:

```json
{ "kind": "segment", "operator": "in", "segmentUuid": "other-segment-uuid" }
{ "kind": "segment", "operator": "notIn", "segmentUuid": "other-segment-uuid" }
```

### enrollment

Filter records based on whether they have been enrolled (or not) in a workflow. Useful to find records that have never been processed by a play/tool.

**Records NOT enrolled in a workflow (never entered):**

```json
{
  "kind": "enrollment",
  "workflowUuid": "<workflow-uuid>",
  "activityKind": "workflowEntered",
  "frequency": { "operator": "not" },
  "period": { "operator": "moreThan", "value": 0, "unit": "day" }
}
```

**Records enrolled more than 3 times:**

```json
{
  "kind": "enrollment",
  "workflowUuid": "<workflow-uuid>",
  "activityKind": "workflowEntered",
  "frequency": { "operator": "moreThan", "value": 3 },
  "period": { "operator": "moreThan", "value": 0, "unit": "day" }
}
```

**Records that left a workflow in the last 30 days:**

```json
{
  "kind": "enrollment",
  "workflowUuid": "<workflow-uuid>",
  "activityKind": "workflowLeft",
  "frequency": { "operator": "moreThan", "value": 0 },
  "period": { "operator": "lessThan", "value": 30, "unit": "day" }
}
```

**Records where a specific node was executed:**

```json
{
  "kind": "enrollment",
  "workflowUuid": "<workflow-uuid>",
  "activityKind": "workflowNodeExecuted",
  "nodeSlug": "enrich_company",
  "frequency": { "operator": "moreThan", "value": 0 },
  "period": { "operator": "moreThan", "value": 0, "unit": "day" }
}
```

`activityKind` values: `workflowEntered`, `workflowNodeExecuted`, `workflowLeft`.

`frequency.operator` values: `not` (never), `moreThan`, `lessThan`, `exactly`.

`period.operator` values: `moreThan`, `lessThan`, `exactly`. `unit` is always `"day"`.

`nodeSlug` is optional — only used with `workflowNodeExecuted`.

### occurrence

Filter records based on related model activity (e.g. a contact's company has certain events).

```json
{
  "kind": "occurrence",
  "relatedModelUuid": "<related-model-uuid>",
  "frequency": { "operator": "moreThan", "value": 0 },
  "period": { "operator": "lessThan", "value": 30, "unit": "day" },
  "conjonction": "and",
  "conditions": [
    { "kind": "string", "columnSlug": "event_type", "operator": "is", "values": ["meeting_booked"] }
  ]
}
```

Same `frequency` and `period` syntax as enrollment. The `conditions` array can contain any string/number/date/boolean conditions to filter the related model's records.

### sql

Raw SQL clause (advanced):

```json
{ "kind": "sql", "name": "custom_filter", "clause": "revenue > 1000000 AND country = 'US'" }
```

## Complete example

Filter for companies with 100+ employees whose name contains "tech", created after 2025-01-01:

```json
{
  "conjonction": "and",
  "groups": [
    {
      "conjonction": "and",
      "conditions": [
        { "kind": "number", "columnSlug": "employee_count", "operator": "greaterThan", "value": 100 },
        { "kind": "string", "columnSlug": "name", "operator": "contains", "values": "tech" },
        { "kind": "date", "columnSlug": "created_at", "operator": "greaterThan", "value": "2025-01-01" }
      ]
    }
  ]
}
```

## OR logic

Filter for companies in the US OR with 500+ employees:

```json
{
  "conjonction": "or",
  "groups": [
    {
      "conjonction": "and",
      "conditions": [
        { "kind": "string", "columnSlug": "country", "operator": "is", "values": ["US"] }
      ]
    },
    {
      "conjonction": "and",
      "conditions": [
        { "kind": "number", "columnSlug": "employee_count", "operator": "greaterThan", "value": 500 }
      ]
    }
  ]
}
```

## Sort syntax

Sort is an **array** of sort objects. Each object has `columnSlug` and `kind`.

```json
[{"columnSlug": "created_at", "kind": "desc"}]
```

- `columnSlug` — the column slug to sort by (from `model list` → `columns[].slug`)
- `kind` — `"asc"` (ascending) or `"desc"` (descending)

Multiple sort columns (first by country ascending, then by employee count descending):

```json
[{"columnSlug": "country", "kind": "asc"}, {"columnSlug": "employee_count", "kind": "desc"}]
```

Usage with `--sort`:

```bash
cargo-ai segmentation segment fetch \
  --model-uuid <uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --sort '[{"columnSlug":"created_at","kind":"desc"}]' \
  --fetching-limit 100
```

## Tips

- Get available column slugs from `cargo-ai storage model list` → `columns[].slug`
- Use the column `type` to pick the right condition `kind` (string → string, number → number, etc.)
- An empty filter `{"conjonction":"and","groups":[]}` returns all records
- `relatedModelUuid` is optional on conditions — only needed for cross-model filters
references/node-diagram.md
# Diagramming a node graph

A workflow the user can't see is a workflow they can't approve. A node graph is a
directed graph with routing, fallbacks, and paid steps in it — prose flattens all
three. Draw it instead: it costs nothing, and the command renders two formats from
the same graph — ASCII for a terminal, Mermaid for anything that renders Mermaid
(GitHub, the Cargo docs, a published page). See [The ASCII format](#the-ascii-format-cli--1056).

`cargo-ai orchestration node diagram` does it (**CLI ≥ 1.0.54**; `unknown command`
means the pin hasn't moved yet — bump per [`../../cargo/SKILL.md`](../../cargo/SKILL.md)
§ "At session start"). Free, runs nothing, no credits — same family as
`node validate`.

## When to draw one

- **At the plan gate**, before `draft-release deploy` / `cdk deploy` — the diagram
  *is* the "nodes and data flow" half of the plan ([`../../cargo/references/interaction.md`](../../cargo/references/interaction.md) §1).
- **When explaining an existing workflow, tool, or play** — "what does this play
  do?" is one command against its `workflowUuid`.
- **When reporting a trace** — the graph with the failing node marked red, next to
  the error ([`../../cargo-diagnostics/references/run-trace.md`](../../cargo-diagnostics/references/run-trace.md)).

Skip it for a linear graph of three nodes or fewer, or a one-node change — say what
changed in a sentence instead. A diagram of `start → enrich → end` is ceremony.

## Generate it

```bash
# An existing workflow, tool, or play (workflowUuid from `tool list` / `play list`)
# --format ascii when SHOWING it to someone; drop it when pasting into a PR or doc
cargo-ai orchestration node diagram --workflow-uuid <uuid> --format ascii --raw

# The draft you are about to deploy — the plan-gate case
cargo-ai orchestration node diagram --workflow-uuid <uuid> --draft --raw

# A graph you are authoring, before it exists server-side
cargo-ai orchestration node diagram --nodes '[...]' --raw

# The graph a run executed, with the failing node marked
cargo-ai orchestration node diagram --run-uuid <uuid> --highlight <node-slug> --raw
```

Pass exactly one source: `--nodes` (or `-` to read stdin), `--file <path>`,
`--workflow-uuid` (deployed, `--draft` for the draft), `--release-uuid`, or
`--run-uuid`.

| Flag | Effect |
| --- | --- |
| `--format ascii\|mermaid` | `ascii` to show it in a terminal, `mermaid` to paste it somewhere that renders it (default). CLI ≥ 1.0.56. |
| `--title <text>` | Title rendered above the diagram. |
| `--direction TD\|LR` | Mermaid flow direction (default `TD`; `LR` reads better for long linear graphs). Ignored by `--format ascii`. |
| `--paid <slugs>` | Comma-separated node slugs/uuids that bill credits — marked 💳. |
| `--highlight <slugs>` | Comma-separated slugs/uuids to mark — red in Mermaid, `◀━` in ASCII. The failing node in a trace. |
| `--raw` | Print the diagram itself instead of JSON: plain text for `ascii`, a fenced block for `mermaid`. |

Without `--raw` it returns `{"diagram": "...", "format": "ascii"|"mermaid", "warnings": [...]}`
like every other command. **Read the `warnings`** — they carry the structural
problems a tidy drawing would otherwise hide (nodes unreachable from `start`,
dangling `childrenUuids`) and belong in what you tell the user.

`--run-uuid` handles both run shapes: a run from `action execute` carries its own
`nodes`, a run of a deployed tool or play carries only a `releaseUuid`, and the
command follows whichever it has.

## What maps to what

You rarely need this table — the command emits it — but it is what to check when
reading a diagram someone else produced, or hand-writing one for a graph that
isn't in Cargo yet.

| Node | Mermaid | Rendered as |
| --- | --- | --- |
| `start` / `end` | `n0(["start"])` | stadium |
| `branch`, `filter`, `switch`, `split` | `n1{"Enterprise?"}` | diamond |
| `connector` | `n2["Enrich Company<br/>companyEnrich.enrichByDomain"]` | rectangle |
| `tool` | `n3[["tool e487d28e"]]` | subroutine box |
| `agent` (node kind or native action) | `n4{{"Apply the taxonomy"}}` | hexagon |
| `python`, `script` | `n5[/"Score and band"/]` | parallelogram |
| `variables`, `delay`, other native | `n6("Coalesce CRM over enrichment")` | rounded |
| `group` | rectangle + `subgraph` holding its `_nodes` | box-in-box |

Edges come from `childrenUuids`, in order, labelled by what the routing node means:

| Node | Edge labels |
| --- | --- |
| `branch` | `yes` (index 0, condition matched), `no` (index 1) |
| `filter` | `if true` — a false filter ends the run, so there is no second edge |
| `switch` | the `routes[i].name` matching each child index |
| `split` | `A <pct>%` / `B <100-pct>%` |
| `fallbackChildUuid` → a *different* node | a **dashed** `-. on failure .->` edge — the waterfall pattern |
| `fallbackChildUuid` → the node's own next step | a `↷` on the label, not a second arrow: a failure here doesn't stop the run |

## Rules that make the diagram true

Why to run the command rather than transcribe a graph by hand. Each of these was
hit against a live workspace, not imagined:

- **Nodes are keyed by `uuid`, never by `slug`.** Slugs repeat within a single
  release — a shipped waterfall has **six** nodes slugged `variables`, and a play
  has an `agent` node and a `variables` node both slugged `classify`. A slug-keyed
  diagram silently collapses them into one node and reroutes every edge that
  touched them. (Same trap downstream: `{{nodes.<slug>...}}` and
  `runContext.<slug>` are ambiguous for a repeated slug, so give any node you
  reference later a distinct slug.)
- **`childrenUuids` order carries meaning.** Index 0 of a `branch` is the matched
  path. Swapping the labels inverts what the workflow appears to do.
- **Fallback edges are the mechanism, not decoration.** In waterfall graphs each
  provider falls through to the next on failure; a diagram without those edges
  shows a chain of unrelated enrichments.
- **A `null` in `childrenUuids`, or a node unreachable from `start`, is a finding.**
  It arrives in `warnings`. Say it out loud rather than drawing a tidy graph over a
  broken one — an orphaned node never runs.
- **`tool` and `agent` nodes are drawn from `toolUuid` / `agentUuid`** (top-level
  node fields; these nodes have no `actionSlug`), so the box reads `tool e487d28e`.
  Resolve the real name with `orchestration tool get` / `ai agent get` when it
  matters to the reader.
- **Mark the paid nodes.** Which action bills is not in the release — check the
  provider playbook (`../../cargo-gtm/provider-playbooks/<slug>.md`) or
  `connection integration list`, then pass those slugs to `--paid`. This is the
  plan gate's "cost shape" made visible; the per-record estimate still goes in the
  text ([`../../cargo-gtm/references/cost-discipline.md`](../../cargo-gtm/references/cost-discipline.md)).

## Worked example

```bash
cargo-ai orchestration node diagram --workflow-uuid b338e04b-… --draft \
  --title "Classify and score accounts" --paid enrich --raw
```

```mermaid
---
title: Classify and score accounts
---
flowchart TD
    n0(["start"])
    n1{"Missing revenue or headcount?<br/>branch"}
    n2["💳 Fill the gap (0.25 credits)<br/>companyEnrich.enrichByDomain"]
    n3("Coalesce CRM over enrichment<br/>variables")
    n4{{"Apply the taxonomy<br/>agent"}}
    n5("classify<br/>variables")
    n6[/"Score and band (deterministic)<br/>script"/]
    n7(["end"])
    n0 --> n1
    n1 -->|yes| n2
    n1 -->|no| n3
    n2 --> n3
    n3 --> n4
    n4 --> n5
    n5 --> n6
    n6 --> n7
```

Read out loud: enrichment only fires for records missing revenue or headcount (so
the credit line scales with the gap, not the segment), the model classifies, and
the score is deterministic afterwards. That sentence is what the user approves —
the diagram is what makes it checkable.

## The ASCII format (CLI ≥ 1.0.56)

`--format ascii` renders the same graph as a drawing that needs no Mermaid
renderer. **Pick the format by where the output goes**, not by preference:

| | `--format ascii` | `--format mermaid` (default) |
| --- | --- | --- |
| Showing it in a terminal or a chat reply | **yes** | no — the reader sees `n4{"branch"}` |
| Pasting into a PR body, a doc, a rendered page | no | **yes** |
| Node shapes, `classDef` colouring, group subgraphs | no | yes |
| Branch labels, fallback edges, `💳`, warnings | yes | yes |

Mermaid stays the default for compatibility. That default is wrong for most agent
replies, because most agent output is read in a terminal. On an older CLI that
rejects `--format`, fall back to the fenced Mermaid block plus a one-line path
summary — `start → branch(missing firmographics) → enrich 💳 → merge → agent →
score → end`.

```
                  start
                    │
                Aviato 💳
           Lookup LinkedIn URL
                    │
              LinkedIn URL?
                    │
                    ├──────────────┐
                   yes             no
                    │              │
                    │            Agent
                    │      Find LinkedIn URL
                    │              │
                    ├──────────────┘
                    │
              Lead Magic 💳
         Enrich LinkedIn profile
                    │
               Apollo.io 💳
          Find company headcount
                    │
                JavaScript
            ICP fit assessment
                    │
                 Tier 1?
                    │
         ┌──────────┴─────────┐
        yes                   no
         │                    │
       Slack             Salesforce
Send to #best-leads     Update record
```

| Mark | Meaning |
| --- | --- |
| centred `│` spine | the main line of the flow |
| `├───┐` … `├───┘` | a **detour**: a branch whose paths reconverge. The spine continues; the rail leaves and rejoins |
| `┌───┴───┐` | a **fork**: a branch whose paths never reconverge. Nothing continues past it |
| `┆` with `on failure` | a `fallbackChildUuid` edge: where the run goes if that step *errors*, as distinct from returning nothing |
| `💳` | the step bills credits (`--paid`) |
| `◀━` | the step was named in `--highlight` |
| `↑ <name>` | a step already drawn above, not repeated |

Each step is two lines: the system it runs on over what it does, both resolved
from the platform's catalogs, so a step reads `Apollo.io` / `Enrich person`
rather than `apolloio` / `enrichPerson`. A step keeps its own name where the
author set one. Branch steps get one line, because the labelled rails leaving them
already say that they route.

A `group` step draws its own graph in a captioned box, one level down, with
`--paid` / `--highlight` carried inside; a broken graph in a loop body is
reported against the loop.

**Width.** Rails stack rightward and each must clear the one below it, so a graph
branching at nearly every step of a long chain gets wide. A 29-node provider
waterfall draws at 57 columns; past 120 the command adds a warning pointing at
`--format mermaid`. It never truncates — a diagram that silently dropped an edge
would be worse than a wide one. Report that warning rather than pasting a drawing
that will wrap in the user's terminal.
references/node-selection.md
# Prefer built-in actions + expressions over code/HTTP nodes

When building a workflow, **use the actions Cargo already provides plus template
expressions. Avoid `python`, `script` (JavaScript), and raw HTTP nodes unless you
genuinely have no other option.**

Code and raw-HTTP nodes feel flexible, but they are the hardest part of a workflow
to build and debug from the CLI: they fail in ways the native nodes don't, and you
can't see inside them as easily. Most of what they get used for is already a
one-line native node or a template expression.

## Use this instead

| Instead of writing… | Use |
| --- | --- |
| `python` / `script` to reshape, rename, or extract fields | a `variables` node — each value is a template expression, e.g. `{{nodes.start.email.split('@')[1]}}` |
| `python` / `script` to call an LLM and parse its JSON | the native `agent` node with `output.type:"jsonSchema"` — it returns structured JSON, no parsing (read it as `{{nodes.<slug>.answer.<field>}}`) |
| a raw **HTTP** request | the integration's **dedicated connector action** (e.g. `clearbit.enrichCompanyFromDomain`) — discover them with `connection integration get-documentation <slug>` |
| `python` / `script` to decide a path | `filter` / `branch` / `switch` with a boolean expression |
| `python` / `script` to loop over a list | a `group` node |
| `time.sleep()` to wait | a `delay` node |

## Template expressions cover most "transforms"

Inside `{{ }}` you can do property/index access, string and number operations, and
boolean logic — so field extraction and conditions belong in a `variables` node or
a condition, not in code:

```
{{nodes.start.email.split('@')[1]}}
{{nodes.enrich.metrics.employeesRange}}
{{nodes.start.employee_count > 100}}
```

One caveat: a reference to a missing path resolves to empty **silently** (the run
still says `success`). When a value comes out blank, check the real shape with
`cargo-ai orchestration run get <run-uuid>` → `runContext.<slug>` (node outputs
*are* returned by the CLI) and fix the path.

## When a code or HTTP node is genuinely warranted

- Multi-step computation that no expression or native node expresses (messy
  parsing, dedup, aggregating a `group` node's array into one object).
- An API with no dedicated connector action.

If you do need code, prefer the JS `script` node for transforms (it ships `lodash`
for array/object work). Either way, both code nodes are sandboxed and have no
normal logging — return your output and inspect it via `runContext`.
references/nodes.md
# Creating nodes

## What is a custom node graph?

A **node graph** is a directed acyclic graph of steps that defines a workflow. Each graph must have exactly one `start` node (entry point) and one `end` node (exit point). Intermediate nodes perform actions — enrichments, transformations, branching, AI calls, etc. — and are linked together via `childrenUuids`.

Pass a custom node graph to override a tool's deployed release:

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"domain":"acme.com"}' \
  --nodes '[...]'
```

Also works with `batch create --nodes`. Cannot be combined with `--release-uuid`.

**Always validate first** — use `node validate` to catch structural errors before running:

```bash
cargo-ai orchestration node validate --nodes '[...]'
```

**Then show it before you deploy it.** `node validate` proves the graph is
well-formed, not that it does what the user wanted. Render it as a Mermaid
flowchart — [`node-diagram.md`](node-diagram.md) — and let them check the routing
and the paid steps against their intent. The two go together: validate, diagram,
ask, deploy.

## Node shape

Every node in the `--nodes` JSON array has these fields:

| Field               | Required | Description                                                                |
| ------------------- | -------- | -------------------------------------------------------------------------- |
| `uuid`              | yes      | Unique ID within the graph — must be a valid UUIDv4 (e.g. `"550e8400-e29b-41d4-a716-446655440000"`) |
| `slug`              | yes      | Human-readable identifier (`start` and `end` are reserved)                 |
| `kind`              | yes      | `native`, `connector`, `tool`, or `agent`                                  |
| `config`            | yes      | Action-specific configuration (`{}` for start)                             |
| `childrenUuids`     | yes      | UUIDs of downstream nodes — array length **must match** the `childrenCount` for the node's action (see native actions tables below) |
| `fallbackOnFailure` | yes      | Continue to the next node even if this one fails                           |
| `position`          | yes      | `{"x": 0, "y": 0}` — layout only, no runtime effect                        |
| `fallbackChildUuid` | no       | UUID of a fallback node to run on failure                                  |
| `retry`             | no       | `{"maximumAttempts": 3, "initialInterval": 1000, "backoffCoefficient": 2}` |
| `name`              | no       | Display name                                                               |
| `description`       | no       | Description                                                                |

## Node kinds

Each kind requires additional fields beyond the common shape.

### `native`

Built-in workflow actions (start, end, branch, filter, variables, etc.).

| Field        | Required | Description                |
| ------------ | -------- | -------------------------- |
| `actionSlug` | yes      | Which native action to run |

### `connector`

Third-party integration actions (Clearbit, HubSpot, HTTP, etc.). Discover identifiers first:

```bash
cargo-ai connection integration list                    # → integrationSlug
cargo-ai connection integration get-documentation <slug> # → actionSlug + config fields
cargo-ai connection connector list                       # → connectorUuid
```

| Field             | Required | Description                              |
| ----------------- | -------- | ---------------------------------------- |
| `integrationSlug` | yes      | Integration identifier (e.g. `clearbit`) |
| `actionSlug`      | yes      | Action within the integration            |
| `connectorUuid`   | yes      | Your connected account UUID              |

`childrenCount` for connector nodes equals the integration action's `children` array length if defined, otherwise defaults to **1**. Most connector actions have exactly 1 child.

**Config values from autocomplete:** When you run `integration get <slug>`, some actions include a `uiSchema` alongside the `jsonSchema`. If a field has `"ui:widget": "IntegrationAutocompleteWidget"` in the `uiSchema`, you **must** fetch its allowed values using `connector autocomplete` rather than guessing or using freeform input. See `cargo-connection/SKILL.md` for the full autocomplete workflow.

### `tool`

Embeds another tool (sub-workflow) as a node. The tool's deployed release config fields become the node's `config`.

| Field          | Required | Description                                              |
| -------------- | -------- | -------------------------------------------------------- |
| `toolUuid`     | no       | Target tool UUID — get from `cargo-ai orchestration tool list` |
| `templateSlug` | no       | Template slug — use when instantiating from a template   |
| `releaseUuid`  | no       | Pin to a specific release of the tool                    |

Provide at least one of `toolUuid` or `templateSlug`. `childrenCount` is **1**.

```bash
# Find toolUuid
cargo-ai orchestration tool list
# → Extract tool.uuid
```

### `agent`

Embeds a saved AI agent as a node. The agent runs to completion and its output is available to downstream nodes.

| Field          | Required | Description                                          |
| -------------- | -------- | ---------------------------------------------------- |
| `agentUuid`    | no       | Target agent UUID — get from `cargo-ai ai agent list` |
| `templateSlug` | no       | Template slug — use when instantiating from a template |
| `releaseUuid`  | no       | Pin to a specific release of the agent               |

Provide at least one of `agentUuid` or `templateSlug`. `childrenCount` is **1**.

```bash
# Find agentUuid
cargo-ai ai agent list
# → Extract agent.uuid
```

The `config` for an `agent` node takes two fields:

| Field    | Description                                                            |
| -------- | ---------------------------------------------------------------------- |
| `prompt` | The user message sent to the agent — string or expression object       |
| `output` | How to parse the agent's response — `{ type: "text" }` or `{ type: "jsonSchema", jsonSchema: {...} }` |

`prompt` can be a plain string or an expression object:

```json
"prompt": "Summarize the company {{nodes.start.domain}}"
```

```json
"prompt": {
  "kind": "templateExpression",
  "expression": "Classify {{nodes.start.company}} into a category",
  "instructTo": "none",
  "fromRecipe": false
}
```

`output` is a discriminated union on `type`:

| `output.type`  | Additional field                  | Description                              |
| -------------- | --------------------------------- | ---------------------------------------- |
| `"text"`       | *(none)*                          | Returns the agent's raw text response    |
| `"jsonSchema"` | `jsonSchema` — a JSON Schema object | Forces structured JSON output matching the schema |

```json
"output": { "type": "text" }
```

```json
"output": {
  "type": "jsonSchema",
  "jsonSchema": {
    "type": "object",
    "properties": {
      "category": { "type": "string" },
      "confidence": { "type": "number" }
    },
    "required": ["category", "confidence"],
    "additionalProperties": false
  }
}
```

> **Reading agent output downstream — the `.answer` wrapper.** The parsed JSON is exposed under `.answer`, not directly on the node. Use `{{nodes.<slug>.answer.<field>}}` (e.g. `{{nodes.classify.answer.category}}`). Referencing `{{nodes.classify.category}}` resolves to undefined silently — branch conditions evaluate to false, end-node variables come out empty, and the run still reports `success`. This bites hard because nothing errors. Same path applies to `output.type: "text"` results — read them via `{{nodes.<slug>.answer}}`.

## Config values and expressions

Config fields that need dynamic data use **expression objects**:

```json
{
  "kind": "templateExpression",
  "expression": "{{nodes.start.domain}}",
  "instructTo": "none",
  "fromRecipe": false
}
```

| Field        | Values                                   | Description                                                                                 |
| ------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
| `kind`       | `"templateExpression"`, `"jsExpression"` | Template expressions use `{{...}}` syntax; JS expressions are raw JS                        |
| `expression` | string                                   | The expression to evaluate                                                                  |
| `instructTo` | `"none"`, `"ai"`                         | `"none"` = JS evaluation; `"ai"` = AI fills the value from the expression as an instruction |
| `fromRecipe` | boolean                                  | `false` for inline expressions                                                              |

### Data flow

Each node's output is stored under its slug. Reference it in downstream nodes with `{{nodes.<slug>.<field>}}`.

```
{{nodes.start.domain}}                        — input data field
{{nodes.enrich_company.name}}                 — output from the "enrich_company" node
{{nodes.enrich_company.metrics.employeesRange}} — nested field access
{{nodes.start.email.split('@')[1]}}           — JS expressions work inside {{ }}
```

Inside a `group` loop, use `{{parentNodes.<slug>.<field>}}` to access the parent run's context.

### Static values

For config fields that don't need expressions, pass the value directly:

```json
{ "minutes": 5 }
```

## Native actions reference

These are the `actionSlug` values available for `kind: "native"` nodes.

### Workflow entry/exit

| actionSlug | Purpose                   | childrenCount | Config                                       |
| ---------- | ------------------------- | -------- | -------------------------------------------- |
| `start`    | Entry point               | 1        | `{}`                                         |
| `end`      | Exit point, define output | 0        | `{"variables": [{"name", "type", "value"}]}` |

The `end` node's `variables` array defines the workflow output. Each variable has:

- `name` — output field name
- `type` — `"string"`, `"number"`, `"boolean"`, `"date"`, `"array"`, `"object"`, or `"any"`
- `value` — an expression object or static value

### Routing

| actionSlug | Purpose               | childrenCount | Config                                                                     |
| ---------- | --------------------- | -------- | -------------------------------------------------------------------------- |
| `filter`   | Continue only if true | 1        | `{"filter": <bool-expression>}`                                            |
| `branch`   | If/else split         | 2        | `{"condition": <bool-expression>}`                                         |
| `switch`   | Multi-way routing     | dynamic  | `{"routes": [{"name": "...", "uuid": "...", "value": <bool-expression>}]}` |
| `split`    | Random A/B split      | 2        | `{"percentage": <0-100>}`                                                  |

**`childrenUuids` ordering matters:**

- **`branch`**: index 0 = condition matched ("yes"), index 1 = not matched ("no")
- **`filter`**: index 0 = condition true (execution stops if false — no child called)
- **`switch`**: first route whose `value` evaluates to `true` wins; its index in the `routes` array determines which `childrenUuids` entry to follow
- **`split`**: index 0 = random number < percentage ("A"), index 1 = otherwise ("B")

### Data

| actionSlug  | Purpose               | childrenCount | Config                                                                   |
| ----------- | --------------------- | -------- | ------------------------------------------------------------------------ |
| `variables` | Create/transform data | 1        | `{"variables": [{"name": "...", "type": "...", "value": <expression>}]}` |

Same shape as `end` variables, but the output is available to downstream nodes via `{{nodes.<slug>.<name>}}`.

### Flow control

| actionSlug | Purpose               | childrenCount | Config                                                      |
| ---------- | --------------------- | -------- | ----------------------------------------------------------- |
| `delay`    | Wait before next node | 1        | `{"minutes": <number>}`                                     |
| `group`    | Loop over array items | 1        | `{"items": <array-expression>, "failOnItemFailure": false, "_nodes": [...]}` |

The `group` node iterates over `items`, running the child subgraph once per item. Each iteration can access the current item via `{{nodes.start.value}}` (for simple values) or `{{nodes.start.<field>}}` (for object items). Use `{{parentNodes.<slug>.<field>}}` to reference the parent run's data.

> **Reading group results downstream:** the group node's output is an **array**, one entry per iteration, where each entry is that iteration's final (`end`) node output. Access it by index: `{{nodes.<groupSlug>[0].<field>}}`. There is **no `.results` wrapper** — `{{nodes.<groupSlug>.results[0]...}}` does not work — and arrow-function array methods like `{{nodes.<groupSlug>.map(x => x.field)}}` are not supported in template expressions. To collapse the array into one value, use a `script` node with `lodash` (or a `python` node). See [`node-selection.md`](node-selection.md) → "Group node results".

> **`delay` and context:** prior node outputs are **not** lost across a `delay` — the full run context is checkpointed and restored, so `{{nodes.<slug>...}}` still resolves after the delay regardless of node kind. The checkpoint is JSON, though, so values you read after a delay must be JSON-serializable. Materialize anything you need post-delay into a `variables` node (plain strings/numbers/objects) before the delay rather than relying on a `python` node's `result`. See [`node-selection.md`](node-selection.md) → "What survives a `delay` boundary".

**Group sub-graph (`_nodes`):** The `_nodes` array inside the group's config defines the internal workflow executed for each item. It follows the **exact same rules** as a top-level node graph:

- Must have a `start` node and an `end` node
- Every node's `childrenUuids` must contain **exactly the number of entries shown in the `childrenCount` column** of the native actions tables above (e.g. `start` → 1, `variables` → 1, `branch` → 2, `end` → 0). This rule applies identically at both the top level and inside `_nodes`
- The `end` node must have `childrenUuids: []`
- Reference the current item with `{{nodes.start.value}}` or `{{nodes.start.<field>}}`
- Reference parent workflow data with `{{parentNodes.<slug>.<field>}}`

**Important:** Do NOT leave the last sub-node with `childrenUuids: []` unless it is the `end` node. Every other node type (variables, connector, tool, agent, etc.) requires exactly 1 child. Always terminate the sub-graph with an explicit `end` node.

### AI and code

| actionSlug | Purpose             | childrenCount | Config                                                                                                 |
| ---------- | ------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `agent`    | Inline AI agent     | 1        | `{"prompt": "...", "advancedSettings": {"connectorUuid": "...", "languageModelSlug": "gpt-4.1-mini"}}` |
| `python`   | Run Python code     | 1        | `{"script": "..."}`                                                                                    |
| `script`   | Run JavaScript code | 1        | `{"script": "..."}`                                                                                    |

The `agent` action requires `advancedSettings.connectorUuid` (an AI provider connector — get it from `connector list`). Optional fields: `actions`, `resources`, `capabilities`, `output` (structured output with `{"type": "jsonSchema", "jsonSchema": {...}}`), `advancedSettings.temperature`, `advancedSettings.maxSteps`, `advancedSettings.systemPrompt`.

The `python` and `script` nodes receive `nodes` and `parentNodes` as context variables. The return value of the script becomes the node's output under `{{nodes.<slug>.result}}` (assign to a variable named `result` in Python; `return` a value in JS).

> **Prefer built-in actions + expressions over code nodes.** Before adding a `python` or `script` node, read [`node-selection.md`](node-selection.md): most transforms belong in a `variables` node, LLM calls in the native `agent` node, API calls in the integration's connector action, and routing in `branch`/`filter`/`switch`. Reach for code only for genuine multi-step computation (prefer the JS `script` node — it ships `lodash`).

## Examples

### Connector node: enrich then output

```bash
# 1. Discover integration, action, and connector
cargo-ai connection integration list
cargo-ai connection integration get-documentation clearbit
cargo-ai connection connector list

# 2. Run with custom nodes
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"domain":"acme.com"}' \
  --nodes '[
    {
      "uuid":"11111111-1111-4111-a111-111111111111","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["22222222-2222-4222-a222-222222222222"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"22222222-2222-4222-a222-222222222222","slug":"enrich_company","kind":"connector",
      "integrationSlug":"clearbit","actionSlug":"enrichCompanyFromDomain",
      "connectorUuid":"<connector-uuid>",
      "config":{
        "domain":{"kind":"templateExpression","expression":"{{nodes.start.domain}}","instructTo":"none","fromRecipe":false}
      },
      "childrenUuids":["33333333-3333-4333-a333-333333333333"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"33333333-3333-4333-a333-333333333333","slug":"end","kind":"native","actionSlug":"end",
      "config":{
        "variables":[
          {"name":"company_name","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.enrich_company.name}}","instructTo":"none","fromRecipe":false}},
          {"name":"employee_count","type":"number","value":{"kind":"templateExpression","expression":"{{nodes.enrich_company.metrics.employeesRange}}","instructTo":"none","fromRecipe":false}}
        ]
      },
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":332}
    }
  ]'
```

### Tool node: call a sub-tool

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"first_name":"Jane","last_name":"Doe","company_domain":"acme.com"}' \
  --nodes '[
    {
      "uuid":"aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbbbb"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbbbb","slug":"find_email","kind":"tool",
      "toolUuid":"<email-finder-tool-uuid>",
      "config":{
        "first_name":{"kind":"templateExpression","expression":"{{nodes.start.first_name}}","instructTo":"none","fromRecipe":false},
        "last_name":{"kind":"templateExpression","expression":"{{nodes.start.last_name}}","instructTo":"none","fromRecipe":false},
        "company_domain":{"kind":"templateExpression","expression":"{{nodes.start.company_domain}}","instructTo":"none","fromRecipe":false}
      },
      "childrenUuids":["cccccccc-cccc-4ccc-accc-cccccccccccc"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"cccccccc-cccc-4ccc-accc-cccccccccccc","slug":"end","kind":"native","actionSlug":"end",
      "config":{
        "variables":[
          {"name":"email","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.find_email.email}}","instructTo":"none","fromRecipe":false}}
        ]
      },
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":332}
    }
  ]'
```

### Branch node: if/else routing

Route based on employee count — enrich large companies, skip small ones.

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"domain":"acme.com","employee_count":500}' \
  --nodes '[
    {
      "uuid":"d1d1d1d1-d1d1-4d1d-ad1d-d1d1d1d1d1d1","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["d2d2d2d2-d2d2-4d2d-ad2d-d2d2d2d2d2d2"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"d2d2d2d2-d2d2-4d2d-ad2d-d2d2d2d2d2d2","slug":"check_size","kind":"native","actionSlug":"branch",
      "config":{
        "condition":{"kind":"templateExpression","expression":"{{nodes.start.employee_count > 100}}","instructTo":"none","fromRecipe":false}
      },
      "childrenUuids":["d3d3d3d3-d3d3-4d3d-ad3d-d3d3d3d3d3d3","d4d4d4d4-d4d4-4d4d-ad4d-d4d4d4d4d4d4"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"d3d3d3d3-d3d3-4d3d-ad3d-d3d3d3d3d3d3","slug":"enrich","kind":"connector",
      "integrationSlug":"clearbit","actionSlug":"enrichCompanyFromDomain",
      "connectorUuid":"<connector-uuid>",
      "config":{
        "domain":{"kind":"templateExpression","expression":"{{nodes.start.domain}}","instructTo":"none","fromRecipe":false}
      },
      "childrenUuids":["d5d5d5d5-d5d5-4d5d-ad5d-d5d5d5d5d5d5"],"fallbackOnFailure":false,
      "position":{"x":-200,"y":332}
    },
    {
      "uuid":"d4d4d4d4-d4d4-4d4d-ad4d-d4d4d4d4d4d4","slug":"skip","kind":"native","actionSlug":"end",
      "config":{"variables":[
        {"name":"status","type":"string","value":"skipped_too_small"}
      ]},
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":200,"y":332}
    },
    {
      "uuid":"d5d5d5d5-d5d5-4d5d-ad5d-d5d5d5d5d5d5","slug":"end","kind":"native","actionSlug":"end",
      "config":{"variables":[
        {"name":"company_name","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.enrich.name}}","instructTo":"none","fromRecipe":false}},
        {"name":"status","type":"string","value":"enriched"}
      ]},
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":-200,"y":498}
    }
  ]'
```

`childrenUuids[0]` (`d3d3d3d3-...`) is the "yes" path, `childrenUuids[1]` (`d4d4d4d4-...`) is the "no" path.

### Filter + variables: transform and continue conditionally

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"email":"[email protected]","company":"Acme Corp"}' \
  --nodes '[
    {
      "uuid":"e1e1e1e1-e1e1-4e1e-ae1e-e1e1e1e1e1e1","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["e2e2e2e2-e2e2-4e2e-ae2e-e2e2e2e2e2e2"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"e2e2e2e2-e2e2-4e2e-ae2e-e2e2e2e2e2e2","slug":"has_email","kind":"native","actionSlug":"filter",
      "config":{
        "filter":{"kind":"templateExpression","expression":"{{nodes.start.email !== undefined && nodes.start.email !== null}}","instructTo":"none","fromRecipe":false}
      },
      "childrenUuids":["e3e3e3e3-e3e3-4e3e-ae3e-e3e3e3e3e3e3"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"e3e3e3e3-e3e3-4e3e-ae3e-e3e3e3e3e3e3","slug":"extract","kind":"native","actionSlug":"variables",
      "config":{
        "variables":[
          {"name":"domain","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.start.email.split('@')[1]}}","instructTo":"none","fromRecipe":false}},
          {"name":"company","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.start.company}}","instructTo":"none","fromRecipe":false}}
        ]
      },
      "childrenUuids":["e4e4e4e4-e4e4-4e4e-ae4e-e4e4e4e4e4e4"],"fallbackOnFailure":false,
      "position":{"x":0,"y":332}
    },
    {
      "uuid":"e4e4e4e4-e4e4-4e4e-ae4e-e4e4e4e4e4e4","slug":"end","kind":"native","actionSlug":"end",
      "config":{
        "variables":[
          {"name":"domain","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.extract.domain}}","instructTo":"none","fromRecipe":false}},
          {"name":"company","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.extract.company}}","instructTo":"none","fromRecipe":false}}
        ]
      },
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":498}
    }
  ]'
```

If `email` is null/undefined the filter stops execution — no downstream nodes run.

### Agent node: inline AI with structured output

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"company":"Acme Corp","website":"https://acme.com"}' \
  --nodes '[
    {
      "uuid":"f1f1f1f1-f1f1-4f1f-af1f-f1f1f1f1f1f1","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["f2f2f2f2-f2f2-4f2f-af2f-f2f2f2f2f2f2"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"f2f2f2f2-f2f2-4f2f-af2f-f2f2f2f2f2f2","slug":"classify","kind":"native","actionSlug":"agent",
      "config":{
        "prompt":{"kind":"templateExpression","expression":"Classify the company {{nodes.start.company}} ({{nodes.start.website}}) into one of these categories: SaaS, E-commerce, Marketplace, Services, Hardware, Other. Return the category and a one-sentence reasoning.","instructTo":"none","fromRecipe":false},
        "output":{
          "type":"jsonSchema",
          "jsonSchema":{
            "type":"object",
            "properties":{
              "category":{"type":"string","enum":["SaaS","E-commerce","Marketplace","Services","Hardware","Other"]},
              "reasoning":{"type":"string"}
            },
            "required":["category","reasoning"],
            "additionalProperties":false
          }
        },
        "advancedSettings":{
          "connectorUuid":"<openai-connector-uuid>",
          "languageModelSlug":"gpt-4.1-mini",
          "temperature":0.3,
          "maxSteps":5
        }
      },
      "childrenUuids":["f3f3f3f3-f3f3-4f3f-af3f-f3f3f3f3f3f3"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"f3f3f3f3-f3f3-4f3f-af3f-f3f3f3f3f3f3","slug":"end","kind":"native","actionSlug":"end",
      "config":{
        "variables":[
          {"name":"category","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.classify.answer.category}}","instructTo":"none","fromRecipe":false}},
          {"name":"reasoning","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.classify.answer.reasoning}}","instructTo":"none","fromRecipe":false}}
        ]
      },
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":332}
    }
  ]'
```

### Python node: custom data transformation

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"name":"ACME CORP","domain":"  Acme.COM  "}' \
  --nodes '[
    {
      "uuid":"a1a1a1a1-a1a1-4a1a-aa1a-a1a1a1a1a1a1","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["a2a2a2a2-a2a2-4a2a-aa2a-a2a2a2a2a2a2"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"a2a2a2a2-a2a2-4a2a-aa2a-a2a2a2a2a2a2","slug":"normalize","kind":"native","actionSlug":"python",
      "config":{
        "script":"name = nodes[\"start\"][\"name\"]\ndomain = nodes[\"start\"][\"domain\"]\nresult = {\"name\": name.strip().title(), \"domain\": domain.strip().lower()}"
      },
      "childrenUuids":["a3a3a3a3-a3a3-4a3a-aa3a-a3a3a3a3a3a3"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"a3a3a3a3-a3a3-4a3a-aa3a-a3a3a3a3a3a3","slug":"end","kind":"native","actionSlug":"end",
      "config":{
        "variables":[
          {"name":"name","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.normalize.result.name}}","instructTo":"none","fromRecipe":false}},
          {"name":"domain","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.normalize.result.domain}}","instructTo":"none","fromRecipe":false}}
        ]
      },
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":332}
    }
  ]'
```

Python scripts receive `nodes` and `parentNodes` dicts. Set `result` to define the node output, accessible via `{{nodes.<slug>.result}}`.

### Group node: loop over items

Process each item in an array through a sub-workflow. The group node creates a child batch, running the inner graph once per item. The `_nodes` array inside the group config defines the sub-graph executed per item — it must have its own `start` and `end` nodes, just like a top-level workflow.

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"domains":["acme.com","globex.com","initech.com"]}' \
  --nodes '[
    {
      "uuid":"b1b1b1b1-b1b1-4b1b-ab1b-b1b1b1b1b1b1","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["b2b2b2b2-b2b2-4b2b-ab2b-b2b2b2b2b2b2"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"b2b2b2b2-b2b2-4b2b-ab2b-b2b2b2b2b2b2","slug":"loop","kind":"native","actionSlug":"group",
      "config":{
        "items":{"kind":"templateExpression","expression":"{{nodes.start.domains}}","instructTo":"none","fromRecipe":false},
        "failOnItemFailure":false,
        "_nodes":[
          {
            "uuid":"b2a1a1a1-a1a1-4a1a-aa1a-a1a1a1a1a1a1","slug":"start","kind":"native","actionSlug":"start",
            "config":{},"childrenUuids":["b2a2a2a2-a2a2-4a2a-aa2a-a2a2a2a2a2a2"],"fallbackOnFailure":false,
            "position":{"x":0,"y":0}
          },
          {
            "uuid":"b2a2a2a2-a2a2-4a2a-aa2a-a2a2a2a2a2a2","slug":"enrich","kind":"connector",
            "integrationSlug":"clearbit","actionSlug":"enrichCompanyFromDomain",
            "connectorUuid":"<connector-uuid>",
            "config":{
              "domain":{"kind":"templateExpression","expression":"{{nodes.start.value}}","instructTo":"none","fromRecipe":false}
            },
            "childrenUuids":["b2a3a3a3-a3a3-4a3a-aa3a-a3a3a3a3a3a3"],"fallbackOnFailure":false,
            "position":{"x":0,"y":160}
          },
          {
            "uuid":"b2a3a3a3-a3a3-4a3a-aa3a-a3a3a3a3a3a3","slug":"end","kind":"native","actionSlug":"end",
            "config":{"variables":[
              {"name":"company_name","type":"string","value":{"kind":"templateExpression","expression":"{{nodes.enrich.name}}","instructTo":"none","fromRecipe":false}}
            ]},
            "childrenUuids":[],"fallbackOnFailure":false,
            "position":{"x":0,"y":320}
          }
        ]
      },
      "childrenUuids":["b3b3b3b3-b3b3-4b3b-ab3b-b3b3b3b3b3b3"],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    },
    {
      "uuid":"b3b3b3b3-b3b3-4b3b-ab3b-b3b3b3b3b3b3","slug":"end","kind":"native","actionSlug":"end",
      "config":{"variables":[]},
      "childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":332}
    }
  ]'
```

Each iteration receives the current item as its start data (`{{nodes.start.value}}` for simple values, `{{nodes.start.<field>}}` for object items). Use `{{parentNodes.start.domains}}` inside the loop to access the parent run's data.

**Sub-graph rules:** The `_nodes` array is a complete node graph — it must have `start` and `end` nodes. Every intermediate node must chain to the next via `childrenUuids`. The `end` node is the only node that should have `childrenUuids: []`.

## Validation

Always validate before running. The command checks for structural errors (missing start/end, broken UUID references, invalid connectors, etc.).

```bash
cargo-ai orchestration node validate \
  --nodes '[
    {
      "uuid":"c1c1c1c1-c1c1-4c1c-ac1c-c1c1c1c1c1c1","slug":"start","kind":"native","actionSlug":"start",
      "config":{},"childrenUuids":["c2c2c2c2-c2c2-4c2c-ac2c-c2c2c2c2c2c2"],"fallbackOnFailure":false,
      "position":{"x":0,"y":0}
    },
    {
      "uuid":"c2c2c2c2-c2c2-4c2c-ac2c-c2c2c2c2c2c2","slug":"end","kind":"native","actionSlug":"end",
      "config":{"variables":[]},"childrenUuids":[],"fallbackOnFailure":false,
      "position":{"x":0,"y":166}
    }
  ]'
```

Success:

```json
{ "outcome": "valid" }
```

Error:

```json
{
  "outcome": "notValid",
  "invalidNodes": [
    {
      "node": { "uuid": "22222222-2222-4222-a222-222222222222", "slug": "enrich_company" },
      "reason": "connectorNotFound"
    }
  ]
}
```

### Common validation errors

| Error                         | Cause                                                | Fix                                 |
| ----------------------------- | ---------------------------------------------------- | ----------------------------------- |
| `startNodeNotFound`           | No node with `slug:"start"` and `actionSlug:"start"` | Add the required start node         |
| `invalidReleaseOrCustomNodes` | Both `--release-uuid` and `--nodes` provided         | Use one or the other, not both      |
| `nodesNotFound`               | `childrenUuids` references a UUID not in the array   | Verify all UUID cross-references    |
| `childrenUuidsInvalid`        | Wrong number of entries in `childrenUuids`            | Check the `childrenCount` column in the native actions tables — each node type requires an exact count (e.g. `variables` needs 1, `end` needs 0, `branch` needs 2). Inside a group's `_nodes`, the last node must be an `end` node (`childrenUuids: []`), not a `variables` or connector node with an empty array |
| `subNodesInvalid`             | A group node's `_nodes` sub-graph has invalid nodes  | Check the nested `invalidNodes` array for details — the sub-graph must follow the same rules as a top-level graph (start + end nodes, correct childrenUuids counts) |
| `connectorNotFound`           | `connectorUuid` doesn't match an active connector    | Check `connector list` for the UUID |
| `nativeInvalid`               | `actionSlug` doesn't match a known native action     | Check the native actions table      |
| `toolInvalid`                 | `toolUuid` doesn't match an existing tool            | Check `tool list` for the UUID      |
| `slugInvalid`                 | Slug contains non-word characters                    | Use only `[a-zA-Z0-9_]`             |

## Node compute

`node compute` evaluates a node's config expressions against a context **without executing any side effects** — no API calls, no credits consumed. Use it to preview what a node's config will resolve to before running it.

```bash
cargo-ai orchestration node compute \
  --node '{
    "uuid": "22222222-2222-4222-a222-222222222222",
    "slug": "enrich_company",
    "kind": "connector",
    "integrationSlug": "clearbit",
    "actionSlug": "enrichCompanyFromDomain",
    "connectorUuid": "<connector-uuid>",
    "config": {
      "domain": {
        "kind": "templateExpression",
        "expression": "{{nodes.start.domain}}",
        "instructTo": "none",
        "fromRecipe": false
      }
    },
    "childrenUuids": ["33333333-3333-4333-a333-333333333333"],
    "fallbackOnFailure": false,
    "position": {"x": 0, "y": 166}
  }' \
  --context '{"nodes": {"start": {"domain": "acme.com"}}}'
```

The `--context` object mirrors what nodes receive at runtime — `nodes.<slug>.<field>` for data from previous nodes. Inside a `group` loop, also pass `groupContext`.

Response shows the resolved config values that would be sent to the connector or action.

> **Don't use `node compute` to debug branch/condition logic.** The local evaluator does not reliably resolve `templateExpression` references against `--context` for boolean conditions — literals (`{{true}}`) work, but `{{nodes.qualify.answer.qualified}}` may return `false` even when the context has `qualified: true`. For branch debugging, prefer running the full graph with a single record (`batch create --data '{"kind":"recordIds",...}'`) and inspecting `run get <run-uuid>` — read `run.executions[].nodeChildIndex` / `nextNodeUuid` to see which branch was taken, and read `runContext.<upstreamSlug>` (returned at the top level of the same response) to verify the field the condition reads. `executions[].title` is only a truncated summary. See `references/troubleshooting.md` → "Debugging a workflow run".

## Node execute

`node execute` runs a **single node of an existing workflow** in isolation with real side effects — it makes the actual API call (connector, tool, or agent). It exists for **one job: testing/debugging a node you are authoring inside a workflow.**

> **Not a general-purpose runner — prefer `action execute`.** If you just want to
> run an operation (enrich a domain, call a connector action, invoke a tool or
> agent) and get its output, use `action execute` / `action execute-batch`. Those
> take a small `--action` + `--data` payload, need no workflow, no release, and no
> hand-built node JSON. Reach for `node execute` **only** when the node already
> belongs to a workflow graph and you are verifying that node's behavior before
> running the full graph.
>
> | You want to… | Use |
> | --- | --- |
> | Run one operation on one record | `action execute` |
> | Run one operation on many records | `action execute-batch` |
> | Test one node of a workflow you're building | `node execute` |
> | Run the whole graph | `run create` / `batch create` |

> **Note:** `node execute` consumes credits. It is a live execution, not a dry run.

**All five flags are required** — `--workflow-uuid`, `--release-uuid`, `--node`,
`--computed-config`, `--context`. The CLI rejects the call client-side if any is
missing, which is why this command is unusable outside an existing workflow +
release: get `--workflow-uuid` from `tool list` / `play list`, and `--release-uuid`
from `release get-deployed --workflow-uuid <uuid>` (or `release get-draft`).

```bash
cargo-ai orchestration node execute \
  --workflow-uuid <tool.workflowUuid> \
  --release-uuid <release.uuid> \
  --node '{
    "uuid": "22222222-2222-4222-a222-222222222222",
    "slug": "enrich_company",
    "kind": "connector",
    "integrationSlug": "clearbit",
    "actionSlug": "enrichCompanyFromDomain",
    "connectorUuid": "<connector-uuid>",
    "config": {
      "domain": {
        "kind": "templateExpression",
        "expression": "{{nodes.start.domain}}",
        "instructTo": "none",
        "fromRecipe": false
      }
    },
    "childrenUuids": [],
    "fallbackOnFailure": false,
    "position": {"x": 0, "y": 166}
  }' \
  --computed-config '{
    "domain": "acme.com"
  }' \
  --context '{"nodes": {"start": {"domain": "acme.com"}}}'
```

**`--computed-config`** — required; the already-resolved config values. Produce them with `node compute` and pass the result through — the CLI does **not** resolve expressions for you here.

**`--release-uuid`** — required; pins the execution to a specific workflow release. Resolve it with `release get-deployed --workflow-uuid <uuid>` (or `release get-draft` while iterating on a draft).

### Recommended debug workflow

Use this while **authoring a workflow**. For a one-off operation that isn't part of a graph, skip straight to `action execute`.

1. **Validate structure** — `node validate --nodes '[...]'` — catches structural errors
2. **Preview expressions** — `node compute --node '{...}' --context '{...}'` — check resolved values
3. **Test live** — `node execute --workflow-uuid <uuid> --release-uuid <uuid> --node '{...}' --computed-config '{...}' --context '{...}'` — confirm real output for that one node
4. **Run full graph** — `run create --nodes '[...]'` — execute the complete workflow

## Polling

Custom node runs are polled the same way as regular runs:

```bash
cargo-ai orchestration run get <run-uuid>
# Poll every 2s until status is success, error, or cancelled
```
references/polling.md
# Async polling reference

All runs, batches, and agent messages in Cargo are asynchronous. This file is the single source of truth for polling patterns, intervals, terminal states, and error handling.

## Skip polling with `--wait-until-finished`

For runs and batches, pass `--wait-until-finished` to `run create` or `batch create` to block until the operation reaches a terminal state and return the final result directly — no manual polling needed:

```bash
# Blocks until the run finishes, returns the final run result
cargo-ai orchestration run create \
  --workflow-uuid <uuid> \
  --data '{"domain":"acme.com"}' \
  --wait-until-finished

# Blocks until the batch finishes, returns the final batch result
cargo-ai orchestration batch create \
  --workflow-uuid <uuid> \
  --data '{"kind":"filter","modelUuid":"...","filter":{"conjonction":"and","groups":[]}}' \
  --wait-until-finished
```

Use `--wait-until-finished` for short-lived runs or when you need the result immediately. For large batches (1000+ records) or long-running workflows, manual polling gives you more control and visibility.

## Polling table

| Operation | Create command | Poll command | Interval | Terminal state |
|---|---|---|---|---|
| Run | `orchestration run create` | `orchestration run get <uuid>` | 2s | `status` is `success`, `error`, or `cancelled` |
| Batch | `orchestration batch create` | `orchestration batch get <uuid>` | 5s | `status` is `success`, `error`, or `cancelled` |
| Agent message | `ai message create` | `ai message get <uuid>` | 2s | `status` is `success` or `error` |

For large batches (1000+ records), increase the polling interval to 10–15s after the first minute to avoid rate-limiting.

---

## Waiting for completion

For runs and batches, pass `--wait-until-finished` to `run create` or `batch create` to block until the operation reaches a terminal state and return the final result — no manual polling needed:

```bash
# Blocks until the run finishes, returns the final run result
cargo-ai orchestration run create \
  --workflow-uuid <uuid> \
  --data '{"domain":"acme.com"}' \
  --wait-until-finished

# Blocks until the batch finishes, returns the final batch result
cargo-ai orchestration batch create \
  --workflow-uuid <uuid> \
  --data '{"kind":"filter","modelUuid":"...","filter":{"conjonction":"and","groups":[]}}' \
  --wait-until-finished
```

For agent messages, poll manually with `ai message get <uuid>` until `status` is `success` or `error`.

---

## What to do when a terminal state is an error

### Run returns `status: "error"`

1. Read node-level output to find the failing node:
   ```bash
   cargo-ai orchestration run get <run-uuid>
   # → Inspect each node's output for error indicators
   ```
2. Check the error message from the connector, agent, or native node.
3. Identify the cause — common patterns:
   - **Connector error:** rate limit, invalid input, credential expired → fix connector config or input data
   - **Agent error:** prompt too long, model quota exceeded → simplify prompt or switch model
   - **Filter node stopped execution:** this is not an error — it means the record didn't meet the filter condition
4. Fix the input data or node config and re-trigger.

### Batch finishes with partial failures

A batch with `status: "success"` can still have individual run failures. After the batch reaches a terminal state:

```bash
# Count errors in the batch
cargo-ai orchestration run count \
  --workflow-uuid <uuid> \
  --batch-uuid <batch-uuid> \
  --statuses error

# Download only failed runs for inspection
cargo-ai orchestration run download \
  --workflow-uuid <uuid> \
  --batch-uuid <batch-uuid> \
  --statuses error

# Check run counts
cargo-ai orchestration batch get <batch-uuid>
# → .runsCount = total records submitted
# → .executedRunsCount = records that completed (success or error)
# → .failedRunsCount = records that errored
```

**Partial retry pattern** — re-run only the failed records:

Get the failed records' IDs from `run list` (JSON on stdout, `runs[].recordId`) or, past its 100-row page, from orchestration SQL. **Not** from `run download` — that returns a signed URL to a gzipped CSV whose column is `_record_id`, so piping its stdout into `jq '.recordId'` yields nothing.

```bash
# 1a. Up to 100 failed records — straight from run list
RECORD_IDS=$(cargo-ai orchestration run list \
  --workflow-uuid <uuid> \
  --batch-uuid <batch-uuid> \
  --statuses error \
  --limit 100 | jq -c '[.runs[].recordId]')

# 1b. More than that — SQL, which has no page cap
RECORD_IDS=$(cargo-ai orchestration query execute \
  "SELECT record_id FROM runs
   WHERE batch_uuid = '<batch-uuid>' AND status = 'error'" \
  | jq -c '[.rows[].record_id]')

# 2. Re-trigger with only those record IDs
cargo-ai orchestration batch create \
  --workflow-uuid <uuid> \
  --data "{\"kind\":\"recordIds\",\"recordIds\":$RECORD_IDS}"
```

Re-running bills the paid nodes again — apply the pilot gate in [`../../cargo-gtm/references/cost-discipline.md`](../../cargo-gtm/references/cost-discipline.md) before enrolling the whole failed set.

### Agent message returns `status: "error"`

```bash
cargo-ai ai message get <msg-uuid>
# → Read .message.errorMessage for the root cause
```

Common causes:
- Agent ran out of steps (`maxSteps` exceeded) — increase `--max-steps` on the next call.
- Model quota or rate limit — retry after a delay.
- Tool call failed inside the agent — simplify the task or check the tool's connector credentials.

---

## Adding retry to nodes

For transient failures (rate limits, timeouts), add a `retry` config to the node definition:

```json
{
  "uuid": "...",
  "slug": "enrich",
  "kind": "connector",
  "retry": {
    "maximumAttempts": 3,
    "initialInterval": 1000,
    "backoffCoefficient": 2
  },
  ...
}
```

- `maximumAttempts` — how many times to try before marking the node as failed
- `initialInterval` — milliseconds before the first retry
- `backoffCoefficient` — multiplier for each subsequent retry interval (2 = exponential backoff)

Use `fallbackOnFailure: true` if you want execution to continue to the next node even when all retries are exhausted.

---

## Stuck / never-finishing operations

| Symptom | Likely cause | Fix |
|---|---|---|
| Run stuck in `pending` for >30s | Workflow not enabled, no deployed release | Check `isEnabled` on the tool/play; verify a release exists with `release list --workflow-uuid <uuid>` |
| Batch never reaches a terminal `status` | Individual runs are stuck or erroring | Run `run list --workflow-uuid <uuid> --batch-uuid <uuid>` to inspect per-record status |
| Agent message stuck in `generating` for >60s | Agent running multi-step actions | Wait up to 120s for complex agents; check `message get` for partial progress |
| Batch shows `executedRunsCount` less than `runsCount` after a long time | Some records are stuck | Check `run list` for `pending` or `generating` statuses; contact support if persistent |
references/response-shapes.md
# Response shapes

JSON response structures returned by Cargo CLI commands used in the `cargo-orchestration` skill.

## cargo-ai orchestration play list

```json
{
  "plays": [
    {
      "uuid": "play-uuid",
      "name": "Enrich new companies",
      "workflowUuid": "workflow-uuid",
      "modelUuid": "model-uuid",
      "segmentUuid": "segment-uuid",
      "changeKinds": ["added", "updated"],
      "runCreationRule": "always",
      "isEnabled": true,
      "schedule": null,
      "description": "Enriches companies when they enter the segment",
      "healthThreshold": 80,
      "folderUuid": "folder-uuid-or-null",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-15T00:00:00Z"
    }
  ]
}
```

**Key fields:** `name` (match by name), `workflowUuid` (needed for run/batch commands), `modelUuid`, `segmentUuid` (the segment the play watches).

## cargo-ai orchestration tool list

```json
{
  "tools": [
    {
      "uuid": "tool-uuid",
      "name": "Company Enrichment",
      "workflowUuid": "workflow-uuid",
      "description": "Enriches a company record with firmographic data",
      "creditsCost": { "kind": "minMax" },
      "triggers": [],
      "isReadOnly": false,
      "folderUuid": "folder-uuid-or-null",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-15T00:00:00Z"
    }
  ]
}
```

**Key fields:** `name` (match by name), `workflowUuid` (needed for run/batch commands), `description`.

## cargo-ai orchestration workflow list

```json
{
  "workflows": [
    {
      "uuid": "abc-123",
      "workspaceUuid": "...",
      "playUuid": "play-uuid-or-null",
      "toolUuid": "tool-uuid-or-null",
      "folderUuid": "folder-uuid-or-null",
      "nodes": [],
      "lastBatch": {
        "uuid": "...",
        "status": "success",
        "runsStatus": "healthy",
        "createdAt": "2025-01-15T10:00:00Z",
        "finishedAt": "2025-01-15T10:05:00Z"
      },
      "deployedRelease": {
        "uuid": "...",
        "version": "3",
        "description": "Added email step",
        "deployedAt": "2025-01-10T09:00:00Z"
      },
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-15T10:05:00Z"
    }
  ]
}
```

**Key fields:** `uuid` (needed for run/batch commands), `playUuid`, `toolUuid`.

**Workflows don't have a `name` field.** Use `play list` or `tool list` instead — they have `name` and `workflowUuid` to cross-reference.

## cargo-ai ai agent list

```json
{
  "agents": [
    {
      "uuid": "agent-uuid",
      "name": "Sales Research Agent",
      "description": "Researches leads and enriches data",
      "isReadOnly": false,
      "folderUuid": "...",
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-15T00:00:00Z"
    }
  ]
}
```

**Key fields:** `uuid` (needed for chat create), `name` (match by name).

## cargo-ai storage model list

```json
{
  "models": [
    {
      "uuid": "model-uuid",
      "name": "Companies",
      "slug": "companies",
      "datasetUuid": "dataset-uuid",
      "idColumnSlug": "_id",
      "titleColumnSlug": "name",
      "playsCount": 5,
      "segmentsCount": 3,
      "isPaused": false,
      "columns": [
        { "slug": "name", "type": "string", "label": "Company Name" },
        { "slug": "domain", "type": "string", "label": "Domain" },
        { "slug": "employee_count", "type": "number", "label": "Employees" }
      ],
      "additionalColumns": [],
      "lastRun": {
        "uuid": "...",
        "status": "success",
        "createdAt": "2025-01-15T08:00:00Z",
        "finishedAt": "2025-01-15T08:02:00Z"
      },
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-15T08:02:00Z"
    }
  ]
}
```

**Key fields:** `uuid` (needed for segment fetch, get-ddl), `name`, `slug`, `columns[].slug` (for filter conditions).

## cargo-ai segmentation segment list

```json
{
  "segments": [
    {
      "uuid": "segment-uuid",
      "name": "Enterprise accounts",
      "slug": "enterprise-accounts",
      "modelUuid": "model-uuid",
      "recordsCount": 1520,
      "filter": { "conjonction": "and", "groups": [] },
      "sort": null,
      "limit": null,
      "createdAt": "2025-01-01T00:00:00Z",
      "updatedAt": "2025-01-15T00:00:00Z"
    }
  ]
}
```

**Key fields:** `uuid` (for run/batch data), `modelUuid` (needed for segment fetch — use this, not the segment uuid), `name`, `recordsCount`.

**IMPORTANT:** `segment fetch` requires `--model-uuid`, not `--segment-uuid`. Get `modelUuid` from the segment list response.

## cargo-ai orchestration action execute

```json
{
  "run": {
    "uuid": "run-uuid",
    "status": "pending",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

With `--wait-until-finished`, returns the terminal run state (same shape as `run get`).

**Status values:** `pending`, `running`, `success`, `error`, `cancelled`.

## cargo-ai orchestration action execute-batch

```json
{
  "batch": {
    "uuid": "batch-uuid",
    "status": "pending",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

With `--wait-until-finished`, returns the terminal batch state (same shape as `batch get`).

## cargo-ai orchestration run create

```json
{
  "run": {
    "uuid": "run-uuid",
    "workflowUuid": "...",
    "status": "pending",
    "batchUuid": null,
    "releaseUuid": "...",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

**Status values:** `pending`, `running`, `success`, `error`, `cancelled`.

## cargo-ai orchestration run get

```json
{
  "run": {
    "uuid": "run-uuid",
    "workflowUuid": "...",
    "status": "success",
    "batchUuid": null,
    "releaseUuid": "...",
    "recordId": "...",
    "recordTitle": "...",
    "contextS3Filename": "...",
    "computedConfigsS3Filename": "...",
    "executions": [
      {
        "nodeUuid": "...",
        "nodeSlug": "qualify",
        "nodeKind": "agent",
        "nodeActionSlug": "...",
        "nodeChildIndex": 0,
        "nextNodeUuid": "...",
        "status": "success",
        "title": "✅ {\"qualified\":true,\"score\":8,...}",
        "creditsUsedCount": 1,
        "startedAt": "...",
        "updatedAt": "...",
        "finishedAt": "..."
      }
    ],
    "createdAt": "2025-01-15T10:00:00Z",
    "finishedAt": "2025-01-15T10:00:05Z"
  },
  "runContext": {
    "start":   { "_id": "...", "domain": "acme.com" },
    "qualify": { "answer": { "qualified": true, "score": 8, "reasoning": "..." } },
    "is_qualified": { "condition": true },
    "post_slack": { "ok": true, "channel": "C123", "ts": "..." },
    "end": { "qualified": true, "score": 8, "slack_message_ts": "..." }
  },
  "runComputedConfigs": {
    "qualify": { "...resolved config that was sent to the node..." }
  }
}
```

**`releaseUuid` or `nodes`, never both.** A run created from a deployed tool or
play looks like the shape above — `releaseUuid` set, no graph, because the nodes
live on the release. A run from `action execute` (or `run create --nodes`) is the
mirror image: a full `run.nodes` array and no `releaseUuid`. Anything that needs
the graph — [diagramming it](node-diagram.md), reading a node's config — reads
`run.nodes` when present and falls back to `release get <releaseUuid>`.

**Key fields for debugging:**

- `run.executions[].title` — quick human-readable summary of each node's output; **may be truncated**, do not treat as the full output.
- `runContext.<nodeSlug>` — the actual per-node output, keyed by `nodeSlug`. This is the canonical source for what `{{nodes.<slug>...}}` resolves to downstream. Agent nodes wrap their structured output under `.answer` (e.g. `runContext.qualify.answer.qualified`).
- `runComputedConfigs.<nodeSlug>` — the resolved config values each node was actually called with (after template expression evaluation).

## cargo-ai orchestration batch create

```json
{
  "batch": {
    "uuid": "batch-uuid",
    "workflowUuid": "...",
    "status": "pending",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

## cargo-ai orchestration batch get

```json
{
  "batch": {
    "uuid": "batch-uuid",
    "workflowUuid": "...",
    "releaseUuid": "...",
    "status": "success",
    "runsStatus": "healthy",
    "runsCount": 100,
    "executedRunsCount": 100,
    "failedRunsCount": 2,
    "creditsUsedCount": 48,
    "errorMessage": null,
    "createdAt": "2025-01-15T10:00:00Z",
    "finishedAt": "2025-01-15T10:05:00Z"
  }
}
```

Poll until `status` is `success`, `error`, or `cancelled`. Note: `batch get` also returns `releaseUuid` — needed to discover output node slugs via `release get`.

## cargo-ai ai chat create

```json
{
  "chat": {
    "uuid": "chat-uuid",
    "agentUuid": "agent-uuid",
    "name": "Research session",
    "createdAt": "2025-01-15T10:00:00Z"
  }
}
```

**Key field:** `chat.uuid` (needed for message create).

## cargo-ai ai message create

```json
{
  "userMessage": {
    "uuid": "user-msg-uuid",
    "chatUuid": "chat-uuid",
    "status": "success",
    "parts": [{ "type": "text", "text": "Find the VP of Sales at Acme Corp" }]
  },
  "assistantMessage": {
    "uuid": "assistant-msg-uuid",
    "chatUuid": "chat-uuid",
    "status": "pending",
    "parts": []
  }
}
```

**Key field:** `assistantMessage.uuid` (needed for polling).

## cargo-ai ai message get

```json
{
  "message": {
    "uuid": "assistant-msg-uuid",
    "chatUuid": "chat-uuid",
    "status": "success",
    "parts": [
      { "type": "text", "text": "The VP of Sales at Acme Corp is John Smith..." }
    ],
    "errorMessage": null
  }
}
```

**Status values:** `pending`, `generating`, `success`, `error`.

## cargo-ai segmentation segment fetch

```json
{
  "records": [
    { "_id": "rec-1", "name": "Acme Corp", "domain": "acme.com", "employee_count": 500 },
    { "_id": "rec-2", "name": "Globex", "domain": "globex.com", "employee_count": 1200 }
  ],
  "count": 2,
  "columns": [
    { "slug": "_id", "type": "string", "label": "ID", "modelUuid": "model-uuid" },
    { "slug": "name", "type": "string", "label": "Company Name", "modelUuid": "model-uuid" },
    { "slug": "domain", "type": "string", "label": "Domain", "modelUuid": "model-uuid" },
    { "slug": "employee_count", "type": "number", "label": "Employees", "modelUuid": "model-uuid" }
  ]
}
```

## cargo-ai storage model get-ddl

```json
{
  "ddl": "CREATE TABLE `project.datasets_default.models_companies` (\n  `_id` STRING,\n  `name` STRING,\n  `domain` STRING,\n  `employee_count` INT64\n);",
  "language": "bigquery"
}
```

The table name in the DDL follows the pattern `datasets_{datasetSlug}.models_{modelSlug}` (or `datasets_{datasetSlug}__models_{modelSlug}` in BigQuery dataset-scoped). Use this name in SoR queries.

## cargo-ai orchestration query execute

Tables (`spans`, `runs`, `batches`, `records`) are referenced without a schema prefix; workspace scoping is applied automatically.

**Success:**

```json
{
  "rows": [
    { "status": "success", "count()": 1248 },
    { "status": "error",   "count()": 42 }
  ]
}
```

**Failure (non-zero exit):**

```json
{ "errorMessage": "Code: 60. Table orchestration.unknown doesn't exist" }
```

```json
{ "errorMessage": "Code: 158. Memory limit exceeded ..." }
```

## cargo-ai orchestration release list

```json
{
  "releases": [
    {
      "uuid": "release-uuid",
      "workflowUuid": "...",
      "version": "3",
      "description": "Added email step",
      "createdAt": "2025-01-10T09:00:00Z"
    }
  ]
}
```

Supports `--workflow-uuid`, `--limit`, `--offset`.

## cargo-ai orchestration release get

```json
{
  "release": {
    "uuid": "release-uuid",
    "workflowUuid": "...",
    "userUuid": "...",
    "version": "3",
    "description": "Added email step",
    "nodes": [
      {
        "uuid": "node-uuid-1",
        "slug": "enrich_company",
        "name": "Enrich Company",
        "kind": "connector",
        "integrationSlug": "companyEnrich",
        "actionSlug": "enrichByDomain",
        "config": { "domain": { "kind": "templateExpression", "expression": "{{nodes.start.domain}}" } },
        "childrenUuids": ["node-uuid-2"],
        "fallbackChildUuid": "node-uuid-3",
        "fallbackOnFailure": false,
        "position": { "x": 0, "y": 0 }
      }
    ],
    "createdAt": "2025-01-10T09:00:00Z"
  }
}
```

**Key field:** `nodes[].slug` — needed for `batch download --output-node-slug`.

`nodes[]` is the complete graph, not a summary: every node carries its `config`,
`childrenUuids`, and (where set) `fallbackChildUuid`, which is enough to
[draw the workflow](node-diagram.md) without another call. Two traps when reading
it: `name` is optional, and **`slug` is not unique** — one shipped release has six
nodes slugged `variables`. Key anything graph-shaped by `uuid`. `tool` and `agent`
nodes carry `toolUuid` / `agentUuid` as top-level fields alongside `kind`, and have
no `actionSlug`.

## cargo-ai ai chat list

```json
{
  "chats": [
    {
      "uuid": "chat-uuid",
      "agentUuid": "agent-uuid",
      "name": "Research session",
      "createdAt": "2025-01-15T10:00:00Z"
    }
  ]
}
```

Supports `--agent-uuid`, `--limit`, `--offset` for filtering.

## cargo-ai orchestration record list

```json
{
  "records": [
    {
      "id": "record-id",
      "title": "Acme Corp",
      "workflowUuid": "...",
      "runUuid": "run-uuid",
      "releaseUuid": "release-uuid",
      "batchUuid": "batch-uuid",
      "status": "success",
      "errorMessage": null,
      "isGroupParent": false,
      "parentRunUuid": null,
      "parentNodeUuid": null,
      "parentBatchUuid": null,
      "createdAt": "2025-01-15T10:00:00Z",
      "updatedAt": "2025-01-15T10:01:00Z"
    }
  ],
  "count": 42
}
```

**`status`** values: `pending`, `running`, `success`, `error`, `cancelled`, `cancelling`.

Supports `--workflow-uuid` (required), `--batch-uuid`, `--release-uuid`, `--statuses`, `--limit`, `--offset`.

## cargo-ai orchestration record count

```json
{
  "count": 42
}
```

Same filter params as `record list`.

## cargo-ai orchestration record download

```json
{
  "url": "https://signed-download-url..."
}
```

Returns a signed URL to download records as a file.

## cargo-ai orchestration record get-metrics

```json
{
  "recordMetrics": [
    {
      "nodeUuid": "node-uuid",
      "totalExecutionsCount": 100,
      "pendingExecutionsCount": 0,
      "runningExecutionsCount": 0,
      "successExecutionsCount": 95,
      "errorExecutionsCount": 5,
      "cancelledExecutionsCount": 0,
      "creditsUsedCount": 95
    }
  ]
}
```

Broken down per node. Supports `--workflow-uuid` (required), `--release-uuid`, `--batch-uuid`, `--created-after`, `--created-before`.

## cargo-ai segmentation segment get

```json
{
  "segment": {
    "uuid": "segment-uuid",
    "workspaceUuid": "...",
    "userUuid": "...",
    "modelUuid": "model-uuid",
    "slug": "high-value-accounts",
    "name": "High-Value Accounts",
    "filter": { "conjonction": "and", "groups": [] },
    "sort": null,
    "limit": null,
    "recordsCount": 1234,
    "fromPlay": false,
    "trackingColumnSlugs": null,
    "syncedAt": "2025-01-15T10:00:00Z",
    "lastChange": {
      "uuid": "change-uuid",
      "slug": "2025-01-15",
      "totalRecordsCount": 1234
    },
    "createdAt": "2025-01-01T00:00:00Z",
    "updatedAt": "2025-01-15T10:00:00Z",
    "deletedAt": null
  }
}
```

**Key fields:** `uuid`, `modelUuid`, `name`, `filter`, `recordsCount`, `lastChange`.

## cargo-ai segmentation segment create

```json
{
  "segment": {
    "uuid": "segment-uuid",
    "modelUuid": "model-uuid",
    "slug": "new-segment",
    "name": "New Segment",
    "filter": { "conjonction": "and", "groups": [] },
    "recordsCount": 0,
    ...
  }
}
```

Same shape as `segment get`.

## cargo-ai segmentation change list

```json
{
  "changes": [
    {
      "uuid": "change-uuid",
      "workspaceUuid": "...",
      "segmentUuid": "segment-uuid",
      "slug": "2025-01-15",
      "totalRecordsCount": 1234,
      "addedRecordsCount": 10,
      "updatedRecordsCount": 5,
      "removedRecordsCount": 2,
      "unchangedRecordsCount": 1217,
      "createdAt": "2025-01-15T00:00:00Z"
    }
  ]
}
```
references/troubleshooting.md
# Troubleshooting

Common errors and recovery steps for `cargo-orchestration` commands.

> For async polling patterns, partial batch failures, and retry node configuration, see `references/polling.md`.

## General

| Symptom                                      | Cause                            | Fix                                                                       |
| -------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------- |
| `{"errorMessage": "..."}` with non-zero exit | Any CLI error                    | Read the `errorMessage` — it usually says exactly what's wrong            |
| `command not found: cargo-ai`                | CLI not installed or not in PATH | Run `npm install -g @cargo-ai/cli` or prefix with `npx @cargo-ai/cli`     |
| `Unauthorized` or `Forbidden`                | Bad or expired credentials       | Re-run `cargo-ai login --oauth` (browser sign-in) or `cargo-ai login --token <token>`; verify with `cargo-ai whoami` |

## Runs and batches

| Symptom                             | Cause                                              | Fix                                                                                                         |
| ----------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Run stuck in `pending`              | Workflow may be paused or have no deployed release | Check that the play/tool `isEnabled` is `true`; verify a release exists with `release list --workflow-uuid` |
| Batch never finishes                | Individual runs may be erroring or stuck           | List runs for the batch: `run list --workflow-uuid <uuid> --batch-uuid <uuid>` and check for errors         |
| `Workflow not found`                | Wrong UUID                                         | Re-run `play list` or `tool list` and double-check the `workflowUuid`                                       |
| `playNotCompatible` on `run create` | Used a play's `workflowUuid` with `run create`     | Plays don't support `run create` — use `batch create` instead                                               |
| `invalidDataKind` on `batch create` | Data kind not allowed for the workflow type        | Plays accept: `segment`, `change`, `filter`, `recordIds`. Tools accept: `file`, `records`                   |
| Run finishes with `error` status    | Workflow step failed                               | Get run details with `run get <uuid>` — the error usually points to a specific node                         |

## AI agents

| Symptom                                                              | Cause                                    | Fix                                                                        |
| -------------------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------- |
| Assistant message stuck in `pending` or `generating` for a long time | Agent may be running multi-step actions   | Wait longer (up to 60s for complex tasks); check with `message get <uuid>` |
| Message returns `error` status                                       | Agent encountered an unrecoverable error | Read `.message.errorMessage`; try again with simpler input or fewer actions |
| `Chat not found`                                                     | Wrong UUID or chat was deleted           | Re-create with `chat create`                                               |
| `Agent not found`                                                    | Wrong UUID                               | Re-run `agent list` to get current UUIDs                                   |

> For `storage query execute` / `storage query download` troubleshooting, see the `cargo-storage` skill's `references/troubleshooting.md`.

## Orchestration queries (`orchestration query execute`)

| Symptom                                                  | Cause                                            | Fix                                                                                                            |
| -------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `errorMessage` with "Table … doesn't exist"              | Used a schema-prefixed name                      | Reference tables as `runs`, `batches`, `spans`, `records` — no schema prefix                                   |
| `errorMessage: "Code: 158. Memory limit exceeded"`       | Scanned too many rows                            | Narrow the time window (`created_at > now() - INTERVAL N DAY`) or aggregate before returning                   |
| `errorMessage: "Code: 159. Timeout exceeded"`            | Query took longer than 30s                       | Push more work into a `WHERE` filter or reduce columns selected                                                |
| `errorMessage: "Code: 497. ... ACCESS_DENIED"`           | Used a forbidden function or table               | Only `SELECT` against `runs`/`batches`/`spans`/`records`; no table functions, dictionaries, or introspection   |
| Query returns empty `rows`                               | Filter too restrictive, or wrong status enum     | Confirm enum values (`status` is `success`/`error`/`pending`/`running`/`cancelled`/…) and broaden the filter   |
| Result truncated at 10 000 rows                          | Hit `max_result_rows` cap                        | Aggregate (`count`, `GROUP BY`) instead of returning raw rows, or paginate with `LIMIT` + `WHERE created_at`   |

## Debugging a workflow run

A run can finish with `status: "success"` and still be wrong — wrong branch taken, empty downstream values, no Slack message sent. Use this checklist when behavior doesn't match expectations.

### 1. Pull the per-node executions and context

```bash
cargo-ai orchestration run get <run-uuid>
```

Don't have the UUID? `run list` won't help — it **requires** `--workflow-uuid` and has no "most recent run" form. Query the runtime table instead, which needs no filter: `cargo-ai orchestration query execute "SELECT uuid, workflow_uuid, record_title, status, created_at FROM runs ORDER BY created_at DESC LIMIT 10"`. The full discovery ladder (by symptom, by company/domain via `record_title`, by play name) is [`../../cargo-diagnostics/references/run-trace.md`](../../cargo-diagnostics/references/run-trace.md) § 0.

The response has three top-level fields:

| Field                | What it gives you                                                                                |
| -------------------- | ------------------------------------------------------------------------------------------------ |
| `run.executions[]`   | Node-by-node trace (which node ran, status, routing)                                             |
| `runContext`         | Full per-node output, keyed by `nodeSlug` — the actual data referenced as `{{nodes.<slug>...}}`  |
| `runComputedConfigs` | Per-node resolved config values (what each node was actually called with), also keyed by `nodeSlug` |

Each `executions[]` item has:

| Field            | What it tells you                                                                                |
| ---------------- | ------------------------------------------------------------------------------------------------ |
| `nodeSlug`       | Which node ran                                                                                   |
| `status`         | `success` / `error` for this node                                                                |
| `nextNodeUuid`   | Where execution went next — for a `branch`, this reveals which child was taken                   |
| `nodeChildIndex` | Index into `childrenUuids` that was followed (`0` = matched/yes, `1` = not matched/no for branch)|
| `title`          | Human-readable **summary only** — may be truncated; do not treat as the full output              |
| `creditsUsedCount` | Per-node cost; agent and connector nodes are non-zero, native nodes are zero                  |

`title` is a quick sanity check, not a source of truth — it can be truncated. To verify the exact data a node produced, read `runContext.<nodeSlug>` from the same response. Deep-dive into it to confirm the path you're referencing in `{{nodes.<slug>....}}` actually exists (for example, an agent's structured output is nested under `.answer`, so the right path is `{{nodes.<slug>.answer.<field>}}` and not `{{nodes.<slug>.<field>}}`).

### 2. Spot wrong-branch routing

If a `branch` node's `title` says "❌ Condition is not matched" but you expected the yes-path, the condition expression resolved to falsy. Most common causes:

| Cause | Fix |
|---|---|
| Path in the expression doesn't exist | Read `runContext.<upstreamSlug>` from `run get <run-uuid>` and check the actual shape — common case: an agent's output is nested under `.answer`, so `{{nodes.qualify.qualified}}` resolves to undefined while `{{nodes.qualify.answer.qualified}}` works |
| Stringified boolean comparison | `{{nodes.qualify.answer.qualified === true}}` may evaluate the inner expression as a string template — prefer the truthy form `{{nodes.qualify.answer.qualified}}` |
| Field actually missing from the output | The upstream node didn't produce it — `runContext.<upstreamSlug>` will confirm. To see what an action *should* emit without running it, read `output.schema` from `integration get <slug>` (connector actions) or resolve `orchestration action get-output-schema --action '<json>'` (any kind). For agents, this usually means revisiting the prompt + `jsonSchema` |
| Typo in slug or field | Slugs are case-sensitive; `nodes.Qualify.answer.qualified` won't resolve |

### 3. Re-run a single record after fixing

```bash
# Stage the fix without deploying (per workflow safety)
cargo-ai orchestration draft-release update \
  --workflow-uuid <play.workflowUuid> --nodes '[...]'

# After approval, deploy (do NOT pass --version — it collides with the global flag)
cargo-ai orchestration draft-release deploy \
  --workflow-uuid <play.workflowUuid> --nodes '[...]' \
  --form-fields 'null' --description "Fix branch condition"

# Re-test against the same record IDs that exposed the bug
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"recordIds","modelUuid":"<model>","ids":["id1","id2","id3"]}'
```

### 4. Common silent-failure modes

| Symptom in `run get` | Likely cause |
|---|---|
| Run is `success` but nothing downstream of a node fired | Downstream expression resolved to undefined — open `runContext.<upstreamSlug>` from `run get` and compare against the path you wrote |
| Branch always takes the same child | Condition references a non-existent path; `{{...}}` resolved to undefined which is falsy — verify against `runContext.<upstreamSlug>` |
| `find_email`/connector node ran but downstream values are empty | Connector returned a partial response — read `runContext.<nodeSlug>` from `run get` to see the exact shape, then use the actual field names (e.g. `nodes.find_email.email` may be `nodes.find_email.contact.email` for some integrations) |
| Slack post succeeded but message body has `{{...}}` literals | Template syntax error — a stray space or a missing closing `}}` makes the template engine treat the segment as plain text |
| End-node variables are all empty in a successful run | The `value` expressions reference paths that don't exist; cross-check each upstream node's `runContext.<slug>` from `run get` (don't rely on `title` — it may be truncated) |

## Run error recovery

When a run reaches `status: "error"`, follow this sequence:

1. **Inspect the run:** `cargo-ai orchestration run get <run-uuid>` — look at each node's output for error details.
2. **Identify the failing node:** The error message typically names the connector, agent, or native node that failed.
3. **Common causes and fixes:**

| Error pattern | Likely cause | Fix |
|---|---|---|
| Rate limit / 429 from connector | Too many requests to external API | Add `retry` config to the node with exponential backoff; reduce batch size |
| Connector credentials expired | OAuth token or API key is stale | Re-authenticate the connector in the Cargo app or via `connection connector update` |
| Agent `maxSteps` exceeded | Agent ran too many tool calls | Increase `--max-steps` on the message, or simplify the agent's task |
| `filter` node stopped execution | Record didn't meet the filter condition | This is expected behavior, not an error — adjust the filter if needed |
| Null reference in expression | Upstream node returned empty/null | Add a `filter` node before the failing node to skip records with missing data |
| `errorMessage` on `orchestration query execute` | Schema-prefixed table name or hit a query cap | Reference tables as `runs`/`batches`/`spans`/`records`; narrow the time window if you hit memory/row caps |

4. **Re-trigger after fixing:** Create a new run with the corrected data or node config.
5. **For systematic failures:** Use `run download --statuses error` to export all failed records, fix the data, then re-batch with `batch create --data '{"kind":"recordIds",...}'`.

## Batch sizing and third-party connector rate limits

> **Scope:** Rate limits only apply to **third-party connector nodes** (`kind: "connector"`) — integrations like Clearbit, HubSpot, Salesforce, etc. Native nodes (`kind: "native"`) have no rate limits.

Third-party connector rate limits are the most common production failure mode for large batches. The pattern below avoids hitting them.

**Recommended approach — start small and scale:**

| Batch size | What to do |
|---|---|
| 1 record | Run a single record first with `run create`. Confirm it succeeds and check per-record credit cost. |
| 10–50 records | Run a small pilot batch with `batch create --data '{"kind":"recordIds",...}'`. Check error rate with `run count --statuses error`. |
| 100–500 records | If error rate is < 5%, proceed. Monitor with `run count` mid-batch. |
| 1000+ records | Only after validating at smaller scale. Increase polling interval to 10–15s. Set `retry` on connector nodes. |

**Signs you are being rate-limited by a third-party connector:**

- Error count grows proportionally as the batch runs (not all failures upfront).
- Connector error messages include `429`, `rate limit`, or `too many requests`.
- Errors are clustered in time rather than spread evenly.

**Mitigations:**

- Add `retry` with exponential backoff to the connector node (see `references/polling.md`).
- Split large batches into smaller sub-batches run sequentially.
- Use `--data '{"kind":"recordIds",...}'` to process in controlled chunks rather than an entire segment at once.

## Segment fetch

| Symptom                    | Cause                             | Fix                                                                                                           |
| -------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| Empty results              | Wrong model UUID or over-filtered | Verify `--model-uuid` (not `--segment-uuid`); try with empty filter `{"conjonction":"and","groups":[]}` first |
| Parse error on filter JSON | Malformed JSON or wrong spelling  | Check: it's `conjonction` (not `conjunction`); validate JSON syntax; see `references/filter-syntax.md`        |
| `Model not found`          | Wrong UUID                        | Re-run `storage model list` or `segment list` to get the correct `modelUuid`                                  |
| Wrong columns in results   | Column slug mismatch              | Run `storage model list` and check `columns[].slug` for exact slugs                                           |
skill-metadata.json
{
  "$comment": "Generated by .github/scripts/skills-metadata.mjs — do not hand-edit. Regenerate with: node .github/scripts/skills-metadata.mjs --write .",
  "name": "cargo-orchestration",
  "version": "1.9.0",
  "documents": [
    {
      "path": "SKILL.md",
      "kind": "entrypoint",
      "title": "Cargo CLI — Orchestration"
    },
    {
      "path": "references/examples/actions.md",
      "kind": "example",
      "title": "Action examples"
    },
    {
      "path": "references/examples/agents.md",
      "kind": "example",
      "title": "AI agent examples"
    },
    {
      "path": "references/examples/plays.md",
      "kind": "example",
      "title": "Play examples"
    },
    {
      "path": "references/examples/queries.md",
      "kind": "example",
      "title": "Orchestration query examples"
    },
    {
      "path": "references/examples/segments.md",
      "kind": "example",
      "title": "Segment data examples"
    },
    {
      "path": "references/examples/templates.md",
      "kind": "example",
      "title": "Orchestration templates"
    },
    {
      "path": "references/examples/tools.md",
      "kind": "example",
      "title": "Tool examples"
    },
    {
      "path": "references/filter-syntax.md",
      "kind": "reference",
      "title": "Filter syntax"
    },
    {
      "path": "references/node-diagram.md",
      "kind": "reference",
      "title": "Diagramming a node graph"
    },
    {
      "path": "references/node-selection.md",
      "kind": "reference",
      "title": "Prefer built-in actions + expressions over code/HTTP nodes"
    },
    {
      "path": "references/nodes.md",
      "kind": "reference",
      "title": "Creating nodes"
    },
    {
      "path": "references/polling.md",
      "kind": "reference",
      "title": "Async polling reference"
    },
    {
      "path": "references/response-shapes.md",
      "kind": "reference",
      "title": "Response shapes"
    },
    {
      "path": "references/troubleshooting.md",
      "kind": "reference",
      "title": "Troubleshooting"
    }
  ],
  "contentHash": "b52a05c9c3e5006b548650672f0dd7b6a9bd808aa3149b086becc58a8f1629bf"
}
SKILL.md
---
name: cargo-orchestration
description: "Make Cargo actually run something, or show what it would run — execute one connector action, run a multi-step workflow, trigger a batch across a whole segment or model, message an AI agent, build or edit a node graph, draw a workflow, tool or play as a diagram, and query the runtime tables (runs, batches, spans, records) with SQL. Triggers: \"run this on all my contacts\", \"execute the action\", \"kick off a batch\", \"build a workflow\", \"schedule a play\", \"make it run every morning\", \"ask the agent\", \"show me the workflow\", \"what does this tool do\", \"visualize this play\", \"draw the graph\", \"explain this workflow\", \"how many runs failed today\", \"what is the output schema for this action\", \"add a step that\". Skip when: explaining why a run misbehaved — use cargo-diagnostics; downloading result files — use cargo-analytics; committing the workflow as code — use cargo-cdk."
version: "1.9.0"
compatibility: Requires @cargo-ai/cli (npm). Sign in or create an account with `cargo-ai login --email` (emailed code, no browser), `--oauth`, or an API token
homepage: https://github.com/getcargohq/cargo-skills
metadata:
  author: getcargo
  openclaw:
    requires:
      bins:
        - cargo-ai
    install:
      - kind: node
        package: "@cargo-ai/cli@latest"
        bins:
          - cargo-ai
    homepage: https://github.com/getcargohq/cargo-skills
---

# Cargo CLI — Orchestration

Runtime operations for the Cargo platform.

**What do you want to run?**

```
Need to run something?
├── Don't know the action yet    → action list <keywords>
├── One action, one record       → action execute
├── One action, many records     → action execute-batch
├── Multiple actions chained
│   ├── One-off / ad-hoc         → run create --nodes (one record)
│   │                              batch create --nodes (many records)
│   └── Reusable workflow        → build a tool, then run create --workflow-uuid
│                                  or batch create --workflow-uuid
├── Conversational AI agent      → message create
└── Testing ONE node of a
    workflow you're building     → node execute (debug only — see below)
```

> **Fanning out across many records (`action execute-batch`, `batch create`)? Sample first.** Run 10–20 records, report the observed cost and hit-rate, then ask the user to approve the full enrollment — quoting the **record count** and the **credit estimate**. See [Create a batch → the sample gate](#the-sample-gate).

> **Find the action before you hand-write the JSON.** `cargo-ai orchestration
> action list <keywords>` searches the integration catalog, Cargo native actions,
> workspace tools, and agents in one call — free, runs nothing — and each result
> carries a ready-to-paste `action` object (with `connectorUuid` already filled
> in), the action's **credit costs**, and its autocomplete slugs. Narrow with
> `--kind connector|native|tool|agent`, `--integration-slug <slug>`, `--limit`
> (default 20, max 50). `unknown command` means the CLI predates it — refresh.

> **`action execute`, not `node execute`, is the default for running something.**
> `node execute` is a **debug** surface for a node that already lives in a workflow:
> it requires `--workflow-uuid`, `--release-uuid`, `--node`, `--computed-config`
> **and** `--context` (all five, enforced client-side), and it bills like any live
> call. If you just want an operation's output — enrich a domain, call a connector
> action, invoke a tool or agent — use `action execute` / `action execute-batch`
> with a small `--action` + `--data` payload. Only reach for `node execute` when
> verifying one node's behavior before running the full graph.

> **Terminology:** An orchestration **tool** is a saved on-demand workflow (listed via `tool list`). An **action** is a single operation you execute without building a workflow — it can embed a saved orchestration tool (`kind: "tool"`), call a third-party connector (`kind: "connector"`), invoke an AI agent (`kind: "agent"`), or run a built-in platform operation (`kind: "native"`).

> **Composing a node graph? Prefer built-in actions + expressions.** Use the
> actions Cargo already provides plus template expressions; avoid `python`,
> `script` (JS), and raw HTTP nodes unless you truly have no alternative. Reshape
> data → `variables`; call an LLM and get parsed JSON → native `agent` node; call an
> API → the integration's dedicated **connector action**; route → `branch`/`filter`/`switch`.
> See **`references/node-selection.md`**.

> **Show the graph, don't describe it.** Before deploying a draft, and whenever
> the user asks what a workflow or play does, draw it:
> `cargo-ai orchestration node diagram --workflow-uuid <uuid> --format ascii --raw`
> (free, runs nothing; `--format` needs CLI ≥ 1.0.56, the command itself ≥ 1.0.54).
> Routing, fallback edges, and which steps bill are what the user is actually
> approving, and prose flattens all three. **Pick the format by where the output
> goes:** `ascii` renders a picture a person can read in a terminal or a chat
> reply; `mermaid` (the default) is source code, correct only when you are
> pasting into a PR, a doc, or a page that renders it. Sources, the ASCII legend,
> cost marking, and the duplicate-slug footgun: **`references/node-diagram.md`**.

**References:**

> `references/examples/actions.md` — action execute and execute-batch examples
> `references/examples/tools.md` — tool (on-demand workflow) examples
> `references/examples/plays.md` — play (segment-driven automation) examples
> `references/examples/agents.md` — AI agent chat examples
> `references/examples/templates.md` — pre-built workflow templates
> `references/examples/queries.md` — `orchestration query execute` (ClickHouse: runs/batches/spans/records) SQL examples. For `storage query` (workspace storage), see the `cargo-storage` skill.
> `references/examples/segments.md` — segment fetch and filter examples
> `references/nodes.md` — full node creation guide (kinds, native actions, expressions, validation, routing)
> `references/node-diagram.md` — **draw a node graph as a Mermaid flowchart** (`node diagram`): every source (workflow / draft / release / run / raw nodes), marking paid nodes, highlighting a failing node, and why diagrams key on `uuid` rather than `slug`
> `references/node-selection.md` — **how to pick the right node and avoid unnecessary `python` nodes** (decision table, native LLM `agent` node, template-expression limits, the silent-undefined footgun, inspecting node data via `runContext`, Pyodide sandbox limits, what survives a `delay`, group result access)
> `references/filter-syntax.md` — complete filter condition reference
> `references/polling.md` — async polling patterns, error handling, retry strategies
> `references/response-shapes.md` — full JSON response structures
> `references/troubleshooting.md` — common errors, plus a "Debugging a workflow run" section for runs that succeed but produce wrong output (wrong-branch routing, empty downstream values)

> **Diagnosing after the fact?** For the ordered forensic runbooks built on these surfaces — trace one run, sweep a batch for errors grouped by root cause, profile a play's credit spend — load the [`cargo-diagnostics`](../cargo-diagnostics/SKILL.md) skill.

## Bootstrap

Already signed in (`cargo-ai whoami` returns a workspace)? Skip to the next section.

```bash
npm install -g @cargo-ai/cli            # no global install? prefix every command with `npx @cargo-ai/cli`
cargo-ai login --email [email protected]  # emailed code, no browser; creates the account on first use
                                        # alternatives: --oauth (browser) · --token <api-token> (CI)
cargo-ai whoami                         # confirm the active workspace before any write
```

Every command prints JSON to stdout; failures exit non-zero with `{"errorMessage": "..."}`. Anything that creates a run or a batch is async — pass `--wait-until-finished` or poll the matching `get`. When the full skill bundle is installed, [`../cargo/references/prerequisites.md`](../cargo/references/prerequisites.md) adds the CLI version pin, token scopes, and the admin-only surface.

## Discover resources first

Most commands require UUIDs. Always discover them before acting.

```bash
cargo-ai orchestration action list <query>  # actions across connectors, native, tools, agents (+ credits)
cargo-ai orchestration play list            # all plays (name, workflowUuid, modelUuid, segmentUuid)
cargo-ai orchestration tool list            # all tools (name, workflowUuid, description)
cargo-ai orchestration workflow list        # all workflows (uuid only — no name)
cargo-ai orchestration template list       # all workflow templates (slug, name, kind)
cargo-ai ai agent list                     # all agents (uuid, name)
cargo-ai ai template list                  # all AI agent templates (slug, name, languageModelSlug)
cargo-ai storage model list                # all models (uuid, name, slug, columns)
cargo-ai storage dataset list              # all datasets
cargo-ai segmentation segment list         # all segments (uuid, name, modelUuid)
cargo-ai connection connector list         # all connectors
```

**Plays vs tools:** Both are backed by a workflow. A **play** is a segment-driven automation — it reacts to data changes in a segment (records added, updated, removed). A **tool** is an on-demand workflow — triggered manually, via API, or on a cron schedule. Workflows don't have a `name` field; use `play list` or `tool list` to find names and extract the `workflowUuid`.

**Retrieve in the UI:** plays live at `app.getcargo.io/workspaces/<WORKSPACE_UUID>/plays/<PLAY_UUID>` and tools at `app.getcargo.io/workspaces/<WORKSPACE_UUID>/tools/<TOOL_UUID>`. Get `<WORKSPACE_UUID>` from `cargo-ai whoami` under `workspace.uuid`.

**Designing a new tool or play?** Check templates first — they are pre-built node graphs for common automation patterns (enrichment pipelines, CRM syncs, lead scoring) and are an excellent starting point. List templates with `cargo-ai orchestration template list` and inspect a specific one with `cargo-ai orchestration template get <slug>`. Templates are tagged by `kind` so you can find ones suited for tools (`"kind":"tool"`) or plays (`"kind":"play"`) right away. See `references/examples/templates.md` for the full guide.

**Compatibility rules:**

- **`run create`** — only works with **tool** workflows (or no `workflowUuid`). Play workflows return `playNotCompatible`.
- **`batch create`** — allowed data kinds depend on the workflow type:
  - **Play** workflows: `filter`, `recordIds`, `segment`, `change`. Trigger a play with `filter`; `segment` takes a standalone segment only, never the `segmentUuid` from `play list`.
  - **Tool** workflows (or no `workflowUuid`): `file`, `records`

## Quick reference

```bash
# Find an action (free — no run, no credits)
cargo-ai orchestration action list enrich company
cargo-ai orchestration action list send --kind connector --integration-slug slack

# Single actions
cargo-ai orchestration action execute --action '{"kind":"tool","toolUuid":"<uuid>"}' --data '{"domain":"acme.com"}'
cargo-ai orchestration action execute-batch --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany"}' --records '[{...},{...}]'
cargo-ai orchestration action get-output-schema --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany","config":{}}' # → {"schema": <JSON Schema>} without executing

# Workflows (chain multiple actions)
cargo-ai orchestration run create --workflow-uuid <uuid> --data '{"company":"Acme","domain":"acme.com"}'
cargo-ai orchestration run create --data '{"domain":"acme.com"}' --nodes '[...]'
cargo-ai orchestration batch create --workflow-uuid <uuid> --data '{"kind":"filter","modelUuid":"...","filter":{"conjonction":"and","groups":[]}}'

# AI agents
cargo-ai ai message create --chat-uuid <uuid> --parts '[{"type":"text","text":"..."}]'

# Data
cargo-ai orchestration query execute "SELECT count() FROM runs WHERE status='error'" # ClickHouse: spans, runs, batches, records
cargo-ai segmentation segment fetch --model-uuid <uuid> --filter '{"conjonction":"and","groups":[]}' --fetching-limit 100
# For SQL against workspace storage (Companies, Contacts, …), see the cargo-storage skill: `storage query execute`
```

## Polling async operations

All operations are asynchronous. Either poll until terminal state, or pass `--wait-until-finished` to block.

`action execute` returns a run. `action execute-batch` returns a batch. They poll the same way:

| Result type     | Poll command         | Interval | Done when                                      |
| --------------- | -------------------- | -------- | ---------------------------------------------- |
| Run             | `run get <uuid>`     | 2s       | `status` is `success`, `error`, or `cancelled` |
| Batch           | `batch get <uuid>`   | 5s       | `status` is `success`, `error`, or `cancelled` |
| Agent message   | `message get <uuid>` | 2s       | `status` is `success` or `error`               |

For long-running batches (1000+ records), increase the interval to 10-15s after the first minute.

## Execute actions

Run a single action — no workflow or node graph needed.

### Find it first — `action list`

```bash
cargo-ai orchestration action list enrich company          # all kinds
cargo-ai orchestration action list --kind tool             # this workspace's tools
cargo-ai orchestration action list send --kind connector --integration-slug slack
```

Free, executes nothing. All query terms must match (AND); a hit on the action
slug or name ranks above the integration, which ranks above the description.
Returns `{query, totalMatches, results[]}` where each result carries `name`,
`description`, `score`, an `action` object to paste straight into `execute` /
`execute-batch` / `get-output-schema`, the workspace `connectors` for that
integration, `credits` (the cost table, when the action bills), and
`autocompletes` (config fields that need a picked id — a HubSpot object type, a
Slack channel). Defaults to 20 results, max 50.

```bash
# One action, one record → returns a run
cargo-ai orchestration action execute \
  --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany"}' \
  --data '{"domain":"acme.com"}' \
  --wait-until-finished

# One action, many records → returns a batch
cargo-ai orchestration action execute-batch \
  --action '{"kind":"tool","toolUuid":"<tool-uuid>"}' \
  --records '[{"domain":"acme.com"},{"domain":"globex.com"}]' \
  --wait-until-finished
```

Action kinds: `tool`, `connector`, `agent`, `native`. See `references/examples/actions.md` for all action kinds, parameters, retry config, response shapes, and end-to-end examples.

> **A top-level action has no `config` — omit it.** Inputs belong in `--data` /
> `--records`; `execute` and `execute-batch` take the action with no `config` key
> at all, which is exactly what `action list` hands back, so its result pastes
> straight in. (`"config": {}` is still accepted there, harmlessly.)
>
> **The exception that will bite you: `get-output-schema` still *requires*
> `config`.** Give it the same action object from `action list` and it fails
> `400 — expected record, received undefined` at `action.config`. Add `"config": {}`
> for that one command. Workflow **nodes**, an alert's `--actions`, a play's
> `healthAlertActions`, and an agent's or MCP server's `--actions` require it too
> — that is where a node's real configuration lives.
>
> **Inputs put in `config` are now dropped, not rejected.** The guard that used to
> answer `A top-level action does not use action.config…` is gone, so the action
> runs with *no input* — you get a provider-side missing-field error or an empty
> result that never mentions `config`. Check this first when a call comes back
> empty for no visible reason.

> **`execute-batch` bills per record.** Pass a 10–20 record slice of `--records` first, report the observed per-record cost and hit-rate, and get approval (with the full record count and credit estimate) before sending the rest — same gate as [Create a batch](#the-sample-gate).

### Resolve an action's output schema (without executing)

**Never guess what an action outputs.** Two free sources — no run, no credits:

1. **Connector actions:** the integration catalog carries the output schema inline — `integration get <slug>` (and `integration list`) return `actions.<actionSlug>.output.schema` next to the input `config.jsonSchema`. Not every action declares one.
2. **Any action kind** (`tool` / `connector` / `agent` / `native`) — resolve it with the same `--action` object as `action execute`:

```bash
cargo-ai orchestration action get-output-schema \
  --action '{"kind":"connector","integrationSlug":"clearbit","actionSlug":"enrichCompany","config":{}}'
# → {"schema": {"type": "object", "properties": {...}}}  — the JSON Schema is under the top-level "schema" key
```

Actions that declare no output schema fail with `"Action has no output schema."` (non-zero exit, status 404) — that's the signal to fall back to inspecting `runContext` from a real run. Use these to:

- Know which fields a downstream node can read (`{{nodes.<slug>.<field>}}`) **before** wiring the graph.
- See an `agent` action's real output envelope — a default free-text agent resolves to `{"schema":{"type":"object","properties":{"answer":{"type":"string"}}}}`, which is why downstream references need `{{nodes.<slug>.answer...}}`.
- Map an action's output onto storage columns without a throwaway run.

See `references/examples/actions.md` ("Resolve an action's output schema") for verified per-kind examples and the response/error shapes.

## Create a run

A run processes a single record through a workflow. Use `run create` when you need to **chain multiple actions** together via a node graph, or when running an existing tool workflow.

**Runs only work with tool workflows.** Play workflows return `playNotCompatible` — use `batch create` instead.

```bash
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"company":"Acme","domain":"acme.com"}'
# → Poll with: cargo-ai orchestration run get <run-uuid>

# Or wait synchronously — blocks until the run reaches a terminal state and returns the final result
cargo-ai orchestration run create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"company":"Acme","domain":"acme.com"}' \
  --wait-until-finished
```

Also supports `--release-uuid` to pin a specific release.

**Cancelling runs:**

```bash
cargo-ai orchestration run cancel --workflow-uuid <uuid> --uuids run-uuid-1,run-uuid-2
```

See `references/examples/tools.md` for file uploads, monitoring, and cancellation. See `references/nodes.md` for custom node graphs.

## Create a batch

> **Sample first, then ask before enrolling everything — blocking.** A batch fans one workflow across every record in its data source, so a mistake and a full bill land together. Never enroll a full segment/file/model on the first attempt: run a **10–20 record sample**, report what it cost and returned, then ask the user to approve the full enrollment with the **record count and credit estimate** in the question. Mechanics below; the spend rules behind it are [`../cargo-gtm/references/cost-discipline.md`](../cargo-gtm/references/cost-discipline.md).

### The sample gate

**1. Count the pool first (free).** Never quote an estimate from a guess:

```bash
cargo-ai segmentation segment get <segment-uuid>          # → recordsCount (also on `segment list`)
cargo-ai storage query execute "SELECT count() FROM <dataset>.<model>"   # for a filter/model source
# For a file source: wc -l on the CSV, minus the header row.
```

**2. Run 10–20 records through the exact workflow and config.** Sample by data kind:

```bash
# Play workflow, segment source → reuse the segment's own filter, capped by `limit`
cargo-ai segmentation segment get <segment-uuid>          # → copy .filter and .modelUuid
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"<modelUuid>","filter":<segment.filter>,"limit":15}' \
  --wait-until-finished

# Play workflow, explicit records → pick 10–20 ids
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"recordIds","modelUuid":"<modelUuid>","ids":["id-1","…","id-15"]}'

# Tool workflow, inline records → slice the array
cargo-ai orchestration batch create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"kind":"records","records":[ /* first 15 only */ ]}'

# Tool workflow, file → upload a truncated CSV (header + 15 rows), not the full file
head -n 16 leads.csv > leads-sample.csv
cargo-ai workspaceManagement file upload --file ./leads-sample.csv
```

`limit` is the sampling lever for `kind: "filter"`. `kind: "segment"` and `kind: "change"` have **no limit** — they always enroll the whole set, so sample via `filter` or `recordIds` and switch to `segment` only for the approved full run.

**3. Report the sample, then ask.** The confirmation must carry both numbers the user needs to decide:

```
Sample: 15 of 1,240 records · 6.2 credits (0.41/record) · 13/15 enriched (87%)
Full enrollment: 1,225 remaining records ≈ 502 credits (balance: 780)

Enroll all 1,225? Or:
  1. Enroll all 1,225 (≈502 cr, leaves ~278)
  2. Trim scope — e.g. the 610 records with a domain set (≈250 cr)
  3. Stop here and review the sample output first
```

Wait for an explicit answer. **Do not enroll the full set on an unanswered question**, and don't treat approval of the sample as approval of the full run. Skip the gate only when the batch is free (no paid nodes) *and* small, or when the user has already named the scope and approved the cost this session.

Batches process multiple records at once. Allowed data kinds depend on the workflow type:

- **Play** workflows: `filter`, `recordIds`, `segment`, `change`
- **Tool** workflows (or no `workflowUuid`): `file`, `records`

Use `filter` to trigger a play — it queries the model directly. `segment` only
accepts a **standalone** segment from `segmentation segment list`; passing the
`segmentUuid` that `play list` returns is rejected (`segmentLinkedToPlay`, or
`noRecords` on older backends) because a play's generated segment never has a
populated record count.

```bash
# Play workflow — run over the play's model (empty filter = all rows)
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"...","filter":{"conjonction":"and","groups":[]}}'

# Tool workflow — run on a file
cargo-ai orchestration batch create \
  --workflow-uuid <tool.workflowUuid> \
  --data '{"kind":"file","s3Filename":"..."}'
# → Poll with: cargo-ai orchestration batch get <batch-uuid>

# Or wait synchronously — blocks until the batch reaches a terminal state and returns the final result
cargo-ai orchestration batch create \
  --workflow-uuid <play.workflowUuid> \
  --data '{"kind":"filter","modelUuid":"...","filter":{"conjonction":"and","groups":[]}}' \
  --wait-until-finished
```

**Downloading results:** get the `releaseUuid` from batch get, then `cargo-ai orchestration release get <release-uuid>` to find `nodes[].slug`, then `cargo-ai orchestration batch download --uuid <batch-uuid> --output-node-slug <slug>`.

**Cancelling a batch:**

```bash
cargo-ai orchestration batch cancel <batch-uuid>
```

See `references/examples/plays.md` and `references/examples/tools.md` for filtering, record IDs, file uploads, monitoring, and cancellation.

## Send a message to an AI agent

```bash
cargo-ai ai agent list                                    # 1. Find the agent
cargo-ai ai chat create \                                 # 2. Create a chat
  --trigger '{"type":"draft"}' \
  --agent-uuid <agent-uuid> --name "Research session"
cargo-ai ai message create \                              # 3. Send a message
  --chat-uuid <chat-uuid> \
  --parts '[{"type":"text","text":"Find the VP of Sales at Acme Corp"}]'
# → Extract assistantMessage.uuid, poll with: cargo-ai ai message get <uuid>
#   Done when .message.status is "success" (read .parts) or "error" (read .errorMessage)
```

Also supports `--actions`, `--resources`, `--language-model-slug`, `--temperature`, `--max-steps`, and `--wait-until-finished` (blocks until the assistant message reaches a terminal status). See `references/examples/agents.md` for multi-turn conversations, action/resource injection, and model selection.

## Inspect records

Records are individual items processed by a workflow. Use these commands to list, count, download, or cancel records within a workflow.

```bash
# List records for a workflow
cargo-ai orchestration record list --workflow-uuid <uuid> --limit 50

# Filter by batch or status
cargo-ai orchestration record list --workflow-uuid <uuid> --batch-uuid <uuid> --statuses error

# Count records
cargo-ai orchestration record count --workflow-uuid <uuid>

# Download records as a file
cargo-ai orchestration record download --workflow-uuid <uuid>

# Get per-node execution metrics
cargo-ai orchestration record get-metrics --workflow-uuid <uuid>

# Cancel records
cargo-ai orchestration record cancel --workflow-uuid <uuid> --ids record-id-1,record-id-2
```

## Query orchestration history (orchestration query)

Run SQL against orchestration runtime tables — `spans`, `runs`, `batches`, `records` — with `orchestration query execute`. Use this for ad-hoc analytics on workflow execution (error rates, throughput, slowest nodes) without the workflow-scoped filters of `run get-metrics` / `run count`.

```bash
cargo-ai orchestration query execute "SELECT count() FROM runs WHERE status = 'error'"
cargo-ai orchestration query execute "SELECT status, count() FROM batches GROUP BY status"
cargo-ai orchestration query execute "SELECT * FROM spans ORDER BY execution_started_at DESC LIMIT 10"
```

Tables are referenced without a schema prefix — just `spans`, `runs`, `batches`, or `records`. Workspace scoping is applied automatically. The query is read-only; DDL, table functions, dictionary accessors, and introspection are denied. See `references/examples/queries.md` for the schemas, example queries, and limits.

## Fetch segment data

Retrieve live records from a segment. **IMPORTANT:** requires `--model-uuid` (not `--segment-uuid`). Get the `modelUuid` from `segment list`. Filter JSON uses `conjonction` (not `conjunction`) — this is intentional.

```bash
cargo-ai segmentation segment fetch \
  --model-uuid <uuid> \
  --filter '{"conjonction":"and","groups":[]}' \
  --fetching-limit 100 --fetching-offset 0
```

Supports `--sort`, `--enrich`, and `--sync`. See `references/filter-syntax.md` for the full filter syntax and `references/examples/segments.md` for filtering, pagination, sorting, enrollment filters, and enrichment.

**Managing segments:**

```bash
# Update a segment's name or filter
cargo-ai segmentation segment update --uuid <segment-uuid> --name "Updated Name"
cargo-ai segmentation segment update --uuid <segment-uuid> --filter '{"conjonction":"and","groups":[...]}'

# Remove a segment (fails if linked to a workflow)
cargo-ai segmentation segment remove <segment-uuid>
```

## Use a workflow template

Templates are pre-built node graphs for common automation patterns (enrichment pipelines, CRM syncs, lead scoring). Browse with `template list`, inspect with `template get <slug>`, fill in placeholders, validate, and run.

```bash
cargo-ai orchestration template list              # list available templates
cargo-ai orchestration template get <slug>        # get template nodes + config
```

See `references/examples/templates.md` for the full guide including placeholder conventions and end-to-end examples.

## Validate and test nodes

Always validate custom node graphs before running them.

```bash
cargo-ai orchestration node validate --nodes '[...]'
# → { "outcome": "valid" } or { "outcome": "notValid", "invalidNodes": [...] }
```

Then **show it before deploying it** — `validate` proves the graph is well-formed,
not that it does what the user asked for:

```bash
cargo-ai orchestration node diagram --nodes '[...]' --format ascii --raw   # free, runs nothing
```

Same command draws a deployed workflow (`--workflow-uuid`), a draft (`--draft`), a
release (`--release-uuid`), or the graph a run executed (`--run-uuid`). See
`references/node-diagram.md`.

For debugging, use `node compute` (dry-run expressions) or `node execute` (live test of one node **of an existing workflow** — needs `--workflow-uuid` + `--release-uuid` + `--computed-config`, and costs credits; for anything that isn't node-level debugging, use `action execute` instead). For runs that complete with `status: success` but produce wrong output (wrong branch taken, empty downstream values), use `run.executions[].title` from `run get` only as a quick summary — it may be truncated — and read `runContext.<nodeSlug>` (returned at the top level of the same `run get <run-uuid>` response) to verify field-level data. See `references/troubleshooting.md` → "Debugging a workflow run" and `references/nodes.md` for the full node creation guide, validation error codes, and examples.

## Help

Every command supports `--help`:

```bash
cargo-ai orchestration run create --help
cargo-ai orchestration template list --help
cargo-ai orchestration node validate --help
cargo-ai ai message create --help
cargo-ai orchestration query execute --help
```