> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getcargo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> defineWorkflow — the TypeScript DSL that backs tools and plays. Author logic in code that compiles to the same nodes as the visual editor.

A **workflow** is the logic that a [tool](/tools/overview) runs or a [play](/plays/overview) executes per row. You author it in TypeScript with `defineWorkflow` from `@cargo-ai/cdk` (re-exported from `@cargo-ai/workflow-sdk`).

Workflows authored in code and workflows drawn on the canvas compile to the **same** artifact and run identically — the code DSL is just a different authoring surface.

## Define a workflow

```ts theme={null}
import { defineWorkflow } from "@cargo-ai/cdk";
import { z } from "zod";
import { enricher } from "../agents/enricher";

export const enrichFlow = defineWorkflow(
  "enrich-contact",
  {
    input: z.object({ email: z.string() }),
    output: z.object({ company: z.string(), enriched: z.boolean() }),
    uses: { enricher }, // tools/agents this workflow calls, by handle
  },
  ({ input, uses, ai }) => {
    const company = uses.enricher({
      prompt: `Which company owns the email domain of ${input.email}?`,
    });
    return { company, enriched: true };
  },
);
```

* `input` / `output` are Zod schemas — they become the workflow's form fields and end variables.
* `uses` forward-references tools and agents by handle so Cargo orders the deploy and injects real uuids.
* The **body is parsed, not executed.** The SDK reads the function's source and lowers it to workflow nodes; it never runs at build time. That's why the body can't be `async` and why only a subset of JavaScript is supported (see below) — anything that must run at runtime goes in a `js()` block.

## What you can express in code

| Capability              | In code                                                             |
| ----------------------- | ------------------------------------------------------------------- |
| Connector actions       | `integrations.<slug>.<action>(input, { retry, continueOnFailure })` |
| Native actions          | `native.<action>(input)` — `script`, `python`, `note`, `scoring`    |
| Call a tool / agent     | `uses.<key>(input)` (declared in `uses`)                            |
| Branch                  | `if (cond) { … } else { … }`                                        |
| Switch                  | `if … else if … else …`                                             |
| Loop over a list        | `for (const x of items) { … }`                                      |
| Inline AI value         | `ai(\`prompt \${input.x}\`)\`                                       |
| Custom JavaScript       | `js(({ nodes }) => …)`                                              |
| Human approval          | `humanReview(config, { approved, declined })`                       |
| Memory (KV)             | `memory.get / set / getOrSet / remove / increment / decrement`      |
| Traffic split / balance | `split(30, { left, right })`, `balance([...])`                      |

Supported statements: `const` / `let` (single identifier), `if` / `else if` / `else`, `for…of`, and `return`. Not supported: `async`/`await`, `try/catch`, `throw`, closures, nested function declarations, and destructuring assignments.

## Built-in actions

### `ai()` — inline AI value

Ask an LLM for a single value anywhere an expression fits:

```ts theme={null}
const summary = ai(`Summarize what ${input.domain} sells in one sentence.`);
```

### `js()` — runtime JavaScript

Escape hatch for logic the parser can't lower — the function you pass **does** run at runtime, as a script node, with access to upstream node outputs:

```ts theme={null}
const normalized = js(({ nodes }) => {
  const raw = nodes.enricher.company;
  return raw.trim().toLowerCase();
});
```

Use `js()` for transformations, parsing, and computation; keep orchestration (branches, loops, action calls) in the DSL where it compiles to inspectable nodes. `native.python(...)` is the same idea in Python.

### `humanReview()` — human-in-the-loop gate

Posts a Slack message with Approve / Decline buttons and **blocks the run** until a reviewer clicks one (or the timeout fires — timeouts auto-decline):

```ts theme={null}
humanReview(
  {
    connectorUuid: slack.uuid,        // the Slack connector to post through
    channelId: "C0123456789",
    title: "Approve outreach?",
    content: `Sending sequence to *${input.email}*`,
    timeoutMilliseconds: 86_400_000,  // default: 24 hours
    enableEditButton: true,           // default: true
  },
  {
    approved: () => integrations.lemlist.createLead({ campaignId: "cam_123", email: input.email }),
    declined: () => memory.increment({ key: "declined", expiresIn: { value: 30, unit: "day" } }),
  },
);
```

### `memory.*` — key-value state

Persist state across runs without a model. Each call is one node and returns `{ result }`:

```ts theme={null}
const seen = memory.get<number>({ key: input.email });
if (seen.result === null) {
  memory.set({ key: input.email, value: 1, expiresIn: { value: 7, unit: "day" } });
}
```

* `scope: "workflow"` (default) is private to this workflow; `"workspace"` is shared across all workflows.
* `set`, `getOrSet`, `increment`, and `decrement` require `expiresIn` (`{ value, unit }` with `second` / `minute` / `hour` / `day`).
* `increment` / `decrement` are atomic counters — the safe way to rate-limit or cap sends.

### `split()` and `balance()` — traffic shaping

```ts theme={null}
// A/B: ~30% of records run left, the rest right
split(30, {
  left: () => integrations.lemlist.createLead({ campaignId: "cam_A", email: input.email }),
  right: () => integrations.lemlist.createLead({ campaignId: "cam_B", email: input.email }),
});

// Round-robin: each record runs exactly ONE route; returns the first non-null output
const result = balance([
  () => integrations.hunter.findEmail({ domain: input.domain }),
  () => integrations.dropcontact.findEmail({ website: input.domain }),
]);
```

Route bodies must be inline zero-argument arrows.

## The action catalog

Cargo ships **120+ actions** across enrichment, CRM, AI, communication, and logic. Three ways to browse what's callable:

```bash theme={null}
cargo-ai connection integration list             # every integration and its slug
cargo-ai connection integration get hubspot      # one integration's actions + config schemas
cargo-ai cdk types                               # generate the typed integrations.* registry
```

* **In code**, `cargo-ai cdk types` writes the registry into `.cargo-ai/`, so `integrations.<slug>.<action>` autocompletes real slugs and config shapes in your editor.
* **In the UI**, the same catalog is the node palette in the [tool editor](/tools/using-ui) — searchable by category, with per-field documentation inline.
* **Per integration**, each page in the [Integrations tab](/integration/overview) documents its actions, parameters, and credit costs.

## Code covers a subset of the canvas

The code DSL is designed for linear and branching tool/play logic. It does **not** yet mirror the entire visual node palette. These are **canvas-only** today — build them in the [tool editor](/tools/using-ui) if you need them:

* **Filter**, **Delay**, and **Variables** nodes
* **Model** actions — search, insert, update, upsert, remove, custom column
* **File search** and **Model ask** standalone AI nodes
* **Allocate** (lead routing)

<Note>
  Because both paths compile to the same node format, you can start a workflow
  in code and finish complex, canvas-only parts in the UI, or vice versa. But
  the CDK never reads UI edits back into your files — once you finish a
  workflow in the UI, re-deploying a changed code definition overwrites the
  canvas work. See [Code and UI
  round-trips](/deploy/state-and-drift#code-and-ui-round-trips) before mixing
  the two on one resource.
</Note>

## Retry and failure options

Every connector, tool, and agent call accepts a second options argument:

```ts theme={null}
integrations.hunter.findEmail({ domain }, {
  retry: { maximumAttempts: 3, initialInterval: 1000, backoffCoefficient: 2 },
  continueOnFailure: true,
});
```

## Deploying workflow changes

A workflow is deployed as part of the tool or play that owns it (`cargo-ai cdk deploy`). You can also push a Workflow SDK module to an existing workflow directly:

```bash theme={null}
cargo-ai orchestration release deploy-draft --file ./enrichFlow.ts
```
