Getting started
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 ps | list running twins — status from a live probe, not the record |
rystic stop | stop instances (SIGTERM, then SIGKILL after -timeout) |
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 export | write a docker-compose.yaml (digest-pinned, license from .env) or the Antithesis scaffold from a stack file |
rystic status | license, 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 check | boot an installed twin and confirm it serves and resets |
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 doctor | check this machine end to end without running anything |
rystic features | list what the installed twin can do |
rystic list | released sims your license covers — latest, installed, running (—installed: the old per-version lines) |
rystic login | install 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 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. VERSION) |
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.
Default port 8080; a busy port is a conflict (exit 9); --port 0 picks a free one.
| Flag | Default | Meaning |
|---|---|---|
--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) |
--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 (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-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 |
--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 |
--version string | unset | installed version to run (default: current) |
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)
| Flag | Default | Meaning |
|---|---|---|
--all | false | stop every instance |
--timeout duration | 3s | 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 | 3s | 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, and
services: {name: {product, version, runner, port, scenario, seed, tapes,
tape, speed, env, args, seed_rng, egress_retain, network, live}} — the same
keys as run's flags; the service name is the instance name; the project
labels every twin for `ps --project` and `down`. Already-running services
are said, 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 |
--project string | unset | project to stop (instead of reading a stack file) |
--timeout duration | 3s | grace before SIGKILL |
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 — 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 |
--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, 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 four environment variables — RYSTIC_ADDR, RYSTIC_WORLD,
RYSTIC_WORLD_KIND, RYSTIC_RESULT, 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}} {{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.
| Flag | Default | Meaning |
|---|---|---|
--bot-argv-placeholders | false | capability handshake set by ‘batch —runner docker’ on the container’s own argv; not for callers |
--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 |
--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>]
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 has not got and which grid therefore refuses). Columns default to the
archive's declared worlds; -worlds overrides
('all' runs the twin's whole catalog). Prints the grid and exits non-zero
if any cell failed.
| Flag | Default | Meaning |
|---|---|---|
--dry-run | false | print the plan and exit (‘-worlds all’ still boots one twin briefly to read the catalog) |
-j int | 2 | concurrent cells (twin+bot pairs) |
--license string | unset | path to the license file |
--out string | grid-<timestamp>; must not already exist | output directory |
--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 |
--version string | unset | installed version to run (default: current) |
--worlds string | unset | comma-separated scenario names, or ‘all’ (default: the archive’s declared grid worlds) |
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) |
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 (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-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) |
doctor
usage: rystic doctor [flags] [product]
check this machine end to end without running anything
Verifies the license, the install, that the twin and your 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) |
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 |
|---|---|---|
-v | false | include the long description and control-plane endpoints for each feature |
--version string | unset | installed version to describe (default: current) |
list
usage: rystic list [flags]
released sims your 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] <file>
install a license file, and log docker in to the registry with it
Verifies the license and parks it in $RYSTIC_HOME, where every later
command finds it without a flag (`rystic license` shows it afterwards).
The same file is the password for the Rystic image registry, so when a
container runtime answers this also runs `docker login <registry> -u
license --password-stdin` — `docker pull <registry>/<product>:<version>`
and `rystic run --runner docker` then work with nothing else. --no-docker
skips that half.
| Flag | Default | Meaning |
|---|---|---|
--force | false | activate even if the license is expired |
--no-docker | false | activate the license only; do not ‘docker login’ to the registry |
--pub string | unset | path to a license public key (overrides the built-in one) |
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) |
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, image_source (registry|archive), registry, docker.binary,
docker.host.
| Flag | Default | Meaning |
|---|---|---|
--json | false | emit the JSON envelope instead of a table |
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. VERSION) |
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.