Skip to Content
ConceptsReset and determinism

Concepts

Reset and determinism

The same starting state and the same requests give byte-identical ids, timestamps and events, run after run. The examples use the Lob simulator; every simulator takes the same calls.

The loop

curl -s -X POST localhost:$PORT/_rystic/reset                 # empty; ids and clock rewound
curl -s -X POST localhost:$PORT/_rystic/seed -d @world.json   # or POST /_rystic/scenario
# ... run your code ...
curl -s localhost:$PORT/_rystic/inspect                       # assert exact records
curl -s localhost:$PORT/_rystic/egress                        # assert exact events

Worlds and seeding covers world.json; Observing a simulator covers what to assert on.

Every boot starts the same

  1. The same command line boots the same state: --scenario, --seed, both, or neither.
  2. State lives in memory. A restart discards everything since boot and boots that state again.
  3. Requests run one at a time, so each one sees the state the one before it left.

What reset does

POST /_rystic/reset answers 204, and:

  1. Wipes every register to empty. It does not load the boot --scenario again; POST /_rystic/scenario resets and loads a world in one call.
  2. Restarts id sequences, so the first record after a reset gets the same id every time.
  3. Sets the clock back to initial_clock.
  4. Restarts the RNG from the last pinned seed.
  5. Empties the egress log and the call log, and restarts their seq at 1.
  6. Keeps a network profile you posted and the egress retention you set.

What each plan boots lists what else a reset clears or keeps.

rystic pull lob-twin
PORT=$(rystic run lob-twin -d --name loop --port 0 --scenario design_studio --json | jq -r .data.port)
add() { curl -s -u test_x: localhost:$PORT/addresses -d name=Ada -d address_line1='185 BERRY ST' \
  -d address_city='SAN FRANCISCO' -d address_state=CA -d address_zip=94107 | jq -c '{id, date_created}'; }
add
# -> {"id":"adr_0000000000000001","date_created":"2026-03-01T00:00:00.001Z"}
curl -s -X POST localhost:$PORT/_rystic/reset
curl -s localhost:$PORT/_rystic/inspect
# -> {}
add
# -> {"id":"adr_0000000000000001","date_created":"2026-01-01T00:00:00.001Z"}
curl -s -X POST localhost:$PORT/_rystic/scenario -d '{"name":"design_studio"}'
add
# -> {"id":"adr_0000000000000001","date_created":"2026-03-01T00:00:00.001Z"}

The clock

Every simulator keeps its own clock, and timestamps come from it.

  1. Where it starts. A world sets its own instant: design_studio starts at 2026-03-01T00:00:00Z. An empty boot starts at 2026-01-01T00:00:00Z, except on S3, Clerk and Slack, which start at the wall time the process started. GET /_rystic/version shows the empty-boot instant as initial_clock.
  2. How it moves. Each request that stamps a time moves it one millisecond. It never follows wall time unless you pace it (below): two writes seconds apart still read .001Z and .002Z.
  3. Set it. POST /_rystic/clock with {"time":"<RFC3339>"} sets it; {"advance_seconds":N} moves it forward. A world document’s clock sets it too. Moving it past a loaded world’s events fires them.
  4. Pin the start. rystic run --clock <RFC3339> (RYSTIC_CLOCK) sets initial_clock, so an empty boot and every reset start there. Use it on S3, Clerk and Slack to make two processes agree.
  5. After a reset it reads initial_clock, not the world’s instant. Load the world again to get its clock back.
curl -s -X POST localhost:$PORT/_rystic/reset
curl -s localhost:$PORT/_rystic/version | jq .initial_clock
# -> "2026-01-01T00:00:00Z"
curl -s -X POST localhost:$PORT/_rystic/clock -d '{"time":"2026-06-01T09:00:00Z"}'
add
# -> {"id":"adr_0000000000000001","date_created":"2026-06-01T09:00:00.001Z"}

The RNG

  1. Every boot seeds the random stream with 1.
  2. POST /_rystic/seed-rng with {"seed":N} pins another; so do rystic run --seed-rng N and a world document’s rng_seed. Every reset restarts the stream from the last pinned seed.
  3. Ids are sequences, not random draws: the seed does not change them.
  4. Network profiles draw from it: the same seed and profile refuse the same requests in the same order.

Pacing

The clock is manual by default: while your code is idle, nothing moves and no timeline event fires.

  1. RYSTIC_PACE=<speed> moves the clock speed simulated seconds per wall second, firing events as they come due. rystic run --speed <speed> sets it.
  2. rystic run paces at 1 on its own for an --inject schedule or a --seed world with events. --speed 0 keeps the clock manual.
  3. A paced clock follows wall time, so timestamps depend on when requests arrive.
rystic run lob-twin -d --name paced --port 0 --scenario design_studio --speed 3600
# one simulated hour per second: the world's hourly events fire as they come due

Timelines covers scheduling events.

What is and isn’t deterministic

From the same boot or reset, with the same requests in the same order, these repeat byte for byte:

  1. Ids, numbers and sequences.
  2. Timestamps, on a manual clock.
  3. The egress log: its events, their order and their seq.
  4. Network-profile rejections, for the same seed and profile.

These do not:

  1. Concurrent requests. They run one at a time in the order they arrive, so which one gets which id changes run to run. Send them in a fixed order.
  2. A paced clock. It follows wall time.
  3. An empty boot on S3, Clerk or Slack. Its clock starts at the wall time the process started. Within one process every reset returns to that instant; across processes, pass --clock.
  4. Kalshi’s recorded tape. btc15m_tape plays on the wall clock, so what your code meets depends on when it acts (Kalshi → Determinism & reset).
  5. Restarts. They discard everything since boot.

Done with the example: rystic stop loop and rystic stop paced.

Last updated on