Concepts
Observing a simulator
Every simulator records what it holds, what it was asked and what it would have sent. The examples use the Stripe simulator with one request made; every simulator serves the same routes.
rystic pull stripe-twin
PORT=$(rystic run stripe-twin -d --name observe --port 0 --scenario design_studio --json | jq -r .data.port)
curl -s localhost:$PORT/v1/customers -u sk_test_twin: -d email=ada@example.com | jq -r .id
# -> cus_lllllllllllllc
| Want | Read |
|---|---|
| The whole state | GET /_rystic/inspect |
| Every request your code made | GET /_rystic/calls |
| A live feed of requests, effects and control calls | GET /_rystic/events, rystic tail |
| What it would have sent out | GET /_rystic/egress |
| All of it in a browser | /_rystic/ui |
State
GET /_rystic/inspect returns every register that holds a record, keyed by register, then by record key. An empty simulator answers {}.
curl -s localhost:$PORT/_rystic/inspect | jq -c 'map_values(length)'
# -> {"state_charge":269,"state_customer":31,"state_event":1,"state_payment_intent":276,…}
- The dump is a register map: post it to
/_rystic/seedafter a reset and the state comes back the same (Seeding). ?view=identitiesfolds the alias keys a world files a user under, such asmemberon Slack and GitHub, into the user they name. Read with it; seed from the default dump, which keeps the aliases.GET /_rystic/registersdescribes each register (Registers).
Requests
GET /_rystic/calls lists every API request since the last reset, oldest first. Control-plane calls are not in it.
curl -s localhost:$PORT/_rystic/calls | jq -c '.calls[0]'
# -> {"actor":"member","at":"2026-03-01T00:00:00.001Z","duration_ms":0,"method":"POST","path":"/v1/customers","principal":"","seq":1,"status":200,"template":"/v1/customers"}
actoris the identity the request acted as,templatethe route it matched, andatthe simulator’s clock.totalcounts every call since the last reset; a reset empties the list and restartsseqat 1.- It keeps as many calls as the egress log keeps events (below).
Live feed
GET /_rystic/events is a Server-Sent Events stream. Each event is an id: line and a data: line, of kind request, effect or control.
curl -sN localhost:$PORT/_rystic/events
# -> id: 1
# -> data: {"seq":1,"kind":"control","at":"2026-01-01T00:00:00Z","data":{"detail":null,"op":"reset"}}
# -> id: 2
# -> data: {"seq":2,"kind":"control","at":"2026-03-01T00:00:00Z","data":{"detail":{"frames":0,"name":"design_studio"},"op":"scenario"}}
# -> …
- On connect it sends up to the last 1,000 events since the last reset, then streams live.
seqkeeps counting across resets. Reconnect withLast-Event-ID: <seq>or?cursor=<seq>to get only what came after.
rystic tail prints the same feed, one line per event, stamped with the simulator’s time of day:
rystic tail --addr http://localhost:$PORT
# -> 00:00:00.000 CTL reset
# -> 00:00:00.000 CTL scenario frames=0 name=design_studio
# -> 00:00:00.001 REQ POST /v1/customers -> 200 (0ms)
- The default shows writes and control calls.
--events alladds reads (REQ GET …) and effects (FX customer.created …). CLI flag reference → tail lists the scopes. - A detached simulator writes the same lines to its log:
rystic logs -f observe.
Egress
GET /_rystic/egress holds what the simulator would have sent out, in order: Stripe’s webhooks, Resend’s emails, Kalshi’s WebSocket frames. Webhooks covers them.
curl -s localhost:$PORT/_rystic/egress | jq -c '{total, dropped, types: [.events[].type]}'
# -> {"total":1,"dropped":0,"types":["customer.created"]}
- Each event carries
seq,effect,typeandpayload.?tail=Nreturns the last N. totalcounts everything emitted since the last reset;eventsmay hold fewer.- A reset empties the log and restarts
seqat 1.
Retention
- The log keeps 100,000 events by default. Beyond that the oldest drop, and
droppedcounts them: check it before trusting the log as complete. RYSTIC_EGRESS_RETAIN(rystic run --egress-retain) sets the bound at boot;POST /_rystic/egress-retainwith{"retain":N}sets it at runtime.0removes it.GET /_rystic/versionshows the bound asegress_retain. A reset keeps it.
The state UI
Open http://localhost:$PORT/_rystic/ui in a browser. Its cards:
- Activity: the live feed.
- Store: the state, register by register.
- Effects: the egress log.
- Network: the network profile (Network profiles).
- Reset, in the header, wipes the simulator as
POST /_rystic/resetdoes.
A simulator with its own view of the vendor’s app, such as Stripe, Lob and PayPal, adds its cards above these; the simulator’s page describes them.
Done with the example: rystic stop observe.