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" }
  }
}

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.

step.mode is the per-step routing intent: "discover" puts the step into discovery mode even when the request’s model names one (the pin becomes the search’s anchor and cost ceiling); "off" excludes it from discovery even when the agent has discovery enabled. Most specific wins: this annotation > the model field’s sentinel ("discover") > the agent’s discovery setting.

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 pinned model
actual_costWhat was spent, including exploration
served_costThe optimized path alone
exploration_costPaid to learn whether a downshift was safe
savingbaseline_cost − actual_cost

Compare served_cost to baseline_cost for the honest gap. exploration_cost converges toward 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, …