Config as code

Mode, allowed providers, guardrails and capture policy can be set in the dashboard. That puts the rules your search runs under out-of-band from the code they govern: they never appear in review, they’re absent from git, and nobody can say when they changed or who changed them.

This makes that configuration an ordinary file.

Define

// shimmy.config.ts
import { defineConfig } from '@rfa-labs/shimmy';

export default defineConfig({
  routing: {
    mode: 'multi-provider',
    allowed_providers: ['openai', 'anthropic'],
  },
  guard: { redact_stored_pii: true },
  capture: { capture_content: false, retention_days: 14 },
});

Plan, then apply

import { plan, apply, formatPlan } from '@rfa-labs/shimmy';

console.log(formatPlan(await plan(shimmy.control, config)));  // read-only
await apply(shimmy.control, config);

Two steps rather than one, because a search change is not cosmetic: a new Mode or provider list changes which models Tuning tries and Production fails over to, and learning_mode: 'fast' spends budgeted optimization runs. Seeing that before it happens is worth one extra call.

plan() only reads, so it is safe to run in CI on every pull request. A non-empty diff on a branch that did not touch the config is itself a finding — someone changed settings through the dashboard.

Why validation runs first

Tier names are persisted keys, not display text.

The server accepts an unrecognized one without complaint and simply starts an empty evidence chain under it. So a typo does not fail — it silently discards every sample accumulated under the correct spelling and starts the search from zero. The failure is invisible until someone notices a Step that never settles.

Valid tiers, in order: nano, economy, standard, premium, frontier.

Also caught locally:

  • An unknown mode, an allowed provider you can’t reach, or — for open-weights — one that hosts no open-weights model.
  • A negative band or reverify_days.
  • A ladder entry that names no model.

All problems are reported at once, so one round trip fixes a whole config rather than N.

Apply order

When a plan touches several sections, they are written capture → guard → routing.

Routing is the change that alters which model serves live traffic; the other two are policy about what gets stored. If something fails partway, having tightened privacy settings before changing behavior is the better half to have completed.

A validation problem in any section blocks the whole apply, so you never land in a state matching no config file.

What you can set

SectionFields
routingladder, mode, allowed_providers, tiebreak, band, allow_sub_standard_probing, learning_mode, reverify_days
guardredact_stored_pii, redact_upstream_pii, block_on_injection, injection_threshold
capturecapture_content, retention_days

Omitted sections are untouched. Setting ladder: null clears the override and falls back to the server’s defaults — distinct from omitting the key, and reported as a change.

Next