Configuration
A deployment config is one YAML file. reef serve -c <file> reads it, starts every process in its services list in dependency order, and hands the reef section to the HTTP service.
reef:
host: 0.0.0.0
port: 8900
recipe: recipe
token: ${REEF_TOKEN}
upstream_url: ${REEF_UPSTREAM_URL}
upstream_api_key: ${REEF_UPSTREAM_API_KEY}
services:
- name: reef
command: ["${REEF_PYTHON}", "-m", "reef.service"]
ready: curl -sf http://127.0.0.1:${reef.port}/healthzValues interpolate from the environment with ${VAR} and from the config itself with ${dotted.path}. Any value can be overridden on the command line: a bare --model_path /models/demo targets the reef section, and a dotted --training.checkpoint_dir /tmp/ckpt targets any other. Each process writes a log under /tmp/reef-stack/; set run_dir to move it.
REEF_PYTHON defaults to the interpreter that launched reef serve and can be overridden in the environment. Use it when a service must share Reef's Python environment. A literal python keeps its normal meaning and is resolved from that service's PATH; Reef never rewrites command names.
Start from a cookbook stack #
The source checkout's runnable stacks live under the recipes/ cookbook and tutorials/: the learn-nothing ones use the core recipe implementation in recipes/basic/, each weight-training method owns its examples, and harness evolution ships as a tutorial.
|
File |
What it starts |
|---|---|
|
recipes/basic/external-provider.yaml |
no GPU, no local model: one Reef process proxying an HTTP provider |
|
recipes/basic/local-sglang.yaml |
local inference: an SGLang server plus Reef, no training |
|
recipes/<method>/examples/<example>/serve.yaml |
weight training: Ray head, Slime driver, Reef, and the method's own services |
|
tutorials/harness_evolve/serve.yaml |
harness evolution: one Reef process, no GPU; run.sh materializes its recipe preset and starts the stack |
Each weight-training example ships its stack as serve.yaml. recipes/sao/examples/sao/serve.yaml is the smallest, two GPUs for one actor and one rollout engine; recipes/tttd/examples/tttd/serve.yaml adds LoRA training, and recipes/openclawrl/examples/openclawrl/serve.yaml adds a PRM engine and a student model.
The reef section #
reef.recipethe recipe this deployment serves. Required.reef.host0.0.0.0bind addressreef.port8900bind portreef.tokenthe bearer token the service accepts. Use tokens: [...] to accept several while rotating.reef.model_patha local HF model directory or a repo id, downloaded on startreef.upstream_urlthe OpenAI-compatible provider, with no /v1 suffixreef.upstream_api_keyits credential. Reef is the only party that sees it.reef.upstream_modelthe model to request upstreamreef.upstream_apiopenaithe provider dialect; anthropic for an Anthropic-style endpointreef.inference_urlthe address the training backend reportsthe local engine; set only to front the engines with something elsereef.inference_timeout_s300.0per-request timeoutreef.allow_implicit_scenario_creationtruewhen false, an unknown scenario is HTTP 404reef.checkpoint_every_n_versions1how often a version becomes durableStorage paths default under .reef/, which the basic and sao stacks keep; the openclawrl stack overrides them to /var/lib/reef. Point them somewhere persistent.
reef.artifact_repository.reef/artifacts.gitthe Git-backed release chainreef.artifact_work_dir.reef/artifact-workmaterialization scratchreef.artifact_cache_dir.reef/artifact-cachefetched artifact cachereef.agent_record_dir.reef/agent-recordthe record storeRecipe settings such as batch_size sit beside these in the same section, along with any others the recipe declares with config_field. When reef.recipe is a dotted weight-training class, keys the service does not recognize are handed to the recipe, and the recipe rejects any key it does not declare. With the core recipe or a named preset, the recipe reads its configuration from the preset, and unrecognized keys here are silently ignored.
Recipe configuration #
A recipe is selected three ways:
-
The core record-only recipe: recipe: recipe
-
A dotted class: recipe: "my_pkg.my_method:MyMethodRecipe"
-
A named preset: recipe: my-preset, resolved to my-preset.yaml under REEF_RECIPE_CONFIG_DIR
There is no recipe-implementation registry. A bare name other than recipe is always a preset name; it never imports a learning method implicitly.
REEF_RECIPE_CONFIG_DIR is the directory preset YAML is read from, and it has no default: a bare recipe name resolves to a preset only when it is set.
A preset is read as-is. ${VAR} interpolates in a deployment config, never in a preset. A preset carries its own implementation, model, and data sections, plus an optional runtime section when the recipe builds its own runtime instead of using the deployment's upstream proxy. Harness-evolution presets also carry an evolution section:
implementation: reef.train.cordis_backend.recipe:CordisRecipe
model:
path: qwen3-8b
data:
batch_size: 1
max_score: 0.0
evolution:
adapter: pi
propose: methods.mine:propose
evaluate: methods.mine:evaluate
tasks: ["..."]The preset's implementation is recipe or a dotted recipe class. Weight-training recipes are selected directly by dotted class in the deployment config, so the service can assemble their Ray training runtime; their fields are flat reef.<name> keys. Presets suit recipes whose runtime can be built from the preset or the deployment's upstream proxy. There, data holds batching fields and a recipe-specific section holds the rest.
Harness evolution keys #
batch_size and max_score go under data:; the rest goes under evolution:. Evolve your harness describes what each one changes.
data.batch_size1traces per mutation attemptdata.max_score0.0upper bound of the score window that batchesdata.batch_policyreportsrecords batches recorded traffic alone, every batch_size requests, with unscored samplesThe window has no lower bound, so the default keeps only traces at or below zero.
evolution.proposea Proposer, a plain callable, or a dotted module:attributeevolution.evaluatean EpisodeScorer, likewiseevolution.selectionscore_comparisonalways, or a dotted reference to an object with decideevolution.tasksnon-empty list of episode prompts, scored once per tree per stepevolution.adapterpiopencode, claude, dsh (DeepSeek Harness), or an entry-point adapterevolution.binaryoverrides the adapter's binary nameevolution.episode_timeout_s600seconds one evaluation episode may runevolution.episode_repeats1episode pairings per task per step; each repeat tallies on its ownevolution.forbid_residuefalsewhen true, an episode leaving files outside the cleanup whitelist scores as one that could not runevolution.max_steps0stop after this many evolve steps; 0 disables the limitevolution.max_failure_streak0stop after this many consecutive rejected steps; 0 disables the limitevolution.max_model_calls_per_step0cap the proposer's model calls in one step; 0 disables the limitevolution.executorlocallocal runs episodes as a plain subprocess (development, hermetic tests); sandbox runs each in a bubblewrap jail for a hosted service and refuses to start without itevolution.sandboxthe sandbox executor's policy: egress_hosts (allowlisted model endpoints; empty denies network) and limits (cpu_seconds, memory_bytes, processes, file_bytes)evolution.promote_failuresfalsewhen true, a failing trace's prompt becomes a permanent gate task, so no later candidate can win while bringing the failure back; the seed tasks stay the floorevolution.max_promoted_tasks50the cap on promoted tasks; admission stops there so the suite is boundedevolution.promoteoptional callable or dotted module:attribute choosing which trace prompts to promote; receives the step's samples (and the failure manifest when its signature names manifest); without it every failing trace's user prompt is promoted, and the cap and credential screen still applyevolution.seedentry options loaded into the tree on first boot; recovered state takes precedenceevolution.modelsauxiliary models for the method: url, model, optional api (default openai) and timeout_s, with the credential as a literal api_key or an api_key_env variable nameevolution.version_checkappends the adapter's update notice; an interactive pulled tree offers to run the update or skip when behindThe served model's binding is appended at render time; it never enters the published files. The seed defines the baseline the first mutation is measured against.
The services list #
Each entry is one process. command can be a command-line string or an argv list. Prefer the list form when exact argument boundaries matter; existing string commands retain their current shlex parsing.
services[].namethe service's id, used by depends_on; unique within one stackservices[].commandthe command line string or argv list to runservices[].readya shell command that succeeds once the service is upservices[].ready_timeoutseconds to wait for ready before giving up; the top-level ready_timeout sets the defaultservices[].depends_onservices that must be ready firstservices[].cudathe value of CUDA_VISIBLE_DEVICES for this processservices[].envextra environment variablesThe training section #
Read by the weight-training stack. See Evolve your model for how to size it.
training.num_gpusGPUs handed to the Ray headtraining.cuda_visible_devicesthe devices Ray and Slime may usetraining.global_batch_sizesamples in one optimizer step. Must equal the recipe's batch_size.training.checkpoint_dirwhere Megatron and HF checkpoints are writtentraining.megatron_checkpoint_pathoptional pre-converted torch_dist checkpoint, to skip HF conversion on every starttraining.checkpoint_retentionstorage-fraction bounds and the retention policytraining.slime_flagsGPU layout, optimizer, sequence length, and loss settings, as one literal stringSlime fills architecture flags such as layer counts and hidden sizes from reef.model_path. Do not put them in the config.
The evaluation section #
Only weight-training recipes read this section; a deployment that pairs it with any other recipe fails at startup, because a harness recipe builds its evaluator in code. Absent by default, in which case a successful weight-training step publishes without a gate. When present, Reef calls the named factory once per scenario and hands the plugin the exported but unpublished checkpoint.
evaluation.modulea package.module:factory reference to the plugin factory. Required.evaluation.configopaque mapping handed to the factory; Reef never reads itevaluation:
module: my_pkg.evaluation:build_evaluator
config:
benchmark: gsm8k
threshold: 0.8The plugin interface is in Write a recipe.
Experiment tracking #
Tracking is optional, off by default, and belongs to a Reef scenario rather than to one training backend. The same provider-neutral logger is shared by the recipe, the processor, backend results, and the commit lifecycle. Install reef[wandb] when the training extra does not already provide it.
observability:
wandb:
enabled: true
project: reef
entity: your-team # optional
group_prefix: prod-us-east # optional scenario-group namespace
name_prefix: baseline # optional run-name prefix
tags: [openclawrl, qwen]
mode: online # online, offline, or disabled
directory: /var/lib/reef/wandb
upload_checkpoints: falseExport WANDB_API_KEY before starting, or log in once with the credential store on the cluster.
online sends data to the project. offline makes no network calls and writes syncable data below directory for a later wandb sync. disabled makes no calls even when enabled is true.
Each scenario maps to a group named after the scenario or <group_prefix>/<scenario>. Within it, Reef opens one run when the scenario binds and another after each rollback. The deterministic run id includes those identities, so restarting resumes the same run with resume=allow. A rollback finishes the current run, marks its summary with the source and target, and resets train/step to zero; the globally monotonic reef/step stays attached for joining a run back to the commit log.
Recipe and processor code logs through the same object without importing W&B:
experiment_logger.log({"temperature": 0.6}, namespace="recipe")
self.experiment_logger.log({"accepted": 12}, namespace="processor")Those become recipe/* and processor/*, each namespace on its own <namespace>/event axis. Only finite numeric values are sent.
Durable commit metrics carry experiment/provider, experiment/project, experiment/group, and experiment/run_id. Use them to open the run from a Reef version, and use the run's reef/training_job_id to go the other way. Checkpoint paths are metadata only unless upload_checkpoints: true.
Import, initialization, logging, summary, and upload failures are reported in the service log and never fail a training step or its commit.