Migrating legacy code
Legacy migrations work well when you break them into small, repeatable graph nodes. Start by discovering patterns, then migrate in slices.
Identify migration patterns first
Run deterministic scans to find candidates and group them.
steps:
- id: list_candidates
run: rg "oldApiCall" src -l
- id: group_patterns
structured_prompt:
prompt: |
Group these files by migration pattern:
{{ outputs.list_candidates }}
returns:
type: object
required: [groups]
properties:
groups:
type: array
items:
type: object
required: [pattern, files]
properties:
pattern: { type: string }
files:
type: array
items: { type: string }
starts_with: list_candidates
ends_with: group_patterns
edges:
- from: list_candidates
to: group_patterns
Generate a migration guide artifact
Use a prompt or agent to write a guide, then save it as an artifact so the rest of the workflow can reuse it.
Guides are useful for:
- codifying the new API or pattern
- listing constraints and exceptions
- defining test or verification steps
Migrate in slices
Use for_each to apply one pattern or file at a time. Keep instructions narrow and add constraints that prevent unrelated changes.
steps:
- id: group_patterns
structured_prompt:
prompt: "Group files by migration pattern."
returns:
type: object
required: [groups]
properties:
groups:
type: array
items:
type: object
required: [pattern, files]
properties:
pattern: { type: string }
files:
type: array
items: { type: string }
- id: migrate_one_file
name: Migrate one file
for_each:
from: '{{ outputs.group_patterns.groups[0].files | json_encode() }}'
agent:
extends: Coding
instructions: "Migrate {{ for_each.value }} to the new API."
constraints:
- "Do not change other files."
- "Keep public behavior the same."
starts_with: group_patterns
ends_with: migrate_one_file
edges:
- from: group_patterns
to: migrate_one_file
Handle long runs
- Use
continue_on_error: trueon the migration step so you can collect failures. - Use task graphs if you want to branch to a recovery path.
- Pair the workflow with Incremental workflow builds so every slice saves a resumable snapshot and you can restart from the last healthy step instead of replaying the entire migration.