Skip to main content
A recipe pack declares a post-training method: which entrypoint runs, which hyperparameters are tunable, which framework versions and images it supports, and which metrics the console should chart. It contains no code the platform executes.

Layout

plugin.yaml
Executable files are refused everywhere except template/. That directory is scaffolding handed to the user and run in their container; the root is parsed by the platform and must contain nothing it could be expected to import.

recipe.yaml

recipe.yaml

Parameter declarations

Each entry under params: is what makes sf validate able to reject a typo on your laptop instead of on the cluster.
int | float | str | bool | enum
required
Anything else is refused at load with the parameter name.
dot.path
Where the value lands in the training framework’s own config. This is what lets the platform translate a flat --set length_norm_power=1.2 into the framework’s native override.
string
Display grouping in the console and CLI. No effect on validation.
mixed
The validation contract. min/max are inclusive unless exclusive_minimum is set.
{version: path}
For when upstream moves a config key between framework versions. Declare the move here rather than branching on the version inside adapter code.

Metrics contract

metrics.primary is an ordered list; the first two go on the overview chart, and the diagnosis thresholds read the same keys. aliases maps a canonical key to whatever names a framework version actually emits, so a rename upstream does not blank a chart.

Bilingual display copy

Write the manifest in English and carry translations in one i18n: block. The console asks for the reader’s language and falls back to the manifest text where a translation is missing.
Parameter translations live under the locale rather than on each parameter. A recipe declares dozens of parameters, and an i18n: key on every one would bury the contract — path, type, range — that a reader opens the file to find. Translating a parameter that was never declared is an error, so a rename cannot leave a translation pointing at nothing. Only display copy is translatable. Names, paths and metric keys are the contract and read the same in every language.

Naming

A published method’s id is always <framework>/<owner>.<name>:
That namespace segment makes a collision with a built-in method structurally impossible — you cannot publish something that shadows nemo-rl/grpo.

Images

A recipe declares which container image the job runs. That is the one genuinely dangerous thing a declarative pack can express: plain YAML could otherwise make the cluster pull any image.Images must pass the deployment’s registry allowlist (FORGE_ALLOWED_IMAGE_REGISTRIES). Configure it before opening recipe publishing to users.

Confirm it worked

sf validate exercises your parameter declarations without touching a cluster. If a bad value passes there, the declaration is missing a range.

Versioning

version in recipe.yaml tracks the upstream framework release. The pack’s content identity is a separate bundle digest over the manifest plus the template, so changing a default or fixing a template updates the digest without pretending the framework changed. An experiment pins the digest in recipe.lock.json. When you publish a new version, existing experiments keep working until their owner runs sf recipe upgrade.