config.yaml schema
Every block in the tree — root or child — has a config.yaml with the same top-level shape. Live state is not stored here; that lives in artifacts/index.yaml. The schema is machine-enforced by scripts/validate_config.py (run by every block's dryrun.sh, the CI schema tests, and the :check skills).
Top-level structure
meta_info:
name: # short identifier; must equal the block's directory name
label: # human-friendly title
description: # one-sentence purpose
parent: # parent block's `name`, or null for the root
blocks: # parent blocks only: children with a role one-liner — NO wiring
<child_name>: {role: "<one phrase>"}
dependencies: # this block's own upstream AND downstream edges — see "Wiring rule"
from:
<input.dot.path>: <source_block>.output.<key>
to:
<output_key>: <consumer_block>.input.<their.dot.path>
repos: {} # name → {commit_id, role}; pinned vendored deps under repos/
resources:
ip: # 'local' (default) or null = current host; remote IP = SSH+tmux
directory: # working directory on the remote node (when ip is remote)
runtime_info:
input: {} # ONLY values originating outside the block tree
output: {} # values produced for downstream blocksThese are the only two top-level sections. Legacy status: and evolving: sections and a top-level environment: are validation failures (schema:legacy-status, schema:legacy-evolving, schema:unknown-toplevel); per-run env vars live in runtime_info.input.env_extra.
Wiring rule
This is the rule the whole pipeline depends on:
meta_info.dependenciesis a mapping with exactly two keys,fromandto— a block declares both directions it participates in, from its own file. A block with neither declares an explicitdependencies: {from: {}, to: {}}. The root'sblocksentries carry roles only — never wiring.
from — this block's own upstream hand-offs. Key is a dot-path into this block's own runtime_info.input; value names the producer's output. Two value forms:
# string form — required dependency
from:
task_source.dataset_name: curator.output.swe_tasks_dir
# mapping form — conditional and/or optional
from:
source.job_dir:
from: tracer.output.raw_trajectories_dir
when: {source.type: harbor_job} # enforced only while these inputs hold these values
required: false # null producer output → warning, not failureto — the mirror image, declared by the producer. Key is one of this block's own runtime_info.output keys; value names the exact consumer input field:
# string form
to:
swe_tasks_dir: tracer.input.task_source.dataset_name
# mapping form — when-keys are fully-qualified <consumer>.input.<path>, since the
# condition lives on the consumer's own state, not this (producer) block's
to:
swe_tasks_dir:
to: tracer.input.task_source.dataset_name
when: {tracer.input.task_source.provider: local}The same edge is declared twice — once in the consumer's from, once in the producer's to — each block only ever editing its own file. The validator cross-checks the two declarations and warns (dep:link-mismatch) if they've drifted apart.
Resolution reads the producer's runtime_info.output.<key>: a non-null value first, else path. A dangling reference (missing producer/consumer block or field) always fails validation, even when the dependency is inactive or optional. The validator also warns (dep:path-mismatch) when a consumer's configured path does not lie under the producer's declared output path — catching stale paths after renames.
Fill markers in runtime_info.input
| Marker | Meaning | Validator |
|---|---|---|
human | User must replace before a run | input:unfilled (fail) |
"" | Auto-derived or env/file-supplied; never edit to run | — |
null | Semantic unset / default / all | — |
| anything else | Real working default | — |
<...>, REPLACE_ME, YOUR_API_KEY, changeme | Retired legacy placeholders | input:placeholder (fail) |
runtime_info.output shape
Each output key is a mapping with path: (a static location fixed at authoring time, block-relative) and/or value: (run-produced — null until the block's run script writes it back, as trainer's train.sh does after training). Consumers resolve value first, then path.
resources.ip
localornull→ run on the current host (default; no SSH).- Real remote IP → the agent must SSH into that node and run inside a tmux session, with
meta_info.resources.directoryas the working directory.
Do not "restore" an old remote IP from git history. The local default is intentional.
Validation
python3 scripts/validate_config.py --root . # whole tree + cross-block deps
python3 scripts/validate_config.py --block blocks/tracer # one block
python3 scripts/validate_config.py --block blocks/tracer --config tests/smoke/config.yamlFindings are printed as [OK]/[WARN]/[FAIL] lines with stable labels (schema:*, dep:* — including dep:bad-shape for a malformed from/to mapping and dep:link-mismatch (warning) when the two sides of an edge disagree — input:*, output:shape, tree:*); the exit code is non-zero iff any [FAIL] was reported.
Where to read more
- The canonical contract:
.claude/plugins/root-plugin/resources/BLOCK_DEFINITION.md. - The schema template:
.claude/plugins/root-plugin/resources/config.template.yaml. - A worked example scaffold:
.claude/plugins/root-plugin/resources/example_block/.