tokenlab-decisions
Get typed answers (yes/no probability, one choice from a list, or a score on a scale) about a piece of text or JSON with TokenLab System One decision models, for routing, gating, scoring, and triage i
Files4
View on GitHubSKILL.mdskills/tokenlab-decisions/SKILL.md
GitHubDecisions
Ask a decision model named, typed questions about a state, and get structured answers back: a probability for yes/no, a choice from your options, or a score on your scale. The call is synchronous. There is no text to parse.
Inputs to collect
- State: the thing to judge. A string, a JSON object, or a JSON array (a ticket, a message plus account facts, an agent's current step).
- Questions: for each, a short name, a type (
noul,choice, orscore), instructions, and criteria. - Actions: what the app does with each answer, and the threshold for each action. These stay in your code.
- Labeled examples, if the user has them, to check the answers before they drive real actions.
Models
<!-- tokenlab:models:start --> <!-- Generated from the live catalog by scripts/sync_models.py. Do not edit; change preferredModels in catalog.json. -->| Operation | Model | Pick | Use |
|---|---|---|---|
systemone |
jev-1.13 |
default | Typed decisions on text or JSON |
This list refreshes daily from the live catalog. Preferred models come first; otherwise models are ranked by their 24-hour success rate. Before a call, confirm the model with GET /v1/models/{model} and read its price from GET /v1/models/{model}/pricing.
How to choose:
- Typed decisions on text or JSON: Jev. It prices per input token.
Exact fields come from the model contract.
Discover decision models with GET /v1/models?category=decision. recommended_for=decision is also meant to work; if the API rejects it with invalid_recommended_for, use category=decision.
Decisions vs chat
| Use a decision model when | Use a chat model when |
|---|---|
| You need a branch, a route, a gate, or a score | You need prose, a summary, or a rewrite |
| The answer is one of your options or a number | The answer is open-ended |
| Code acts on the answer with a threshold | A person reads the answer |
| You want probabilities and confidence as data | You need tools, streaming, or a long conversation |
The decision endpoint takes only model, state, and questions. It has no messages, system prompt, tools, streaming, or batch.
Workflow
Run each call with tokenlab-run (contract → estimate → call → poll → result). Commands below use its helper, ../tokenlab-run/scripts/tokenlab_run.py, a path relative to this skill's folder. If you installed this skill without tokenlab-run, send the same requests with curl. --wait stops after 100 s; a longer task keeps running, so continue with the resume command it prints.
- Discover and read the contract:
GET /v1/models?category=decision, thenpython ../tokenlab-run/scripts/tokenlab_run.py contract MODEL_ID --operation systemone. TakeMODEL_IDfrom the Models list above or from that discovery call. The contract listsstateandquestions. - Write the questions (see Technique and
references/question-design.md). Give each a stable name; the response uses the same names. - Estimate with
GET /v1/models/{model}/pricing. Jev prices per input token. Example: 469 input tokens × (input price per 1M tokens) / 1,000,000. Estimate a batch from one real call'susage.input_tokens. - Call
POST /v1/systemone:python ../tokenlab-run/scripts/tokenlab_run.py run /v1/systemone --json @body.json. Keep thex-request-id. There is no task to poll. - Read
answers.<name>andusage. Keepnoul,probabilities,confidence, andusageexactly as returned. Do not round a probability into a Boolean in storage. - Apply your thresholds in code (see below). Log the request ID, the answers, and the action taken.
- Before the answers drive real actions, run them on the user's labeled cases and tune the thresholds.
HTTP (take MODEL_ID from the Models list above):
curl https://api.tokenlab.sh/v1/systemone \
-H "Authorization: Bearer $TOKENLAB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "MODEL_ID",
"state": {"ticket": "I was charged twice for my October invoice. Please refund the duplicate payment today.", "plan": "pro"},
"questions": {
"refund_requested": {"type": "noul", "instructions": "Does the customer explicitly ask for a refund?",
"criteria": {"true": "The message asks for money back.", "false": "The message only reports a problem or asks a question."}},
"team": {"type": "choice", "instructions": "Which team should handle this ticket?",
"criteria": {"billing": "Charges, invoices, refunds", "technical": "Bugs, errors, API problems", "sales": "Plans, quotes, upgrades"}},
"urgency": {"type": "score", "instructions": "How urgent is this ticket?",
"criteria": ["Routine question", "Money or access affected", "Outage or safety issue"]}
}
}'
Response (live, 2026-10-12, trimmed):
{"model": "MODEL_ID",
"answers": {
"refund_requested": {"type": "noul", "noul": 0.99},
"team": {"type": "choice", "choice": "billing", "probabilities": {"billing": 1, "technical": 0, "sales": 0}, "confidence": 1},
"urgency": {"type": "score", "score": 1, "legend": {"0": "Routine question", "1": "Money or access affected", "2": "Outage or safety issue"}, "probabilities": {"0": 0, "1": 1, "2": 0}, "confidence": 1}},
"usage": {"input_tokens": 469, "output_tokens": 70}}
MCP (core or full profile), same fields:
{"name": "evaluate_decisions",
"arguments": {"model": "MODEL_ID",
"state": {"ticket": "Please refund the duplicate payment."},
"questions": {"refund_requested": {"type": "noul", "instructions": "Does the customer explicitly ask for a refund?"}}}}
Check isError, then read structuredContent.answers and structuredContent.usage. The tool is synchronous; do not use get_task_status. Allow a client timeout of at least 150 s.
Act in code, not in the prompt:
a = resp["answers"]
if a["refund_requested"]["noul"] >= 0.8 and a["team"]["choice"] == "billing":
queue = "billing-refunds"
elif a["urgency"]["score"] >= 2:
queue = "on-call"
else:
queue = a["team"]["choice"]
Technique
noul: one yes/no fact per question. Optionalcriteriamust have bothtrueandfalseguidance. The answer is a probability from 0 to 1, not a Boolean.choice:criteriamaps 1–255 option names to guidance (ornull). Use short, stable option names your code can switch on. The answer is always one of your names.score:criteriais an ordered list of 2–10 level descriptions, low to high. Thescoreruns from 0 to the last index and can be a fraction.- Put facts in
state, not in the question. Questions say what to judge;stateholds the case. JSON state with clear keys works well. - One decision per question. Split "is it urgent and about billing?" into a
scoreand achoice. Several questions on one state go in one call. - Thresholds and actions stay in your code. Do not put "if over 0.8 then..." in instructions. Tune thresholds on labeled cases.
- Keep the raw answers. Store
noul,probabilities,confidence, andusageas returned for audit and tuning. - A request with any other field (
messages,stream,tools) fails validation. A question with a wrong shape (for example ascorewith one level) fails too.
Quality bar
- Each question has one clear decision, a stable name, and valid criteria for its type.
- Answers are stored as returned; thresholds live in code and were checked on labeled cases.
- The app has a safe default when a call fails or an answer is near the threshold.
- The final answer names the model, request ID, answers, and token usage.
Related skills
tokenlab-run: the call and error mechanics.tokenlab-native-endpoints: System One next to Responses, Messages, and Gemini routes.tokenlab-api-integration: put the decision call into application code.tokenlab-cost-routing: route between decision and chat models by cost.