Client reference
Constructing
const shimmy = new Shimmy(); // reads the environment
const shimmy = new Shimmy({ apiKey, baseUrl }); // or explicit | Option | Default | Notes |
|---|---|---|
apiKey | SHIMMY_API_KEY, then OPTIMIZER_API_KEY | Required. |
baseUrl | SHIMMY_BASE_URL, then OPTIMIZER_BASE_URL | Defaults to https://rfa-labs.com. |
sourceContext | true | Attach file, line and release to each call. |
release | auto-detected | From SHIMMY_RELEASE, GITHUB_SHA, GIT_COMMIT, … |
objective | — | cost, quality, or latency. |
onError | warn | Where telemetry failures go. |
phase | SHIMMY_PHASE, else dev | dev, tuning or production. |
devSource | SHIMMY_DEV_SOURCE | Dev: local (the repo) or remote (Shimmy). |
recording | SHIMMY_RECORDING | Dev: auto, record or strict. |
production | SHIMMY_PRODUCTION, else managed | Production: managed or passthrough. |
recordingsDir | SHIMMY_RECORDINGS_DIR | Where local Recordings live. |
lockfile | SHIMMY_LOCKFILE | What passthrough reads. |
Python spells these in snake case (dev_source, recordings_dir); Rust takes them
on ShimmyOptions as enums. Defaults, and how test runners change them, are on configuration. settings() returns
what a call made now would use.
Scopes
run(name, fn, { id }) — opens a run. id resolves itself (an active
OpenTelemetry trace id, else a uuid); pass one only when a request is not one
run, such as a batch job where each item is its own.
step(id, options, fn) — opens a step. Options: kind, name, minScore, loopIndex, isRetry, mode ("discover" / "off"), latencyBudgetMs, objective.
TypeScript and Python take a callback or a context manager, so the scope closes
itself. Rust returns a guard that closes on drop — RAII rather than a callback,
because ? makes early returns idiomatic and a hand-closed scope would leak on
the first error path someone wrote.
await shimmy.run('r', async () => {
await shimmy.step('classify', { kind: 'classification' }, async () => {
// …
});
}); Opening a step outside a run starts an implicit single-step run rather than failing — partial instrumentation should degrade, not break.
Decorating a client
wrap(client) (TypeScript, Python) — returns your client, pointed at
Shimmy, signed in with your Shimmy key, and annotated. Nothing is reimplemented,
so unmodelled features keep working. What a call then does follows the Phase:
replay a Recording, search, serve — or, in passthrough, leave the client’s own
base URL and key alone and only rewrite each Step’s model from shimmy.lock.
An Anthropic client (anthropic.Anthropic(), new Anthropic()) can be wrapped
too. Its messages.create calls are translated to Shimmy and the answer back into
an Anthropic Message; in passthrough they go straight to Anthropic. Streaming is
passthrough-only for Anthropic clients.
Python rides on extra_body, which is openai-python’s documented channel for
unmodelled fields — so it survives version churn that would break a patched
method. TypeScript patches the method, because JS has no equivalent.
instrument_openai(shimmy) (Python only) — a global patch for clients
constructed inside a framework you do not own. Returns an undo callable, so test
suites stay clean. It does not repoint base_url; it cannot know which
clients in the process are meant for the optimizer.
decorate(&mut body) (Rust) — merges annotations into a JSON body. Rust has
no dominant OpenAI client, so the seam is the request body rather than a
wrapper. Attaches the Phase always; returns whether the call was inside a scope.
chat(body) (Rust) — the call itself, following the Phase exactly as wrap() does in the other SDKs: Recordings in Dev, Shimmy in Tuning and managed
Production, your provider directly in passthrough.
Reporting
report(signals, { stepId, runId }) — see report outcomes.
verify(fn, kind) — runs fn, reports success or failure, rethrows. For
where “did it work” is “did it throw”.
Best-effort: failures are logged, never thrown.
Reading the accounting
import { optimizerMeta } from '@rfa-labs/shimmy';
const meta = optimizerMeta(res);
meta?.saving; // baseline − actual
meta?.exploration_cost; // paid to learn; → 0 as the step settles Escaping the scope
| TypeScript | Python | Rust | |
|---|---|---|---|
Across await | automatic | automatic | automatic |
| Into a spawned task | automatic | automatic | explicit parent |
| Current ids | currentRunId() | current_run_id() | current() |
Rust’s task-locals do not survive tokio::spawn. See instrument an agent.