Reference
CLI flag reference
Every command below is generated from rystic help <command>. Use this reference to choose flags; Docker configuration shows where to put them in Compose.
View command help directly from the Docker image without installing anything locally:
docker run --rm registry.rystic.ai/kalshi-twin:<version> rystic help run
Flags belong to a command; there are no inherited global flags. -h or --help prints help for any command. Long flags accept one or two leading hyphens. Booleans use --flag=false to turn off a default; --flag false does not set a boolean to false. Arguments after -- in run, batch and grid belong to the child process.
Defaults below come from help in a clean environment. Empty strings are shown as unset; numeric zero and boolean false are shown explicitly. run --port uses -1 internally to mean “resolve environment/config, then 8080.” See configuration precedence and live environment variables for resolved defaults.
| Command | Purpose |
|---|---|
rystic pull | download and install a twin release |
rystic update | install a newer twin release, or just ask whether one exists |
rystic run | run a twin (pulls it if missing); -d detaches, args after — go to the twin |
rystic ui | start a twin’s verified UI against a running instance (Pro and Enterprise; fetched on first use) |
rystic ps | list running twins — status from a live probe, not the record |
rystic stop | stop instances (SIGTERM, then SIGKILL after -timeout); with no name, stops the only instance |
rystic restart | stop and start again from the recorded spec |
rystic rm | remove a stopped instance’s record and log (refuses a live one) |
rystic logs | print a detached twin’s log; -f follows |
rystic report | the batch P&L table — per-world declared columns, exact totals, settled vs left open |
rystic up | start every service of a stack file — each exactly one run -d |
rystic down | stop every instance of a stack file’s project |
rystic clock | set or advance the clock of every running instance of a project, together |
rystic export | write a docker-compose.yaml (digest-pinned, license from .env) or the Antithesis scaffold from a stack file |
rystic status | license file, runtime, registry, installed, running, updates — one screen |
rystic batch | run a bot against every tape in your library — natively, or one container per world with —runner docker |
rystic grid | run the bot × world matrix: bundled example bots (plus yours) against the twin’s scenario worlds |
rystic grade | grade finished agent cells from their saved files: PASS, FAIL or ERROR with one reason |
rystic check | boot an installed twin and confirm it serves and resets |
rystic mcp | serve MCP on stdio: an agent starts, seeds, clocks, inspects and diffs twins through it |
rystic tail | stream a running twin’s state events to this terminal |
rystic feed | feed a running twin live exchange frames (the twin’s manifest declares the source; a release build carries the key it needs) |
rystic evaluator | run the command evaluators a twin dispatches, sandboxed, and report their outcomes |
rystic exec | run a command whose calls to a vendor’s hosts land on the project’s —intercept twins |
rystic env | print the exports rystic exec sets, for a shell to eval |
rystic gateway | run the project’s HTTPS gateway in the foreground (exec and env start it detached) |
rystic doctor | check this machine end to end without running anything |
rystic trust | trust the gateway CA in the macOS login keychain, for Go tools built before Go 1.27 |
rystic features | list what the installed twin can do |
rystic describe | what an installed twin serves: scenarios, seedable registers with an example record each, operations |
rystic docs | list, read and search every Rystic doc — the site’s pages (built in, offline) and each installed twin’s HOWTO and SCENARIOS |
rystic list | released twins your API key or license covers — latest, installed, running (—installed: the old per-version lines) |
rystic login | sign in through the browser, or install an API key or a license file, and log docker in to the registry with it |
rystic license | show the active license, or install one |
rystic config | show every setting with its source, or set one |
rystic feedback | report a bug, a point of friction or a feature request about the CLI or a twin; filed under your account |
rystic self-update | replace this CLI with the latest release |
rystic version | print this CLI’s version, then every running twin’s |
rystic help | print this message; help <command> shows a command’s flags |
pull
usage: rystic pull [flags] [product]
download and install a twin release
Installs into $RYSTIC_HOME/twins/<product>/<version> and marks it current,
verifying the download against the release checksum before unpacking.
| Flag | Default | Meaning |
|---|---|---|
--arch string | unset | target arch (default: this machine’s): amd64 or arm64 |
--endpoint string | $RYSTIC_RELEASE_ENDPOINT or https://www.rystic.ai/api/twin-release | release endpoint |
--license string | unset | path to the license file |
--os string | unset | target OS (default: this machine’s); linux for the docker runner |
--version string | “latest” | release version to install (e.g. x.y.z) |
update
usage: rystic update [flags] [product]
install a newer twin release, or just ask whether one exists
With -check, prints what is available and exits 10 when a newer
version exists — nothing is downloaded.
| Flag | Default | Meaning |
|---|---|---|
--check | false | only report whether newer versions exist (exit 10 if so); download nothing |
--cli-endpoint string | $RYSTIC_CLI_ENDPOINT or https://www.rystic.ai/api/cli-release | CLI release endpoint |
--endpoint string | $RYSTIC_RELEASE_ENDPOINT or https://www.rystic.ai/api/twin-release | release endpoint |
--license string | unset | path to the license file |
--no-self | false | do not update the CLI itself |
run
usage: rystic run [flags] [product] [-- args...]
run a twin (pulls it if missing); -d detaches, args after -- go to the twin
Prints one identity line — product, version, model, licensee, URL — once
the twin serves. --scenario/--seed/--tapes/--tape choose the starting state,
--clock the starting instant.
Default port 8080; a busy port is a conflict (exit 9); --port 0 picks a free one.
| Flag | Default | Meaning |
|---|---|---|
--clock string | unset | start the twin’s clock at this RFC3339 instant (RYSTIC_CLOCK); it stands in for the boot world’s own clock, and a reset returns to it |
--copies int | 0 | play —tape as this many markets at once, each under its own ticker (<tape>-1 … <tape>-N) on its own WS stream |
-d | false | detach: return once the twin serves; ‘rystic stop <name>’ ends it |
--egress-retain string | unset | RYSTIC_EGRESS_RETAIN for the twin |
--env value | unset | extra KEY=VALUE for the twin (repeatable) |
--inject string | then: $RYSTIC_INJECT | boot with this inject schedule (RYSTIC_INJECT); a world’s own events are replaced. With —tape it loads alongside the replay and its at values are tape time |
--intercept | false | boot behind the project’s gateway: vendor hostnames stay in responses, and ‘rystic exec’ routes the twin’s hosts to it |
--json | false | print the JSON envelope instead of the identity line |
--license string | unset | path to the license file |
--live | false | feed the twin live exchange frames from this process (the manifest declares the source; a release build carries the key it needs; default: $RYSTIC_LIVE) |
--live-carry int | 0 | pre-carry the busiest N before anything is picked; 0 carries nothing until the UI picks; ignored when markets or events are named (default: $RYSTIC_LIVE_CARRY) |
--live-env string | unset | exchange environment from the twin’s manifest (default: $RYSTIC_LIVE_ENV, then the manifest’s default_env) |
--live-events string | unset | comma-separated events whose open markets to carry, looked up again on every rescan; adds to —live-markets (default: $RYSTIC_LIVE_EVENTS) |
--live-events-file string | unset | startup event-list file: one event ticker per line or comma-separated; read once at startup (default: $RYSTIC_LIVE_EVENTS_FILE; mutually exclusive with —live-events) |
--live-markets string | unset | comma-separated markets to carry before anything is picked in the UI (default: $RYSTIC_LIVE_MARKETS) |
--live-markets-file string | unset | startup watch-list file: one ticker per line or comma-separated; read once at startup (default: $RYSTIC_LIVE_MARKETS_FILE; mutually exclusive with —live-markets) |
--live-max-markets int | 500 | how many of the busiest open markets to offer in the twin UI (default: $RYSTIC_LIVE_MAX_MARKETS) |
--live-series string | ”*” | comma-separated series to carry; * = every open market on the exchange (default: $RYSTIC_LIVE_SERIES) |
--live-shards int | 3 | exchange sockets to spread the carried markets over (the exchange caps connections per key; default: $RYSTIC_LIVE_SHARDS) |
--name string | “default” | instance name — what ps/stop/logs call it |
--network string | unset | RYSTIC_NETWORK for the twin: off, on, or a profile file |
--port int | -1 | listen port; 0 picks a free one (default: $RYSTIC_PORT, config.json, then 8080) |
--project string | “default” | project label — what ‘ps —project’ and ‘down’ group by |
--runner string | unset | native or docker (default: $RYSTIC_RUNNER, config.json, then native) |
--scenario string | unset | boot into a named scenario (the twin’s own catalog) |
--seed string | unset | boot seed file, applied before the first request |
--seed-rng string | unset | pin the twin’s RNG once serving |
--speed float | 1 | replay speed for —tape; paces a world’s timeline otherwise, never beside —live (0 = manual clock) |
--tape string | unset | start replaying this tape once serving (needs a tape library); a comma-separated list plays them as ONE synchronized group — merged onto a single clock and trimmed to the window they share |
--tapes string | unset | tape library for replay (validated before start; default: $RYSTIC_TAPES, config.json) |
--timeout duration | 30s | how long to wait for the twin to serve |
-v | false | a metered twin: print one line per lease heartbeat |
--version string | unset | installed version to run (default: current) |
ui
usage: rystic ui [flags] [product]
start a twin's verified UI against a running instance (Pro and Enterprise; fetched on first use)
Installs the UI once under the twin's install dir, with your API key or
license exactly as pull does, then starts its loopback server against the
instance named by --name (default: default) and prints the URL; Ctrl-C stops
it. The download and the start both need the verified_ui entitlement.
| Flag | Default | Meaning |
|---|---|---|
--endpoint string | $RYSTIC_RELEASE_ENDPOINT or https://www.rystic.ai/api/twin-release | release endpoint |
--fetch-only | false | install the UI’s archive and stop without starting it |
--license string | unset | path to the license file |
--name string | “default” | the running instance to point the UI at (what ‘rystic ps’ lists) |
--port int | 0 | the UI’s loopback port (default: the UI’s own) |
--runner string | unset | docker or native (default: $RYSTIC_RUNNER, config.json, then docker when it answers, else native) |
--url string | unset | the twin’s origin, instead of a running instance’s record |
--version string | unset | installed version whose UI to run (default: the instance’s) |
ps
usage: rystic ps [flags]
list running twins — status from a live probe, not the record
| Flag | Default | Meaning |
|---|---|---|
-a | false | include exited instances (and do not prune their records) |
--json | false | print the JSON envelope |
--project string | unset | only instances of this project (‘rystic up’ labels a stack file’s services with its project) |
stop
usage: rystic stop [flags] [<name>…] | --all
stop instances (SIGTERM, then SIGKILL after -timeout); with no name, stops the only instance
| Flag | Default | Meaning |
|---|---|---|
--all | false | stop every instance |
--json | false | print the JSON envelope |
--timeout duration | 8s | how long to wait after SIGTERM before SIGKILL |
restart
usage: rystic restart [flags] <name>… | --all
stop and start again from the recorded spec
| Flag | Default | Meaning |
|---|---|---|
--all | false | restart every instance |
--json | false | print the JSON envelope |
--timeout duration | 8s | how long to wait after SIGTERM before SIGKILL |
rm
usage: rystic rm <name>…
remove a stopped instance's record and log (refuses a live one)
No flags.
logs
usage: rystic logs [flags] [-f] <name>
print a detached twin's log; -f follows
| Flag | Default | Meaning |
|---|---|---|
-f | false | follow: keep printing new lines |
--feed | false | print the feeder’s log instead of the twin’s (‘run —live’) |
report
usage: rystic report [flags] <batch-out-dir> [-sort col] [-csv]
the batch P&L table — per-world declared columns, exact totals, settled vs left open
| Flag | Default | Meaning |
|---|---|---|
--csv | false | CSV to stdout (no totals row — spreadsheets sum themselves) |
--json | false | print the JSON envelope |
--sort string | unset | sort by a declared column, worst first; prefix - for best first |
up
usage: rystic up [flags] [-d] [-f rystic.yaml] [service…]
start every service of a stack file — each exactly one `run -d`
A stack file is a hand-picked set of twins: version: 1, project, clock,
speed, and services: {name: {product, version, runner, port, scenario, seed,
inject, tapes, tape, speed, clock, copies, env, args, seed_rng, egress_retain,
network, live, live_env, live_series, live_max_markets, live_shards,
live_carry, live_markets, live_markets_file, live_events, live_events_file}}
— the same keys as run's flags (live_x_y is --live-x-y); the service name is
the instance name; the project labels every twin for `ps --project`, `clock`
and `down`. A top-level clock (RFC3339) starts every twin's clock at that
instant and a top-level speed paces every twin; a service's own clock or
speed wins. The clock stands in for the clock a boot world (scenario, seed)
names, so its events count from it, and a reset returns to it; `rystic
clock` moves every twin's clock together afterwards. Already-running
services are reported, not restarted. Without -d, up holds the terminal and
Ctrl-C is `down`.
| Flag | Default | Meaning |
|---|---|---|
-d | false | detach: return once every service serves; ‘rystic down’ ends them |
-f string | ./rystic.yaml | stack file |
--json | false | print the JSON envelope instead of identity lines |
--timeout duration | 30s | how long to wait for each twin to serve |
down
usage: rystic down [flags] [-f rystic.yaml | --project p]
stop every instance of a stack file's project
| Flag | Default | Meaning |
|---|---|---|
-f string | ./rystic.yaml | stack file whose project to stop |
--json | false | print the JSON envelope |
--project string | unset | project to stop (instead of reading a stack file) |
--timeout duration | 8s | grace before SIGKILL |
clock
usage: rystic clock [flags] [-f rystic.yaml | --project p] --set <RFC3339> | --advance <duration>
set or advance the clock of every running instance of a project, together
Posts one /_rystic/clock to every running instance of the project (the stack
file's, or --project) at once, then prints each one's clock as its
/_rystic/version reads it. --set pins every clock to one RFC3339 instant;
--advance moves each by a duration of whole seconds (90s, 15m, 48h), so
clocks in step stay in step. Events fire as the clock passes them. Under a
manual clock (speed: 0) the instances then read the same instant, apart from
the millisecond each request they serve adds; a paced clock keeps moving,
and a playing tape or live feed re-pins its own. An instance that refuses
(a plan without state_set) or does not answer is named, and the command
exits non-zero.
| Flag | Default | Meaning |
|---|---|---|
--advance string | unset | move every clock by this many whole seconds (90s, 15m, 48h; negative moves back) |
-f string | ./rystic.yaml | stack file whose project to move |
--json | false | print the JSON envelope |
--project string | unset | project to move (instead of reading a stack file) |
--set string | unset | pin every clock to this RFC3339 instant |
--timeout duration | 5s | how long each instance has to answer |
export
usage: rystic export [flags] compose|antithesis [-f rystic.yaml] [-o dir]
write a docker-compose.yaml (digest-pinned, license from .env) or the Antithesis scaffold from a stack file
compose: each service becomes a compose service on registry.rystic.ai/<product>@<digest>
(resolved through the front door with your license file — the run is reproducible),
RYSTIC_LICENSE from .env (.env.example is written beside it, never a value),
init: true, the image's HEALTHCHECK for depends_on. antithesis: the same plus
platform linux/amd64, Dockerfile.config (FROM scratch, the compose file + .env)
and a README with the tenant push commands (--tenant, --gcp-project). A tape:
key cannot be expressed in compose and is reported.
| Flag | Default | Meaning |
|---|---|---|
-f string | ./rystic.yaml | stack file |
--gcp-project string | unset | antithesis: the GCP project of your tenant repository |
--json | false | print the JSON envelope; with no -o it carries the compose file |
--no-digest | false | pin images by tag, no registry call (a digest pin is what makes a run reproducible) |
-o string | unset | write the files here (compose: default stdout; antithesis: required) |
--tenant string | unset | antithesis: your tenant name (the README’s push commands) |
status
usage: rystic status [flags]
license file, runtime, registry, installed, running, updates — one screen
| Flag | Default | Meaning |
|---|---|---|
--json | false | print the JSON envelope |
--license string | unset | path to the license file |
--offline | false | do not ask the release endpoint about updates |
--pub string | unset | path to a license public key (overrides the built-in one) |
batch
usage: rystic batch [flags] [product] [flags] -- <bot command>
run a bot against every tape in your library — natively, or one container per world with --runner docker
One twin+bot pair per tape, -j at a time, longest tape first, at speed 1 —
real time, so the code under test keeps a coherent clock. One JSONL ledger
row per tape in -out. -dry-run prints the plan without running anything.
The bot contract is five environment variables — RYSTIC_ADDR, RYSTIC_WORLD,
RYSTIC_WORLD_KIND, RYSTIC_RESULT, PORT (a loopback port the cell's bot alone
holds), plus RYSTIC_MARKET on a tape cell — and the same values are
substituted into the bot's own argv as {{addr}} {{world}} {{world_kind}}
{{result}} {{port}} {{market}}, for a bot that takes them as
flags rather than from the environment: `-- python3 bot.py --event
{{market}}` gives each cell its own market instead of one market N times.
{{market}} is the MARKET ticker the tape directory is named for; a bot
wanting a coarser id derives it itself, and on -scenarios it is refused
rather than substituted empty. With --runner docker the substitution happens
inside your image, and an image whose rystic predates placeholders is
refused before it runs rather than passing the braces through verbatim.
--runner docker --image <ref> runs each world in its own container from YOUR
image (FROM registry.rystic.ai/<product>:<version> + your bot's runtime): the
same ledger, state and logs, kernel-isolated, no shared ports; the license
reaches each container only through its environment.
-json emits completed slots (not bot passes), failed slot names, skipped
world count and an interrupted flag; interruptions include a warning.
Bot outcomes are recorded in the ledger. Exit 7 means a slot failed.
| Flag | Default | Meaning |
|---|---|---|
--dry-run | false | print the plan and exit without running anything |
--egress-retain string | unset | value for RYSTIC_EGRESS_RETAIN on each twin (” = the twin’s default) |
--image string | unset | with —runner docker: the image each world runs in — yours, built FROM registry.rystic.ai/<product>:<version> plus your bot’s runtime |
-j int | 2 | concurrent slots (twin+bot pairs); -j auto is not built yet |
--json | false | print one summary as the JSON envelope when the run ends; progress goes to stderr |
--license string | unset | path to the license file |
--limit int | 0 | run at most N tapes (0 = all) |
--market string | unset | only tapes whose market matches this pattern (e.g. ‘KXBTC15M*’) |
--out string | batch-<timestamp> | output directory |
--runner string | unset | native (one twin process per world here) or docker (one container per world from —image; default: $RYSTIC_RUNNER, config.json, then native) |
--scenarios string | unset | run named scenarios instead of tapes: ‘all’, or a comma-separated list; the catalog is the twin’s own, so planning boots one twin briefly — even with -dry-run |
--settle-route string | unset | settlement endpoint to POST to (default: whatever the twin archive’s manifest declares) |
--settlements string | unset | JSON file of market -> outcome, applied when each tape ends so held positions realize (a capture never records how a market resolved) |
--tape string | unset | exactly one tape, by market id — the unit a container runs per world; not with -market |
--tapes string | $RYSTIC_TAPES, then $RYSTIC_HOME/tapes, then where the twin manifest says the twin looks | tape library |
--timeout duration | 30m0s | per-tape budget: twin boot, playback, teardown |
--version string | unset | installed version to run (default: current) |
grid
usage: rystic grid [flags] [product] [flags] [-- <bot command>] | <agent folder> -evals <dir|list> [flags] [-- <agent command>]
run the bot × world matrix: bundled example bots (plus yours) against the twin's scenario worlds
One twin per cell. Rows are the archive's declared grid bots, plus your own
command after `--` (same contract as batch: read RYSTIC_ADDR, exit 0 for
pass, and the same argv placeholders — except {{market}}, which a scenario
world lacks and which grid therefore refuses). Columns default to the
archive's declared worlds; -worlds overrides them ('all' runs the twin's
whole catalog). Each cell's bot gets a loopback port
of its own as $PORT and {{port}}, so parallel webhook agents never share a
listener, and each cell's twin delivers webhooks to the endpoints its bot
registers (-webhooks=false keeps cells hermetic). Prints the grid and exits
non-zero if any cell failed.
With -evals, grid runs the agent (a folder carrying agent.yaml, or a command
after `--`) once per eval instead: each cell boots the eval's twins, applies
its world, seeds them with -seed and loads its injected events, then starts
the agent behind a strict gateway of its own, so the agent's unchanged calls
to the vendor's hosts land on the cell's twins and any host outside the world
and agent.yaml's allow list is refused. The cell ends on the manifest's done
rule or -timeout, and <out>/<eval>/ keeps each twin's state before and after,
its calls and egress and the agent's log.
Each cell is then graded as `rystic grade` grades it:
its verdict.json and its <out>/ledger.jsonl row carry PASS, FAIL or ERROR,
the one reason and the checks digest. A cell passes on its verdict, never on
the agent's exit code; exit 7 when a cell is not PASS.
-ui serves the grid page on a local port over the live run: "Run selected"
and "Run failing" run those cells with the folder as it is now, "Run all"
reruns every cell, and grid serves until interrupted, then exits on each
cell's latest verdict.
| Flag | Default | Meaning |
|---|---|---|
--clock-step duration | 0 | with -evals and -pace 0, how far grid moves every twin’s clock each wall second |
--dry-run | false | print the plan and exit (‘-worlds all’ still boots one twin briefly to read the catalog) |
--evals string | unset | run the agent named in place of the product (a folder carrying agent.yaml), or the command after ’—’, on these evals: a directory of eval files, or a comma-separated list |
-j int | 2 | concurrent cells (twin+bot pairs) |
--json | false | print one summary as the JSON envelope when the grid ends; progress and the grid go to stderr |
--license string | unset | path to the license file |
--out string | grid-<timestamp>; must not already exist | output directory |
--pace float | 1 | with -evals, simulated seconds each twin’s clock moves per wall second (0 keeps it manual) |
--seed int | 1 | with -evals, the RNG seed every cell’s twins are pinned to |
--ticks int | 15 | tick budget handed to each cell’s bot via RYSTIC_TICKS (0 = no budget) |
--timeout duration | 5m0s | per-cell budget: twin boot, bot run, teardown; with -evals, the agent’s wall-clock cap |
--ui | false | with -evals, serve the grid page on a local port: rows go green or red on each cell’s verdict, Run selected and Run failing run those cells, Run all reruns the grid; runs until interrupted |
--version string | unset | installed version to run (default: current) |
--webhooks | true | each cell’s twin delivers webhooks to the endpoints its bot registers (a bot listens on its own $PORT); false keeps every cell hermetic |
--worlds string | unset | comma-separated scenario names, or ‘all’ (default: the archive’s declared grid worlds) |
grade
usage: rystic grade [flags] <cell-dir | grid-out-dir>…
grade finished agent cells from their saved files: PASS, FAIL or ERROR with one reason
Runs each cell's checks over the files its run saved (each twin's inspect
before and after, calls, egress and world.json), never a live twin, so a
cell regrades without rerunning its agent. Checks run in order: "nothing
destructive" (a row deleted, or a change the twin lists such as a channel
archived, unless the eval's allow_delete permits it), the eval's checks,
then the agent folder's checks/ (YAML files, and executable scripts printing
`PASS <name>` or `FAIL <name> (<reason>)` lines). FAIL carries the first
failing check's desc, or "never finished" when the time cap was hit; ERROR is
infrastructure only. Writes verdict.json into each cell. Exit 7 when a
cell is not PASS, 1 when one could not be graded.
--draft <cell-dir> grades nothing: it prints an eval drawn from one finished
cell, so a good run becomes a regression case. Its world, prompt and inject
are the cell's; its checks expect each row the run created or changed (the
fields it changed), forbid any other new row, changed field, call actor or
call method, and its allow_delete names each row the run deleted or
destructively changed. Values the run generated, its own ids and clock
times, stay out. The cell passes its draft; trim the draft to what the
task requires before keeping it.
| Flag | Default | Meaning |
|---|---|---|
--draft | false | print a draft eval drawn from one cell’s run, its checks for a person to trim; grade nothing |
--json | false | print the JSON envelope |
check
usage: rystic check [flags] [product]
boot an installed twin and confirm it serves and resets
| Flag | Default | Meaning |
|---|---|---|
--license string | unset | path to the license file |
--timeout duration | 15s | how long to wait for the twin to serve |
--version string | unset | installed version to check (default: current) |
mcp
usage: rystic mcp [flags]
serve MCP on stdio: an agent starts, seeds, clocks, inspects and diffs twins through it
A client config runs `rystic mcp` as its command (command: rystic, args:
["mcp"]). Tools: list_twins, start_twin, stop_twin, list_scenarios,
load_scenario, seed, set_clock, advance_clock, reset, inspect_state,
list_calls, list_sent, diff_state, save_test_case, check_test_case,
snapshot_twin and restore_twin. start_twin starts a twin as `run -d` does,
on a free port unless told one, pulling it first, with progress on stderr,
when it is not installed. snapshot_twin saves an instance's state, clock,
pending events and network shaping under a name in
$RYSTIC_HOME/run/snapshots/; restore_twin resets the instance to it.
inspect_state, list_calls, list_sent and diff_state answer a page of at
most about 20 KB: next_cursor reads the next page, filters and limit narrow
it, format detailed carries whole entries, and truncated with a hint says
what was cut and which call gets the rest. inspect_state links a larger
state whole as the resource rystic://instances/<twin>/<name>/state, and
diff_state's baseline is the resource .../baseline.
diff_state compares an instance's state with the baseline taken when the
server started it, loaded a scenario or reset it, kept beside the instance
record in $RYSTIC_HOME/run/ so it outlives the server; an instance with
none gets one on its first diff. save_test_case writes the run since that
baseline as a grid cell dir and returns the eval `grade --draft` drafts
from it, which the cell passes; check_test_case grades a later run, saved
as a new cell, against that eval's checks as `rystic grade` does. Each tool declares MCP annotations. read-only: diff_state, inspect_state, list_calls, list_scenarios, list_sent, list_twins; destructive: advance_clock, load_scenario, reset, restore_twin, seed, set_clock, stop_twin; open-world: start_twin. The prompts reproduce_webhook_bug and
write_test_for_flow walk an agent through those tools in order, and
completions offer their twin and scenario arguments from the running and
installed twins. Each tool also declares an outputSchema: its result
shapes or the error object, which every result's structuredContent
matches. Every tool error, a malformed argument included, is an error
result, never a protocol error, carrying a stable code, its cause, its
fix and retryable (with retry_after_ms when a wait helps); -errors prints
the table. A stdin line that is not one JSON-RPC message (invalid JSON,
nested over 10000 levels, over 32 MiB, or a batch) gets a JSON-RPC error,
-32700 or -32600, and the session goes on. Stdout carries only the
protocol, everything else goes to stderr; twins it started keep running
after it exits. `rystic mcp install --client <client>` writes the config
that runs it for Claude Code, Cursor, Codex, VS Code or Windsurf.
The server's instructions give the call order; the rystic://guide resource
carries each step, the instance forms, every error code and an example.
--twin <twin> serves one twin, named as `run` takes it: its tools take no
instance argument and act on its one running instance, or on --instance
<name>, and a call naming another twin is refused as out_of_scope;
start_twin starts that instance, and refuses while an instance it serves runs.
--read-only serves only the read-only tools; --tools a,b,c only those named
(with --read-only, read-only ones only). The instructions and the guide
then say what the server serves. A bad scope flag exits 2.
| Flag | Default | Meaning |
|---|---|---|
--errors | false | print every tool error code with its cause and fix, as a markdown table, and exit |
--instance value | unset | with —twin, act on this instance of it (default: its one running instance) |
--read-only | false | serve only the tools that change no twin state |
--tools value | unset | serve only these tools, comma-separated |
--twin value | unset | serve only this twin, as ‘rystic list’ names it; its tools then take no instance argument |
tail
usage: rystic tail [flags]
stream a running twin's state events to this terminal
Connects to the twin's /_rystic/events stream and prints one line per
event. -events scopes the feed (private, public, writes, reads, control;
or all) — the default shows fills, order updates and other state changes
and skips public market-data floods.
| Flag | Default | Meaning |
|---|---|---|
--addr string | “http://localhost:8080 ” | twin base URL |
--events string | private,writes,control; or off/all/comma list | event scope |
feed
usage: rystic feed [flags] [product] --instance <name> | --url <base>
feed a running twin live exchange frames (the twin's manifest declares the source; a release build carries the key it needs)
Runs in the foreground until Ctrl-C. `rystic run --live` starts a twin and
this feeder together (either runner: the feeder is a host process posting
to the twin's URL); use `feed` alone for a twin already running — any
runner, any machine. --live-series * carries whatever the twin's Markets
card picks from the exchange's busiest open markets; nothing is carried
until something is picked (--live-carry N pre-carries the busiest N,
--live-markets A,B carries exactly those). Every --live-x-y flag defaults
from RYSTIC_LIVE_X_Y. The twin's manifest names its exchange key vars: an
id, then either <X>_PRIVATE_KEY_FILE (a path) or <X>_PRIVATE_KEY_B64 (the PEM).
| Flag | Default | Meaning |
|---|---|---|
--instance string | unset | feed the named running instance (‘rystic ps’) |
--live-carry int | 0 | pre-carry the busiest N before anything is picked; 0 carries nothing until the UI picks; ignored when markets or events are named (default: $RYSTIC_LIVE_CARRY) |
--live-env string | unset | exchange environment from the twin’s manifest (default: $RYSTIC_LIVE_ENV, then the manifest’s default_env) |
--live-events string | unset | comma-separated events whose open markets to carry, looked up again on every rescan; adds to —live-markets (default: $RYSTIC_LIVE_EVENTS) |
--live-events-file string | unset | startup event-list file: one event ticker per line or comma-separated; read once at startup (default: $RYSTIC_LIVE_EVENTS_FILE; mutually exclusive with —live-events) |
--live-markets string | unset | comma-separated markets to carry before anything is picked in the UI (default: $RYSTIC_LIVE_MARKETS) |
--live-markets-file string | unset | startup watch-list file: one ticker per line or comma-separated; read once at startup (default: $RYSTIC_LIVE_MARKETS_FILE; mutually exclusive with —live-markets) |
--live-max-markets int | 500 | how many of the busiest open markets to offer in the twin UI (default: $RYSTIC_LIVE_MAX_MARKETS) |
--live-series string | ”*” | comma-separated series to carry; * = every open market on the exchange (default: $RYSTIC_LIVE_SERIES) |
--live-shards int | 3 | exchange sockets to spread the carried markets over (the exchange caps connections per key; default: $RYSTIC_LIVE_SHARDS) |
--url string | unset | feed the twin at this base URL, e.g. http://localhost:8080 |
--version string | unset | installed version whose manifest to read (default: current) |
evaluator
usage: rystic evaluator [flags] --instance <name> | --url <base>
run the command evaluators a twin dispatches, sandboxed, and report their outcomes
Runs in the foreground until Ctrl-C (--once: what is waiting, then exit).
Claims work from the twin's evaluation engine, unpacks the snapshot it
streams, and runs each command in a digest-pinned image (default --runner
docker) with no network and capped resources; --runner podman is also
supported.
Local commands require --runner local --trusted-local and a trusted_local
manifest. They have host access and produce unpinned feedback only. A
command exits 0 to pass, 1 to fail, 78 to decline the input, or writes an
outcome to $EVAL_RESULT; anything else is an infrastructure error.
| Flag | Default | Meaning |
|---|---|---|
--concurrency int | 2 | dispatches run at once |
--instance string | unset | serve the named running instance (‘rystic ps’) |
--once | false | run what is waiting, then exit |
--poll duration | 500ms | wait between empty claims |
--runner string | “docker” | how a command evaluator runs: local, docker or podman; an evaluator that names an image needs a container runner |
--trusted-local | false | allow local commands with host filesystem and network access; trusted code only |
--url string | unset | serve the twin at this base URL, e.g. http://localhost:8080 |
exec
usage: rystic exec [flags] -- <command> [args…]
run a command whose calls to a vendor's hosts land on the project's --intercept twins
Starts the project's gateway when none runs (detached, loopback only, recorded
in $RYSTIC_HOME/run/gateway/), then becomes the command, so its exit code and
signals are the command's own. The command's HTTPS_PROXY and HTTP_PROXY name the
gateway; SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, GIT_SSL_CAINFO
and AWS_CA_BUNDLE name a bundle of the system roots plus the gateway CA
($RYSTIC_HOME/ca/); NODE_EXTRA_CA_CERTS names the CA; GODEBUG lets a Go
1.27+ tool read SSL_CERT_FILE on macOS; NO_PROXY keeps localhost direct.
A host declared by a twin started with `rystic run --intercept` is decrypted and
served by that twin; a host Rystic simulates with no such twin running is
refused (403, naming the twin to start), apart from a model provider's API,
which reaches the real service; any other host is tunneled unread, unless
--strict, which refuses every host --allow does not name. On a Mac, Go tools
built before Go 1.27 read only the keychain; run `rystic trust` once.
Requires a client that honors these proxy and CA settings. Custom transports
or certificate pinning may need configuration. --strict controls requests
reaching the gateway; it does not prevent direct connections.
| Flag | Default | Meaning |
|---|---|---|
--allow value | unset | with —strict, a host still tunneled (repeatable) |
--project string | “default” | the project whose —intercept twins the gateway routes to |
--strict | false | refuse every host no twin claims, except —allow ones |
env
usage: rystic env [flags]
print the exports rystic exec sets, for a shell to eval
eval "$(rystic env)" routes the rest of the shell's session the way `rystic exec`
routes one command, starting the project's gateway when none runs.
| Flag | Default | Meaning |
|---|---|---|
--allow value | unset | with —strict, a host still tunneled (repeatable) |
--json | false | print the JSON envelope |
--project string | “default” | the project whose —intercept twins the gateway routes to |
--strict | false | refuse every host no twin claims, except —allow ones |
gateway
usage: rystic gateway [flags]
run the project's HTTPS gateway in the foreground (exec and env start it detached)
One gateway per project routes the hosts its --intercept twins declare on
/_rystic/version, re-read as requests arrive, so a twin started later is
picked up. Two twins claiming one host is a conflict (exit 9) naming both.
-stop ends the project's detached gateway; its log is
$RYSTIC_HOME/run/gateway/<project>.log.
| Flag | Default | Meaning |
|---|---|---|
--allow value | unset | with —strict, a host still tunneled (repeatable) |
--port int | 0 | loopback port; 0 picks a free one |
--project string | “default” | the project whose —intercept twins it routes to |
--stop | false | stop the project’s detached gateway |
--strict | false | refuse every host no twin claims, except —allow ones |
doctor
usage: rystic doctor [flags] [product]
check this machine end to end without running anything
Verifies the license file, the install, that the twin and the license agree
on a signing key, and that the data the twin needs is where it expects it.
Exits 0 when everything passes; warnings (!) are features you do not have
yet, not failures. Run it before a demo, not during one.
| Flag | Default | Meaning |
|---|---|---|
--license string | unset | path to the license file |
--pub string | unset | path to a license public key (overrides the built-in one) |
--version string | unset | installed version to inspect (default: current) |
trust
usage: rystic trust [flags]
trust the gateway CA in the macOS login keychain, for Go tools built before Go 1.27
Adds $RYSTIC_HOME/ca/ca.pem to the login keychain as a trusted root (one
password prompt); --remove revokes current and superseded Rystic gateway CAs
belonging to this RYSTIC_HOME from the login keychain. The CA is constrained to
hosts Rystic simulates, so trusting it vouches for no other site. When a newer
rystic simulates more hosts it replaces the CA and says to run this again.
SSL_CERT_FILE carries the CA to bundle-aware clients on other platforms.
On Windows, Go tools built before Go 1.27 require native-store trust.
| Flag | Default | Meaning |
|---|---|---|
--remove | false | remove the CA’s trust and the certificate from the login keychain |
features
usage: rystic features [flags] [product]
list what the installed twin can do
Read from the twin's own manifest, so it describes the build you have rather
than what this CLI was written against. -v adds detail and control-plane URLs.
| Flag | Default | Meaning |
|---|---|---|
--json | false | print the JSON envelope: every feature in full, and whether its data is here |
-v | false | include the long description and control-plane endpoints for each feature |
--version string | unset | installed version to describe (default: current) |
describe
usage: rystic describe [flags] [product…]
what an installed twin serves: scenarios, seedable registers with an example record each, operations
Asks each installed twin's own binary (`<twin> --describe`; for a linux-only
install, the image built from it) and adds what the CLI knows: the manifest's
summary and features, the hosts `rystic exec` routes to the twin, and the
installed version. With no product named and $RYSTIC_PRODUCT unset, it
describes every installed twin. Text shows the scenarios, each register's
kind, key and field count, and the operation count per method; -v lists every
operation. -json (data.twins[]) carries everything: each register's typed
fields, the endpoint that seeds it and an example record — POST
{<register>: <example>} to /_rystic/seed. For owner-scoped registers, set
the field named by owner_field to your caller identity (see example_note).
Otherwise the record can be accepted but invisible to your API requests.
[start] (start_scenario) is the scenario the quickstart offers.
Without --scenario every build starts empty.
It never pulls; a linux-only install's image is built and loaded into the
local image store the way `run --runner docker` does. A named twin that is not
installed exits 5 before anything runs. A build cut before --describe gets
the CLI's half and a warning, exit 0. A twin that crashes or does not answer
is reported with its error and exits 7; a container runtime that does
not answer, 8. Every other twin is still described.
| Flag | Default | Meaning |
|---|---|---|
--json | false | print the JSON envelope: every twin in full, with an example record per register |
-v | false | list every operation, not only the count per method |
docs
usage: rystic docs [flags] [<topic> [scenarios]] | search <terms…>
list, read and search every Rystic doc — the site's pages (built in, offline) and each installed twin's HOWTO and SCENARIOS
With no topic, a numbered index: the site's pages, from a snapshot built
into this CLI (-online reads the live site instead), then each installed
twin's HOWTO.md and SCENARIOS.md; the last line names the snapshot, and
-json carries its content_sha256. A topic prints raw markdown: a page slug
(agent-quick-start, simulators/<name>, or the page's /docs/ path or site
URL), or a product or its short name, which prints the installed twin's
HOWTO — plus the live routes of each running instance — and falls back to
the site's page for it. `<product> scenarios` prints SCENARIOS.md;
`<topic>#<anchor>` prints one section. search lists every section holding
all the terms as <topic>#<anchor>, release notes last.
| Flag | Default | Meaning |
|---|---|---|
--endpoint string | $RYSTIC_DOCS_ENDPOINT, config.json, then https://www.rystic.ai | site -online reads |
--json | false | print the JSON envelope |
-n int | 20 | search: how many sections to print |
--online | false | read the live site instead of the snapshot built into this CLI |
docs search
rystic docs search is covered by the docs help above.
list
usage: rystic list [flags]
released twins your API key or license covers — latest, installed, running (--installed: the old per-version lines)
| Flag | Default | Meaning |
|---|---|---|
--endpoint string | unset | catalog endpoint (default $RYSTIC_CATALOG_ENDPOINT, config.json, then https://www.rystic.ai/api/catalog ); file:// reads a JSON file |
--installed | false | only what is installed here, one line per version (the old output) |
--json | false | print the JSON envelope |
--license string | unset | path to the license file |
login
usage: rystic login [flags] [rys_key | file]
sign in through the browser, or install an API key or a license file, and log docker in to the registry with it
With no argument, prints a sign-in link and the code its page shows, and
waits up to 15 minutes: a human opens the link, signs in on www.rystic.ai
and clicks Approve, and the API key this CLI made is stored as below. An
agent relays the link to its human. On a terminal with $CI unset it also
opens the browser; --no-browser prints the link only.
Given an API key (rys_…, made at https://www.rystic.ai/account/api-keys;
or --token-stdin, which keeps it out of shell history), stores it in
$RYSTIC_HOME/token, where pull, run, doctor, status and list read it, and
logs docker in as `token`: `rystic run <product>` then pulls the metered
edition. Given an Enterprise license file, verifies it and parks it in
$RYSTIC_HOME (`rystic license` shows it afterwards), and logs docker in as
`license`. Either way, `docker pull <registry>/<product>:<version>` and
`rystic run --runner docker` then work with nothing else; --no-docker
skips that half, and without a container runtime it is skipped with the
manual command printed.
Last, it offers to start one of the simulators the credential covers,
exactly as `rystic run <product> -d --scenario <its quickstart>` — only
when stdin and stdout are both terminals and $CI is unset. Piped input,
an agent, a script or CI never sees the offer; --no-start skips it.
| Flag | Default | Meaning |
|---|---|---|
--force | false | activate a license file even if it is expired |
--no-browser | false | with no key: print the sign-in link only, do not try to open a browser |
--no-docker | false | store the API key or license only; do not ‘docker login’ to the registry |
--no-start | false | do not offer to start a simulator afterwards (the offer needs a terminal and no $CI) |
--pub string | unset | path to a license public key (overrides the built-in one) |
--token-stdin | false | read the API key (rys_…) from stdin instead of the command line, so it stays out of shell history |
license
usage: rystic license [flags] [activate <file>]
show the active license, or install one
`rystic license activate <file>` verifies a license and parks it in
$RYSTIC_HOME, where every later command finds it without a flag.
| Flag | Default | Meaning |
|---|---|---|
--license string | unset | path to the license file |
--pub string | unset | path to a license public key (overrides the built-in one) |
license activate
usage: rystic license activate [flags] <file>
verify a license file and park it in $RYSTIC_HOME
Verifies the license file's signature and expiry, then installs it as the
active license, so every later command finds it with no flag and no env
var.
| Flag | Default | Meaning |
|---|---|---|
--force | false | activate even if the license is expired |
--pub string | unset | path to a license public key (overrides the built-in one) |
config
usage: rystic config [flags] [set <key> <value>]
show every setting with its source, or set one
Settings resolve flag > environment > $RYSTIC_HOME/config.json > built-in,
and this prints which. Keys: runner, port, tapes, release_endpoint,
catalog_endpoint, docs_endpoint, image_source (registry|archive), registry,
docker.binary, docker.host.
| Flag | Default | Meaning |
|---|---|---|
--json | false | emit the JSON envelope instead of a table |
config set
usage: rystic config set <key> <value>
No flags.
feedback
usage: rystic feedback [flags] [message | -]
report a bug, a point of friction or a feature request about the CLI or a twin; filed under your account
Sends one report with the API key `rystic login` stored, filed under your
account, and prints its ID. --type is bug,
feature_request or friction; --command names the rystic command or twin
endpoint ("stripe POST /v1/charges"); --task, --expected and --actual say
what you tried, expected and got. The message is the argument, or stdin;
on a terminal with neither, it asks for each field. The CLI adds its
version, OS/arch, runner (native or a container engine) and the running
twins, never an env file, a request body or a key. An agent sends one only
when its user asks.
| Flag | Default | Meaning |
|---|---|---|
--actual string | unset | what happened |
--command string | unset | the rystic command or twin endpoint it is about, e.g. “stripe POST /v1/charges” |
--expected string | unset | what you expected |
--json | false | print the JSON envelope: the filed issue’s ID and URL |
--task string | unset | what you were trying to do |
--type string | unset | what this is: bug | feature_request | friction |
self-update
usage: rystic self-update [flags]
replace this CLI with the latest release
The CLI itself is not license-gated: it downloads from the public release
endpoint. With -check, reports and exits 10 without replacing anything.
| Flag | Default | Meaning |
|---|---|---|
--check | false | only report whether a newer CLI exists (exit 10 if so); download nothing |
--endpoint string | $RYSTIC_CLI_ENDPOINT or https://www.rystic.ai/api/cli-release | CLI release endpoint |
--force | false | replace even a dev build, or re-install the version already running |
--version string | “latest” | CLI version to install (e.g. x.y.z) |
version
usage: rystic version [flags]
print this CLI's version, then every running twin's
Prints this CLI's version, commit, build date and platform, then every
running twin's product, version and model, and warns when a running twin
is behind the installed one.
| Flag | Default | Meaning |
|---|---|---|
--json | false | print the JSON envelope |
help
rystic help lists commands; rystic help <command> lists that command’s flags.
Environment and exit codes
From the end of rystic help.
Files
$RYSTIC_HOME (default ~/.rystic) holds twins/ (installs), token (the API key
login stored), rystic-license.json (the active license file), config.json
(rystic config) and run/ (each instance's record and log).
$RYSTIC_PRODUCT names the twin used by any command run without a product
argument.
Twins on an API key
rystic finds the API key in $RYSTIC_TOKEN or the file $RYSTIC_TOKEN_FILE
names, else /run/secrets/rystic_token (a Compose secret), else config.json's
token, else $RYSTIC_HOME/token. A token file named or mounted but unreadable
or empty is an error, never a reason to try the next place.
The twin keeps its own lease; $RYSTIC_LEASE_URL, $RYSTIC_REGISTER_WAIT and
$RYSTIC_LEASE_CACHE reach it, and a container, when set; run -v prints its
heartbeats. A twin whose lease is stopped exits 1 with the reason on stderr.
Exit codes
| Code | Meaning |
|---|---|
| 0 | ok |
| 1 | error |
| 2 | usage |
| 3 | license |
| 4 | network |
| 5 | not found (not installed, or no such doc) |
| 6 | integrity |
| 7 | twin unhealthy |
| 8 | runtime unavailable |
| 9 | conflict |
| 10 | update available (-check) |
| 11 | API key missing, or refused by the release endpoint |