Types reference

Step kinds

An advisory prior on difficulty. The classifier’s fallback matches ten English keywords and rounds up when unsure, so every ambiguity is billed as overspend — and a non-English workload has almost no signal at all.

KindWork
classificationPick a label from a known set
extractionPull structured fields from supplied text
summarizationCondense supplied text
formattingReshape without adding judgment
translationConvert between languages or notations
question_answeringAnswer from supplied context
generationOpen-ended prose a person will read
planningDecide the next action
reasoningMulti-hop inference where the chain is the work
codeWrite or modify code
evaluationGrade or verify another output
otherCarries no prior

An unrecognized value degrades to other rather than failing the request, so an SDK newer than the edge does not break your calls.

Outcome signals

FieldTypeMeaning
schema_validboolThe response parsed and validated
tool_executedboolThe emitted tool call ran
retriedboolYou re-ran this step
run_completedboolThe run reached its terminal state
human_verdictaccepted | edited | rejectedWhat a person did
score0..1Your own eval score

All optional. Report only what you know — inferring from silence would manufacture evidence.

Precedence: a hard failure (schema invalid, tool failed, retried, human rejected) scores 0 and outranks everything, including a supplied score. Then your score, then a human verdict, then a clean structural pass at 0.95. Nothing conclusive means inconclusive, and the judge decides as before.

Annotations

What the SDK puts on a request, under the wire field optimizer.

{
  "optimizer": {
    "run":  { "id": "run-42", "name": "nightly", "seq": 0,
              "parent": "…", "is_start": true },
    "step": { "id": "classify", "name": "classify", "kind": "classification",
              "quality": { "min_score": 0.8 },
              "loop_index": 1, "is_retry": false, "mode": "discover",
              "requires": { "input_modalities": ["image"], "tools": "full",
                            "min_context_tokens": 200000 } },
    "source": { "file": "src/triage.ts", "line": 88,
                "fn": "classifyIntent", "release": "git:abc1234" },
    "intent": { "latency_budget_ms": 800, "objective": "cost" },
    "phase": "dev",
    "recording": "auto"
  }
}

Every field is optional, and every level is omitted when empty — the server distinguishes “said nothing” from “said false”, so an explicit null would read as a declaration.

phase is the process’s Phase — dev, tuning or production — and is always sent, even outside any scope. recording is sent in the Dev Phase only: auto, record or strict, as in Recordings.

step.mode is the per-step search intent: "discover" gives the Step a Discovery search even when the request’s model names one (the pin becomes the search’s anchor and cost ceiling); "off" keeps the Step out of Discovery. Most specific wins: this annotation > the model field’s "discover".

step.requires declares the hard capabilities this step needs from whatever model serves it. It is a filter, not a preference: the router never probes a model that can’t meet a requirement, so a tool-calling step never wastes a probe on a tool-less model and a vision step never lands on a text-only one.

FieldTypeMeaning
tools"none" \| "partial" \| "full"Minimum tool-calling support
structured_outputsboolNeeds JSON / structured output mode
reasoningboolNeeds a model with reasoning-effort control
min_context_tokensintSmallest context window the step can use
input_modalitiesstring[]text \| image \| audio \| video \| file the step sends
output_modalitiesstring[]What the step needs produced (text today)

Most requirements are inferred from the request — tools present, JSON mode set, prompt size, and input modality. The edge accepts OpenAI’s polymorphic content (a plain string, or an array of text / image_url / input_audio parts), so a turn that actually carries an image is routed to a vision-capable model automatically — no hint required. Declaring requires.input_modalities: ["image"] is only needed ahead of the image: when a later turn of this step will attach one, but the request you’re making now is still text-only. A declared requirement wins over an inferred one, per field — you know the step’s future turns; the server sees only this one.

Requirements are priors, never verdicts: they decide which models are eligible to probe. The quality judge still decides which eligible model actually holds up.

Response accounting

FieldMeaning
cache_hitServed from cache
baseline_costWhat it would have cost on your Baseline
actual_costWhat was spent, including the search’s exploration
served_costThe model that answered, alone
exploration_costPaid to grade candidates against the Baseline
savingbaseline_cost − actual_cost

Every response through Shimmy carries this block, under optimizer; replays and passthrough calls don’t. exploration_cost falls to zero as a Step settles — faster when you report outcomes.

Source annotation

file, line, fn, release. Source metadata — never source text, argument values, or anything a user typed. Turn it off with sourceContext: false.

TypeScript and Python walk a stack trace and filter their own frames. Rust uses #[track_caller], which resolves the location at compile time with no unwinding at all — different mechanisms, same three fields on the wire.

Environment

VariableFalls back to
SHIMMY_API_KEYOPTIMIZER_API_KEY
SHIMMY_BASE_URLOPTIMIZER_BASE_URL
SHIMMY_RELEASEOPTIMIZER_RELEASE, then GITHUB_SHA, GIT_COMMIT, VERCEL_GIT_COMMIT_SHA, …