From zenml-io-skills
Migrate Amazon SageMaker Pipelines and workflow code to idiomatic ZenML pipelines. Handles concept mapping (Pipeline->@pipeline, ProcessingStep/TrainingStep->@step, PropertyFile/JsonGet->artifacts), code translation, SagemakerOrchestratorSettings mapping, scheduling, model-registration strategy, and flags unsupported or high-risk patterns (CallbackStep, LambdaStep handshake semantics, step.properties placeholders, dynamic-pipeline scheduling on SageMaker) for human review. Use this skill whenever the user mentions SageMaker migration, converting SageMaker Pipelines, porting workflow code from SageMaker, replacing SageMaker DSL authoring with ZenML, or asks how a SageMaker Pipelines concept maps to ZenML -- even if they do not explicitly say "migrate". Also use when they paste `sagemaker.workflow.*` code and ask to make it work with ZenML, or when they describe a workflow using SageMaker terms (`ProcessingStep`, `TrainingStep`, `ConditionStep`, `PropertyFile`, `JsonGet`, `ModelStep`, `PipelineSession`) in a ZenML context. If the user just asks a quick conceptual question ("what's the ZenML equivalent of PropertyFile?"), answer it directly from the concept map -- no need to run the full migration workflow.
npx claudepluginhub joshuarweaver/cascade-ai-ml-engineering --plugin zenml-io-skillsThis skill uses the workspace's default tool permissions.
This skill translates Amazon SageMaker Pipelines and related workflow code into idiomatic ZenML pipelines. It handles the full migration workflow: analyzing the SageMaker pipeline, classifying each construct, translating what maps cleanly, flagging what needs redesign, and producing a working ZenML project.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
This skill translates Amazon SageMaker Pipelines and related workflow code into idiomatic ZenML pipelines. It handles the full migration workflow: analyzing the SageMaker pipeline, classifying each construct, translating what maps cleanly, flagging what needs redesign, and producing a working ZenML project.
SageMaker Pipelines and ZenML can run on the same AWS substrate, but they ask you to think about pipelines differently. Native SageMaker Pipelines is a step-type DSL: you choose ProcessingStep, TrainingStep, TuningStep, TransformStep, ConditionStep, and other managed-service wrappers, then wire them with pipeline parameters, step .properties, PropertyFile, and JsonGet.
ZenML is Python-step-first: you write @step functions that consume and produce typed artifacts, and the active stack decides where they run. When you use ZenML's SageMaker orchestrator, you are usually not leaving SageMaker. You are keeping SageMaker as the execution backend while changing the authoring model to ZenML pipelines and stack settings.
That means migration is not a rename-the-primitives exercise. Some patterns translate directly, some are approximate, and some require deliberate redesign.
Every SageMaker concept falls into one of these categories:
| Type | Meaning | Action |
|---|---|---|
| Direct | Clean 1:1 mapping exists | Translate automatically |
| Approximate | Conceptual equivalent exists but semantics differ | Translate with caveats noted in migration report |
| Absent | No ZenML equivalent | Flag for human review with redesign suggestions |
See references/concept-map.md for the full mapping tables.
Ask the user for their SageMaker pipeline code and any helper modules used by the pipeline. Useful inputs include:
Pipeline(...), pipeline.upsert(), pipeline.start())PropertyFile / JsonGetPipelineSchedule, put_triggers, EventBridge role setup)Read everything thoroughly before doing anything else. For each pipeline, identify:
ProcessingStep, TrainingStep, TransformStep, TuningStep, ConditionStep, FailStep, ModelStep, CreateModelStep, RegisterModel, CallbackStep, LambdaStep, QualityCheckStep, ClarifyCheckStep, EMRStep, NotebookJobStep, AutoMLStep)Parameter*, step .properties, PropertyFile, JsonGet, ExecutionVariables, or raw S3 URI handoffs used?ConditionStep branches, multiple condition types, or failure gates?For each component identified in Phase 1, classify it using the mapping type (direct / approximate / absent). Use the decision logic below and the full tables in references/concept-map.md.
Direct translations (translate automatically):
Pipeline -> @pipelineParameterString / ParameterInteger / ParameterFloat / ParameterBoolean -> typed pipeline parametersProcessingStep -> @stepTrainingStep -> @stepFailStep -> raise an exception in a stepApproximate translations (translate with caveats):
ConditionStep -> Python control flow, often @pipeline(dynamic=True) plus .load() for small control artifacts (note that dynamic pipelines are still experimental, and SageMaker-orchestrator scheduling does not support them)PropertyFile / JsonGet intent -> typed artifacts and normal Python accessSagemakerOrchestratorSettingsStepRetryConfig, while noting that SageMaker service-specific retry semantics may differTuningStep, TransformStep, QualityCheckStep, ClarifyCheckStep, EMRStep, NotebookJobStep, AutoMLStep -> ZenML @step that explicitly launches the corresponding AWS-native behaviorModelStep, CreateModelStep, RegisterModel -> explicit SageMaker model / registry calls in steps, or an intentional redesign to ZenML Model Control PlaneAbsent / needs redesign (flag for human review):
.properties placeholder semanticsPropertyFile / JsonGet backend-evaluation behaviorCallbackStep token-handshake semanticsLambdaStep semantics when the original design relies on its I/O and timeout constraintsBefore writing any code, present a summary to the user:
"Here's what I found in your SageMaker pipeline:
- Direct translations (will migrate cleanly): [list]
- Approximate translations (will work but with noted caveats): [list]
- Needs redesign (cannot auto-migrate safely): [list with brief explanation]
Shall I proceed with the migration?"
If there are HIGH-severity flags, explain each one concretely: what the SageMaker code does, why ZenML cannot replicate it directly, and what the recommended redesign looks like.
Translate the SageMaker workflow into a ZenML project. Follow these conventions strictly.
Every migrated project MUST use this layout:
migrated_pipeline/
├── steps/ # One file per step
│ ├── preprocess.py
│ ├── train.py
│ └── evaluate.py
├── pipelines/
│ └── my_pipeline.py # Pipeline definition
├── materializers/ # Custom materializers (if needed)
├── configs/
│ ├── dev.yaml
│ └── prod.yaml
├── run.py # CLI entry point (argparse, not click)
├── README.md
└── pyproject.toml
This matches the zenml-pipeline-authoring skill's conventions. Key rules:
steps/run.py uses argparse (click conflicts with ZenML)pyproject.toml with zenml>=0.94.1 and requires-python = ">=3.12"configs/dev.yaml AND configs/prod.yamlREADME.md explaining the migrated pipeline and what still needs human reviewzenml init at project rootFor each SageMaker construct, apply the appropriate translation. See references/code-patterns.md for detailed side-by-side examples covering the major migration patterns.
The core translation rule: move the pipeline's real business logic into @step functions with typed inputs and outputs. Use artifacts for data flow. Use SagemakerOrchestratorSettings only for runtime concerns you intentionally want to preserve on SageMaker.
# SageMaker: step-type DSL + placeholder-driven wiring
# ZenML: explicit Python step interface + artifact wiring
@step
def evaluate(model_uri: str) -> dict[str, float]:
return {"accuracy": 0.93}
@pipeline
def training_pipeline(model_uri: str) -> None:
metrics = evaluate(model_uri)
register_model(metrics=metrics)
PropertyFile / JsonGet -> Artifact passing: replace JSONPath-style placeholder extraction with structured return values:
@step
def evaluate_model() -> dict[str, float]:
return {"accuracy": 0.93, "f1": 0.91}
@pipeline(dynamic=True)
def gated_pipeline() -> None:
metrics = evaluate_model()
if metrics.load()["accuracy"] >= 0.9:
deploy_model(metrics=metrics)
Use .load() only for small control artifacts. For large datasets or models, keep the value as an artifact edge and make decisions on a summarized artifact instead.
Keep-SageMaker runtime mapping: when preserving SageMaker runtime behavior, move instance types, S3 channels, warm pools, tags, and similar infrastructure knobs into SagemakerOrchestratorSettings, not business-logic parameters.
Do not force every migrated project to depend on the full SageMaker SDK.
boto3 and/or sagemaker only if the migrated project still makes AWS-native service calls or uses SageMaker-specific settings importsKeep migration-related comments concise and actionable. Use # Migration note: for brief inline caveats and # TODO(migration): for items requiring user action. Put the long explanation in MIGRATION_REPORT.md, not in code comments.
When translating approximate patterns, add a short comment explaining what changed:
@step
def register_in_sagemaker(model_package_group: str, model_data_uri: str) -> str:
# Migration note: the original pipeline used SageMaker ModelStep/RegisterModel.
# This ZenML step keeps AWS-native registration explicitly instead of pretending
# ZenML MCP is a 1:1 replacement for SageMaker Model Registry.
...
For patterns that have no ZenML equivalent, do NOT silently approximate them. Instead:
# TODO(migration) comment in the generated code# TODO(migration): UNSUPPORTED -- the original pipeline used CallbackStep token
# handshake semantics. ZenML has no equivalent native callback step. Keep this
# portion as an explicit AWS workflow (for example Step Functions or an AWS API
# step) or redesign the orchestration boundary.
@step
def request_external_review() -> None:
...
After generating the ZenML project, produce a MIGRATION_REPORT.md in the project root:
# Migration Report: [SageMaker Pipeline Name] -> [ZenML Pipeline Name]
## Summary
- **Source**: SageMaker pipeline `[pipeline_name]`
- **Target**: ZenML pipeline `[target_pipeline_name]`
- **Steps migrated**: X direct, Y approximate, Z flagged
## Direct Translations
| SageMaker Construct | ZenML Equivalent | Notes |
|---|---|---|
| ProcessingStep `Preprocess` | `steps/preprocess.py` | Clean translation to `@step` |
## Approximate Translations
| SageMaker Construct | ZenML Equivalent | What Changed |
|---|---|---|
| ConditionStep `Gate` | dynamic pipeline branch | Backend placeholder evaluation became Python control flow |
| RegisterModel | explicit registration step | Kept SageMaker registry call explicit |
## Flagged for Review
| SageMaker Pattern | Severity | Issue | Suggested Redesign |
|---|---|---|---|
| CallbackStep | HIGH | No native callback-token step in ZenML | Keep as explicit AWS workflow or redesign boundary |
## SageMaker Runtime Mapping
| Native SageMaker concern | ZenML equivalent | Notes |
|---|---|---|
| Instance type / volume | `SagemakerOrchestratorSettings` | Runtime concern, not business logic |
| S3 channels | `SagemakerOrchestratorSettings` | Preserve only when exact AWS semantics matter |
| Model Registry | explicit SageMaker step or ZenML MCP | Choose intentionally |
## Scheduling
- **Original**: [native schedule / trigger details]
- **Migrated**: [ZenML `Schedule(...)` or manual trigger design]
- **Note**: SageMaker orchestrator supports cron / interval / one-time schedules, but dynamic pipelines cannot be scheduled there
## Limitations and Key Differences
[Put the most important semantic differences here before the benefits section.]
## What's NOT Migrated
[List SageMaker-native service behavior that was intentionally left AWS-specific or requires manual review.]
## What You Get for Free After Migration
- **Artifact lineage and versioning**
- **Step caching**
- **Stack abstraction**
- **Experiment tracker integrations**
- **Model Control Plane**
- **Service connectors**
## Recommended Next Steps
1. Run the `zenml-quick-wins` skill
2. Install the ZenML docs MCP server
3. Review the flagged items with the linked docs
4. Use `zenml-pipeline-authoring` for deeper customization
After migration is complete, always include a "Recommended Next Steps" section in the migration report AND communicate it to the user.
zenml-quick-wins skillAlways suggest this as the immediate next step:
"Now that the migration is done, I'd recommend running the
zenml-quick-winsskill to add metadata logging, experiment tracking, alerters, and other production features to your pipeline."
For every flagged pattern, include links to the relevant ZenML docs:
https://docs.zenml.io/stacks/stack-components/orchestrators/sagemakerhttps://docs.zenml.io/concepts/steps_and_pipelines/schedulinghttps://docs.zenml.io/concepts/steps_and_pipelines/dynamic_pipelineshttps://docs.zenml.io/how-to/infrastructure-deployment/auth-management/aws-service-connectorhttps://docs.zenml.io/stackshttps://docs.zenml.io/how-to/model-management-metrics/model-control-plane/register-a-modelWhen the migrated project intentionally preserves AWS-native service calls, include AWS documentation links for those preserved service-specific patterns too.
"For easier access to ZenML documentation while you work, you can install the ZenML docs MCP server:
claude mcp add zenmldocs --transport http https://docs.zenml.io/~gitbook/mcp"
When the migration has HIGH-severity flags -- patterns that could not be directly migrated -- offer to help the user get support from the ZenML community. When there are 2+ HIGH-severity flags, generate a pre-made Slack message for zenml.io/slack summarizing the SageMaker pipeline, the unsupported patterns, the attempted workarounds, and the concrete question for the ZenML team.
When the migration reveals a genuine missing feature in ZenML, offer to open a GitHub issue on zenml-io/zenml using gh issue create. Include the SageMaker pattern, the attempted workaround, and why the feature would help real migrations.
/simplify to clean up the migrated codeAfter migration is complete, always suggest running /simplify on the generated code. Migration often leaves behind caveat comments, duplicated wrappers, and portability notes that should be tightened up before the code feels production-ready.
zenml-pipeline-authoringThe zenml-pipeline-authoring skill handles deeper customization:
These are the most common sources of confusion after migration. Always mention the relevant ones in the migration report.
SageMaker pipeline code encodes AWS service choices directly in the graph: ProcessingStep, TrainingStep, TransformStep, TuningStep, and so on. ZenML uses a uniform @step abstraction, so service-specific behavior often moves inside the step body as an explicit SDK call or into orchestrator settings.
SageMaker uses backend-evaluated placeholders such as step .properties, PropertyFile, and JsonGet. ZenML uses typed artifacts and normal Python values. This changes:
ZenML can schedule SageMaker-orchestrated pipelines, but schedule lifecycle management is not the same as native SageMaker. Dynamic pipelines are supported on the SageMaker orchestrator in general, but they cannot currently be scheduled there. Re-running a scheduled pipeline creates a new SageMaker pipeline / schedule rather than updating the old one.
SageMaker Model Registry and SageMaker Experiments are not 1:1 with ZenML MCP and experiment trackers. For SageMaker Feature Store specifically, do not present the ZenML feature-store abstraction as a practical out-of-the-box replacement. If the user needs first-class SageMaker Feature Store support, keep the AWS-native usage explicit and suggest discussing the need in zenml.io/slack or contributing an integration.
| Anti-pattern | Why it's wrong | What to do instead |
|---|---|---|
Keeping PropertyFile / JsonGet just to mimic the old DSL | Recreates placeholder complexity and loses most ZenML benefits | Return typed artifacts and index them normally |
| Mapping infrastructure parameters to ordinary business-logic args | Blurs runtime config with pipeline semantics | Move instance types, S3 channels, tags, warm pools into orchestrator settings |
| Pretending SageMaker Model Registry == ZenML MCP | They overlap in intent, not in semantics | Choose a registry strategy explicitly |
| Pretending SageMaker Feature Store is covered by the ZenML feature-store abstraction | That overstates current support and sets the user up for the wrong migration plan | Keep SageMaker Feature Store explicit, or discuss a custom/community integration path |
Translating ConditionStep without noting control-flow changes | Backend placeholder evaluation and Python branching behave differently | Flag the change and use dynamic pipelines carefully |
Silently collapsing TuningStep into one training step | Destroys original HPO behavior | Keep an explicit HPO step design |
| Claiming Lambda / callback primitives are preserved | They are not native ZenML concepts | Keep them as explicit AWS integrations or redesign |
For topics beyond migration (stack setup, deployment, experiment tracking, model management), query the ZenML docs at https://docs.zenml.io.