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 — foropen-weights— one that hosts no open-weights model. - A negative
bandorreverify_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
| Section | Fields |
|---|---|
routing | ladder, mode, allowed_providers, tiebreak, band, allow_sub_standard_probing, learning_mode, reverify_days |
guard | redact_stored_pii, redact_upstream_pii, block_on_injection, injection_threshold |
capture | capture_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
- Control plane reference — the endpoints underneath.