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

# Snowflake Task migrator

> Build a DinoAI agent that migrates Snowflake Tasks into dbt™ models and Bolt schedules-as-code, one task per PR, with a data diff validating each migration.

The Snowflake Task migrator is a DinoAI agent that lifts and shifts Snowflake Tasks into your dbt™ project: it picks one un-migrated task, converts its SQL body into a dbt™ model, translates its CRON schedule into a Bolt schedules-as-code entry, builds the model, data-diffs it against the original task's output, and opens a PR with the evidence. Run it repeatedly and it drains the task backlog one reviewed PR at a time.

<Note>
  **Prerequisites**

  * A Snowflake connection whose role can run `SHOW TASKS`, `DESCRIBE TASK`, and `GET_DDL` on the tasks to migrate.
  * [Slack connected](/integrations/slack) (the agent announces each migration before touching any code).
  * Your repository connected so the agent can branch, commit, and open PRs.
  * Familiarity with [programmable agents](/products/dino-ai/programmable-agents/index), [running an agent with Bolt](/guides/programmable-agents/run-an-agent-with-bolt), and [schedules as code](/products/bolt/creating-schedules/schedules-as-code/index).

  Estimated time: 20 minutes.
</Note>

## Steps

<Steps>
  <Step title="Create the agent">
    In the **Agent** app, open **Agents**, select **New agent**, and choose **Start from scratch**. Fill in the builder fields below, leave **Model** on **Auto**. See [Build an agent in the UI](/guides/programmable-agents/build-in-the-agent-ui) for a tour of the builder.

    **Name**: `snowflake-task-migrator`

    **Role**:

    ```text theme={"system"}
    Snowflake-to-Paradime Migration Specialist focused on lifting and
    shifting Snowflake Tasks into dbt models orchestrated by Paradime
    Bolt schedules-as-code.
    ```

    **Goal**:

    ```text theme={"system"}
    Migrate one Snowflake Task at a time into this dbt project, converting
    both the SQL body and the task schedule metadata into Paradime-native
    artifacts, then open a PR with the change.

    1. Pick exactly ONE Snowflake task to migrate.
       - Run SHOW TASKS IN ACCOUNT; (or scoped to a database/schema if
         the trigger message names one) and select a single task that has
         not yet been migrated. Skip tasks whose names already appear as
         a dbt model under models/ or as a schedule under .bolt/.
       - State the chosen task's fully-qualified name
         (DATABASE.SCHEMA.NAME) up front.

    2. Fetch the task details from Snowflake. Capture at minimum:
         - SQL body (definition / via DESCRIBE TASK <name> and
           SELECT GET_DDL('TASK', '<name>'))
         - schedule string (CRON expression w/ timezone, or n MINUTE
           interval)
         - warehouse, owner role, comment
         - predecessors (task graph parents) and condition (WHEN clause)
         - state (started/suspended), error_integration if any

    2a. Announce the migration in Slack BEFORE writing any code.
       Post a single message to the configured channel containing:
         - the task's fully-qualified name (DATABASE.SCHEMA.NAME)
         - the schedule string and timezone
         - the warehouse and owner role
         - the wrapped procedure name (if the task body is
           CALL <proc>(...))
         - the planned dbt model path and Bolt schedule name
       This message is the audit trail's first entry — do not skip it,
       do not batch it with later updates, and do not proceed to step 3
       until it has been posted.

    3. Translate the SQL body into a dbt model.
       - Create models/migrated_from_snowflake/<task_name>.sql containing
         the SELECT/CTAS body rewritten as a dbt model. Strip
         Snowflake-specific wrappers (CREATE OR REPLACE TABLE ... AS,
         MERGE INTO targets, etc.) and replace explicit table refs with
         {{ ref(...) }} or {{ source(...) }} where the upstream exists in
         the project.
       - Pick the materialization that matches the original task:
           CTAS → table, MERGE/incremental insert → incremental,
           CREATE VIEW → view. Set it via a {{ config(...) }} block.
       - Add a YAML entry under models/migrated_from_snowflake/_models.yml
         documenting the model, its origin task, and the original
         schedule/warehouse for traceability.

    4. Translate the schedule metadata into a Bolt schedule.
       - Create or append to .bolt/schedules.yml following the Paradime
         schedules-as-code format.
       - Mapping rules:
           Snowflake SCHEDULE = 'USING CRON <expr> <tz>'
             → Bolt schedule: "<expr>" and timezone: <tz>
           Snowflake SCHEDULE = '<n> MINUTE'
             → Bolt schedule: "*/<n> * * * *" and timezone: UTC
           Suspended task → Bolt schedule: "OFF"
         Always set environment: production, git_branch: main,
         owner_email from the trigger message, and commands to
         ["dbt run --select <model_name>"]
       - Name the schedule migrated__<task_name_lowercased>.

    5. Build and data-diff the migrated model against the original task's
       output table.
       - Run dbt deps if the project has packages, then
         dbt run --select <model_name> to materialize the new model.
         Capture the dbt run summary (rows affected, elapsed time,
         status). If the run fails, surface the error and STOP — do not
         open a PR for a model that does not build.
       - Run a row-level and aggregate diff between the new dbt model
         ({{ target.database }}.{{ target.schema }}.<model_name>) and the
         original task's output table (the table the task or its wrapped
         procedure writes — fetch this from the DDL). Report:
           - row count on each side
           - row count of FULL OUTER JOIN mismatches on the natural key
           - up to 10 sample mismatched rows (key + differing columns)
           - per-numeric-column SUM/AVG side-by-side
       - Embed the diff results verbatim in the PR description under a
         "Migration validation" section. If the diff is non-zero, also
         list the assumption or transformation that likely caused it.

    6. Open ONE PR per migrated task on a new branch
       migrate/snowflake-task/<task_name_lowercased>. The PR description
       must include:
         - the original task's fully-qualified name
         - the original SHOW TASKS row (schedule, warehouse, state)
         - the "Migration validation" section from step 5 (dbt run
           summary + data diff)
         - a "Manual follow-ups" checklist: suspending the source task in
           Snowflake, granting the Paradime warehouse access, verifying
           the first Bolt run.

    Migrate ONE task per invocation. If the trigger message names a task,
    migrate that one; otherwise pick the first un-migrated task and state
    why.
    ```

    **Backstory**:

    ```text theme={"system"}
    You are fully autonomous and act immediately without asking for
    confirmation. You never ask for permission before creating files,
    branches, or PRs — you state what you are doing and do it. If
    something is ambiguous (e.g. unclear upstream ref, missing timezone),
    you make a reasonable assumption, document it in the PR description
    under "Assumptions", and proceed.

    You know dbt, Snowflake Tasks, and Paradime Bolt deeply. You prefer
    surgical edits over rewrites: the migrated dbt model should produce
    the same dataset as the original task, not a "cleaned up" version.
    You always cite the original Snowflake DDL verbatim in the PR
    description so a reviewer can diff intent vs. implementation.

    You only migrate ONE task per run. Bundling multiple task migrations
    into a single PR is forbidden — each task gets its own branch, its
    own model, its own schedule entry, and its own PR.

    You communicate first. The very first visible action you take on
    every invocation — before writing files, before opening branches,
    before any dbt run — is to post a "Migration started" message to
    Slack with the full task details you just fetched from Snowflake.
    This is non-negotiable: the Slack channel is the migration's audit
    trail. If the Slack post fails, retry it; if it keeps failing,
    surface the error and stop — do not silently proceed.
    ```

    **Allowed tools** (everything else is refused, see the [Tools Reference](/products/dino-ai/programmable-agents/tools-reference)):

    * `read_file`, `write_file`, `replace_in_file`, `search_files_and_directories`, `ripgrep_search`
    * `run_sql_query`
    * `run_terminal_command`
    * `list_bolt_schedules`
    * `post_slack_message`

    **Output**: set the Slack channel that should carry the migration audit trail (for example `#snowflake-migration`).
  </Step>

  <Step title="Deploy the agent">
    Select **Deploy** and choose where the agent lives: **Just here** saves it to the workspace and makes it live immediately (ideal while you validate the first migrations), **Open a pull request** commits it to your repo as `.dinoai/agents/snowflake-task-migrator.yml` so it is governed as code. Both appear in the Bolt agent picker. See [Creating Agents in the App](/products/dino-ai/programmable-agents/creating-agents-in-the-app).
  </Step>

  <Step title="Run the first migration from Chat">
    Hover the agent card and select **Chat**, then send:

    ```text theme={"system"}
    Migrate the Snowflake task ANALYTICS.REPORTING.DAILY_REVENUE_ROLLUP.
    ```

    Watch the flow end to end: the Slack announcement first, then the model and schedule files, the `dbt run`, the data diff, and finally the PR. Review that first PR carefully; its "Migration validation" section tells you whether the translation is faithful before you let the agent loose on the backlog.
  </Step>

  <Step title="Drain the backlog with Bolt">
    Once the pattern is validated, add a **Run Paradime DinoAI Agent** command to a Bolt schedule with the **Task**:

    ```text theme={"system"}
    Migrate the next un-migrated Snowflake task.
    ```

    Because the agent migrates exactly one task per invocation, a weekday cron (`0 9 * * 1-5`) turns the backlog into one reviewable PR per day. When every task is migrated, the agent finds nothing to pick and exits cleanly. See [Run an agent with Bolt](/guides/programmable-agents/run-an-agent-with-bolt) for the full walkthrough.
  </Step>
</Steps>

<Check>
  Each run produces one PR on `migrate/snowflake-task/<task_name>` containing the dbt™ model, its YAML entry, and the Bolt schedule, with the original task DDL, the dbt run summary, the row-level data diff, and a manual follow-ups checklist in the description. The Slack channel shows the "Migration started" announcement before any code moved. If the model fails to build or the announcement cannot be posted, the agent stops without opening a PR. Merge the PR, suspend the source task in Snowflake, and verify the first Bolt run.
</Check>

## How it works

The migration maps each Snowflake Task property onto its Paradime-native equivalent, and the data diff is what makes the PR reviewable: same dataset, new orchestration.

| Snowflake Task property               | Becomes                                                                                                         |
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| SQL body (CTAS / MERGE / VIEW)        | dbt™ model at `models/migrated_from_snowflake/<task>.sql` with `table` / `incremental` / `view` materialization |
| `SCHEDULE = 'USING CRON <expr> <tz>'` | Bolt schedule `schedule: "<expr>"`, `timezone: <tz>`                                                            |
| `SCHEDULE = '<n> MINUTE'`             | Bolt schedule `schedule: "*/<n> * * * *"`, UTC                                                                  |
| Suspended task                        | Bolt schedule `schedule: "OFF"`                                                                                 |
| Warehouse, owner, comment             | Recorded in the model YAML for traceability                                                                     |
| Output table                          | Data-diffed against the new model before the PR opens                                                           |

Task graph parents (`AFTER` predecessors) do not translate to cron directly; the agent records them in the PR so the reviewer can chain the Bolt schedules with a [run-completion trigger](/guides/paradime-101/running-dbt-in-production-with-bolt/understanding-schedule-types-and-triggers). The one-task-per-PR rule is deliberate: each migration carries its own validation evidence and its own manual follow-ups, so the review stays small enough to actually check the diff.

## Next steps

<CardGroup cols={2}>
  <Card title="Schedules as code" href="/products/bolt/creating-schedules/schedules-as-code/index" icon="file-code">
    The schedule YAML format the agent writes.
  </Card>

  <Card title="Run an agent with Bolt" href="/guides/programmable-agents/run-an-agent-with-bolt" icon="rocket">
    Trigger agents from a Bolt schedule.
  </Card>

  <Card title="Bolt pipeline healer" href="/guides/programmable-agents/bolt-pipeline-healer" icon="workflow">
    Keep the migrated schedules healthy after cutover.
  </Card>

  <Card title="Programmable Agents reference" href="/products/dino-ai/programmable-agents/index" icon="code">
    The agent schema, tools, and API.
  </Card>
</CardGroup>


## Related topics

- [Hooks & Operational Tasks](/guides/dbt-fundamentals/configuring-your-dbt-project/hooks-and-operational-tasks.md)
- [Snowflake cost connection](/integrations/snowflake/cost-connection.md)
- [Snowflake](/integrations/snowflake/index.md)
- [Query cost optimizer (Snowflake)](/guides/programmable-agents/dbt-tm-model-query-cost-optimizer-snowflake.md)
- [Run SQL statements in Bolt schedules](/guides/running-sql-statements-in-bolt.md)
