agentsclimarketplace

Migrate to rivers

Skill ion-elgreco/rivers/skills/migrate-to-rivers

Translate a Dagster or Prefect project to rivers, the Rust-powered asset orchestrator. Use when porting assets, ops, flows, tasks, jobs, schedules, sensors, partitions, IO managers, resources, retries, or automation conditions to rivers — or when asked to migrate/convert a data pipeline to rivers.From its SKILL.md

Install
npx -y skills add ion-elgreco/rivers --skill migrate-to-rivers

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

4.8 KB, as published. Nobody here has run it

Migrate to rivers

Port a Dagster or Prefect project to rivers without inventing API.

rivers is asset-centric like Dagster, so most Dagster concepts map closely — but the spellings differ in ways that look right and fail at import. Prefect maps loosely: its flows are imperative, rivers' graph is declarative, so parts of a Prefect project need design decisions rather than translation.

Ground rule

Never write a rivers symbol you have not verified. The API is small enough to check and niche enough that a plausible guess is usually wrong. Before using any name:

  1. Check references/rivers-api.md (in this skill) — signature-exact, generated from the type stubs.
  2. If it isn't there, read the stubs directly: python/rivers/_core/**/__init__.pyi in the rivers source, or python -c "import rivers; help(rivers.X)".
  3. Only then, the docs site.

Guessing costs more than checking: PartitionsDefinition.static_ (trailing underscore), MetadataValue.float_, daily(start=datetime(...)) (a datetime, not a string) are all things a confident guess gets wrong.

Workflow

1. Inventory the source project

Read the whole source project before porting anything. Produce a written inventory:

  • Assets/flows — name, upstream deps, partitioning, IO manager, resources used
  • Orchestration — jobs, schedules, sensors, automation conditions/triggers
  • Infrastructure — executors, work pools, K8s config, concurrency limits
  • Config & secrets — dg.Config classes, ConfigurableResources, Prefect Blocks, env vars
  • Things with no rivers equivalent — asset checks, dbt/dlt integrations, Prefect transactions, Cloud-only features

Report the last group to the user early. Do not silently drop or fake them.

2. Map concepts

Read the relevant reference file in full before writing code:

  • Dagster → references/from-dagster.md
  • Prefect → references/from-prefect.md

Both carry a mapping table, before/after code for every concept, and a gotchas section covering the traps that survive a naive port (sensor cursors, config generics, partition key formats).

3. Port, in dependency order

Bottom-up. The graph is the skeleton; everything else attaches to it.

  1. Resources and IO handlers — assets reference them, so they exist first
  2. Assets — the DAG, without partitions or automation
  3. Partitions — partition defs, then partition mappings on the edges
  4. Automation — automation conditions, schedules, sensors
  5. Jobs, executors, concurrency — the run-shaping layer
  6. CodeRepository — assembled last, since it registers all of the above

Port a slice at a time and validate it (step 4) before moving on. A 40-asset project ported in one shot fails with 40 tangled errors.

Keep the source project's module layout unless the user asks otherwise — a diffable port is easier to review than a reorganized one.

4. Validate continuously

Three gates, cheapest first:

python -c "from my_pipeline import repo; repo.validate()"   # graph only — no storage, no side effects
rivers materialize my_pipeline --memory                     # end-to-end, in-memory storage
rivers dev my_pipeline                                      # UI on :3000 — does the graph look right?

repo.validate() is the fast inner loop: it catches cycles, missing upstreams, unresolvable resource params, and bad partition defs in milliseconds without touching storage. Run it after every slice; only reach for the other two once a slice validates.

Then port the tests. If the source project has none, write at least one materialization test per asset group — rivers tests are ordinary pytest against repo.materialize() and repo.load_node().

5. Report

Summarize for the user:

  • What ported cleanly
  • What changed semantically — every place the rivers version behaves differently (see the gotchas sections; sensor cursors and Prefect's imperative control flow are the usual culprits)
  • What was dropped, and why
  • What needs their decision

Semantic drift is the thing a migration must surface. A port that runs but computes something subtly different is worse than one that fails loudly.

Scope discipline

Translate what exists. A migration is not the moment to redesign the pipeline, add error handling the original lacked, or "improve" asset boundaries. If the source has a genuine bug, mention it — don't fix it silently in the port.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.