Your GTM belongs in gitRegister
Blog

Declare One-Off Go-to-Market Batches and Runs in Code

16 Oct
5min read

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 #

typescript
// 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,
  },
});
  • id is 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.
  • workflow is a play, a tool, or a workflow written inline with defineWorkflow. For one the project does not define, pass workflowRef(uuid).
  • data is where the records come from:
kindRuns on
filtera model’s records matching a filter, at most limit (required)
segmentthe records in a segment
recordIdsexactly these record ids
formrecords written in code, checked against the workflow’s form
filethe 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:

typescript
// 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:

typescript
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 #

shellscript
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 changeWhat happens
The file name or folderNothing, the id is the same
An execution that already ranNothing; the plan lists it as in the ledger
A batch’s CSV, after it ranNothing; it was read once
The idA new execution, run by the next deploy
Delete one that has not runIt 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.json still holds the full map refuses them until cargo-ai project state create moves 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.

AurelienOct 16, 2026

Give your agents a runtime

Bring the agents you have.Start free, deploy in one command.