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| Field | Type | Required | Notes |
|---|---|---|---|
url | string | yes | HTTPS or SSH. SSH requires an injected key — see Secrets |
branch | string | no | Branch, tag, or commit SHA |
depth | int | no | Shallow clone depth. 1 for "just the latest commit" |
path | string | no | Target 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 fileWatch 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 inlinesql:for anything under a few thousand rows. For larger datasets, restore from external storage insetup.commands(which runs after fixtures).
Credentials
The fixture connects to the service using credentials derived from the service spec's env: block:
| Service env var | Used as |
|---|---|
POSTGRES_USER | psql user. Default: postgres |
POSTGRES_PASSWORD | psql password |
POSTGRES_DB | target database. Default: testdb |
MYSQL_ROOT_PASSWORD | MySQL root password |
MYSQL_DATABASE | MySQL 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 workspaceUseful 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 runsdrift 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.
| Strategy | Effect |
|---|---|
random_mismatches | Pick count rows, mutate one column to a non-matching value |
random_nulls | Pick count rows, set one nullable column to NULL |
duplicate_rows | Insert 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.jsonUseful 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/ordersFailure semantics
Any fixture failure aborts sandbox creation:
git_repofails on clone error (auth, network, repo doesn't exist).sqlfails on syntax error, connection error, or non-zero exit frompsql/mysql.directoryfails ifsourcedoesn't exist or copy fails.driftfails iftargettable 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.