Skip to main content
patch.py
plugin.yaml
That is a complete algorithm plugin. The framework runtime bridge imports patch, calls install, and your change is in effect for the rest of the job.

When your function is called

Two load modes, and the choice is about one thing: whether your patch needs the training context.
Called in the training entrypoint interpreter before the training entrypoint runs, with the job’s hyperparameters.
params is spec.hyperparams — the flattened --set overrides and the resolved config values for this submission. Use eager when your patch only needs to replace a function.
The name passed to install_deferred is the manifest’s name, not the full <owner>/<name>. Calling it with an unregistered name raises and lists what is registered.

What the launcher does

1

Verifies the injected package

Recomputes the digest of tuneplane_plugins/<name>/ and compares it to the JobSpec. A mismatch stops the job.
2

Checks SDK compatibility

If the manifest declares requires.core, the running tuneplane must satisfy it.
3

Puts the package root on sys.path and imports

Which is why top-level names that shadow real dependencies are refused at publish time.
4

Calls or registers

eager → calls install(params) immediately. deferred → registers it under the manifest name for the training entrypoint to call.
Only capability: algorithm is loaded on the cluster. Other executable capabilities are skipped with a log line — an environment plugin is loaded by the environment machinery, and a data-prep plugin never leaves your machine.

Writing the function

Rules that are worth stating because breaking them fails in confusing places:
  • Raise on a real problem. An exception during install stops the job. That is correct: a patch that silently did not apply produces a run whose numbers mean something other than what the experiment claims.
  • Be idempotent. With multi-node or restart-based executors your module can be imported more than once. Guard against double-wrapping a function you already wrapped.
  • Do not import the training framework at module import time unless you are certain it is present. Import inside install, where you know the container is the training container.
  • Read configuration from params, not from the environment. params is recorded with the job, so a reader can see what your patch was told. An environment variable is not.

Why monkey-patching is a supported mechanism here

For a cluster on an isolated network that cannot install new dependencies, a zero-dependency patch applied at runtime is a decisive advantage over shipping a forked framework image.What makes it acceptable rather than reckless is that the patch is declared, versioned and digest pinned: the platform can say which patch, at which version, ran in which job. An undeclared monkey-patch buried in a training script has all the same risks and none of that record.

Explicit bindings and supported scope

spec.recipe.plugins declares required algorithm names. Each must be satisfied by an exact spec.plugins lock. There is no common/algorithms/registry.py fallback or compatibility adapter. Installation occurs inside the interpreter that runs the training entrypoint, before framework code. The supported scope is entrypoint, including fresh ranks that re-enter the runtime bridge. It does not inject arbitrary Ray actors or workers spawned by a framework. Use a framework-native worker initialization hook for those. Other scopes are refused. Algorithm providers are rejected for custom and evalkit, whose execution paths do not use this algorithm bridge. Prefer native callbacks or registries over monkey-patching when the framework offers them. In-process providers share Python dependencies; distinct wheels do not create isolated dependency environments.

Check it worked

The job log shows one line per plugin:
If you see neither, the experiment does not reference the plugin — check plugins.lock.json and resubmit.