Command-Line Interface

The CLI is a convenience entry point for generic framework operations. It accepts one GenerationConfig YAML document plus optional Hydra overrides.

Command

Decisive invocation and options

Observable result / boundary

init

rfgen init {narrowband-baseline|chirp-radar|nr-pusch-baseline|conformant-ofdm-baseline|fec-constellation-baseline|multi-emitter-baseline|radar-response} DIRECTORY

Writes DIRECTORY/config.yaml; refuses an existing config.

explain

rfgen explain CONFIG or --config-dir DIR [--config-name NAME] [--override KEY=VALUE] [--view configured|materialized|typed|projection|all] [--sample-index N] [--seed N] [--format text|json|compact|mermaid] [--mode configured|materialized] [--expand-subgraphs] [--no-record] [--out PATH]

Renders the graph without evaluating a node. --format selects the rendering and defaults to text; see Reading a graph with explain below. Invalid input exits 3 with a JSON diagnostic on stderr.

validate

rfgen validate CONFIG or --config-dir DIR [--config-name NAME] [--override KEY=VALUE] [--check-backends]

Validates one configuration and prints valid; invalid input exits 3 with a JSON diagnostic. Validation is the generation contract, not the schema alone — see What validate and generate both require below.

generate

rfgen generate [--config-dir DIR] [--config-name NAME] [--dry-run] [--output PATH] [--num-samples N] [--shard-size N] [--seed N] [OVERRIDE ...]

Runs the selected executor. --dry-run prints resolved JSON and writes nothing.

generate sweep

Add --multirun/-m with a comma-separated override such as run.seed=1,2.

Runs each composed configuration; without a sweep it exits 3 with JSON on stderr.

inspect

rfgen inspect STORE_URI [--backend BACKEND] [--sample-size 0..100] [--sample-ids]

Prints a read-only store report as one JSON object: a fields inventory keyed by stored field name, each with its axis names, dtypes, and shapes; record_count; and sample_ids for the first --sample-size records (default 3, 0 for none). --sample-ids prints those ids alone, one per line, instead of the JSON report. STORE_URI is a Signal Dataset snapshot root. --backend defaults to signal_dataset and is an open selector: any store registered under the rfgen.dataset_stores entry-point group is reachable by name, and an unregistered name exits 1 with a config_error naming what is available.

annotate submit

rfgen annotate submit --config-dir DIR --config-name NAME [--override KEY=VALUE]

Starts the unified annotation lifecycle. Returns a terminal result on the synchronous paths and a durable handle on the asynchronous ones; see the table below.

annotate wait

rfgen annotate wait --handle-json JSON

Advances a submission by one polling cycle and returns; it does not block until completion. Prints {"handle": ...} while work remains, and adds a result object when terminal. Call it in a loop. Exit code: 0 complete, 2 complete_with_errors, 3 failed, 4 cancelled.

annotate cancel

rfgen annotate cancel --handle-json JSON

Requests cancellation of a remote submission; returns the same handle with status cancellation_requested. Follow with wait for the terminal outcome. Refused for a signal_dataset Dataproc handle; see the table below.

annotate resume

rfgen annotate resume --handle-json JSON

Recovers the same immutable submission after an interrupted polling process; returns a handle to continue wait against.

export-plan

rfgen export-plan --output/-o PATH.usda (--plan-file ARTIFACT | --config-dir DIR [--config-name NAME] [--override KEY=VALUE] [--run-seed N] [--sample-index N]) [--time-codes-per-second RATE] [--exporter NAME]

Writes one scene plan as a USD stage. Exactly one plan source: --plan-file reads back an artifacts/plans/<scene_id>/scene-plan.json artifact, --config-dir composes a configuration and mints its plan template. The composition and minting options --config-name, --override, --run-seed, and --sample-index belong to --config-dir and are refused beside --plan-file rather than accepted and ignored, because a written artifact already carries the identity they would decide. Prints a JSON summary carrying plan_hash, scene_id, and the byte count. Exit codes: 2 for a usage error (both or neither plan source, a composition option beside --plan-file, a --time-codes-per-second that is not finite and strictly positive or is too small to invert, or a missing extra) and also for a --plan-file artifact that exists but is not a valid scene plan, which reaches the CLI as a bare ValueError and is reported as {"error": "ValueError", "message": "runtime command failed"}; 1 for an --exporter selector no installed distribution registers; 3 for a refused request or plan, reported as {"error": "validation_error", "message": "validation failed"} with neither the refusal’s own sentence nor its context code. The default usda exporter requires the usd extra; without it the command exits 2 naming that extra.

list-nodes

rfgen list-nodes [--json]

Prints every bindable node read from the live registry, grouped by role and then by category family, with the implementation each selector resolves to. An installed distribution’s nodes appear beside core’s, tagged with the distribution that registered them, and are described from entry-point metadata rather than imported, so a missing optional dependency does not hide one. --json emits the same entries as one JSON document (family, implementation, origin, role, selector). Replaces the removed list-emitters and list-channels.

Every selector in that listing is an ordinary name. None is spelled with a leading underscore, and none is a primitive you are being warned off.

The combiner selector collect used to be the exception, spelled _collect, and this page used to explain the underscore as marking “a plan-building primitive you rarely author by hand”. That explanation was the problem. collect is the collection primitive the scene-plan builder synthesizes for a repeat/collect construct, and it is also the operator a scene needs to concatenate a ragged collection with a fixed row — the entities a repetition draws, together with the ones it does not. A use-case package that needed exactly that read the underscore as “not mine to use”, concluded core had no such node, and worked around it in a way core’s own readers could not then read back. The name is now collect, and the underscore spelling is retired rather than aliased. See the architecture reference for its contract.

Reading a graph with explain

explain answers four questions about a graph, and --view picks which: configured is the authored graph with every branch intact, materialized is the one graph a given (--sample-index, --seed) produced, typed is that graph with every port’s resolved type and evaluation schedule, and projection is the record’s fields mapped to the ports that produce them. all returns all four.

--format decides how those views are rendered. Nothing about the graph changes with the format; only what a reader gets back.

--format

What it emits

Use it for

text (default)

Sectioned, indented text: nodes with role, selector and reproducibility class, typed ports, edges, draws, record projection.

Reading a graph.

mermaid

One Mermaid flowchart.

Reviewing a graph, or pasting a diagram into a pull request.

json

The same result as indented JSON.

Reading a specific value.

compact

One line, key-sorted, separators compressed.

A machine consumer. Pin this format if you parse the output.

compact is the format explain emitted before --format existed. A script that parses explain should pass --format compact explicitly.

What the views carry

Three facts the graph already computed now survive into the result.

  • materialized.draws maps each qualified draw key to the outcome materialization resolved for it: the branch a select or conditional took, the count a repeat drew, the value a $draw parameter chose, along with the options that were on offer. Expansion previously kept only the consequences, and a branch body is expanded under its operation’s name rather than its branch’s, so no other part of the result says which branch produced a record. A decision the author wrote as a literal is recorded too, with a null key.

  • materialized.sample_index and materialized.run_seed state the coordinate the specialization is of, so a set of draw outcomes names the record it describes.

  • configured.completed is the authored graph with every omitted port type filled in by type completion. It is what lets an all-branches diagram say what a wire carries in a branch that was not taken, where the materialized and typed views have nothing to say. configured.graph and configured.identity are unchanged — completed is a sibling key, not a rewrite — but it is large (roughly eight times graph on the shipped narrowband config), so a consumer that only wants the authored graph should read configured.graph rather than the whole view.

typed.materialized_nodes rows now also carry repro, each node’s reproducibility class. It backs every determinism claim made about a node and was previously computed during binding and dropped.

The diagram

--format mermaid has two modes, matching the graph’s own staging split.

--mode configured draws the authored graph with every branch shown. A select, conditional or repeat becomes a labelled container carrying its draw key, and each branch is a cluster inside it, so a reviewer sees the whole space of graphs one configuration can produce. A subgraph call is drawn collapsed; --expand-subgraphs opens it.

--mode materialized draws one record’s graph: the branch actually taken, each repeat at its actual iteration count, and a header stating the draw outcomes that produced it. It reads --sample-index and --seed.

Both modes use the same conventions:

  • A dashed edge is a plan edge, known after materialization. A solid edge is a realized edge, known only once its producer runs.

  • An edge is labelled with the port name and the value type’s kind and units (SceneIQ[complex64, time, V], TransmitPower[dBm]), not the full type spec.

  • A label-role node is drawn as a hexagon and the evidence edges feeding it are highlighted, so a ground-truth field’s provenance is legible at a glance.

  • Each node carries its reproducibility class as a badge: det, env, obs or nondet.

  • The record projection is a terminal rank of field names, each pointing at the port that produces it. --no-record omits it.

Nothing that requires executing a node is drawn. explain never reads a value or a waveform; inspect is the command for that, and the two stay separate.

--out PATH writes the rendering to a file instead of stdout. A --format mermaid --out graph.html writes a standalone page that renders the diagram in a browser; any other suffix writes the Mermaid source, conventionally graph.mmd. The HTML page loads Mermaid from a CDN, so it needs network access the first time it is opened; the .mmd source needs no toolchain at all and renders natively in GitHub and in published artifacts.

What validate and generate both require

A configuration that satisfies the Pydantic schema can still be refused. Both commands run the same four checks before anything is composed, so validate tells you what generate would do rather than only what the models accept:

Requirement

Refusal

run and storage are present

requires GenerationConfig.run and GenerationConfig.storage

projections declares at least one entry

generation requires at least one observation projection; a run declares what it observes through projections and nothing else

plan is authored

generation requires an authored ScenePlan configuration

storage.backend is signal_dataset and executor.name is a supported route

generation requires local or Dataproc Signal Dataset execution

The projections and plan requirements are new in this release and are the hard break: a run declares what it observes through projections, and there is no fallback for a configuration that omits them. Every shipped template (rfgen init) satisfies all four. A configuration written against an earlier release will not, and the fix is to add the two blocks — copy the shape from src/rfgen/generation/configs/narrowband-baseline.yaml.

For executor.name: dataproc_serverless, generate submits asynchronously and prints exact gcloud status and cancellation commands. It does not wait for remote shard publication; see Dataproc Serverless for prerequisites and lifecycle.

Annotation lifecycle by source

source_kind and mode

submit returns

Terminal status from

cancel / resume

signal_dataset, execution_mode: local

A terminal result, synchronously, publishing an annotation set beside the snapshot

submit itself

Refused

signal_dataset, dataproc_serverless

A durable handle on stdout, exiting 0

wait, reporting succeeded and failed row counts read from the published set

Refused

On the synchronous paths, submit exits 0 for complete, 2 for complete_with_errors (the run finished and at least one record failed: read terminal_counts and the per-record error_code), and 1 for any error that stopped the run before a terminal result.

The signal_dataset Dataproc route reports only 0 and 2 from wait, because a batch that finished without publishing left no terminal result to describe. That case exits 1 with an annotation_failed payload whose message names the batch and the gcloud command that explains it.

cancel on a signal_dataset Dataproc handle prints the gcloud dataproc batches cancel command with the batch id, region, and project filled in from the handle; rfgen does not cancel the batch itself. A handle saved before those fields existed leaves <region> and <project> as placeholders. resume is refused for that mode because the handle is already the durable record, and wait may be called against it any number of times.