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

# Write Plays

> Author a durable Deepline Play for custom GTM logic, then validate it before a run or deployment.

[Human guide: work with your agent →](https://deepline.com/docs/human/sdk-v2/plays.md)

> [!NOTE]
>
> Author one bounded Play, check its contract, and inspect a representative run
> before publication.

Write a Play when the business decision is yours: custom routing, a specific
provider order, a scheduled refresh, or an outbound destination. Use a
[Prebuilt Play](https://deepline.com/docs/sdk-v2/prebuilt-plays.md) when the maintained contract already
does the job.

## A Play is a function

A Play is a `.play.ts` file that default-exports an async function of its
input. The file name is the default play name;
`export const config = { name }` overrides it. `deepline` is the play runtime; every
call on it is recorded in run history.

```ts theme={"languages":{"custom":["/languages/deepline-prompt.json"]}}
import deepline from 'deepline';

type Lead = {
  lead_id: string;
  linkedin_url: string;
};

export default async function (input: { csv: string }) {
  const leads = await deepline.csv<Lead>(input.csv);
  const rows = await deepline
    .dataset('lead_email', leads)
    .withColumn('email', (lead) =>
      deepline.runPlay(
        'person_email',
        'person-linkedin-to-email',
        { linkedin_url: lead.linkedin_url },
        { description: 'Find a verified work email for this lead.' },
      ),
    )
    .run({
      key: 'lead_id',
      description: 'Find one email for each lead.',
    });

  return { rows };
}
```

Save it as `enrich-lead-file.play.ts`, then check and run it:

```bash theme={"languages":{"custom":["/languages/deepline-prompt.json"]}}
deepline plays check enrich-lead-file.play.ts
deepline plays run enrich-lead-file.play.ts --input '{"csv":"leads.csv"}'
deepline plays run -e "deepline.log('hello'); return { ok: true };"
```

`plays check` validates without deploying or spending. `-e` runs code inline.

* The function's one parameter is the input contract; destructuring it is fine.
* The plain object it returns is the result; `return` early to end the play.
* `export const config = { ... }`, once at the top of the file, holds the
  description, triggers, and limits.
* The file imports `deepline` and exports nothing but the play function and
  its optional `config`.
  `deepline` works only in the play file; pass it to helper modules as an
  argument typed `DeeplinePlayRuntimeContext`.

The example keeps one durable row per `lead_id` under edition 10, so a failed
row is visible rather than silently dropped. Edition 11 CSV inputs retain each
source-row occurrence, including repeated keys. Non-CSV datasets and earlier
admitted editions keep their existing key-based deduplication behavior. Choose
the row policy from the admitted contract and reconcile every expected input
occurrence.

## Discover a live contract before provider work

Do not copy a provider ID or payload from an old example. Search the current
workspace, then read the schema:

```bash theme={"languages":{"custom":["/languages/deepline-prompt.json"]}}
deepline search --type tools "company search"
deepline describe free_simple_company_search
```

Use `deepline describe <id>` for both tool and Prebuilt Play contracts.

## Choose the runtime primitive

| Use | When the work is | What must be stable |
| - | - | - |
| `deepline.dataset(...).withColumn(...).run(...)` | Row-oriented and exportable | The dataset row key |
| `deepline.step(...)` | One scalar action or checkpoint | The step key |
| `deepline.tools.execute(...)` | One provider-backed operation | The tool call `id` |
| `deepline.runPlay(...)` | A statically resolvable scalar child Play | The child-call key |
| `deepline.fetch(...)` | An external HTTP request that should appear in run history | The request id and downstream idempotency key |

Use `deepline.runPlay(...)` for a Play, not `deepline.tools.execute(...)`. A
child Play must be small enough to run inline. A child that owns a dataset,
CSV, event wait, or explicit timeout must run as a top-level Play instead.
Compose a local child with `import child from './child.play'` and pass `child`
in place of its name.

## Validate before deployment or spend

```bash theme={"languages":{"custom":["/languages/deepline-prompt.json"]}}
deepline plays check ./enrich-lead-file.play.ts
```

This validates the Play without deploying it or starting provider-backed work.
Fix the reported contract or authoring error before you publish or run it.

## Add operational guardrails

```ts theme={"languages":{"custom":["/languages/deepline-prompt.json"]}}
import deepline from 'deepline';

export const config = {
  billing: { maxCreditsPerRun: 25 },
  cron: { schedule: '0 9 * * *', timezone: 'America/New_York' },
};

export default async function () {
  return { refreshed_at: new Date().toISOString() };
}
```

Deepline enforces `maxCreditsPerRun` on every run. To replace it for one run,
pass `--max-credits` to `deepline plays run`; see
[Cost control](https://deepline.com/docs/cost-control.md#cap-one-run).

Use the smallest credit cap and schedule that fit the job. For a file pilot and
export review, use [Batch CSV enrichment](https://deepline.com/docs/sdk-v2/batch-csv.md). For trigger
semantics, use [Webhooks and schedules](https://deepline.com/docs/sdk-v2/webhooks-and-schedules.md).

## Compose an Engine from Plays

Use the [Engine pattern](https://deepline.com/docs/plays/engines.md) when records need durable business
states across executions. Keep the orchestrator and new transition Plays in a
versioned project directory.

1. Define the state matrix: current state, transition Play, allowed next states,
   terminal outcome, and input/output table roles.
2. Search for existing owned or prebuilt Plays for each transformation. Inspect
   the input and output contracts before choosing one.
3. Use `deepline.runPlay` only for compatible scalar children. A child that owns a
   dataset, CSV, event wait, or explicit timeout needs a top-level lifecycle.
4. Implement state persistence through `deepline.customerDb.query(...)` in permitted,
   schema-qualified customer tables. Give each business record a stable key.
5. Define deterministic transition and handoff keys. Use database constraints
   and supported conflict handling, such as `INSERT ... ON CONFLICT`, to
   recognize completed writes. Repair missing handoffs without inserting a
   second completed transition.
6. Record invalid states, forbidden moves, child failures, and invalid results.
   Do not hand a failed transition to the next state.
7. Check each new child and the orchestrator, then publish compatible children
   before code that invokes them. Run a synthetic pilot before provider work.

Test terminal states, unknown states, invalid next states, child failures,
repeated inputs, and an interrupted handoff. If concurrent runs can advance the
same record, implement and test the workflow's conflict policy explicitly.
Stable call labels alone do not prevent independent executions.

For provider-backed pilots, set the smallest practical per-run credit cap.
External writes also need destination-supported idempotency or reconciliation.
The Engine pattern does not provide an automatic cross-system transaction.

## Next steps

> **[Prebuilt Plays](https://deepline.com/docs/sdk-v2/prebuilt-plays.md)**
>
> Discover a maintained Play or compose a scalar child Play.

> **[SDK reference](https://deepline.com/docs/sdk-v2/sdk-reference.md)**
>
> Read the exact `deepline`, dataset, and tool contracts.

## Verify this task

Check the actual source file. Test a supported small input and inspect outputs and errors against the intended business rule.

Ask your agent to explain a failed check before repeating work or expanding the scope.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.