Recipes without schema: 2 (or with schema: 1) use the pre-2.0 layout: worker topology under resources, the engine and its per-mode settings under backend, the discovery plane under infra, and placement as per-block booleans. This layout no longer loads.srtctl apply, srtctl dry-run, and every other reader reject a recipe that has no schema: 2 key, and reject any of the keys below even under schema: 2, with an error that names the keys and points here. srtctl migrate -f <recipe> --in-place rewrites a v1 recipe into the 2.0 layout, preserving comments and key order (a directory is walked recursively); it is the only part of srtctl that still reads this layout. The 2.0 layout is documented in schema-reference.md and config-reference.md.
Internally, srtctl still stores the resolved configuration in fields with these names: the 2.0 vocabularies (engine:, roles:, placement:, services:, dynamo.source) expand into them at load. That is an implementation detail; a recipe cannot reach those fields by their old spelling.
engine: (the type, plus engine-wide knobs) and roles.<role>.args / .env
infra (top level)
services: entries of type etcd and nats (placement.node: dedicated, options.max_payload_mb)
resources.prefill_nodes
roles.prefill.nodes
resources.prefill_workers
roles.prefill.workers
resources.gpus_per_prefill
roles.prefill.gpus
resources.prefill_critical
roles.prefill.critical
resources.decode_nodes
roles.decode.nodes (colocate replaces the 0 sentinel)
resources.decode_workers
roles.decode.workers
resources.gpus_per_decode
roles.decode.gpus
resources.decode_critical
roles.decode.critical
resources.agg_nodes
roles.agg.nodes
resources.agg_workers
roles.agg.workers
resources.gpus_per_agg
roles.agg.gpus
resources.agg_critical
roles.agg.critical
frontend.orchestrator_placement
frontend.placement.node: <location>
frontend.dedicated_node
frontend.placement.node: dedicated
benchmark.client_placement
benchmark.placement.node: <location>
benchmark.client_dedicated_node
benchmark.placement.node: dedicated
dynamo.version
dynamo.source.pypi
dynamo.wheel
dynamo.source.wheel
dynamo.hash
dynamo.source.git + dynamo.source.rev (a commit)
dynamo.cargo_patches
dynamo.source.patches
backend.prefill_environment
roles.prefill.env
backend.decode_environment
roles.decode.env
backend.aggregated_environment
roles.agg.env
backend.sglang_config
roles.<role>.args (one mapping per role; the prefill / decode / aggregated keys)
backend.kv_events_config
roles.<role>.kv_events
backend.mooncake_kv_store
a services: entry of type mooncake-master plus the worker env on roles.<role>.env (see mooncake-kv-store.md)
backend.prefill_extra_args
roles.prefill.extra_args
backend.decode_extra_args
roles.decode.extra_args
backend.aggregated_extra_args
roles.agg.extra_args
backend.trtllm_config
roles.<role>.args (one mapping per role; the prefill / decode / aggregated keys)
backend.vllm_config
roles.<role>.args (one mapping per role; the prefill / decode / aggregated keys)
backend.mocker_config
roles.<role>.args (one mapping per role; the prefill / decode / aggregated keys)
dynamo.top_of_tree is not in this table: it is still a 2.0 key. It has no immutable equivalent under dynamo.source, so the migrator leaves it in place and prints a note; pin a commit in dynamo.source.rev when you can.
These keys exist in both layouts, but the value means something else in 2.0. srtctl migrate writes the 2.0 spelling for a schema 1 document; a schema: 2 document keeps the 2.0 meaning.
Key
schema 1 value
2.0 spelling
2.0 meaning of the old value
frontend.type
sglang (the SGLang Model Gateway router)
sglang-router
sglang is the router-free single sglang.launch_server worker
Benchmark fields the recipe's benchmark.type never reads (isl on gsm8k, num_shots on sa-bench) are removed. They were silent no-ops in v1; schema 2 rejects them.
infra under a frontend that runs no etcd or NATS (sglang-router, vllm-router, the direct frontends) is not turned into services: nats_max_payload_mb is dropped (it had no effect) and etcd_nats_dedicated_node: true is left as is with a note, because it still reserves a node. Drop it by hand to give the node back to the workers.
A v1 recipe that never named a Dynamo to install pip-installed PyPI 0.8.0 implicitly. That default still applies when dynamo.source is absent; set dynamo.source or dynamo.install: false to make the choice explicit.
Types and defaults of the v1 keys, as the last loader that accepted them saw them, for translating by hand when a recipe cannot go through srtctl migrate.
trtllm_serve: which node runs the disaggregated orchestrator. "head" (default) -> nodes.head (first prefill/CTX node) "first_decode" -> first decode/GEN worker-leader node
dedicated_node
bool
False
If True, reserve a node exclusively for the frontend/orchestrator instead of running it on a worker node. Requires at least 2 nodes. Not supported together with resources.het_jobs: true. Default: False.
Which node runs the benchmark client: "head" (default) -> nodes.head (co-located with orchestrator by default) "last_decode" -> last decode/GEN worker-leader node (isolate the client off the CTX/orchestrator node). When the client lands on a different node than the orchestrator, use the injected $SRT_FRONTEND_HOST env in the benchmark command's URL.
client_dedicated_node
bool
False
If True, reserve a node exclusively for the benchmark client instead of running it on a worker node. Requires at least 2 nodes. Not supported together with resources.het_jobs: true. Default: False.
ai-dynamo package version to install via staged wheels
cargo_patches
list[str] | None
None
Optional dependency-declaration overrides applied to the dynamo Cargo.toml tree before a source build (requires hash). Each entry is a full <crate> = <spec> TOML line, e.g. 'dynamo-tokenizers = { git = "https://github.com/ai-dynamo/frontend-crates", branch = "..." }' The crate's existing declaration is replaced tree-wide, letting a source build pull a crate from an unmerged branch without waiting for a crates.io release.
backend.type selected the engine; the 2.0 layout writes engine: instead and moves the per-mode keys below onto roles.<role>. The engine-wide knobs (everything not listed here) are unchanged and documented under Engine types in schema-reference.md.
Extra trtllm-serve CLI flags per mode, appended verbatim to the worker command (frontend.type: trtllm_serve only). Used for options that configure the OpenAI server layer rather than the engine, such as --tool_parser.
If True, run etcd and nats on a dedicated node instead of the head node. This reserves the first node exclusively for infrastructure services. Default: False.
nats_max_payload_mb
int | None
None
Maximum NATS message payload in MB. Default: None (uses NATS default of 1MB). Set to 24+ for disaggregated serving with long ISL (e.g. 65K+ tokens where prompt data exceeds 1MB in NATS messages).