Use for ZenML repo work involving tests, PRs, migrations, docs, integrations, models, orchestrators, server, or storage.
复制下面这句话,粘贴给 Claude Code、Codex、Cursor 等 AI 编程工具,它会读取安装说明并在你确认后完成安装。
请阅读 https://ai.atlankj.com/install/asset/gh-zenml-repo-workflows-254e4b9b8cc3 ,按照其中的说明把「zenml-repo-workflows」安装到你(当前 AI 工具)中。执行前先告诉我将运行的命令和写入的位置,等我确认。
查看 AI 将读取的安装说明正在读取 GitHub 原文…
内容来自 GitHub 原始文件,由原作者维护。在 GitHub 查看
Use this skill when work in the ZenML repository needs more detail than the
always-loaded AGENTS.md files provide. The root guide keeps safety rules,
universal conventions, and the most common commands in memory. This skill keeps
the longer recipes, examples, and subsystem checklists.
Former root headings covered here:
uv for Python package installation because it resolves
dependencies more quickly and reliably than plain pip.ZENML_LOGGING_VERBOSITY=DEBUGMLSTACKS_ANALYTICS_OPT_OUT=trueAUTO_OPEN_DASHBOARD=falseZENML_ENABLE_RICH_TRACEBACK=falseTOKENIZERS_PARALLELISM=falseZENML_ANALYTICS_OPT_IN=false: Disables analytics during developmentZENML_DEBUG=true: Uses the development ZenML analytics server to avoid
sending analytics to the official ZenML analytics server (IMPORTANT!). This
must be set even if ZENML_ANALYTICS_OPT_IN=true because in a client-server
setup, the server controls the client-side analytics opt-in status.bash scripts/format.sh.bash scripts/lint.sh.src/zenml tests/harness, yamlfix,
zizmor, unused import/variable checks, Ruff formatting checks, and mypy.bash scripts/test-coverage-xml.sh, but that does
not run every test.develop is the primary working branch. PRs should target develop.main is only updated during the release process.feat:.release-notes for user-facing features, API changes, important fixes, or
changes users should know about.no-release-notes for internal work, CI fixes, refactors, minor docs-only
changes, or maintenance work.ZenML uses a two-tier CI setup:
run-slow-ci label triggers full CI. Maintainers add it when required.Use comments to explain intent, trade-offs, invariants, and edge cases. Prefer clear names and small functions over comments that restate the code.
Good comments answer questions such as:
Avoid:
Args, Returns,
Yields, and Raises sections whenever the function contract requires them;
do not use a summary-only docstring to omit applicable sections.Put a helper on a class when it only makes sense in that class context or when subclasses call it frequently. Use a utility module for generic behavior shared across unrelated modules.
Example: BaseOrchestrator.requires_resources_in_orchestration_environment
could be a global helper, but it lives on BaseOrchestrator because
orchestrator subclasses call it often and the behavior is part of orchestrator
execution decisions.
Useful utility locations:
src/zenml/utils/src/zenml/orchestrators/utils.pysrc/zenml/orchestrators/step_run_utils.pysrc/zenml/orchestrators/publish_utils.pySymbols with a leading underscore are private and should only be called from inside their class or module.
When changing a non-underscore symbol, check whether it is exported from
zenml.__init__. Root exports and public methods on those exports are public
API and generally require deprecation before breaking changes.
Internal non-underscore code that is not exported at the root can usually be changed without deprecation, but update all internal usages.
Integrations should avoid ZenML private methods because future external integration packages will not be protected by in-repo mypy checks.
Prefer Protocol, ABCs, Union with isinstance narrowing, or typed adapters
over getattr/hasattr for capability checks. Use dynamic attribute checks
only when the object is truly untyped, and isolate that behavior in a small
typed helper.
ZenML OSS FastAPI work expects strong FastAPI, SQLModel, SQLAlchemy 2.0, and Pydantic v2 judgment.
Preferred patterns:
def route handlers in OSS Codex contributions.else blocks after returns.HTTPException with precise status codes for expected failures.Import rule: code outside src/zenml/zen_server/ must not import from
zen_server/. Client-side code should use Client and shared models from
src/zenml/models/.
ZenML uses SQLModel and SQLAlchemy. Avoid raw SQL unless it is genuinely needed.
Database schema changes require Alembic migrations.
Migration rules:
alembic revision -m "Add X to Y table".alembic upgrade head.main or develop.scripts/check-alembic-branches.sh to verify migration consistency.Migration testing workflow:
develop or the relevant old release.alembic upgrade head.Useful MySQL query for foreign key dependency tracing:
SELECT
kcu.CONSTRAINT_NAME AS fk_name,
kcu.TABLE_SCHEMA AS referencing_schema,
kcu.TABLE_NAME AS referencing_table,
kcu.COLUMN_NAME AS referencing_column,
kcu.REFERENCED_TABLE_SCHEMA AS referenced_schema,
kcu.REFERENCED_TABLE_NAME AS referenced_table,
kcu.REFERENCED_COLUMN_NAME AS referenced_column,
rc.UPDATE_RULE,
rc.DELETE_RULE
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
WHERE kcu.REFERENCED_TABLE_SCHEMA = '<DB_NAME>'
AND kcu.REFERENCED_TABLE_NAME IN ('table1', 'table2')
ORDER BY kcu.TABLE_NAME, kcu.CONSTRAINT_NAME, kcu.ORDINAL_POSITION;
Use it before adding cascade behavior, dropping columns/tables, debugging FK failures, or auditing existing delete rules.
Documentation content lives in docs/book/. Generated docs directories such as
docs/mkdocs/ and docs/site/ are not edit targets.
GitBook URLs follow the table of contents, not direct file paths. Pro docs under
docs/book/getting-started/zenml-pro/ are served under
https://docs.zenml.io/pro/....
When adding or removing docs pages, update the relevant toc.md. Assets belong
in a .gitbook folder beside the relevant toc.md.
Before committing docs changes with absolute URLs:
toc.md to understand the public URL.Link checking:
scripts/check_broken_links.py and scripts/check_relative_links.py check
relative markdown links.lychee --no-progress 'docs/book/**/*.md'.lychee --offline --no-progress 'docs/book/**/*.md'.Models live under src/zenml/models.
Core concepts:
.cursor/rules/zenml-domain-models.mdc and
.cursor/rules/zenml-orm-schema.mdc.Common base patterns:
BaseRequest -> {Entity}RequestBaseUpdate -> {Entity}UpdateBaseResponse[Body, Metadata, Resources]BaseFilter, UserScopedFilter, ProjectScopedFilter, TaggableFilterAdding a new entity usually means implementing Request, ResponseBody, ResponseMetadata, ResponseResources, Response, and Filter classes. Choose the narrowest scope that matches ownership semantics.
When adding a field to a filter model, update three locations:
Client list method signature.If the field should not become a CLI option, add it to CLI_EXCLUDE_FIELDS.
Test the new filter through the CLI, for example:
zenml pipeline runs list --new_field=some_value
Relationship-backed filter fields can also require custom ORM join behavior in the store layer.
Usually safe:
Risky or breaking:
Safe evolution pattern: add optional fields, deprecate before removal, use defaults when adding required behavior, and consider versioned responses for major changes.
Important files:
src/zenml/cli/utils.py for list_options.src/zenml/cli/pipeline.py for pipeline, run, legacy schedule, and
replay/resume commands.src/zenml/cli/trigger.py for native trigger commands.src/zenml/cli/resource_pool.py and resource_request.py for resource pool
and queue inspection commands.src/zenml/cli/stack.py for stack management.src/zenml/cli/base.py for core CLI setup and common decorators.List commands use @list_options(FilterModel) to generate options from filter
model fields, then pass them to explicit client method parameters. If a field is
missing from the client method, the CLI can raise:
TypeError: list_pipeline_runs() got an unexpected keyword argument 'new_field'
Scheduling command families:
zenml pipeline schedule ... for legacy schedule records.zenml trigger schedule ... for native schedule triggers.zenml trigger platform-event ... for platform event triggers.Resource pools use both management commands and request/queue inspection
commands. Check ResourcePool*, ResourcePoolSubjectPolicy*, and
ResourceRequest* models when editing this area.
Import rules:
zen_server/ in CLI code.zen_stores/; use Client.The most important integration rule: flavor files must not import integration libraries at module level.
Flavor files live at src/zenml/integrations/*/flavors/*.py. They are imported
for component discovery, so optional third-party libraries must only be imported
inside methods or under TYPE_CHECKING.
Correct pattern:
from typing import TYPE_CHECKING, Type
from zenml.orchestrators import BaseOrchestratorConfig
from zenml.orchestrators.base_orchestrator import BaseOrchestratorFlavor
if TYPE_CHECKING:
from zenml.integrations.aws.orchestrators import SagemakerOrchestrator
class SagemakerOrchestratorFlavor(BaseOrchestratorFlavor):
@property
def implementation_class(self) -> Type["SagemakerOrchestrator"]:
from zenml.integrations.aws.orchestrators import SagemakerOrchestrator
return SagemakerOrchestrator
Integration package shape:
src/zenml/integrations/<name>/
├── __init__.py
├── flavors/
├── orchestrators/
├── step_operators/
├── materializers/
└── ...
Step operators should implement the async-first lifecycle:
submit(...)get_status(...)wait(...)cancel(...)StepLauncher calls submit() plus wait() by default and falls back to
legacy launch() only for backwards compatibility. Store backend job IDs in run
metadata immediately after submission.
Dependency updates:
Key files:
base_orchestrator.pycontainerized_orchestrator.pyutils.pystep_launcher.pystep_runner.pycache_utils.py, input_utils.py, publish_utils.pyMain submission methods:
submit_pipeline(...) for static pipelines.submit_dynamic_pipeline(...) for dynamic pipelines.submit_isolated_step(...)get_isolated_step_status(...)wait_for_isolated_step(...)stop_isolated_step(...)BaseOrchestrator.run(...) already prunes steps skipped by replay or
client-side caching before submission. Integration orchestrators should not
re-implement that pruning.
get_orchestrator_run_idThis method must return an ID that is unique per backend run and stable for all steps in the same ZenML pipeline run.
Static pipelines often start directly with the first step. The first step uses the orchestrator run ID to create the ZenML run, and downstream steps use the same ID to find it.
Dynamic pipelines have an initial orchestration container. Use an ID that is stable for retries of that orchestration environment.
Kubernetes is special because it has an orchestration container even for static pipelines. Prefer the configured Kubernetes run ID for static pipelines and the parent Kubernetes job name for dynamic pipelines, falling back only when the job lookup fails.
Implementation checklist:
ContainerizedOrchestrator if steps run in containers.get_orchestrator_run_id() correctly.submit_pipeline() for static pipelines.submit_dynamic_pipeline() when supporting dynamic pipelines.SubmissionResult with wait_for_completion for synchronous
execution.self.get_image(deployment, step_name).orchestrator_utils.get_step_entrypoint_command(...).ORM schemas live under src/zenml/zen_stores/schemas.
Rules:
BaseSchema or NamedSchema.src/zenml/zen_stores/schemas/__init__.py.Foreign keys:
schema_utils.build_foreign_key_field.back_populates.Conversions:
to_model.from_request or from_model where appropriate.update or update_from_model.updated on mutations.Eager-loading rules:
selectinload; joinedload on collections multiplies rows.joinedload for get paths when the related
row usually exists.selectinload for nullable FKs that are usually empty.Import rule: code outside zen_stores/ should not import SQL-related code
directly from this directory. Use Client, or client.zen_store when lower
level access is truly needed.
Some features span many layers. When touching these, trace the whole path:
src/zenml/execution/pipeline/dynamic/src/zenml/pipelines/dynamic/step_launcher.pysrc/zenml/triggers/src/zenml/models/v2/core/resource_pool*.pyresource_request.pysrc/zenml/zen_stores/resource_pools/src/zenml/container_engines/ instead of direct Docker-specific callsget_orchestrator_run_id is unique per run and stable for
all steps in that run.zen_server imports outside zen_server.zen_stores.BaseStepOperator, StepLauncher, and at least
one concrete integration.