patch.py
plugin.yaml
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.- eager (default)
- deferred
Called by the launcher 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
forge_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 starforge-core 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.kind: algorithm is loaded on the cluster. Other executable kinds are skipped with a log line —
an environment plugin is loaded by the environment machinery, and a data-prep plugin never
leaves your laptop.
Writing the function
- Raise on a real problem. An exception during
installstops 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.paramsis 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
It is not an accident, and it is not a workaround
It is not an accident, and it is not a workaround
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.
Two sources, one resolution order
A job can carry patches from two places:
When both name the same patch, the plugin package wins. An explicitly locked version has to beat
an implicit built-in.
Confirm it worked
The job log shows one line per plugin:plugins.lock.json and
resubmit.