Skip to content

feat(db): add model experiment schema for preview A/B tests#3299

Merged
markijbema merged 3 commits into
mainfrom
mark/experimental-models-schema
May 18, 2026
Merged

feat(db): add model experiment schema for preview A/B tests#3299
markijbema merged 3 commits into
mainfrom
mark/experimental-models-schema

Conversation

@markijbema
Copy link
Copy Markdown
Contributor

Summary

Lands the database schema for the preview-model A/B experiment system described in .plans/experimental-models-1.md. Schema-only — no gateway routing, admin tRPC, or UI yet. Splitting this out first so subsequent phases don't fight rebase conflicts on migration journal/snapshot files.

New tables (packages/db/src/schema.ts + 0134_black_union_jack.sql):

  • model_experiment — partial unique index on public_model_id where status IN ('active', 'paused') so only one routing-relevant experiment per public id can exist. status CHECK constrains to draft | active | paused | completed. CHECK forbidding active + is_archived = true.
  • model_experiment_variant(experiment_id, label) unique, weight > 0 CHECK. The slot identity for a bucketed user.
  • model_experiment_variant_version — immutable per-variant version row. Holds upstream jsonb (validated by ExperimentUpstreamSchema in app code in a later phase) and a sibling encrypted_api_key jsonb typed EncryptedData (same shape as byok_api_keys.encrypted_api_key) so secrets stay column-isolatable. (variant_id, effective_at desc) index for "current version" lookups.
  • model_experiment_request — 1:1 with microdollar_usage via usage_id PK FK with on delete cascade. Stores prompt sha256 hashes (or reserved sentinel values __absent__ / __failed__ / __deleted__) — content goes to R2 in a later phase. (variant_version_id, created_at) index for per-RC reports, partial index on client_request_id for feedback joins.

The plan doc has been updated with an "Implementation Status" table at the top so a follow-up agent can quickly see what's already landed.

Verification

  • pnpm drizzle generate produced the migration with no manual edits.
  • pnpm test:db (postgres + drizzle-kit migrate) applies the migration cleanly on a fresh database.
  • pnpm --filter @kilocode/db typecheck passes.
  • pnpm format clean.

Visual Changes

N/A — backend schema only.

Reviewer Notes

  • Per AGENTS.md the migration is fully tool-generated; no hand edits.
  • upstream is plain jsonb for now. Strict zod validation (ExperimentUpstreamSchema) lives in app code added in a later phase. Storing as plain jsonb here keeps the migration simple and lets the schema evolve without column shape changes.
  • encrypted_api_key is intentionally a sibling column rather than embedded in upstream so reporting views and admin selects can simply omit the column — see plan §"API Keys".
  • No PII added in this change → no GDPR softDeleteUser update required (per plan §"GDPR and consent", experiment data is governed by a dedicated retention policy, not the default soft-delete; that test lands with the GDPR-test phase).
  • Status CHECK uses string literals instead of an enum to keep parity with the rest of the schema's text-with-CHECK convention.

Adds tables for opt-in preview-model A/B experiments (see
.plans/experimental-models-1.md):

- model_experiment (with partial unique index per public_model_id where
  status IN (active, paused) and an 'active cannot be archived' check)
- model_experiment_variant (positive-weight, unique label per experiment)
- model_experiment_variant_version (immutable; upstream JSONB + sibling
  encrypted_api_key typed EncryptedData; (variant_id, effective_at desc) idx)
- model_experiment_request (1:1 with microdollar_usage via usage_id PK FK
  cascade; prompt sha256 hash columns with sentinel-or-hex CHECK)

Schema-only change. No gateway routing, admin UI, or tRPC yet.
@markijbema markijbema enabled auto-merge (squash) May 18, 2026 12:46
@markijbema markijbema merged commit 5be8667 into main May 18, 2026
43 checks passed
@markijbema markijbema deleted the mark/experimental-models-schema branch May 18, 2026 12:47
markijbema added a commit that referenced this pull request May 18, 2026
Phase 5 of .plans/experimental-models-1.md. Schema-only PR #3299
provides the tables; this PR adds the editing surface.

- ExperimentUpstreamSchema (strict subset of CustomLlmDefinitionSchema;
  no api_key, no extra_headers)
- Redis key helpers: EXPERIMENTED_PUBLIC_IDS_REDIS_KEY and
  modelExperimentRedisKey(publicId), plus redisDel() helper
- adminModelExperimentsRouter with CRUD + state machine
  (activate / pause / complete / setArchived / delete-on-draft) and
  variant ops (addVariant / removeVariant / updateVariantLabel /
  swapVariantVersion / rotateApiKey). encrypted_api_key is never
  returned by list/get/swap/rotate; admin selects enumerate non-key
  columns explicitly. BYOK_ENCRYPTION_KEY missing rejects key-touching
  ops.
- React Query hooks + ModelExperimentsContent (list + inline detail +
  Monaco hot-swap dialog + rotate-key dialog).
- Mounted as a 4th tab inside /admin/gateway with a redirect at
  /admin/model-experiments mirroring /admin/custom-llms.

Cache invalidation runs on every routing-affecting mutation
(per-public-id cache + recomputed membership set). Phase 3 gateway
routing reads these caches; until that lands, invalidation is a no-op
for routing but writes the membership set so it's ready.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants