Spec

Fixtures

Seed data — git repos, SQL scripts, directories, and adversarial drift — applied before the agent starts.

A fixture is seed data loaded into the sandbox after services boot but before the agent runs. Four fixture types ship with Keystone: git_repo, sql, directory, and drift. Each one runs once per sandbox creation, in declaration order.

When fixtures run

The boot pipeline:

1. Resolve secrets
2. Start isolation runtime
3. Start services + wait for readiness
4. Apply fixtures   ◄── here
5. Apply setup (files, commands)
6. Configure network policy
7. Take before-run snapshot
8. Mark "ready"

Fixtures see the workspace at /workspace and have access to every running service over the private network — so a SQL fixture can write to db:5432, a directory fixture can shell out to psql, and a git_repo fixture clones into /workspace.

If any fixture fails, the sandbox enters error state and create() returns an error.

git_repo — clone a repository

fixtures:
  - type: git_repo
    url: "https://github.com/your-org/your-repo"
    branch: "main"               # optional, default: repo's default branch
    depth: 1                     # optional, default: full clone
    path: /workspace             # optional, default: /workspace
FieldTypeRequiredNotes
urlstringyesHTTPS or SSH. SSH requires an injected key — see Secrets
branchstringnoBranch, tag, or commit SHA
depthintnoShallow clone depth. 1 for "just the latest commit"
pathstringnoTarget directory inside the workspace

For private repos, set up auth via setup.commands to write a ~/.netrc, or inject an SSH key via secrets[*].scope.file_template. Keystone doesn't ship a "git auth" abstraction — git's existing mechanisms work fine.

sql — seed a database

The sql fixture runs SQL against a service. Two ways to provide the script:

Inline (preferred)

fixtures:
  - type: sql
    service: db                            # must match a services[].name
    sql: |
      CREATE TABLE customers (
        email TEXT PRIMARY KEY,
        name  TEXT NOT NULL,
        plan  TEXT NOT NULL
      );
      INSERT INTO customers VALUES
        ('alice@example.com', 'Alice', 'pro'),
        ('ben@example.com',   'Ben',   'free');

Inline is the recommended pattern. The script is self-contained in the spec — no separate seed file to ship, no path resolution surprises.

From file

fixtures:
  - type: sql
    service: db
    path: seeds/schema.sql       # relative to the spec file

Watch out: path: only works if the file is inside the workspace at fixture-apply time. For most users this is unreliable because fixtures run before snapshot extraction. Use inline sql: for anything under a few thousand rows. For larger datasets, restore from external storage in setup.commands (which runs after fixtures).

Credentials

The fixture connects to the service using credentials derived from the service spec's env: block:

Service env varUsed as
POSTGRES_USERpsql user. Default: postgres
POSTGRES_PASSWORDpsql password
POSTGRES_DBtarget database. Default: testdb
MYSQL_ROOT_PASSWORDMySQL root password
MYSQL_DATABASEMySQL database

So if you set POSTGRES_DB: northwind on the service, fixtures run against northwind. If you don't set POSTGRES_PASSWORD, the fixture defaults to test.

Multi-statement transactions

The SQL is run as one batch. Wrap related statements in a transaction if you need atomicity:

fixtures:
  - type: sql
    service: db
    sql: |
      BEGIN;
      CREATE TABLE orders (id INT, total NUMERIC);
      CREATE INDEX orders_total ON orders(total);
      INSERT INTO orders VALUES (1, 100.00), (2, 250.00);
      COMMIT;

directory — copy files into the workspace

fixtures:
  - type: directory
    source: /path/to/test-data           # absolute path on the Keystone server
    target: data/                         # relative to the workspace

Useful when you have static test inputs that don't fit cleanly inline (large JSON fixtures, image bundles, sample CSVs).

For self-hosted deployments, source is read directly from the server's filesystem. Keystone-hosted users typically can't ship arbitrary host paths — use the agent snapshots feature to bundle data with your agent instead.

drift — adversarial data perturbation

fixtures:
  - type: drift
    target: db.customers_a               # service.table
    strategy: random_mismatches          # or random_nulls, duplicate_rows
    count: 15                             # number of perturbations
    seed: "42"                            # deterministic across runs

drift deliberately corrupts data to test agents on imperfect inputs. Use it for reconciliation, validation, and data-cleanup scenarios where the real-world input is messy.

StrategyEffect
random_mismatchesPick count rows, mutate one column to a non-matching value
random_nullsPick count rows, set one nullable column to NULL
duplicate_rowsInsert count duplicate rows (with new primary keys)

seed makes the perturbation deterministic — the same seed produces the same drift on every run, which is what you want for reproducible scenarios.

Examples

Schema + data + adversarial drift

fixtures:
  - type: sql
    service: db
    sql: |
      CREATE TABLE customers_a (id INT PRIMARY KEY, email TEXT, name TEXT);
      CREATE TABLE customers_b (id INT PRIMARY KEY, email TEXT, name TEXT);
      INSERT INTO customers_a VALUES
        (1, 'a@co', 'Alice'), (2, 'b@co', 'Ben'), (3, 'c@co', 'Carol');
      INSERT INTO customers_b SELECT * FROM customers_a;
 
  - type: drift
    target: db.customers_a
    strategy: random_mismatches
    count: 10
    seed: "{{ determinism.seed }}"

The {{ determinism.seed }} interpolation pulls from the spec's determinism.seed field, so you only set the seed in one place.

Codebase + database + tests

fixtures:
  - type: git_repo
    url: "https://github.com/acme/backend"
    branch: "main"
    depth: 1
 
  - type: sql
    service: db
    sql: |
      \i /workspace/db/schema.sql        -- run a script committed in the repo
      \i /workspace/db/seed.sql
 
setup:
  commands:
    - "cd /workspace && npm install"

The git_repo fixture clones first, then sql runs (the repo's schema is now on disk so \i /workspace/db/schema.sql resolves), then setup.commands install dependencies.

Mock external API state

fixtures:
  - type: directory
    source: /var/keystone/seeds/stripe-snapshots/v1
    target: stripe-replay/
 
services:
  - name: stripe-mock
    type: http_mock
    record: true
    routes:
      - method: GET
        path: /v1/customers/cus_alice
        # http_mock can read response from a file in the workspace
        response_file: stripe-replay/customers/alice.json

Useful when you want a mock API to behave like a recorded snapshot of the real one.

Order matters

Fixtures run in the order they're declared. A git_repo fixture that clones into /workspace should come before any directory or sql fixture that depends on the cloned files.

fixtures:
  - type: git_repo                  # 1. clone the repo first
    url: "https://github.com/acme/backend"
    
  - type: sql                       # 2. then seed the DB (script lives in the repo)
    service: db
    path: db/schema.sql
    
  - type: directory                 # 3. then copy in extra test data
    source: /var/keystone/seeds/orders
    target: data/orders

Failure semantics

Any fixture failure aborts sandbox creation:

  • git_repo fails on clone error (auth, network, repo doesn't exist).
  • sql fails on syntax error, connection error, or non-zero exit from psql/mysql.
  • directory fails if source doesn't exist or copy fails.
  • drift fails if target table doesn't exist or query errors.

Errors are surfaced in the sandbox's error state and bubbled up to whatever called create(). The sandbox is destroyed and the workspace cleaned up.