About openrouter-decisions
openrouter-decisions is a skill for using decision models through the OpenRouter Decisions API. It helps developers separate bounded, probabilistic judgments from deterministic workflow logic, with guidance from question design through model selection and threshold testing.
Key features
- 01Choice, noul, score, and box primitives
- 02Guidance for state and question design
- 03Live decision-model catalog lookup
- 04Scripted comparison of model responses
- 05Probability gates applied in code
- 06Threshold probing with representative inputs
Use cases
- 01Classify text and route it using probabilities
- 02Select among predefined options
- 03Apply rubric scores within code workflows
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ openrouterteam/ skills --skill openrouter-decisionsAfter installing, type/openrouter-decisionsin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
README.mdagents/openai.yamlreferences/decision-model-limits.mddecisions-api.mdmodels.mdquestion-design.mdscripts/decide.tslib.test.tslib.tsmodels.tspackage-lock.jsonpackage.jsonDetails
From the project READMEOpenRouter Decisions
A decision model reads application state and answers typed questions with probabilities, never text. The model judges, code computes. Code owns the workflow and every deterministic step, and the model fills in the judgments code cannot make. It is not a chat model, a coding agent, or a replacement for the LLM that drives one.
Every decision model on OpenRouter speaks the same Decisions API (also served as the TypeSafe-SDK-compatible System One API) and the same primitives, evaluated independently and in parallel within one request.
| Need | Primitive | Returns |
|---|---|---|
| One option from a defined set | choice | choice, probabilities per option, confidence |
| Whether a condition holds | noul | noul, the probability of yes |
| Degree along ordered levels | score | score (probability-weighted position), probabilities per level, confidence |
| Where something is in an image (Jev 1.14 only) | box | box edges as fractions of the image, confidence |
Read references/decisions-api.md when writing a request or reading a response, sending images, or calling through the System One API or TypeSafe SDK. Read references/question-design.md when designing the truth table, state, wording, and criteria, when an answer looks wrong, and for cookbook patterns. Read references/models.md when picking between the decision models the live catalog returns. Read references/decision-model-limits.md whenever a question involves quantities, dates, negation, indirection, or untrusted text, since those are the things a decision model does not do.
Steps
-
Split judgment from computation. A step fits a decision model when its answer is bounded, needs judgment rather than computation, and a probability lets code act, defer, or escalate; a step that must produce text, values, or code, or whose answer already sits in a field code can read, stays as it is. Arithmetic, counting, date ordering, lookups, threshold comparisons, and string or pattern matching that extracts a fact (a path, an ID format, an amount) stay in code, and a value code already holds is never re-asked of the model. A keyword, regex, token-overlap, or similarity heuristic that stands in for a judgment (does this text mean X, is this the same request, is this relevant) is the judgment itself: the decision model replaces it, and its rules move into the question's inclusion and exclusion criteria rather than staying as a pre-filter. When code-side facts settle the action for an input (a path rule, a hard policy, an amount over a cap, an empty input), code returns that action and skips the model for that input. When code parses a value out of free text before comparing it, write the parser against the formats in real inputs and treat a parse miss as its own outcome. Generation goes to a generative model, and a decision model can then pick among its candidates. Done when every remaining item is a judgment that fits one primitive.
-
Pick the primitive per judgment. Mutually exclusive alternatives are a
choice, which is relative and settles which option. An independently testable condition is anoul, which is absolute and can be low for every label, so labels that can co-occur are onenouleach. A degree along one dimension is ascorewhose levels each describe a situation that stands alone. One unordered label from a written rubric is onechoicewhose criteria restate the labels, not severalnouls recombined in code. Ordered levels (a 1 to 5 rating, low to critical) are ascorewhose criteria restate the levels in order, even if code stores the result as a label. Split intonouls only when code combines them with facts of its own or reuses them elsewhere. Comparable per-itemscores ranked in code give graded ranking. Locating a target in an image is abox. Done when each judgment has a primitive and a reason. -
Build the state. Send only what the questions read. Source text, identities, relationships, policies, and current facts, each in a named JSON field, filtered and retrieved in code first. Anything only code uses stays out (flags, counts, computed booleans, and any field that feeds a code-side rule such as a plan tier that caps a score), and code applies that rule to the returned answer. Unrelated material costs accuracy. Done when every field in
stateis read by at least one question and nothing a question needs is missing. -
Write the questions.
instructionsstates the judgment and carries the task's own inclusion and exclusion rules (what counts, what looks similar but does not).criteriadefines the possible answers and reads as an extension of the instructions, asked one way with no double negatives. Ask about the fact, not the text. "Is this change breaking" holds where "Does the description mention breaking changes" and "Does the report state that" both fail, so phrase each question as a property of the thing judged ("Is this review promotional") and let the model infer from the state. Point at a nested field with a backticked path such as`ticket.messages[0].text`. Achoicelists every viable option. When nothing may fit, pair thechoice(or abox) with anoulin the same request that asks whether anything fits, which works better on Jev 1.14 than anoneoption; probe both on other models. When the options are runtime candidates, key the state by option name and name the option in its criterion, never an array index. Question keys are for code and are not sent to the model, so each question carries its full meaning. Independent questions over the same state go in one request. A second request is needed only when an answer decides what state to fetch or which options to offer next. Done when a careful person could answer every question given onlystate,instructions, andcriteria. -
Pick a model and call the Decisions API. The set of decision models changes, so never pick from memory. List what OpenRouter serves right now with
scripts/models.ts(orGET /api/v1/models?output_modalities=decisions) and shortlist by the criteria in references/models.md: the context length holds your real state with room to spare, the price fits the call volume, and the provider footprint fits your availability needs. The catalog cannot tell you how a model answers your questions, so when more than one candidate remains, run the step 7 probe set through every candidate withdecide.ts --compareand pick on the observed answers, latency, and cost. Pin the chosencanonical_slug(or the versionedid) in one config value and never an alias, since an alias can shift probabilities under you. Swapping models later is that config change plus a rerun of step 7. Keep the API key server-side. For request types, validation, and the HTTP and SDK calls, import or copyparseRequestanddecidefrom scripts/lib.ts instead of rewriting them. Done when a request returns one typed answer per question key and the responsemodelstring is logged with the answer. -
Gate in code. Thresholds and business rules live in code as named constants next to the raw
noulandprobabilities. Before probing, anoulgate is>= 0.5(or yes above 0.7, no below 0.3, and the band between routed to the fallback when there is one) and achoiceis itschoicefield, and every stricter number waits for step 7. Achoiceorboxpaired with a presencenoulis read only in thatnoul's yes branch; the no branch takes the no-match outcome, the uncertain band (when there is one) takes the named fallback, and the truth table names all three actions. Compare in the direction the question asks, sois_breaking.noul >= Tmeans breaking and "safe" is theelse. A single gate fits only when the code path has two outcomes. When it already has a fallback (a review queue, a default, a bigger model), keep it as the outcome for probabilities near the gate and set that band's width in step 7. Useconfidenceonly when such a fallback is cheaper than a wrong answer. It measures how concentrated the distribution is, not whether the answer is correct or the action is authorized. Anoulnear 0.5 means yes and no are similarly likely, not medium intensity. A threshold does not carry from anoulto achoiceor from one model to another, andP(yes)from one question plusP(not yes)from another need not sum to 1. Done when every threshold is a named constant with the consequence of each mistake written next to it. -
Probe before you trust. Run the real questions over representative inputs with the bundled script and read the raw probabilities. Cover the clear cases, an ambiguous case, a no-match case, an empty or off-topic input, a negated statement, and adversarial text that argues for its own classification. Set thresholds from those numbers, not from cookbook defaults, and repeat when the model changes. Done when each edge case produces the routing you want and every threshold traces to an observed number.
cd <skill-path>/scripts && npm install
npx tsx models.ts request.json # live catalog with context, price, providers, and fit for this request
npx tsx decide.ts request.json --model <model-id> # raw HTTP to one model
npx tsx decide.ts request.json --sdk # through @openrouter/sdk
npx tsx decide.ts request.json --compare # same request to every pinned model in the catalog
request.json holds { "state", "questions" } and optionally "model". --model overrides the request's model, then DECISION_MODEL from the environment fills in when neither is set. The scripts print the answers, resolved model version, latency, and cost, and --compare prints one entry per model with the error inline when a model fails.