Skip to content

CLI & Commands

synthkit ships several binaries under cmd/. All configuration is environment-driven (read from .env or the process environment) unless flags override specific values. Build everything with go build ./....

synthkit — the generator

The main binary. Scans the directory named by BLUEPRINTS (default ./blueprints) and loads only the runtime identities selected by BLUEPRINT_NAMES. Empty/unset starts setup mode; * explicitly loads the complete catalog.

./synthkit [flags]
FlagDefaultDescription
-oncefalseRun one full cycle and exit.
-dumpfalseWith -once: print the full series/label inventory to stdout (diff against signals/).
-preflightfalseValidate and probe mandatory live Grafana endpoints, then exit with redacted lane/reason output.
-healthcheckfalseExit successfully only when the local control plane reports delivery readiness; used by Compose.
-versionfalsePrint {"version":"X.Y.Z","revision":"<40-hex>"} as JSON, then exit. Local builds report dev/unknown unless stamped.
-env <path>.envPath to the .env file (optional; falls back to process environment).

Verification modes (I32):

# Print the full inventory of distinct series names + label keys — push nothing.
DRY_RUN=true BLUEPRINT_NAMES=otlp-native ./synthkit -once -dump

# One live cycle (DRY_RUN=false to push real data).
DRY_RUN=false BLUEPRINT_NAMES=otlp-native ./synthkit -once

# The continuous loop (default).
./synthkit

DRY_RUN defaults to true. You must explicitly set DRY_RUN=false to push synthetic data to Grafana Cloud. See Configuration for the full environment variable reference, and Credentials for how the Grafana Cloud tokens are scoped.

What -dump output is deterministic, and what isn't. The authoritative section of a -dump — each sink's <name> {[sorted label/attribute keys]} inventory block — is the structural contract: series names, label/attribute key sets, and (for sigil) the ingest-kind → operation-name mapping. That block is byte-identical across consecutive runs and is what a diff against signals/ should compare.

The [dry-run <sink>] N series e.g. <series> lines underneath it are NOT part of that contract — each one logs a single randomly-sampled exemplar per batch, so which exemplar gets printed varies run to run by design. Never diff two raw -dump outputs line-for-line; it will show noise from this sampling (and, for sigil, from genuinely fresh per-run correlation IDs — see below) and prove nothing about a regression.

For the same reason, the sigil block's final summary line — == sigil: generations=N workflow_steps=N scores=N == — is a live COUNT, not part of the structural contract, and it is expected to vary run to run: internal/ledger mints a fresh, cryptographically-random SessionID/correlation ID per conversation on every run (by design — correlation ids must be unique and unguessable, never derived from a seed unit). Per-conversation turn count is a deterministic hash of that id (internal/workload/aiagent/minter.go's TurnCount), so a fresh id set naturally reshuffles the aggregate turn-derived counts (generations, scores) even though the number of conversations minted per tick is itself fully deterministic (fixed sessions_per_min config × the shape engine's fixed-seed PRNG). Do not treat a generations=/scores= count mismatch across two -once -dump runs as a determinism regression; treat a mismatch in the structural inventory block as one.

The control plane is available at http://<bind>:<port>/control/ (default port 8088). See control-plane.md.

synthkit-deploy.py — deployment identity and rollback helper

The stdlib-only helper emits closed, secret-safe JSON reports. It never prints .env values or private state content.

SubcommandMutation boundary
resolve-imageRead-only selector precedence and mutable-tag report.
set-imageCompare-and-swap only SYNTHKIT_IMAGE_REF; preserves unrelated bytes and aborts on drift. Mutable main/latest needs --allow-mutable.
snapshot-stateRequires the named container stopped; creates an integrity-manifested external private snapshot.
restore-stateRequires the container stopped; verifies the snapshot, atomically replaces the validated state target, and retains displaced state.
write-recordWrites one external mode-0600 closed identity record.
verify-imageRead-only exact index/platform/config/binary/signature/provenance verification.
check-composeRead-only minimum-version and default/profile rendering check with a fake env file.
inspect-runningRead-only cross-check of configured index, platform/config/image ID, health, binary version, and revision against expected values.

verify-image trusts the reusable rknightion/.github/.github/workflows/container-publish.yml workflow path across signer revisions. That trade-off puts control of the workflow repository inside the trust boundary; it does not relax the GitHub Actions OIDC issuer, synthkit source repository/digest/ref, OCI version/revision labels, selected platform, or binary identity checks.

See Deployment for the ordered upgrade and rollback commands.

sm-provision — Synthetic Monitoring provisioner

One-shot snapshot-bound provisioner for Synthetic Monitoring. The published image contains both the emitter and provisioner at the same source version. Preview is always the default.

docker compose --profile sm-provision run --rm sm-provision
SM_PROVISION_APPLY=true docker compose --profile sm-provision run --rm sm-provision
docker compose restart synthkit
Environment variableRequiredDescription
GC_SM_URLyesSM API base URL
GC_SM_TOKENyesSM API bearer token
CONFIG_SNAPSHOT_PATHnoEmitter control-state path; Compose sets /data/control-state.json
SM_PROVISION_APPLYnoExact true enables mutations; absent/false previews
SM_PROVISION_ADOPT_LEGACYnoExact true records the exact-match plan during preview and allows only that same plan during apply
SM_PROVISION_MIGRATE_TARGETnoExact true previews or applies a credential/endpoint target migration; apply also requires SM_PROVISION_APPLY=true and a matching preview no older than 15 minutes

DRY_RUN does not control this command. Source checkouts may use go run ./cmd/sm-provision, but the Compose profile is the supported Docker-only deployment route. See synthetic-monitoring.md for ownership and crash-recovery rules.

blueprint-schema — schema artifact generator

Regenerates the blueprint schema artifacts from the live Go types: BLUEPRINT-SCHEMA.md (the human reference) and internal/blueprintschema/fielddocs.json (the embedded field-description index used by the control-plane UI). Run this whenever a blueprint field or construct/workload config changes.

go run ./cmd/blueprint-schema
# or
just gen

The gate test TestSchemaCurrent (run by go test ./...) fails if these artifacts drift from the live types. See blueprint-reference.md.

skcapture — environment snapshot tool

Inspects a Kubernetes environment via kubectl and writes a versioned, optionally age-encrypted inventory file for later processing by skforge.

skcapture [flags]
FlagDefaultDescription
--out <path>capture.ageOutput file path.
--passphrase-file <path>—Path to a file containing the encryption passphrase. Required unless --plain.
--plainfalseWrite unencrypted JSON. Mutually exclusive with --passphrase-file.
--namespaces <list>(all)Comma-separated namespace allow-list.
--exclude-namespaces <list>kube-system,kube-node-lease,kube-publicComma-separated namespace deny-list.
--collectors <list>k8sComma-separated list of enabled collectors.
--version—Print tool version and schema version, then exit.

skcapture imports only internal/capture and the Go standard library — it has no dependency on any blueprint, construct, or workload package. It never captures Secret or arbitrary ConfigMap data values; the former flags promising that behavior were removed. Its optional identity lookup reads only one named ConfigMap's cluster key. See tools.md for the full capture-to-blueprint workflow.

skforge — blueprint forge

Converts a captured inventory into a synthkit blueprint draft. Three subcommands:

skforge inspect <capture> --key <passphrase-file> [--plain]
skforge prompt  <capture> --key <passphrase-file> [--plain] [--report <path>]
skforge validate <blueprint.yaml>
SubcommandDescription
inspectDecrypt (or read plain) a capture file and print it as indented JSON.
promptDecrypt, map the deterministic skeleton, and emit a self-contained LLM prompt to stdout. Optionally write a coverage report to --report.
validateLoad a blueprint through the real registry + cardinality projection and print the result. Exits non-zero if invalid.
FlagApplies toDescription
--key <file>inspect, promptPath to the passphrase file. Required unless --plain.
--plaininspect, promptSkip decryption; treat the file as plain JSON.
--report <path>promptWrite a coverage report to this path.

See tools.md for the full skcapture → skforge → blueprint workflow.

synthkit-dash — dashboard generator

Generates Grafana v2 dashboards for a blueprint's synthetic telemetry. Resolves the blueprint, derives the signal manifest, runs registered templates, and writes dashboard JSON files.

just dashgen -blueprint <path> -out <dir> -datasource <group=name> [flags]
FlagRequiredDescription
-blueprint <path>yesPath to the blueprint YAML.
-out <dir>yesOutput directory for generated JSON files.
-integrations <path>noOptional integrations config YAML for deep-link index.
-folder <uid>noUID for the generated Folder resource (defaults to <blueprint>-dashboards).
-datasource <group=name>yes, repeatedExplicit datasource binding for every rendered query group.

Always emits a Folder resource, a thin index dashboard, a metrics dashboard, and a panel inventory. Per-blueprint templates produce additional dashboards when registered. Use -verify-inventory and -observations to classify every panel as rendered, empty, or errored from normalized read-only observations. Push and validate with gcx. See tools.md.

synthkit-control-dash — control dashboard generator

Generates the customer self-serve control dashboard: an Infinity-datasource-backed Grafana v2 dashboard exposing the master volume multiplier and incident scenario controls as read panels with native action buttons.

go run ./cmd/synthkit-control-dash -ds-name <name> -out <dir> [flags]
FlagRequiredDescription
-ds-name <name>yesInfinity datasource name.
-out <dir>yesOutput directory for generated JSON.
-write-base-url <url>required with infinityBase URL for action-button POSTs; it must not end in /control because the generator appends /control/.... In fetch mode the browser must reach it; in infinity mode it must be an absolute HTTP or HTTPS URL without embedded credentials, a query or a fragment and the Infinity datasource must reach it. An empty fetch-mode value leaves relative paths.
-blueprints <dir>noDirectory of *.yaml blueprints to enumerate scenarios from (default ./blueprints).
-action-mode <mode>nofetch (default): browser-direct POST buttons. infinity: Grafana sends each POST server-side through the Infinity datasource. Requires -ds-uid, -write-base-url, and Grafana's vizActionsAuth feature toggle.
-ds-uid <uid>with infinityInfinity datasource UID used by server-side actions.

Without -write-base-url, the buttons POST to relative /control/... paths on Grafana's own origin. They work only when a reverse proxy on that origin routes /control/ to the synthkit control plane. Otherwise pass a browser-reachable -write-base-url.

In infinity mode, plain HTTP is acceptable only for the hop from Grafana through Private Data Source Connect (PDC), or from inside the cluster, to a ClusterIP control-plane Service. Never expose the control plane over plain HTTP across the public internet; use HTTPS elsewhere.

When CONTROL_TOKEN is set, the Infinity datasource stores the credential for server-side reads and infinity actions; fetch actions use the browser's separate Basic challenge. No token is embedded in the dashboard JSON. Anyone who can query the Infinity datasource can issue authenticated control POSTs and change load or scenarios, even without viewing this dashboard or using its action buttons. Restrict datasource query access and dashboard visibility to operators. Grafana's Viewer restriction on dashboard actions does not protect the datasource from direct queries. See Grafana's feature note.

just recipes

just --list is the authoritative recipe catalogue; use just --show <recipe> to inspect one without guessing its implementation. Run just check before committing. just ci is the Docker-capable CI superset.

RecipeDetail not carried by the recipe list
just corpus-gcx <context> [since]Requires an explicit gcx context and confirms before it cumulatively merges read-back evidence into reality-corpus/.
just published-e2eRequires SYNTHKIT_PUBLISHED_IMAGE_REF, SYNTHKIT_EXPECTED_VERSION, and SYNTHKIT_EXPECTED_REVISION; it exercises that exact published image through committed Compose.
just lab [permutations...]Long-running k3d capture matrix (about 45 minutes) requiring Docker, k3d, Helm, and kubectl.
just proto-drift-checkNetwork call outside just check; run before re-vendoring the RW2 proto or cutting a release.

Claude Code marketplace plugin

The supported marketplace host is Claude Code with plugin marketplace support. It is a host application prerequisite; the following are not shell commands. Open Claude Code, focus its chat input, and enter each slash command there:

/plugin marketplace add rknightion/synthkit
/plugin install synthkit@synthkit

After installation, start each bundled skill from the Claude Code chat input (again, not from a shell):

/synthkit:initial-setup
/synthkit:verify-deployment
/synthkit:create-blueprint
/synthkit:setup-fleet-management

The plugin supplies guidance and helper scripts only. It does not clone synthkit or install its binary. Clone or locate the checkout separately, then open that checkout in Claude Code before starting a deployment or blueprint skill.

Plugin installation is relocation-safe: install it from any directory, then later open or cd to the synthkit checkout and invoke a namespaced skill. The skill resolves its own helper files from the installed plugin and verifies the checkout root before touching repository files. Do not copy the plugin directory into the checkout or try to paste /plugin … commands into a terminal.