Twins

Guides

Testing integrations locally

Your application code, test runner, and editor stay exactly where they are. The only thing that moves is where its integrations point: instead of production APIs (side effects) or hand-kept mocks (drift), the app talks to hosted twins — stateful, deterministic replicas with public per-service endpoints. Nothing to install or run locally for the twins themselves.

How it works

  1. Runs on your machine: your app, its tests, and an env file selecting the twin endpoints.
  2. Runs hosted: the twins (seeded state, REST + MCP + CLI), their console, and their state inspection.
  3. The seam: one base URL per service. Every mounted operation lives under /api/v1/sandbox/worlds/<world_id>/services/<service>/operations/<operation_id>, addressed by the vendor’s own operation ids.

Spin up the twins

Terminal
$ blobfish twin-runs create --twins slack,stripe --ttl 60 --wait
# writes routing.env into the current directory:
#   BLOBFISH_TWIN_ENDPOINT=…            (world MCP)
#   BLOBFISH_TWIN_SLACK_MCP=…           (per-service MCP)
#   BLOBFISH_TWIN_STRIPE_MCP=…

Load routing.envin your test setup (or export the variables) and derive each service’s REST base from its MCP line by dropping the trailing /mcp.

Example: a Slack messaging workflow

Test the send path
# list seeded channels
$ curl -sS "$SLACK_TWIN_BASE/operations/conversations.list?token=xoxb-test" \
    -H "X-API-Key: $BLOBFISH_API_KEY"

# post — the twin validates required params exactly like the vendor
$ curl -sS -X POST $SLACK_TWIN_BASE/operations/chat.postMessage \
    -H "X-API-Key: $BLOBFISH_API_KEY" -H "Content-Type: application/json" \
    -d '{"token":"xoxb-test","channel":"C0000001","text":"deploy finished"}'

# assert on state, not just responses
$ curl -sS $SLACK_TWIN_BASE/state -H "X-API-Key: $BLOBFISH_API_KEY"

The write response carries the row-level diff the call produced; the /state endpoint returns the tables so your test can assert on what actually changed, not only on the HTTP body.

Isolation and repeatability

  1. One session per test run: POST …/sessions (or ./bf-twin session new) and send x-blobfish-session on every call — parallel CI shards each get an isolated copy-on-write fork of the seeded state.
  2. Reset between cases: POST …/services/<service>/reset restores the seed baseline for that fork in one call.
  3. Determinism: the seed is stable per twin, so the same test sees the same starting rows on every run.

Managing the environment

Lifecycle
$ blobfish twin-runs list
$ blobfish twin-runs extend <env_id> --ttl 60
$ blobfish twin-runs delete <env_id>   # delists; the world's data persists

Restoring your original environment is deleting routing.env— your app’s real configuration was never touched.

Tips

  1. Discover operation ids with ./bf-twin <service> ops — the listing is generated from the same specs the routes serve.
  2. Writes sent as GET are rejected with 405 — codify the split in your HTTP helper once.
  3. The environment console page shows the live topology graph and per-service state while your tests run — see the quickstart.