Zurück zu Skills
getcargohq/cargo-skillsVor der Ausführung prüfen

SKILL DETAIL

cargo-cdk

getcargohq/cargo-skills/cargo-cdk

The cargo-cdk skill lets you manage an entire Cargo workspace as code. You declare connectors, models, plays, tools, agents, MCP servers, segments, context, folders, files, workers, and apps in TypeScript using define* builders, then reconcile those declarations with live infrastructure using the cargo-ai cdk commands (init → types → plan → deploy), the way you would run Pulumi or the AWS CDK. This skill is for scenarios where you need reproducibility, version control, and repeatable deployments across environments (e.g., dev to prod). It covers the full lifecycle: plan (offline diff), deploy (create/update resources in dependency order), destroy (tear down), refresh (drift detection), import (adopt existing resources), and rollback. It also supports generating typed configuration to ensure your definitions match your workspace's real integration schemas.

Installationen · 135Quelle ansehen

Installation

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

Skill-Dateien

SKILL.md

Zuletzt synchronisiert · 29.08.2026

guides/authoring-resources.md
# Authoring resources

Every Cargo resource has a `define*` builder imported from `@cargo-ai/cdk`. Each
returns a **handle**; you wire resources together by passing one handle into
another builder. **Importing a `.ts` file is registration** — each `define*` call
registers itself as a side effect, so there is no manifest to maintain. The loader
imports every `.ts` under the project directory (skipping worker/app bundle
sub-directories, which have their own `package.json`).

For the exact spec fields and outputs of each builder, see
[`../references/resources.md`](../references/resources.md).

## The handle / ref model (the one thing to internalize)

Pass the **handle** a builder returned — never `handle.uuid`:

```ts
export const contacts = defineModel("contacts", {
  dataset: hubspot, // ← the connector handle, not hubspot.datasetUuid
});
```

The CDK reads the dependency (connector → model), deploys the connector first, and
injects its real dataset uuid at deploy time. Your variable graph *is* the
dependency graph.

For a resource you did **not** define in code (an existing connector, folder,
etc.), use a `xxRef("uuid")` helper — it produces the same handle shape from a
literal uuid:

```ts
import { defineModel, connectorRef } from "@cargo-ai/cdk";

export const leads = defineModel("leads", {
  dataset: connectorRef("6f0c…"), // an already-authenticated connector, by uuid
});
```

Ref helpers: `connectorRef`, `datasetRef`, `modelRef`, `folderRef`, `playRef`,
`memberRef` (from `@cargo-ai/cdk`) and `toolRef`, `agentRef` (re-exported from the
workflow SDK). Each is branded by kind, so a model can't be passed where a
connector is expected.

**Per-call options:** where a reference takes options, wrap it as
`{ ref, …options }`:

```ts
models: [{ ref: contacts, readOnly: true }],   // handle + options
subAgents: [{ ref: enricher, waitUntilFinished: true }],
tools: [enrich],                               // bare handle when no options
```

## Secrets — `secret()` + `env()`

Wire credentials with `secret("ENV_VAR")`. The value is read from the environment
**at deploy time**, kept out of the content hash and out of `cargo.state.json`, so
rotating the token never reads as drift:

```ts
import { defineConnector, secret } from "@cargo-ai/cdk";

export const hubspot = defineConnector("hubspot", {
  integration: "hubspot",
  config: { method: "privateApp", accessToken: secret("HUBSPOT_API_KEY") },
});
```

`secret()` is only accepted where a credential/encryption field is expected. Use
`env("NAME")` for non-secret config values that come from the environment. Export
the variables before deploying — a missing one fails deploy with an unresolved
`${NAME}` placeholder rather than sending a literal to the API.

## The builder catalog

Data & schema:

```ts
// Connector — a data source or LLM provider. Creating a data connector
// auto-creates a dataset that models source from. OAuth connectors that can't be
// declared in code use `adopt: true` to link the existing authenticated instance.
export const hubspot = defineConnector("hubspot", {
  integration: "hubspot",
  config: { method: "privateApp", accessToken: secret("HUBSPOT_API_KEY") },
});
export const openai = defineConnector("open_ai", { integration: "openAi", adopt: true });

// Model — a table sourced from a connector's dataset by an extractor.
export const contacts = defineModel("contacts", {
  dataset: hubspot,                 // connector handle → its dataset is injected
  extractSlug: "fetchRecords",
  config: { objectType: "contacts", columnSelectionMode: "all" },
  folder: modelsFolder,
  schedule: { type: "cron", cron: "0 * * * *" },
});
```

Automation:

```ts
// Tool — backed by a workflow. defineWorkflow compiles the logic; defineTool
// creates the tool and deploys that workflow as its release.
const enrichFlow = defineWorkflow(
  "enrich-contact",
  { input: z.object({ email: z.string() }), output: z.object({ company: z.string() }), uses: { enricher } },
  ({ input, uses }) => ({ company: uses.enricher({ prompt: `Company for ${input.email}?` }) }),
);
export const enrich = defineTool("enrich", { workflow: enrichFlow, emojiSlug: "mag" });

// Play — runs a per-row workflow as a data model's rows change.
export const onboarding = definePlay("onboarding", {
  model: contacts,
  workflow: onboardRow,
  changeKinds: ["added", "updated"],
  runCreationRule: "always",
  schedule: { type: "watch" },
});

// Agent — references models, tools, sub-agents, connector actions, and the LLM
// connector, each by handle.
export const sdr = defineAgent("sdr", {
  connector: openai,
  languageModel: "gpt-4o",
  systemPrompt: "Qualify inbound leads.",
  models: [{ ref: contacts, readOnly: true }],
  tools: [enrich],
  subAgents: [{ ref: enricher, waitUntilFinished: true }],
  connectorActions: [{ integration: "hunter", actionSlug: "emailFinder" }],
  folder: agentsFolder,
});

// MCP server — bundles tools, agents, and models behind one MCP endpoint.
export const crm = defineMcpServer("crm", { tools: [enrich], agents: [sdr], models: [{ ref: contacts }] });
```

Organization & knowledge:

```ts
// Folder — per-kind (a "model" folder and an "agent" folder are separate).
export const modelsFolder = defineFolder("crm-models", { kind: "model", name: "CRM" });

// RECOMMENDED: route every CDK-managed resource into a dedicated, clearly
// labelled folder (via each builder's `folder:`), so a human in the UI sees at a
// glance that these resources are owned by code and shouldn't be hand-edited —
// manual UI changes read back as drift on the next `plan`. Because folders are
// per-kind, give each kind its own but share ONE short, recognizable prefix, e.g.
// `🔒 CDK` (or `🔒 CDK Models`, `🔒 CDK Agents`). Keep names short — long labels
// truncate in the folder tree; the lock emoji is the "don't touch" cue.
export const cdkModels = defineFolder("cdk-models", { kind: "model", name: "🔒 CDK Models" });
export const cdkAgents = defineFolder("cdk-agents", { kind: "agent", name: "🔒 CDK Agents" });

// File — content uploaded from a local path (hashed at define time, so edits show as drift).
export const playbook = defineFile("playbook", {
  path: new URL("./playbook.md", import.meta.url).pathname,
  name: "SDR Playbook",
});

// Context — the workspace's git-backed GTM knowledge base as code. Singleton;
// additive (files added in the UI are left in place).
export const context = defineContext({ dir: "context" });
```

Revenue org & segmentation:

```ts
// Segment — a saved view over a model (filter is required; model is immutable).
export const hotLeads = defineSegment("hot-leads", { model: contacts, filter: { /* … */ } });

// Capacity / Territory — revenue-org planning over members and a model.
export const capacity = defineCapacity("ae-capacity", { model: contacts, /* … */ });
export const territory = defineTerritory("west", { model: contacts, members: [memberRef("…")] });
```

Hosting (async builds — see [`deploy-and-state.md`](deploy-and-state.md)):

```ts
// Worker — the hosted worker slot. `path` points at a BUILT bundle dir
// (index.js + manifest.json + package.json + package-lock.json). Author the
// runtime code with createWorker from @cargo-ai/worker-sdk and build to index.js.
export const webhook = defineWorker("webhook", {
  path: new URL("./webhook", import.meta.url).pathname,
  description: "Receives inbound lead webhooks.",
});

// App — a hosted Vite SPA. `path` points at the app package root.
export const dashboard = defineApp("dashboard", {
  path: new URL("./dashboard", import.meta.url).pathname,
});
```

Observability:

```ts
// Alert — a scheduled threshold check; fires actions as runs on breach. The
// scope wires the watched resource by handle, and scope + threshold are a
// matched pair (TS narrows the metric menu to the scope's kind).
export const syncErrors = defineAlert("crm-sync-errors", {
  schedule: { type: "cron", cron: "*/30 * * * *" },
  scope: { kind: "runs", workflow: onboarding },   // the definePlay handle
  threshold: { metric: "errorRate", operator: "gte", value: 10 },
  actions: [
    // Bare { ref, config } for an agent; config is templated against the firing
    // context. Typed connector/tool actions use the alertConnectorAction /
    // alertToolAction helpers (config checked against the action's input schema).
    { ref: sdr, config: { message: "CRM sync error rate at {{event.value}}% — {{alert.url}}" } },
  ],
});
```

`defineAlert` is the declarative front for the observability domain — the full
scope/threshold matrix, metric units, and firing semantics live in
[`../../cargo-observability/SKILL.md`](../../cargo-observability/SKILL.md).

The full `full` template wires all of the above together end-to-end — see
[`../references/examples/full-workspace.md`](../references/examples/full-workspace.md).

## Workflow bodies (`defineWorkflow`) — parsed, not executed

`defineTool` and `definePlay` take a `workflow:` built with `defineWorkflow`. The
body callback is **parsed, not executed** — the SDK reads the function's source
and lowers it into the engine's node graph. So the body must be a supported JS
subset (no `await`, `throw`, `try/catch`, closures, or destructuring).

```ts
defineWorkflow(
  "onboard-contact",
  {
    input: z.object({ email: z.string() }),
    output: z.object({ welcomed: z.boolean(), message: z.string() }),
    uses: { enrich }, // reference tools/agents by handle here (or toolRef/agentRef)
  },
  ({ input, uses, ai, integrations, native }) => {
    const enriched = uses.enrich({ email: input.email }); // typed to the tool's I/O
    const message = ai(`Welcome ${input.email} at ${enriched.company}.`);
    return { welcomed: true, message };
  },
);
```

- **`uses.<key>(input)`** calls a referenced tool/agent; it's typed to that
  resource's input and returns a `Ref` you can dot-access (`enriched.company`).
- **`integrations.<slug>.<action>({…})`** calls a connector action. The
  `integrations` registry is **empty until you run `cargo-ai cdk types`** (see
  [`typed-config.md`](typed-config.md)); `native.*` works without a sync.
- Control flow lowers idiomatically: `if/else` → branch, `else if` → switch,
  `for (const x of xs)` → group. `ai("…")` inline-completes; `js(({nodes}) => …)`
  is the escape hatch for logic outside the supported subset. Flow helpers:
  `balance`, `split`, `humanReview`, `memory`.

For the full workflow-authoring surface (per-call retry/fallback, the supported-JS
table, toolchain requirements), that lives in the `@cargo-ai/workflow-sdk` docs —
`defineWorkflow` is re-exported from `@cargo-ai/cdk`, so you import it from the one
package.
guides/deploy-and-state.md
# Deploy & state

The deploy engine compiles your `define*` graph, diffs it against
`cargo.state.json`, and reconciles the difference to live Cargo infrastructure in
dependency order — persisting state after **each** resource so a mid-deploy crash
leaves a recoverable file.

For every flag, see [`../references/commands.md`](../references/commands.md).

## plan → deploy → destroy

```bash
# Offline: compile the graph and diff against cargo.state.json. No API calls.
cargo-ai cdk plan --dir my-workspace

# Create/update resources in dependency order; write cargo.state.json.
cargo-ai cdk deploy --dir my-workspace          # prompts for confirmation
cargo-ai cdk deploy --dir my-workspace --yes    # non-interactive (CI)

# Tear down.
cargo-ai cdk destroy --dir my-workspace --target model:contacts   # one resource
cargo-ai cdk destroy --dir my-workspace --all                     # everything in state
```

Re-running `deploy` only changes what changed — an unchanged workspace is a no-op.
`deploy` is idempotent: for each resource it updates by uuid if state has one,
otherwise adopts a slug-addressable match (connector/model) that already exists,
otherwise creates.

## `cargo.state.json` — commit it

`deploy` writes `cargo.state.json`: the link from your code to the resources Cargo
created. It records only `{hash, uuid, outputs}` per resource — **never secret
values**.

**Commit it.** It is the *only* handle on a deployed **play** or **agent**, which
have no slug (unlike connectors and models, which self-heal by slug). Losing state
orphans those resources. If it happens, re-establish a link with `import` (below).

Git-ignore the generated types and the CDK's working files (but **not**
`cargo.state.json`). `cargo-ai cdk init` scaffolds this:

```gitignore
.cargo-ai/
cargo.state.lock
cargo.state.bak.json
cargo.state.audit.jsonl
```

The `cargo.state.lock` prevents two deploys racing; `--force` steals a stale lock.

**Workspace guard:** state records the workspace it was deployed to; the CDK
refuses to `deploy`/`destroy` when the state's workspace ≠ the currently selected
workspace, so you can't accidentally reconcile a dev definition into prod. Select
the right workspace at `login` (or with the workspace flag) before deploying.

## Prune — deleting resources removed from code

`deploy` does **not** delete a resource just because you removed it from code —
that would make a typo destructive. To also remove resources that are in state but
no longer in code:

```bash
cargo-ai cdk deploy --dir my-workspace --prune
```

Prune deletes in reverse dependency order (dependents before their dependencies).
Adopted resources (linked via `adopt: true` or `import`) are **released** from
state, not deleted.

## Drift — `refresh` and `deploy --refresh`

A resource can change outside the CDK (someone edits an agent in the Cargo UI).
The CDK captures a fingerprint of each resource at deploy and compares on refresh:

```bash
cargo-ai cdk refresh --dir my-workspace          # read-only: report what drifted
cargo-ai cdk deploy  --dir my-workspace --refresh # re-read live, re-apply your code over drift
```

`refresh` reports resources changed or deleted out-of-band; `deploy --refresh`
makes your code the source of truth again.

## Adopting existing resources — `import`

To bring an already-live resource under CDK management, bind it into state by
mapping its **code id** to its **live uuid**:

```bash
cargo-ai cdk import model:contacts 6f0c8e2a-… --dir my-workspace
```

The code id is `kind:slug` (e.g. `connector:hubspot`, `model:contacts`,
`agent:sdr`). After import, `deploy` updates that resource instead of creating a
duplicate. Slug-addressable kinds (connector, model) can also self-adopt on deploy
by matching slug; uuid-only kinds (play, agent, capacity, territory, segment) need
`import` to recover a lost link. See
[`../recipes/migrate-existing-workspace.md`](../recipes/migrate-existing-workspace.md).

## Recovery — `rollback`

`deploy` snapshots the pre-deploy state to `cargo.state.bak.json`. If a deploy went
wrong, restore the snapshot:

```bash
cargo-ai cdk rollback --dir my-workspace
```

This restores the state file — it does not undo live API changes already made;
follow with a corrected `deploy`.

## Async resources — workers & apps

Most resources create synchronously. **Workers and apps build server-side**: on
deploy the reconciler uploads the bundle, waits for the build, and promotes it —
so a deploy touching a worker/app takes longer. Author worker runtime code with
`createWorker` (`@cargo-ai/worker-sdk`) and build to `index.js` before deploying;
the CDK validates the bundle files (`index.js`, `manifest.json`, `package.json`,
`package-lock.json`) exist at define time. The live URL is exposed on the handle
(`webhook.url`, `dashboard.url`). For imperative one-off hosting operations, see
[`../../cargo-hosting/SKILL.md`](../../cargo-hosting/SKILL.md).
guides/typed-config.md
# Typed config — `cargo-ai cdk types`

`defineConnector`/`defineModel` config and the `integrations.*` registry in
workflow bodies are typed against **your workspace's real integration schemas**.
Those types aren't bundled (they're workspace-specific) — you generate them:

```bash
cargo-ai cdk types --dir my-workspace
```

Typing is a **bonus, never a gate**: an integration you haven't synced (or a
custom one) falls back to a loose `Record<string, unknown>`, so `deploy` works
without ever running `cdk types`.

## What it generates

Everything lands in `.cargo-ai/` (git-ignored):

- **`.cargo-ai/cargo-types.d.ts`** — `declare module` augmentations:
  - types `@cargo-ai/cdk`'s `defineConnector`/`defineModel` `config` against your
    connector and extractor schemas. E.g. HubSpot's config becomes a discriminated
    union — the editor completes `method` and requires the matching credential,
    and `secret()` is accepted wherever a credential is expected.
  - adds your workspace's integration slugs to the `Integrations` interface, so
    `integrations.<slug>.<action>({ … })` in a workflow body typechecks against the
    real action schema.
- **`.cargo-ai/cargo-register.ts`** — eager `registerIntegration` /
  `registerNative` calls so those slugs are **callable at runtime** in workflow
  bodies.

Re-run `cargo-ai cdk types` whenever your workspace's integrations change (added a
connector, changed an extractor).

## Wiring it into your project

`cdk init` sets this up; for a hand-rolled project, two steps:

1. **Add the glob to `tsconfig.json` `include`.** A bare `.cargo-ai` (a dot-dir) is
   ignored by TypeScript — you must use an explicit glob:

   ```jsonc
   // tsconfig.json
   "include": ["**/*.ts", ".cargo-ai/**/*.d.ts"]
   ```

2. **Import the runtime registrations** at the top of workflow modules that use
   `integrations.*`:

   ```ts
   import "./.cargo-ai/cargo-register.js";
   ```

   (`native.*` needs no registration — the platform's native actions are identical
   in every workspace, so their types ship with the SDK. Tools and agents are not a
   registry either — reference them by handle through `defineWorkflow`'s `uses`.)

## Symptoms that mean "run `cdk types`"

- `config` on a `defineConnector` isn't autocompleting / isn't rejecting a wrong
  credential shape → types not generated (or `.cargo-ai/**/*.d.ts` not in
  `include`).
- `integrations.myConnector` is `any` / not callable at runtime → missing the
  `cargo-register` import, or types are stale after an integration change.
- A connector `deploy` fails with `Invalid configuration` → often the credential
  wasn't wrapped in `secret()`; generating types surfaces the required shape at
  author time. See [`../references/troubleshooting.md`](../references/troubleshooting.md).
recipes/add-connector-and-model.md
# Recipe: add a connector and a model sourced from it

**Use when** the user wants a new data source plus a model built from it, managed
as code. The key move is wiring the model to the connector **by handle** — the CDK
deploys the connector first and injects its dataset uuid.

## 1. Define the connector

Creating a data connector auto-creates a **dataset** that models source from. Put
the credential behind `secret()`.

```ts
// connectors/hubspot.ts
import { defineConnector, secret } from "@cargo-ai/cdk";

export const hubspot = defineConnector("hubspot", {
  integration: "hubspot",
  config: { method: "privateApp", accessToken: secret("HUBSPOT_API_KEY") },
});
```

For an OAuth/key connector you authenticated in the UI and can't declare in code,
use `adopt: true` so the reconciler links the existing instance instead of creating
one:

```ts
export const openai = defineConnector("open_ai", { integration: "openAi", adopt: true });
```

## 2. Define the model, wired to the connector

Pass the **connector handle** as `dataset` — not `hubspot.datasetUuid`:

```ts
// models/contacts.ts
import { defineModel } from "@cargo-ai/cdk";
import { hubspot } from "../connectors/hubspot";

export const contacts = defineModel("contacts", {
  dataset: hubspot,                 // ← handle: model depends on connector
  extractSlug: "fetchRecords",
  config: { objectType: "contacts", columnSelectionMode: "all" },
  schedule: { type: "cron", cron: "0 * * * *" },  // refresh hourly
});
```

If the connector already exists (not defined in code), reference it by uuid:
`dataset: connectorRef("<connector-uuid>")` — or `datasetRef("<dataset-uuid>")` to
point at a specific dataset.

## 3. Type the config (optional but recommended)

```bash
cargo-ai cdk types    # now `config` on both builders type-checks against HubSpot's schema
```

## 4. Deploy

```bash
export HUBSPOT_API_KEY=...
cargo-ai cdk plan       # shows: create connector:hubspot, create model:contacts
cargo-ai cdk deploy
git add cargo.state.json && git commit -m "Add HubSpot connector + contacts model"
```

The plan orders the connector before the model automatically because the model's
`dataset` handle depends on it. Re-deploying after a config edit updates in place.
recipes/build-an-agent.md
# Recipe: build an agent (model + tool + agent)

**Use when** the user wants an AI agent with a data model, a tool, and an LLM
connector — all as code. Everything wires by handle, so the CDK deploys in
dependency order.

## 1. The LLM connector

An agent needs a language-model provider connector. Adopt an existing one:

```ts
// connectors/openai.ts
import { defineConnector } from "@cargo-ai/cdk";
export const openai = defineConnector("open_ai", { integration: "openAi", adopt: true });
```

## 2. A tool, backed by a workflow

`defineWorkflow` compiles the logic (parsed, not executed); `defineTool` deploys it
as the tool's release. Reference other resources inside the body via `uses`.

```ts
// tools/enrich.ts
import { defineTool, defineWorkflow } from "@cargo-ai/cdk";
import { z } from "zod";

const enrichFlow = defineWorkflow(
  "enrich-contact",
  {
    input: z.object({ email: z.string() }),
    output: z.object({ company: z.string(), enriched: z.boolean() }),
  },
  ({ input, integrations }) => {
    const found = integrations.hunter.companyEnrichment({ domain: input.email });
    return { company: found.name, enriched: true };
  },
);

export const enrich = defineTool("enrich", {
  workflow: enrichFlow,
  description: "Enrich a contact with firmographic data.",
  emojiSlug: "mag",
});
```

> `integrations.*` is typed and callable only after `cargo-ai cdk types` (it reads
> your workspace's integrations). `native.*` works without it. See
> [`../guides/typed-config.md`](../guides/typed-config.md).

## 3. The agent, referencing model + tool + connector

Pass each dependency as a handle — bare, or `{ ref, …options }` when it needs
options:

```ts
// agents/sdr.ts
import { defineAgent } from "@cargo-ai/cdk";
import { openai } from "../connectors/openai";
import { contacts } from "../models/contacts";
import { enrich } from "../tools/enrich";

export const sdr = defineAgent("sdr", {
  connector: openai,
  languageModel: "gpt-4o",
  systemPrompt: "You qualify inbound leads and enrich missing contact info.",
  maxSteps: 12,
  capabilities: ["webSearch", "memory"],
  models: [{ ref: contacts, readOnly: true }],
  tools: [enrich],
  triggers: [{ type: "cron", cron: "0 9 * * *", text: "Daily qualification" }],
  evaluator: { rubric: "Did it correctly qualify the lead?", threshold: 0.8 },
});
```

Add a sub-agent with `subAgents: [{ ref: enricher, waitUntilFinished: true }]`, or a
raw connector action with
`connectorActions: [{ integration: "hunter", actionSlug: "emailFinder" }]`.

## 4. Deploy

```bash
cargo-ai cdk types      # so integrations.* in the workflow body typecheck
cargo-ai cdk plan       # orders: connector → tool (+ its workflow) → model → agent
cargo-ai cdk deploy
git add cargo.state.json && git commit -m "Add SDR agent"
```

Because `agent:sdr` has no slug, `cargo.state.json` is the **only** handle on it —
commit it, or the next deploy can't find it (recover with `cdk import agent:sdr <uuid>`).
recipes/deploy-from-ci.md
# Recipe: deploy from CI

**Use when** the user wants `cargo-ai cdk deploy` to run non-interactively — on
push, on merge, or on a schedule — so the workspace stays in sync with the repo.

## Prerequisites

- **A workspace-scoped API token** (not OAuth) from **Settings → API**. Store it as
  a CI secret (`CARGO_API_TOKEN`). Token values are shown once — capture at
  creation. See [`../../cargo-workspace-management/SKILL.md`](../../cargo-workspace-management/SKILL.md)
  for `token create`.
- **`cargo.state.json` committed** in the repo — CI diffs against it. Without it,
  CI can't tell what already exists and may recreate resources.
- **Any `secret()` env vars** set as CI secrets too (e.g. `HUBSPOT_API_KEY`).

## The CI steps

```bash
# 1. Install the CLI (project already depends on @cargo-ai/cdk via package.json)
npm install -g @cargo-ai/cli@latest
npm ci

# 2. Authenticate non-interactively with the token (selects the token's workspace)
cargo-ai login --token "$CARGO_API_TOKEN"

# 3. Deploy — --yes is REQUIRED (no TTY to confirm at); --json for machine-readable output
cargo-ai cdk deploy --yes --json
```

## Critical CI rules

- **`--yes` is mandatory.** `deploy`/`destroy` prompt for confirmation and refuse
  to run non-interactively without it.
- **The workspace guard still applies.** `cargo.state.json` records the workspace it
  was deployed to; deploy refuses if the token's workspace ≠ the state's workspace.
  Use one state file per workspace (e.g. a branch or directory per environment).
- **Commit the updated state back** if CI is the source of truth. A deploy that
  creates resources writes new uuids into `cargo.state.json`; if CI doesn't commit
  them, the next run won't know they exist. Either commit the file from the CI job,
  or make deploys only ever run from a branch whose state is already current.
- **Preview safely** with `cargo-ai cdk plan --json` (offline, no API calls) on pull
  requests, and gate `deploy` to the protected branch.
- **Prune deliberately.** Add `--prune` only when you want CI to delete resources
  removed from code; leave it off to make deploys purely additive.

## A GitHub Actions sketch

```yaml
# .github/workflows/deploy.yml
- run: npm install -g @cargo-ai/cli@latest && npm ci
- run: cargo-ai login --token "$CARGO_API_TOKEN"
  env:
    CARGO_API_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
- run: cargo-ai cdk deploy --yes --json
  env:
    HUBSPOT_API_KEY: ${{ secrets.HUBSPOT_API_KEY }}
```
recipes/migrate-existing-workspace.md
# Recipe: bring an existing workspace under CDK management

**Use when** a workspace already has live resources (built in the UI or the
imperative CLI) and the user wants to manage them as code going forward — without
creating duplicates.

The tool is `cargo-ai cdk import <id> <uuid>`, which binds a resource's **code id**
(`kind:slug`) to its **live uuid** in `cargo.state.json`. After import, `deploy`
updates that resource in place instead of creating a new one.

## 1. Write the `define*` for the resource

Describe the existing resource in code as accurately as you can. Match the slug you
want to address it by:

```ts
// models/contacts.ts — describe the live model
export const contacts = defineModel("contacts", {
  dataset: connectorRef("<connector-uuid>"),
  extractSlug: "fetchRecords",
  config: { objectType: "contacts", columnSelectionMode: "all" },
});
```

## 2. Find the live uuid

Use the matching capability skill to look it up — e.g. models via
[`../../cargo-storage/SKILL.md`](../../cargo-storage/SKILL.md), connectors via
[`../../cargo-connection/SKILL.md`](../../cargo-connection/SKILL.md), agents via
[`../../cargo-ai/SKILL.md`](../../cargo-ai/SKILL.md):

```bash
cargo-ai storage model list        # find the model's uuid
```

## 3. Import each resource into state

```bash
cargo-ai cdk import model:contacts <model-uuid> --dir my-workspace
cargo-ai cdk import connector:hubspot <connector-uuid> --dir my-workspace
cargo-ai cdk import agent:sdr <agent-uuid> --dir my-workspace
```

- The id is `kind:slug` — the same id the plan output uses.
- **Slug-addressable kinds** (connector, model) can also self-adopt on the next
  `deploy` by matching slug, so `import` is optional for them. **Uuid-only kinds**
  (play, agent, capacity, territory, segment) have no slug — `import` is the only
  way to bind them, and the only way to recover if `cargo.state.json` is ever lost.

## 4. Verify with a plan

```bash
cargo-ai cdk plan --dir my-workspace
```

Imported resources should show as **update** or **no-op**, not **create**. A
`create` for something that already exists means the id/slug didn't match — fix the
`define*` slug or re-import with the right uuid.

## 5. Deploy and commit

```bash
cargo-ai cdk deploy --dir my-workspace
git add cargo.state.json && git commit -m "Adopt existing workspace into CDK"
```

Imported/adopted resources are **released** (dropped from state), not deleted, if
you later `destroy` or `deploy --prune` them — the CDK won't delete something it
only adopted.
recipes/scaffold-a-workspace.md
# Recipe: scaffold a workspace from scratch

**Use when** the user wants to stand up a new Cargo workspace as code — from a
template, reproducibly. Follow these steps as your execution plan.

## 1. Scaffold from a template

```bash
cargo-ai cdk init my-workspace                    # 'blank' (minimal) — the default
cargo-ai cdk init my-workspace --template full    # every resource type, wired up
cargo-ai cdk init --list-templates                # see all templates
```

Use `--template full` when the user wants a worked example spanning connectors,
models, plays, tools, agents, MCP, context, files, workers, and apps; `blank` when
they want to start empty.

## 2. Install and authenticate

```bash
cd my-workspace && npm install         # pulls @cargo-ai/cdk + zod
cargo-ai login                         # authenticate + select the target workspace
cargo-ai whoami                        # confirm the selected workspace
```

## 3. (Optional) Generate typed config

```bash
cargo-ai cdk types                     # types config against this workspace's integrations
```

Not required to deploy, but it makes `defineConnector`/`defineModel` config and
`integrations.*` in workflow bodies type-check. See
[`../guides/typed-config.md`](../guides/typed-config.md).

## 4. Set secrets

Any `secret("NAME")` in the code resolves from the environment at deploy time.
Export each one first:

```bash
export HUBSPOT_API_KEY=...             # matches secret("HUBSPOT_API_KEY") in connectors/hubspot.ts
```

A missing env var fails the deploy with an unresolved `${NAME}` placeholder.

## 5. Plan, then deploy

```bash
cargo-ai cdk plan                      # offline diff — review what will be created
cargo-ai cdk deploy                    # create everything, write cargo.state.json
```

`deploy` prompts for confirmation. Review the plan output first; it lists each
resource as create / update / no-op.

## 6. Commit state

```bash
git add cargo.state.json && git commit -m "Deploy initial workspace"
```

`cargo.state.json` is the link from code to the deployed resources — and the only
handle on deployed plays/agents. Commit it. The `.gitignore` scaffolded by
`cdk init` already excludes `.cargo-ai/`, the lock, backup, and audit files.

## 7. Iterate

Edit `define*` files, then `cargo-ai cdk plan` → `cargo-ai cdk deploy` again — only
what changed is applied. To tear the workspace back down:

```bash
cargo-ai cdk destroy --all
```

See [`../guides/authoring-resources.md`](../guides/authoring-resources.md) to add
resources and [`../guides/deploy-and-state.md`](../guides/deploy-and-state.md) for
prune/drift/rollback.
references/commands.md
# Command reference — `cargo-ai cdk`

All subcommands accept `--dir <path>` (the project root, default `.`) and `--json`
(machine-readable output). Run from the project root so `.cargo-ai/` and
`cargo.state.json` land in the right place. Confirm the surface live with
`cargo-ai cdk <subcommand> --help`.

| Command | What it does |
|---|---|
| `cargo-ai cdk init <directory>` | Scaffold a project from a template. `--template <blank\|full>` (default `blank`), `--list-templates`. |
| `cargo-ai cdk types` | Generate per-workspace types into `.cargo-ai/` for typed config. |
| `cargo-ai cdk plan` | Offline: compile the graph and diff against `cargo.state.json`. No API calls. |
| `cargo-ai cdk deploy` | Create/update resources in dependency order; write state. Prompts unless `--yes`. |
| `cargo-ai cdk refresh` | Read-only: report resources that drifted from code (changed/deleted out of band). |
| `cargo-ai cdk import <id> <uuid>` | Bind an existing live resource (`kind:slug`) to a uuid in state. |
| `cargo-ai cdk rollback` | Restore `cargo.state.json` from the pre-deploy snapshot. |
| `cargo-ai cdk destroy` | Tear down resources recorded in state. `--target <id>` for one, `--all` for everything. |

## Common flags

- `--dir <path>` — project root (default `.`).
- `--yes` — skip the confirmation prompt (**required in CI / non-interactive**).
- `--json` — machine-readable output.
- `--force` — steal a stale `cargo.state.lock`.

## `deploy` modifiers

- `cargo-ai cdk deploy --prune` — also **delete** resources that are in state but
  removed from code (reverse dependency order; adopted resources are released, not
  deleted).
- `cargo-ai cdk deploy --refresh` — re-read live resources and re-apply your code
  over any out-of-band changes.

## `destroy` targets

- `cargo-ai cdk destroy --target <kind:slug>` — remove one resource (refused if
  other state resources still depend on it).
- `cargo-ai cdk destroy --all` — remove everything in state, dependents first.

## Examples

```bash
cargo-ai cdk init acme --template full        # scaffold
cargo-ai cdk types --dir acme                 # type config
cargo-ai cdk plan --dir acme                  # preview
cargo-ai cdk deploy --dir acme --yes          # apply (non-interactive)
cargo-ai cdk refresh --dir acme               # drift report
cargo-ai cdk import agent:sdr <uuid> --dir acme  # adopt a live agent
cargo-ai cdk destroy --dir acme --all --yes   # tear down
```
references/cookbooks.md
<!-- Generated by .github/scripts/sync-cookbooks.ts from gtm-skills' catalog.json. Do not edit by hand. -->

# Cookbooks: worked CDK examples, before you author one from scratch

Skills in [`getcargohq/gtm-skills`](https://github.com/getcargohq/gtm-skills) that are cookbooks: worked CDK resources, the same
jobs as the one-off skills there, as a deployed pipeline that keeps producing the result.

**Every folder is self-contained** (its own models, connectors and folders; no shared
foundation, no requires graph). The agent installing one copies it into the project as a
sibling of what is there, reconciles it with what is already declared (an existing accounts
model, an existing CRM connector), adapts it, plans, deploys on a yes, and walks its `Done
when`. The code is a worked example, not a template to fill in.

```sh
npx skills add getcargohq/gtm-skills/<slug>       # then say what you want; the skill carries its own procedure
cargo-ai cdk init <dir> --template blank      # only if there is no CDK project yet: the shell comes from the CLI
```

## With a skill

| Skill | Deploys | State |
| --- | --- | --- |
| `account-scoring` | Keep every account scored and tiered against your written ICP by a deployed agent that re-scores as accounts arrive and as the ICP changes, writing the rationale back to the CRM. | to-be-approved |
| `tam-building` | Stand up your account universe as a deployed pipeline: a Sales Navigator company search split past the 1,000 extraction cap, resolved to real domains, deduped into a shared accounts model. | to-be-approved |

## When one does not fit as written

Declared adaptations, not forks. Reach for one before concluding a skill is the wrong start.

**`account-scoring`**

- `deterministic-scoring` — You need fixed cost and exact reproducibility, or an LLM judgement is not acceptable to your team. Costs: The criteria move out of the ICP markdown and into code, so they stop being reviewable by non-engineers, and you lose the rationale entirely.
- `skip-crm-roundtrip` — You want the score on the model directly and do not need it visible in the CRM. Costs: Reps lose the score and rationale where they actually work. The native's input is untyped, so confirm the field shape on the first run.
- `no-crm-at-all` — You have no CRM, or you do not want to hand this skill a CRM credential. Costs: Every other CRM-dependent skill you install later brings a CRM connector of its own; reuse one.

**`tam-building`**

- `non-linkedin-source` — You do not want to source from LinkedIn at all, or Sales Nav does not cover your market. Costs: You lose the Sales Nav facet taxonomy that makes the split tactic mechanical, and the splitting has to be redesigned around the new source's own limits.
- `land-without-promoting` — You want to see and filter the raw market before paying to enrich it. Costs: Nothing reaches `accounts`, so no downstream skill (scoring, contact sourcing, signals) has anything to work with until you promote.
- `sample-first` — The market search is large and you want to see the cost curve before committing. Costs: Your TAM is deliberately incomplete until you widen it, so do not score or report on coverage from a sample.

## Routing

**One-off versus standing is the whole test.** A user who wants a list today wants a one-off
skill (or `cargo-gtm`, when this pack is installed); a user who wants a pipeline that keeps
producing it wants one of these. The same words describe both, so listen for whether the
result is meant to keep arriving.

**Never `cargo-ai cdk init --force` into a directory that is not empty.** It replaces the
project's `package.json` and reverts adapted code while `cargo.state.json` survives, so the
next plan diffs a live workspace against code nobody wrote. Copy the skill folder in as a
sibling instead; that is what its own procedure says.
references/examples/full-workspace.md
# Example: a full GTM workspace end-to-end

This walks the `full` template (`cargo-ai cdk init <dir> --template full`) — a
complete, runnable Cargo workspace defined in code that exercises every resource
type and wires them by **handle**.

## The graph

```
hubspot (connector) ──dataset──▶ contacts (model) ──model──────────┬─▶ onboarding (play)
                                                                    ├─▶ sdr (agent)
openai (connector) ──connector──▶ enricher (agent) ──subAgent──▶ sdr│
                    └────────connector──────────────────▶ sdr ─────┘
enrich (tool, backed by a workflow) ─┐
sdr (agent) ─────────────────────────┼─▶ crm (mcpServer)
contacts (model) ────────────────────┘
playbook (file)   webhook (worker)   dashboard (app)   context (repo)
```

## Project layout

```
my-workspace/
  package.json            # depends on @cargo-ai/cdk + zod
  tsconfig.json           # include: ["**/*.ts", ".cargo-ai/**/*.d.ts"]
  .gitignore              # .cargo-ai/, cargo.state.lock, cargo.state.bak.json, cargo.state.audit.jsonl
  connectors/hubspot.ts   # defineConnector + secret()
  connectors/openai.ts    # defineConnector adopt: true
  folders/crm.ts          # defineFolder (per-kind)
  models/contacts.ts      # defineModel dataset: hubspot
  tools/enrich.ts         # defineWorkflow + defineTool
  agents/enricher.ts      # defineAgent (sub-agent)
  agents/sdr.ts           # defineAgent (model + tool + sub-agent + trigger + evaluator)
  plays/onboarding.ts     # definePlay + defineWorkflow
  mcp/crm.ts              # defineMcpServer
  context/context.ts      # defineContext dir: "context" (+ context/*.md)
  files/playbook.ts       # defineFile (+ playbook.md)
  workers/webhook.ts      # defineWorker (+ webhook/ built bundle)
  apps/dashboard.ts       # defineApp (+ dashboard/ Vite app)
```

Importing a `.ts` file **is** registration — there is no manifest. The loader
imports every `.ts` under the project (skipping the worker/app bundle sub-dirs,
which have their own `package.json`), and each `define*` registers as a side
effect.

## A wired slice

```ts
// connectors/hubspot.ts
export const hubspot = defineConnector("hubspot", {
  integration: "hubspot",
  config: { method: "privateApp", accessToken: secret("HUBSPOT_API_KEY") },
});

// models/contacts.ts
export const contacts = defineModel("contacts", {
  dataset: hubspot,                       // handle → connector deployed first, dataset injected
  extractSlug: "fetchRecords",
  config: { objectType: "contacts", columnSelectionMode: "all" },
  folder: modelsFolder,
  schedule: { type: "cron", cron: "0 * * * *" },
});

// agents/sdr.ts
export const sdr = defineAgent("sdr", {
  connector: openai,
  languageModel: "gpt-4o",
  systemPrompt: "You qualify inbound leads and route hot ones to Slack.",
  models: [{ ref: contacts, readOnly: true }],
  tools: [enrich],
  subAgents: [{ ref: enricher, waitUntilFinished: true }],
  folder: agentsFolder,
});
```

## Deploy walkthrough

```bash
cd my-workspace && npm install

cargo-ai login                 # authenticate + select the workspace
cargo-ai cdk types             # type defineConnector/defineModel config against this workspace
export HUBSPOT_API_KEY=...      # matches secret("HUBSPOT_API_KEY")

cargo-ai cdk plan
# → lists every resource as create / update / no-op, in dependency order:
#   create connector:hubspot, create connector:open_ai (adopt), create folder:crm-models, …

cargo-ai cdk deploy
# → creates each in order, writing cargo.state.json after each resource.
#   Workers/apps build server-side (slower). Live URLs appear as webhook.url / dashboard.url.

git add cargo.state.json && git commit -m "Deploy full workspace"
```

Re-run `cargo-ai cdk deploy` after editing a file — only the changed resource is
applied. Tear it all down with `cargo-ai cdk destroy --all`.

Secrets referenced with `secret("HUBSPOT_API_KEY")` resolve from the environment at
deploy time and stay out of the content hash — only `{hash, uuid, outputs}` land in
`cargo.state.json`, never secret values.
references/resources.md
# Resource reference

Every builder is imported from `@cargo-ai/cdk`, takes `(slug, spec)` (except
`defineContext`, which takes only a spec — it's a workspace singleton), and returns
a **handle** carrying deferred output tokens (`uuid`, and for connectors
`datasetUuid`; for workers/apps `url`). Wire resources by passing a handle where a
reference is expected; use `xxRef("uuid")` for a resource not defined in code.

The tables below list the **commonly used** spec fields — the TypeScript types on
each builder are the source of truth for the complete set. Fields that take a
**ref** accept a handle or an `xxRef` (and `{ ref, …options }` when they carry
per-call options).

## Builders

| Builder | Purpose | Key spec fields | Ref fields | Outputs |
|---|---|---|---|---|
| `defineConnector(slug, spec)` | Data source or LLM provider | `integration`, `config` (typed per integration), `adopt?`, `rateLimit?`, `cacheTtlMilliseconds?` | — | `uuid`, `datasetUuid` (data connectors) |
| `defineModel(slug, spec)` | Table sourced from a connector's dataset | `dataset`, `extractSlug`, `config`, `schedule?`, `folder?` | `dataset` (connector/dataset), `folder` | `uuid` |
| `defineTool(slug, spec)` | Tool backed by a workflow | `workflow`, `description?`, `emojiSlug?`, `triggers?`, `folder?` | `folder` | `uuid` |
| `definePlay(slug, spec)` | Per-row automation over a model | `model`, `workflow`, `changeKinds`, `runCreationRule`, `schedule`, `folder?` | `model`, `folder` | `uuid` |
| `defineAgent(slug, spec)` | AI agent | `connector`, `languageModel`, `systemPrompt`, `models?`, `tools?`, `subAgents?`, `connectorActions?`, `capabilities?`, `maxSteps?`, `triggers?`, `evaluator?`, `color?`, `folder?` | `connector`, `models`, `tools`, `subAgents`, `folder` | `uuid` |
| `defineMcpServer(slug, spec)` | MCP endpoint bundling resources | `description?`, `tools?`, `agents?`, `models?`, `folder?` | `tools`, `agents`, `models`, `folder` | `uuid` |
| `defineFolder(slug, spec)` | Per-kind folder | `kind`, `name`, `parent?` | `parent` | `uuid` |
| `defineFile(slug, spec)` | Content file from a local path | `path`, `name`, `folder?` | `folder` | `uuid` |
| `defineContext(spec)` | Workspace context repo (singleton) | `dir?`, `files?` | — | `uuid` |
| `defineSegment(slug, spec)` | Saved view over a model | `model` (immutable), `filter` (required) | `model` | `uuid` |
| `defineCapacity(slug, spec)` | Revenue-org capacity | `model`, `color?`, `description?`, member capacity fields | `model`, members | `uuid` |
| `defineTerritory(slug, spec)` | Revenue-org territory | `model`, `members`, `color?`, `description?`, `fallbackMember?` | `model`, `members` | `uuid` |
| `defineWorker(slug, spec)` | Hosted worker (built bundle) | `path`, `description?`, `folder?` | `folder` | `uuid`, `url` |
| `defineApp(slug, spec)` | Hosted Vite SPA | `path`, `description?`, `folder?` | `folder` | `uuid`, `url` |
| `defineDomain(name, spec)` | Sending domain + its DNS zone | `adopt?`, `dnsRecords?` (**replaces the whole zone**) | — | `uuid` |
| `defineMailbox(slug, spec)` | Sending inbox on a domain (**monthly credit charge**) | `domain`, `type` (`google`/`shared`/`private` — no `outlook`), `username?` (defaults to slug), `firstName`, `lastName`, `signature?`, `folder?`, `adopt?` | `domain`, `folder` | `uuid` |
| `defineAlert(slug, spec)` | Scheduled threshold alert (observability) | `schedule`, `scope`, `threshold`, `actions`, `name?`, `description?`, `enabled?`, `folder?` | scope: `workflow`/`connector`/`tool`/`agent`/`model`; each action's `ref`; `folder` | `uuid` |

`defineWorkflow(slug, { input, output, uses? }, build)` is re-exported from
`@cargo-ai/cdk` for `defineTool`/`definePlay` bodies — see
[`../guides/authoring-resources.md`](../guides/authoring-resources.md).

## Ref helpers

From `@cargo-ai/cdk`: `connectorRef`, `datasetRef`, `modelRef`, `folderRef`,
`playRef`, `memberRef`. From the workflow SDK (re-exported): `toolRef`, `agentRef`.
Each takes a uuid string and returns a kind-branded handle:

```ts
import { defineModel, connectorRef, folderRef } from "@cargo-ai/cdk";

export const leads = defineModel("leads", {
  dataset: connectorRef("6f0c…"),   // existing connector, by uuid
  extractSlug: "fetchRecords",
  folder: folderRef("a1b2…"),
});
```

## Notes on specific fields

- **`defineConnector` `config`** is a per-integration shape — a discriminated union
  for auth (e.g. HubSpot `method: "privateApp" | "oauth"`). `secret()` is accepted
  only on credential/encryption fields. Run `cargo-ai cdk types` to type it (see
  [`../guides/typed-config.md`](../guides/typed-config.md)).
- **`adopt: true`** on `defineConnector` links an existing authenticated connector
  by slug instead of creating one — for OAuth/key connectors you can't declare.
- **`defineModel` `dataset`** takes the **connector** handle (its dataset uuid is
  injected) or a `datasetRef`/`connectorRef`.
- **`schedule`** shapes: `{ type: "cron", cron: "0 * * * *" }` or
  `{ type: "watch" }` (plays react to row changes).
- **`definePlay` `changeKinds`**: `["added", "updated", …]`; `runCreationRule`:
  e.g. `"always"`.
- **`defineFile` `path`** and **`defineWorker`/`defineApp` `path`** point at local
  files/dirs, typically via `new URL("./x", import.meta.url).pathname`. File content
  is hashed at define time, so edits show as drift. Worker `path` must be a **built**
  bundle dir (`index.js` + `manifest.json` + `package.json` + `package-lock.json`).
- **`defineAlert` `scope` + `threshold`** are a **matched pair** — TS narrows the
  threshold menu by `scope.kind`: `spans`/`runs`/`records` take the telemetry metrics
  (`errorRate`, `duration`+`aggregation`, `credits`+`aggregation`, `count`), `model`
  takes `recordsCount`/`recordsShare`/`freshness`/`syncDuration`, and
  `orchestrationQuery`/`storageQuery` take `{ operator, value }` (the query computes
  the value, so no metric). The scope wires the watched resource **by handle**
  (`workflow:` a `definePlay`/`defineTool` handle or `workflowRef`, plus `connector`/
  `tool`/`agent`/`model`), so the reconciler deploys the producer first and injects
  its uuid.
- **`defineAlert` `actions`** fire as runs on breach. Each is a `{ ref, config }`
  wrapper (`config` required — an alert fires unattended, so a missing input is a type
  error, not a silent `{}`). Prefer the typed helpers `alertConnectorAction({ ref:
  slack.actions.postMessage, config })` / `alertToolAction({ ref: enrich, config })` —
  `config` is checked against the action/tool input schema (connector schemas need
  `cargo-ai cdk types` to have run) — or a bare `{ ref: agent, config, release?,
  waitUntilFinished? }`. Every `config` leaf accepts a `{{ … }}` template
  (`{{event.value}}`, `{{alert.name}}`, `{{alert.url}}`, …) interpolated against the
  firing context. Like a play, an alert has **no author-set wire slug** — its identity
  on redeploy is the state uuid, so committing `cargo.state.json` is what keeps it
  addressable. Scope/threshold matrix, metric units, and firing semantics:
  [`../../cargo-observability/SKILL.md`](../../cargo-observability/SKILL.md).
references/troubleshooting.md
# Troubleshooting

## `✗ Deploy failed: connector:<slug>: Invalid configuration`

The connector's `config` doesn't match the integration's schema. Most often the
credential wasn't wrapped in `secret()` — a data connector's credential field
expects an encryption envelope, and `secret("ENV_VAR")` produces it. Fix:

```ts
config: { method: "privateApp", accessToken: secret("HUBSPOT_API_KEY") }, // not a bare string
```

Run `cargo-ai cdk types` so the config type-checks against the real schema at
author time and surfaces the required shape (see
[`../guides/typed-config.md`](../guides/typed-config.md)). The deploy error now also
surfaces the API's structured detail (which field, the reason) — read past the
terse "Invalid configuration" summary.

## `unresolved placeholder "${NAME}"`

A `secret("NAME")` or `env("NAME")` had no matching environment variable at deploy.
The CDK refuses to send a literal `${NAME}` to the API. Export it first:

```bash
export NAME=...
cargo-ai cdk deploy
```

## Deploy refuses: workspace mismatch

`cargo.state.json` records the workspace it was deployed to; `deploy`/`destroy`
refuse when that ≠ the currently selected workspace (a guard against reconciling a
dev definition into prod). Select the right workspace at `login`, or use a separate
state file per environment.

## `.cargo-ai/` or `cargo.state.json` landed in the wrong directory

`npx`/`cargo-ai` resolve from the **nearest `package.json`**, not your shell's cwd.
Run `cdk` commands from the project root, or pass `--dir <project-root>` explicitly.

## `integrations.<slug>` is `any` / not callable, or `config` isn't type-checked

Types aren't generated or aren't wired in. Run `cargo-ai cdk types`, ensure
`tsconfig.json` `include` has the explicit glob `".cargo-ai/**/*.d.ts"` (a bare
`.cargo-ai` dot-dir is ignored by TypeScript), and `import "./.cargo-ai/cargo-register.js";`
at the top of workflow modules that use `integrations.*`. Re-run `cdk types` after
changing workspace integrations.

## `could not parse the workflow body`

A `defineWorkflow` body must be a supported JS subset — it's **parsed, not
executed**. Remove `await`, `throw`, `try/catch`, closures over outer variables,
and destructuring; compile workflow files with a modern target (ES2022+) and don't
instrument them with coverage tools (they rewrite the function source). Use
`js(({ nodes }) => …)` for logic outside the supported subset.

## Deploy is slow / seems to hang on a worker or app

Workers and apps **build server-side** — the reconciler uploads the bundle, waits
for the build, and promotes. This is expected to take longer than other resources.
Ensure the worker bundle dir has a built `index.js` (+ `manifest.json`,
`package.json`, `package-lock.json`) before deploying.

## A play or agent got orphaned (state lost)

Plays and agents have no slug, so `cargo.state.json` is the only link to them.
**Commit the state file.** If it's lost, find the live uuid via the matching
capability skill and rebind: `cargo-ai cdk import agent:<slug> <uuid>`. Never
delete `cargo.state.json` to "start clean" — you'll orphan every play/agent it
tracked.

## `plan` shows `create` for a resource that already exists

Its `kind:slug` id didn't match state. Either the slug in the `define*` changed, or
you migrated a workspace without importing — bind it: `cargo-ai cdk import <kind:slug> <uuid>`
(see [`../recipes/migrate-existing-workspace.md`](../recipes/migrate-existing-workspace.md)).

## Still stuck

File a report so the Cargo team sees it:
`cargo-ai workspaceManagement report create --title "<summary>" --description "<commands tried, errorMessage, expected vs actual>"`
— see [`../../cargo-workspace-management/SKILL.md`](../../cargo-workspace-management/SKILL.md).
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-cdk",
  "version": "1.2.3",
  "documents": [
    {
      "path": "SKILL.md",
      "kind": "entrypoint",
      "title": "Cargo CDK — declarative workspace-as-code"
    },
    {
      "path": "guides/authoring-resources.md",
      "kind": "guide",
      "title": "Authoring resources"
    },
    {
      "path": "guides/deploy-and-state.md",
      "kind": "guide",
      "title": "Deploy & state"
    },
    {
      "path": "guides/typed-config.md",
      "kind": "guide",
      "title": "Typed config — `cargo-ai cdk types`"
    },
    {
      "path": "recipes/add-connector-and-model.md",
      "kind": "recipe",
      "title": "Recipe: add a connector and a model sourced from it"
    },
    {
      "path": "recipes/build-an-agent.md",
      "kind": "recipe",
      "title": "Recipe: build an agent (model + tool + agent)"
    },
    {
      "path": "recipes/deploy-from-ci.md",
      "kind": "recipe",
      "title": "Recipe: deploy from CI"
    },
    {
      "path": "recipes/migrate-existing-workspace.md",
      "kind": "recipe",
      "title": "Recipe: bring an existing workspace under CDK management"
    },
    {
      "path": "recipes/scaffold-a-workspace.md",
      "kind": "recipe",
      "title": "Recipe: scaffold a workspace from scratch"
    },
    {
      "path": "references/commands.md",
      "kind": "reference",
      "title": "Command reference — `cargo-ai cdk`"
    },
    {
      "path": "references/cookbooks.md",
      "kind": "reference",
      "title": "Cookbooks: worked CDK examples, before you author one from scratch"
    },
    {
      "path": "references/examples/full-workspace.md",
      "kind": "example",
      "title": "Example: a full GTM workspace end-to-end"
    },
    {
      "path": "references/resources.md",
      "kind": "reference",
      "title": "Resource reference"
    },
    {
      "path": "references/troubleshooting.md",
      "kind": "reference",
      "title": "Troubleshooting"
    }
  ],
  "contentHash": "9bab53eda7c82d2c830dfb5ed20f8eee3ca499e86bb8cb7a01c38a95664e121f"
}
SKILL.md
---
name: cargo-cdk
description: "Manage a whole Cargo workspace as code — declare connectors, models, plays, tools, agents, MCP servers, segments, context, folders, files, workers, and apps in TypeScript, then reconcile them with `cargo-ai cdk` (init → types → plan → deploy), the way you would run Pulumi or the AWS CDK. Triggers: \"as code\", \"in git\", \"version-controlled\", \"reproducible\", \"Terraform for Cargo\", \"set up a whole workspace\", \"staging and production\", \"deploy from CI\", \"review this in a PR\", \"cargo.state.json\", \"scaffold from a template\", \"is there a cookbook for this\", \"start from a cookbook\". Skills with a CDK example (TAM building, account scoring, contact sourcing, routing, AI SDR, rep cockpit) live in gtm-skills; menu in references/cookbooks.md. Skip when: it is a one-off operation, a read, or an ad-hoc query — use the matching capability skill."
version: "1.2.3"
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 CDK — declarative workspace-as-code

Use this skill to define a Cargo workspace in TypeScript (`define*` builders from
`@cargo-ai/cdk`) and reconcile it to live infrastructure with `cargo-ai cdk deploy`.
It is the **declarative** counterpart to the imperative capability skills: instead
of running one CLI command per resource, you write the whole graph once and deploy
it repeatably, with a committed `cargo.state.json` linking your code to what Cargo
created.

## 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
cargo-ai cdk --help                     # `unknown command` = CLI too old; reinstall @cargo-ai/cli@latest
```

Two CDK-specific extras: the project needs **`@cargo-ai/cdk` as a dependency** for the `define*` builders you import (`cargo-ai cdk init` scaffolds a `package.json` with it — then `npm install`), and the `cargo-ai cdk` domain ships with the CLI itself.

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.

## 1) What this skill governs

- **Authoring** every Cargo resource with a `define*` builder that returns a
  **handle**; wiring resources by passing handles to each other (the dependency
  graph is your variable graph).
- **Deploying** the graph: `plan` (offline diff) → `deploy` (create/update, write
  state) → `destroy` (tear down). Plus drift (`refresh`), adoption (`import`), and
  recovery (`rollback`).
- **Typing** the config against your workspace's real integration schemas
  (`cargo-ai cdk types`).

The CDK spans **every** resource kind — so it overlaps every imperative capability
skill (`cargo-connection`, `cargo-storage`, `cargo-ai`, `cargo-orchestration`,
`cargo-content`, `cargo-hosting`, …). Which to reach for is the first decision:

## 2) CDK or the CLI? — the routing decision

> **Declarative (this skill) vs imperative (a capability skill).**

Use the **CDK** when the user is **managing resources as an artifact**:

- "Set up / stand up / bootstrap a whole workspace (as code / from a template)."
- "Make this reproducible / version-controlled / in git / repeatable across
  environments (dev → prod)."
- "Deploy these connectors + models + agents together" (a multi-resource graph
  wired by dependency).
- Anything that should be re-runnable and diffable, where losing the definition
  would be a problem.

Use the matching **capability skill** (imperative `cargo-ai <domain>`) when the
user is doing a **one-off operation** or **exploring**:

- "Create one connector", "add a column to this model", "list connectors",
  "run this workflow", "query storage", "read this agent's memory."
- Any read, ad-hoc query, or single mutation that doesn't need to live in code.

When unsure, ask whether the result should be committed and re-deployable. If yes
→ CDK. If it's a quick action or a read → the capability skill (see the
[`cargo` router](../cargo/SKILL.md) to pick the right domain).

## 3) The lifecycle

```
cargo-ai cdk init <dir>     scaffold a project from a template (blank | full)
        │
cargo-ai cdk types          generate per-workspace types for typed config (optional)
        │
   (author define* files)   importing a .ts file IS registration — no manifest
        │
cargo-ai cdk plan           offline: compile the graph, diff against cargo.state.json
        │
cargo-ai cdk deploy         create/update resources in dependency order, write state
        │
cargo-ai cdk destroy        tear down resources recorded in state
```

> **`cdk plan` says what resources change; it doesn't show what a play does.**
> For a `definePlay` / `defineTool` graph past three nodes, present a Mermaid
> flowchart of the node graph alongside the plan — routing, fallbacks, and which
> nodes bill on every scheduled run are what the reviewer is approving. Generate it
> from the deployed release after the first deploy, or from the node array while
> authoring:
> [`../cargo-orchestration/references/node-diagram.md`](../cargo-orchestration/references/node-diagram.md).

Side branches: `cargo-ai cdk refresh` (read-only drift report) · `deploy --refresh`
(re-apply code over out-of-band edits) · `deploy --prune` (delete resources removed
from code) · `cargo-ai cdk import <id> <uuid>` (bind an existing live resource into
state) · `cargo-ai cdk rollback` (restore the pre-deploy state snapshot).

## 4) Documentation hierarchy

- **Level 1** — `SKILL.md` (this file): the decision model, lifecycle, critical
  rules, and routing.
- **Level 2** — Guides:
  [`guides/authoring-resources.md`](guides/authoring-resources.md),
  [`guides/deploy-and-state.md`](guides/deploy-and-state.md),
  [`guides/typed-config.md`](guides/typed-config.md).
- **Level 2.5** — Recipes: [`recipes/*.md`](recipes/) — step-by-step playbooks to
  follow as your execution plan.
- **References** — [`references/resources.md`](references/resources.md) (the full
  builder catalog), [`references/commands.md`](references/commands.md) (every
  `cargo-ai cdk` subcommand + flags),
  [`references/troubleshooting.md`](references/troubleshooting.md), and
  [`references/examples/full-workspace.md`](references/examples/full-workspace.md).

## 5) Read behavior — match the task to a doc and READ IT

| When the task involves… | Read this first | What it gives you |
|---|---|---|
| Writing `define*` files, wiring resources, `secret()`/`env()`, `defineWorkflow` bodies (tool/play logic) | [`guides/authoring-resources.md`](guides/authoring-resources.md) | The builder catalog, the handle/ref model, secrets, and how workflow bodies compile. |
| `plan` / `deploy` / `destroy`, the state file, drift, adopting existing resources, CI | [`guides/deploy-and-state.md`](guides/deploy-and-state.md) | The deploy lifecycle, `cargo.state.json` semantics, drift/import/rollback, async builds. |
| Typed config, `cargo-ai cdk types`, tsconfig wiring, `integrations.*` in workflow bodies | [`guides/typed-config.md`](guides/typed-config.md) | What `cdk types` generates and how to wire it into your project. |
| A field/spec/output for a specific builder | [`references/resources.md`](references/resources.md) | Every builder → spec fields → which ref each takes → outputs. |
| Exact command flags | [`references/commands.md`](references/commands.md) | Every `cargo-ai cdk` subcommand and its flags. |
| A deploy error / footgun | [`references/troubleshooting.md`](references/troubleshooting.md) | The known failure modes and fixes. |
| A known GTM outcome, before authoring one | [`references/cookbooks.md`](references/cookbooks.md) | The cookbook menu: gtm-skills that carry a worked CDK example, and the adaptations each supports. |

### Cookbooks — check the menu before authoring a known outcome from scratch

[`getcargohq/gtm-skills`](https://github.com/getcargohq/gtm-skills) holds, beside its
one-off skills, **cookbooks**: skills that carry worked CDK resources, the same job as a deployed
pipeline that keeps producing the result (TAM building, account scoring, contact
sourcing, routing engine, AI SDR, rep cockpit, …). Every folder is self-contained: its
own models, connectors and folders, no shared foundation, no requires graph.

**The menu is local: [`references/cookbooks.md`](references/cookbooks.md).** Read it
before authoring a common GTM outcome from scratch. It is generated from gtm-skills'
`catalog.json`, so it cannot drift.

**A cookbook is a worked example, not a template to fill in.** Each one declares in its `SKILL.md` what may be reshaped, what must hold or it stops
working, and what has to be answered either way, and it carries its own procedure:
look at the repo, `cargo-ai cdk init --template blank` if there is no CDK project yet,
copy the folder in as a sibling and reconcile it with what is already declared, adapt,
plan and stop, deploy on a yes, walk its `Done when`. There is no scaffolder or copy
tool in the middle: **you place the code**, because you can see the project and a tool
cannot.

```sh
npx skills add getcargohq/gtm-skills/tam-building    # then: "keep our TAM current"
cargo-ai cdk init my-project --template blank        # only if there is no CDK project yet
```

**If you are mid-task and the skill is not in this session**, run the `skills add`
above and read `.agents/skills/<slug>/SKILL.md` directly; no reload needed. To read
one without installing, `npx skills use getcargohq/gtm-skills@<slug>` prints it.

**Routing rule: one-off versus standing.** A user who wants the list today wants
`cargo-gtm` (or gtm-skills' one-off `build-tam-list`); a user who wants a pipeline
that keeps producing it wants `tam-building`. The same words describe both ("build
our TAM"), so listen for whether the result is meant to keep arriving. A cookbook
matches → install it and follow it. No match → author from the recipes below.

**Never `cargo-ai cdk init --force` into a directory that is not empty.** It replaces
the project's `package.json` and reverts adapted code, while `cargo.state.json`
survives, so the next `plan` diffs a live workspace against code nobody wrote. Copy the
skill folder in as a sibling instead.

Caveat: the examples typecheck, but they are not yet deploy-verified against a live
workspace, and every one is `to-be-approved`. Treat each skill's `Done when` as the
acceptance test, and always review `cargo-ai cdk plan` before deploying.

### Recipes — follow step-by-step when one matches

| Recipe | Use when… |
|---|---|
| [`recipes/scaffold-a-workspace.md`](recipes/scaffold-a-workspace.md) | Standing up a new workspace from scratch (`init --template full` → types → plan → deploy). |
| [`recipes/add-connector-and-model.md`](recipes/add-connector-and-model.md) | Adding a data source + a model sourced from it, wired by handle. |
| [`recipes/build-an-agent.md`](recipes/build-an-agent.md) | Composing a model + tool + agent (with `uses` / `models` / `tools`) and deploying. |
| [`recipes/migrate-existing-workspace.md`](recipes/migrate-existing-workspace.md) | Bringing an already-live workspace under CDK management via `cdk import`. |
| [`recipes/deploy-from-ci.md`](recipes/deploy-from-ci.md) | Deploying non-interactively from CI (token auth + committed state). |

## 6) Critical rules

- **Commit `cargo.state.json`.** It is the link from your code to the resources
  Cargo created — and the **only** handle on a deployed **play**, **agent**, or
  **alert** (they have no slug). Lose it and those resources orphan; recover a link
  with `cargo-ai cdk import`. It records only `{hash, uuid, outputs}` — never secret
  values. Git-ignore the working files (`cdk init` scaffolds this):
  ```gitignore
  .cargo-ai/
  cargo.state.lock
  cargo.state.bak.json
  cargo.state.audit.jsonl
  ```
- **Secrets:** wire credentials with `secret("ENV_VAR")` (often
  `secret("HUBSPOT_API_KEY")`). The value is read from the environment **at deploy
  time**, kept out of the content hash and out of state, so rotating a token
  doesn't read as drift. Export the env var before deploying — a missing one fails
  the deploy with an unresolved `${ENV_VAR}` placeholder.
- **Wire by handle, never by `.uuid`.** Pass a `define*` handle directly
  (`dataset: hubspot`, `tools: [enrich]`), or `xxRef("uuid")` for a resource you
  didn't define in code (`connectorRef`, `modelRef`, `folderRef`, `toolRef`,
  `agentRef`, …). Where a reference needs per-call options, wrap it as
  `{ ref, …options }` (e.g. `models: [{ ref: contacts, readOnly: true }]`).
- **Run `cargo-ai cdk types` after workspace integrations change** — it
  regenerates `.cargo-ai/` so `defineConnector`/`defineModel` config (and
  `integrations.*` in workflow bodies) type-check against the real schemas. Typing
  is a bonus, never a gate: deploy works without it.
- **Run `cdk` commands from the project root.** `npx`/`cargo-ai` resolve from the
  nearest `package.json`; run elsewhere and `.cargo-ai/` and `cargo.state.json`
  land in the wrong directory. Use `--dir <path>` to be explicit.
- **`--yes` in CI.** `deploy` and `destroy` prompt for confirmation; non-interactive
  runs must pass `--yes`.
- **A `definePlay`/`defineTool` graph with paid nodes gets a sample run before it
  goes wide.** Deploying is not running, but the first thing that runs a deployed
  play is usually a batch over the whole segment — and a scheduled play re-bills
  every node on every run. Before enrolling everything (or enabling a schedule),
  run the deployed workflow on **10–20 records** — `cargo-ai orchestration batch
  create --data '{"kind":"filter","modelUuid":"…","filter":…,"limit":15}'`, or
  `batch create --file ./plays/x.ts` to test-run the module without deploying —
  then ask the user to approve the full enrollment with the **record count** and
  **credit estimate**. Read the provider's playbook
  (`../cargo-gtm/provider-playbooks/<slug>.md`, esp. its *Recurring use* section)
  and the gate in
  [`../cargo-gtm/references/cost-discipline.md`](../cargo-gtm/references/cost-discipline.md).
- **A `defineAlert` whose actions call paid nodes re-bills on every breach.** An
  alert's `actions` fire as real runs, so a badly-sized `threshold` on a tight
  `schedule` can breach — and bill — every tick. Size the threshold with
  `cargo-ai observability alert preview` before deploying, prefer cheap notification
  actions (an agent that posts, a connector notification) over anything that fans
  out, and apply the same cost gate above when an action calls a credits-based
  provider. Scope/threshold and firing semantics:
  [`../cargo-observability/SKILL.md`](../cargo-observability/SKILL.md).
- **`defineMailbox` bills monthly, and `defineDomain` rewrites a DNS zone.** A
  mailbox is 100–160 credits *per month* for as long as it exists (`cargo-ai
  mailboxManagement pricing get` for live figures), so a `+ create mailbox:…` line
  in the plan is a recurring charge the user approves, not a one-off. Its `domain`,
  `username` and `type` are **create-only** — changing any of them is destroy +
  recreate, i.e. a brand-new inbox back at the bottom of a 45-day warm-up ramp. The
  deploy polls `refreshStatus` for up to 5 minutes waiting for `active`. On
  `defineDomain`, `dnsRecords` is the **whole zone, not a patch**: declaring it
  replaces every live record (including the ones the registrar wrote at purchase),
  and omitting it leaves the zone untouched. Use `adopt: true` for a domain or
  mailbox bought in the UI. Ramp, suppression and sending:
  [`../cargo-mailbox-management/SKILL.md`](../cargo-mailbox-management/SKILL.md).
- **Route CDK-managed resources into a clearly-labelled folder.** Set `folder:` on
  each builder so everything CDK owns lands in a dedicated folder whose name signals
  "owned by code — don't hand-edit" to anyone in the UI (manual UI edits read back as
  drift on the next `plan`). Folders are per-kind, so give each kind its own but share
  one short, recognizable prefix — recommended: **`🔒 CDK`** (e.g. `🔒 CDK Models`,
  `🔒 CDK Agents`). Keep names short (long labels truncate in the folder tree); the
  lock emoji is the "don't touch" cue. See
  [`guides/authoring-resources.md`](guides/authoring-resources.md).

## Help

- `cargo-ai cdk --help` and `cargo-ai cdk <subcommand> --help` for the live flag
  surface.
- When a documented command/flag/response doesn't match what you observe, file a
  report: `cargo-ai workspaceManagement report create` (see
  [`../cargo-workspace-management/SKILL.md`](../cargo-workspace-management/SKILL.md)).