To run a one-off go-to-market job from code, such as enrolling 50 contacts into a play, enriching a CSV or backfilling a field, declare it with defineBatch or defineRun in a file under outputs/: cargo-ai project plan lists it, and the next cargo-ai project deploy runs it exactly once. A ledger in the project’s deploy state records it before it starts, so no later deploy runs it again.
A resource says what exists. An execution says what to do, once. Declaring it in the repository means the pilot enrollment gets the same review as the play it enrolls into.
Declare a batch #
// outputs/2026-10-14-expansion-pilot/pilot.ts
import { defineBatch } from "@cargo-ai/cdk";
import { contacts } from "../../infra/models/contacts";
import { expansion } from "../../infra/plays/expansion";
export const pilot = defineBatch("2026-10-14-expansion-pilot", {
workflow: expansion,
data: {
kind: "filter",
model: contacts,
filter: {
conjonction: "and",
groups: [
/* … */
],
},
limit: 50,
},
});
idis what Cargo remembers the execution by: lowercase letters, digits, hyphens and underscores, unique in the project. Start it with a date and executions run in the order you wrote them.workflowis a play, a tool, or a workflow written inline withdefineWorkflow. For one the project does not define, passworkflowRef(uuid).datais where the records come from:
kind | Runs on |
|---|---|
filter | a model’s records matching a filter, at most limit (required) |
segment | the records in a segment |
recordIds | exactly these record ids |
form | records written in code, checked against the workflow’s form |
file | the rows of a CSV in the repository, at most 50 MB |
A play batch takes any kind except file, and its model must be the play’s own.
To import a CSV that sits next to the batch:
// outputs/2026-10-20-import/import.ts
import { defineBatch } from "@cargo-ai/cdk";
import { enrich } from "../../infra/tools/enrich";
export const leads = defineBatch("2026-10-20-import", {
workflow: enrich,
data: {
kind: "file",
path: new URL("./leads.csv", import.meta.url).pathname,
},
});
Declare a run #
defineRun runs a tool or an inline workflow once, on one input:
import { defineRun } from "@cargo-ai/cdk";
import { backfillOwners } from "../../infra/tools/backfillOwners";
export const backfill = defineRun("2026-10-15-backfill-owners", {
workflow: backfillOwners,
data: { since: "2026-01-01" },
});
A play runs on records, so it only takes a batch.
Plan, then deploy #
cargo-ai project plan # lists pending executions and the ones already in the ledger
cargo-ai project deploy # applies resources first, then starts each pending execution
The plan prints pending executions under “Will execute once, after the resources (id order)” and past ones under “In the ledger, so never run again”. The deploy applies resources first, so an execution can use a play created in the same change, then reports each execution as started, refused, unconfirmed or not started. Only not started (it failed before reaching Cargo, such as a file upload) is retried by the next deploy. --draft never runs executions.
Executions are read from outputs/, in any folder under it. In a Manifest repository with resources in infra/, that is the outputs/ at the repository root. A cargo.json can name other folders in executions.
Exactly once #
| You change | What happens |
|---|---|
| The file name or folder | Nothing, the id is the same |
| An execution that already ran | Nothing; the plan lists it as in the ledger |
| A batch’s CSV, after it ran | Nothing; it was read once |
| The id | A new execution, run by the next deploy |
| Delete one that has not run | It never runs |
There is no retry. To run something again, give it a new id. A refused execution also stays in the ledger: fix it and give it a new id.
On a connected repository #
When the repository is connected to Cargo, the pull request previews each execution: records, sends and credits, and an approval gate on large ones. The merge starts an execution only if a plan counted that exact version, the plan was approved, and it now runs on no more records than the preview allowed (up to 10% over a filter’s or segment’s count). Otherwise it is held and stays pending.
Caveats #
- An execution acts on the outside world. A play it enrolls records into may send emails or LinkedIn actions, and reverting the code does not take them back.
- Executions need the deploy state in your workspace. A project whose
cargo.state.jsonstill holds the full map refuses them untilcargo-ai project state createmoves it. - Spelling matters: the filter key is
conjonction, as in the rest of Cargo’s filter API.
Full reference: Executions in the Cargo docs. The whole lifecycle: GTM as code: the complete guide.