Playbook for LLM agents building with Jev
TL;DR Jev answers narrow, typed questions about one
stateand returnschoice/score/noulplus calibrated probabilities — it never generates text, does arithmetic, or picks its own next action. Decompose the user's job into atomic questions, filter the state down to what those questions need, ask them all in onePOST /v1/systemone, and branch in code on the values and onconfidence. Everything deterministic stays in your code.
Step 0 — Is Jev the right tool?
Work backwards from the behavior the user wants: what will the application show, select, change, or hand off? (The typesafe-ai agent skill and Claude Code plugin). Then place each decision in this table.
| Task shape | Verdict | Why |
|---|---|---|
| Classify into a known set of categories | Jev (Choice questions) | The answer space is known in advance and the consumer is code (System One Models). |
| Detect whether a property holds | Jev (Noul (yes/no) questions) | The probability itself is the signal. |
| Rate on a rubric you can describe level by level | Jev (Score questions) | Ordered, thresholdable, comparable across items. |
| Route a request to a handler, queue, or model | Jev + code | Intent routing, Confidence-gated routing. |
| Rank or shortlist on several dimensions | Jev + code | Composite scoring; weights live in your code, not a prompt. |
| Verify another model's output, citations, or tool calls | Jev | "Universal Verification" in Use-case map by industry; Cookbook: Double-checking citations. |
| Select a value that already exists in the text | Jev, as a Choice over candidates | Extraction becomes selection (Cookbook: Pre-parsed value extraction). |
| Score millions of records cheaply | Jev | $0.042/Mtok input, output free (Models, aliases, pricing, rate limits, context). |
| Write a reply, summary, explanation, or code | An LLM | "jev-1.13 is not trained to generate text" (Jev 1.13 jaggedness: known failure modes #9). |
| Decide its own next action in a loop | An LLM agent | System One "does not generate code or choose its own next action" (How to build software with System One). |
| Multi-hop reasoning over several inferred facts | An LLM, or decompose | Indirection costs accuracy (Jev 1.13 jaggedness: known failure modes #4). |
| Arithmetic, sums, counting, percentages | Keep it in code | "Jev is not a calculator" (Jev 1.13 jaggedness: known failure modes #2). |
| Date ordering, durations, windows, weekdays | Keep it in code | Jev reads dates as text (Jev 1.13 jaggedness: known failure modes #3); extract parts, compare in code (Cookbook: Date extraction). |
| Exact lookups, regex matches, status checks, thresholds | Keep it in code | "Use code when you can" (How to build software with System One step 1). |
| Images, audio, video | Pre-process to text | Jev is text-only (State: what you send Jev). |
Do NOT use Jev for: generating text or code; math, counting, or reconstructing an exact number by interpolating between Score levels; date and time comparison; questions requiring several hops of indirection or double negatives; a huge state full of material the question does not need; anything a regular expression, parser, or if already answers exactly. Most of these are documented failure modes in Jev 1.13 jaggedness: known failure modes (nine in total); the last one — what a regular expression, parser, or if already answers — is not on that list but follows the how-to-build rule "Use code when you can" (How to build software with System One step 1).
One more thing to check before you write code: Jev's primary training language is English, and other languages including CJK are accepted but less accurate (Models, aliases, pricing, rate limits, context).
Step 1 — Decompose the job into questions
The docs call this "probably the most important concept" (How to build software with System One step 4).
- List the decisions the workflow actually makes. Each one is either a code rule or a judgment. Keep control flow, deterministic rules, and side effects in code.
- Split every judgment until each question tests exactly one property. If you cannot name the single property a question tests, it is not atomic yet. "Rate this startup pitch" becomes market size, technical feasibility, differentiation — weighted in code (Primitives: Choice, Score, Noul).
- Pick a primitive per question using Choosing between Choice, Score, Noul: unordered options → Choice; a described spectrum → Score; a clean yes/no → Noul; a count → none, one Noul per item plus
sum()in code; free text → none, propose candidates in code and let a Choice pick one. - Ask each decision one way. Structural invariants are not guaranteed: a Noul and a yes/no Choice on the same text returned
0.22vsprobabilities["yes"] = 0.01, and a question plus its negation summed to1.19(Jev 1.13 jaggedness: known failure modes #8). Do not implement "not X" by subtracting X, and do not carry a threshold tuned on a Noul over to a Choice. Enforce identities, totals, and mutual exclusion in code. - Add the speculative questions too. Questions run in parallel and adding them barely changes latency (Speculative fan-out) — but state each speculative premise explicitly, and ignore the answers on branches you did not take.
Step 2 — Shape the state
state is the single input every question in the request sees (State: what you send Jev).
- Shapes: a string, a JSON object, or an array of text values. Nothing else. Prefer an object with descriptive field names so questions can point at paths.
- Budgets: 64k tokens for
stateplus all questions; 32k tokens forstateplus the single longest question. Staying under 64k does not guarantee you are under 32k (Models, aliases, pricing, rate limits, context). - Filter first. Accuracy falls as the state grows with irrelevant detail, and a big state makes a wrong answer hard to localize (Jev 1.13 jaggedness: known failure modes #5). Retrieve and filter in code; where you cannot, use a Noul as a relevance filter (Cookbook: Classifying RAG passages).
- Pre-parse numbers and dates. Compute the number or the named bucket in code and put that in the state; Jev does better on semantic representations than numeric ones (Writing instructions and criteria that Jev reads correctly).
- Point at fields with backticked dot-and-index paths, backticks included:
`ticket.messages[0].text`. - Asymmetry to remember: extra questions are cheap, extra state is not.
Step 3 — Write instructions and criteria
Full guide: Writing instructions and criteria that Jev reads correctly. The rules that matter most:
- Literal reading. Jev answers the question you wrote, not the one you meant. When a wrong answer makes you say "but I meant…", that sentence belongs in
instructionsor in the criteria as a boundary case. - Question ids are never sent to the model — write the complete question even when the id looks self-explanatory (HTTP API: POST /v1/systemone and GET /v1/models).
- Choice: give the full option list (up to 255 options per the launch blog; one cookbook reports ~240 as the practical working limit — Cookbook: Classification using confidence). Add an
other/none of the aboveoption whenever the list might not cover an input. For confusable options, escalate the description from a string to an object withwhat/not_for/examples. - Score:
criteriais an ordered array from low to high, at least 2 and up to 10 levels, each describing a concrete situation rather than a degree (Score questions).["0","1","2"]with "rate 0 to 2" in the instructions scored 0.57 at confidence 0.35 where described levels scored 0.0 at 1.0. One dimension per Score. - Noul: keep
truemeaning yes. A Noul whosetruemaps to "no" performs worse (Jev 1.13 jaggedness: known failure modes #7). Avoid double negatives — rewrite them positively. - Align criteria with the instruction. Contradiction between the two is its own failure mode.
Step 4 — Call it
Get a key from the console: the quickstart points at https://console.typesafe.ai/settings/keys, the agent-skill page at https://console.typesafe.ai/keys — the sources disagree on the path, both are under console.typesafe.ai (console.typesafe.ai (console + playground)).
Environment variables read by both SDKs (TYPESAFE_* environment variables across SDKs):
| Variable | Default | Effect |
|---|---|---|
TYPESAFE_API_KEY |
none — required | Sent as Authorization: Bearer <key>. |
TYPESAFE_BASE_URL |
https://api.typesafe.ai |
API root. |
TYPESAFE_DEFAULT_MODEL |
jev-latest |
Model used when a call omits model. |
TYPESAFE_LOG_LEVEL |
JS: warn; Python: unset |
Logger level. debug prints request and response bodies, which are not redacted. |
Model ids: jev-latest and jev-preview both currently resolve to jev-1.13.0. Pin jev-1.13.0 if you have tuned thresholds; the response's model field reports which version answered (Models, aliases, pricing, rate limits, context).
curl
export TYPESAFE_API_KEY="..." # from console.typesafe.ai
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"state": {
"ticket_message": "I was charged twice for order A-104. Please refund the duplicate.",
"refund_policy": "Duplicate charges are eligible for a refund."
},
"model": "jev-latest",
"questions": {
"refund_requested": {
"type": "noul",
"instructions": "Does `ticket_message` request a refund?"
},
"department": {
"type": "choice",
"instructions": "Which team should handle `ticket_message`?",
"criteria": {
"billing": "Charges, invoices, refunds, subscriptions",
"technical": "Bugs, outages, integrations",
"other": "Anything else"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated does the customer appear in `ticket_message`?",
"criteria": [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language"
]
}
}
}
EOF
Python — typesafe-sdk 0.6.0, sync
# pip install typesafe-sdk (import name: typesafe_sdk; requires Python >= 3.10)
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
STATE = {
"ticket_message": "I was charged twice for order A-104. Please refund the duplicate.",
"refund_policy": "Duplicate charges are eligible for a refund.",
}
QUESTIONS = {
"refund_requested": Noul(instructions="Does `ticket_message` request a refund?"),
"department": Choice(
instructions="Which team should handle `ticket_message`?",
criteria={
"billing": "Charges, invoices, refunds, subscriptions",
"technical": "Bugs, outages, integrations",
"other": "Anything else",
},
),
"frustration": Score(
instructions="How frustrated does the customer appear in `ticket_message`?",
criteria=[
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language",
],
),
}
with TypeSafeClient() as client: # reads TYPESAFE_API_KEY; defaults to jev-latest
response = client.system_one(state=STATE, questions=QUESTIONS, model="jev-1.13.0")
print(response.model, response.usage.input_tokens)
print(response.answers["refund_requested"].noul)
print(response.answers["department"].choice, response.answers["department"].confidence)
print(response.answers["frustration"].score, response.answers["frustration"].legend)
Python — async
import asyncio
from typesafe_sdk import AsyncTypeSafeClient, Noul
async def main() -> None:
async with AsyncTypeSafeClient() as client: # await client.aclose() if not using `async with`
response = await client.system_one(
state="Help! My payouts have been failing for 3 days.",
questions={"is_urgent": Noul(instructions="Does this convey urgency?")},
)
print(response.answers["is_urgent"].noul)
asyncio.run(main())
Build one client per process and reuse it — do not construct a client per request (Python SDK: install, clients, system_one()).
TypeScript — @typesafe-ai/sdk 0.6.0
// npm install @typesafe-ai/sdk (Node >= 20; server-side only unless you opt into
// dangerouslyAllowBrowser, which exposes your key)
import { APIError, choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient(); // reads TYPESAFE_API_KEY, TYPESAFE_BASE_URL, TYPESAFE_DEFAULT_MODEL
try {
const { answers, usage, model } = await client.systemOne({
state: {
ticketMessage: "I was charged twice for order A-104. Please refund the duplicate.",
refundPolicy: "Duplicate charges are eligible for a refund.",
},
model: "jev-1.13.0",
questions: {
refundRequested: noul("Does `ticketMessage` request a refund?"),
department: choice("Which team should handle `ticketMessage`?", {
billing: "Charges, invoices, refunds, subscriptions",
technical: "Bugs, outages, integrations",
other: "Anything else",
}),
frustration: score("How frustrated does the customer appear in `ticketMessage`?", [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language",
]),
},
});
console.log(model, usage.input_tokens);
console.log(answers.refundRequested.noul);
console.log(answers.department.choice, answers.department.confidence);
console.log(answers.frustration.score, answers.frustration.legend);
} catch (err) {
if (err instanceof APIError) {
console.error(`API error ${err.status} (request ${err.requestId ?? "unknown"}):`, err.body);
} else {
throw err;
}
}
Answer types are inferred from the questions you passed, so answers.department.choice narrows to "billing" | "technical" | "other" (JavaScript/TypeScript SDK: install, client, choice/score/noul).
Step 5 — Use the answers
| Type | Read | Notes |
|---|---|---|
| Choice | choice, probabilities, confidence |
probabilities maps every option to 0–1, summing to ~1. |
| Score | score, legend, probabilities, confidence |
score may fall between levels; normalize with score / (len(criteria) - 1) before combining or comparing rubrics of different lengths. |
| Noul | noul |
Probability of yes. No confidence field. 0.5 means yes and no are equally likely, not "medium". |
In Python, response.answers[...] is the mixed map and response.nouls / response.choices / response.scores are per-type views (Python SDK responses, answers, usage, models).
Confidence-gated routing. Check the floor before branching on the answer, then give each action its own threshold sized to the cost of being wrong (Confidence-gated routing):
| Threshold | Context in the sources |
|---|---|
confidence < 0.5 |
Route to a human, any action (Confidence vs probability). |
confidence < 0.6 |
Floor in the voice-banking example (Confidence-gated routing). |
confidence >= 0.7 |
Trust a Score before acting on its value (Confidence vs probability). |
confidence < 0.75 / < 0.8 |
Route a topic Choice to human review (How to build software with System One). |
confidence > 0.85 / > 0.9 |
Act automatically on a high-stakes, destructive action. |
confidence >= 0.9 |
Report the fine label, else fall back to the coarser one (Cookbook: Classification using confidence). |
These are examples from the sources, not defaults. Tune them on your own labelled data (Testing and evaluating a Jev workflow), and keep every question and threshold in one file so a human can review them (The typesafe-ai agent skill and Claude Code plugin).
Fan-out. Put every question the decision tree could need in one request; batching 13 questions about a 54k-character document was 12.2x cheaper and 10.0x faster than 13 single-question calls, with unchanged answers (Cookbook: Parallel questions, Speculative fan-out).
Composite scoring. Normalize each Score to 0–1, weight in code, and re-rank by changing a coefficient rather than re-running inference. Weighted sums are for compensating preferences; an "any serious violation" rule needs separate conditions (Composite scoring).
answers = response.answers
spam_risk = (
0.45 * answers["requests_credentials"].noul
+ 0.30 * answers["sender_identity_mismatch"].noul
+ 0.25 * answers["unexpected_reward"].noul
)
Step 6 — Handle errors and limits
| Status | Retry? | Action |
|---|---|---|
400, 403, 404 |
No | Fix the request, the key's access, or TYPESAFE_BASE_URL. |
401 |
No | Check TYPESAFE_API_KEY and the Bearer prefix. |
422 |
No | Body is {"detail": [{loc, msg, type, ...}]} — read loc and fix the field. |
408, 429, 5xx (incl. 529 Overloaded) |
Yes | Exponential backoff; honor retry-after-ms, then retry-after. |
| Connection error / timeout | Yes | Retry; raise the client timeout if it recurs. |
Both SDKs retry by default: 2 retries after the initial attempt, 500 ms initial backoff doubling to a 5,000 ms cap, 25% jitter, retryable statuses {408, 429, 500–599}; the JS SDK caps an honored server delay at 60,000 ms. Per-attempt HTTP timeout is 10 s (Python DEFAULT_TIMEOUT = 10.0, JS DEFAULT_TIMEOUT_MS = 10_000) (HTTP status codes, rate limits, retry semantics). Log the x-typesafe-request-id (response.request_id / err.requestId) on every failure.
Limits and cost (Models, aliases, pricing, rate limits, context): 250,000 tokens/second and 1,200 requests/minute, either breach returning 429; TypeSafe warns these "can change without notice," so treat 429 as normal rather than exceptional instead of hardcoding a client-side budget. Price is $42 per Btok / $0.042 per Mtok on input tokens only — output tokens are free. There is no uptime SLA (Legal: MCA, DPA, privacy, data retention).
Step 7 — Test before shipping
Do not ship on the strength of a few hand-checked examples.
- Consistency. Jev is not deterministic: 15 repeats of one claim moved a Noul from
0.43to0.53, across a 0.5 threshold (Cookbook: Self-consistency — nouls), and 2 of 8 Choice questions changed their top label (Cookbook: Self-consistency — choices). - Held-out cases. Tune wording and thresholds on one set, validate on another (Writing instructions and criteria that Jev reads correctly).
- Jaggedness edge cases. Regression-test literal reading, negation, numbers, dates, and adversarial content (Jev 1.13 jaggedness: known failure modes).
- Full method, harness, and legal caveat: Testing and evaluating a Jev workflow.
Copy-paste checklist
- Every deterministic rule, sum, date comparison, and exact lookup is in code, not a question.
- Each question tests exactly one named property.
- Each decision is asked one way; invariants and totals are enforced in code.
-
stateis an object with descriptive keys, filtered to what the questions need, under 64k / 32k tokens. - Questions point at state with backticked paths (
`ticket.messages[0].text`). - Choice has a
none/otheroption; Score levels describe situations, 2–10 of them, one dimension; Noultruemeans yes. - All questions for one state go in one request; a second request only when an answer is needed to build it.
-
TYPESAFE_API_KEYset; key never shipped to a browser. -
modelpinned tojev-1.13.0if thresholds are tuned;response.modellogged. -
confidencechecked before branching onchoice/score; Noul thresholded onnoulitself. - Questions and thresholds live in one reviewable file.
-
429/529retried with backoff;401/422never retried;x-typesafe-request-idlogged. -
usage.input_tokensmetered against $0.042/Mtok. - Repeatability, held-out accuracy, and adversarial inputs tested before rollout.
-
TYPESAFE_LOG_LEVEL=debugis off in production (bodies are not redacted).
Common mistakes
| Mistake | Symptom | Fix |
|---|---|---|
| One broad question hiding several judgments | A single confident number that is wrong for a reason you cannot see | Decompose; the tool-call example isolates the unit mismatch only when split into nine questions (How to build software with System One) |
Acting on choice without confidence |
A 0.60/0.38 split and a 1.00/0.00 split behave identically | Gate on confidence first (Confidence-gated routing) |
Reading .confidence on a Noul |
Attribute error / undefined | Noul has no confidence; threshold noul (Confidence vs probability) |
Treating noul = 0.5 as "medium" |
Mid-range answers routed as moderate intensity | 0.5 means yes/no equally likely; use a Score for magnitude (Choosing between Choice, Score, Noul) |
| Asking for a count, a sum, or a date comparison | Errors that grow with the size of the thing counted | One Noul per item plus sum(); extract date parts as Choices and compare in code (Jev 1.13 jaggedness: known failure modes) |
Numeric Score levels ("0", "1", "2") |
Low confidence, scores drifting toward the middle | Describe a concrete situation per level (Writing instructions and criteria that Jev reads correctly) |
No other option on a Choice |
Probability mass lands on the least-wrong listed option | Add a catch-all (Choosing between Choice, Score, Noul) |
Dumping the whole record into state |
Accuracy drops; wrong answers are hard to localize | Filter in code first (State: what you send Jev) |
Asking X and 1 - not_X and expecting agreement |
Probabilities that sum to 1.19 | Ask the one question you want (Jev 1.13 jaggedness: known failure modes #8) |
| Serial calls, one question each | ~12x the cost and ~10x the latency | Batch into one request (Cookbook: Parallel questions) |
Thresholds tuned against jev-latest |
Behavior shifts silently when the alias moves | Pin jev-1.13.0, log response.model (Models, aliases, pricing, rate limits, context) |
Retrying 401 or 422 |
Repeated failures, wasted quota | Retry only 408/429/5xx (HTTP status codes, rate limits, retry semantics) |
Expecting Score.criteria to be an int-keyed dict |
TypeSafeError at build or validation time |
0.6.0 takes an ordered sequence (Python SDK: install, clients, system_one(), JavaScript/TypeScript SDK: install, client, choice/score/noul) |
| Trusting a demo threshold from a cookbook | False positives or negatives in your domain | "Treat cookbook thresholds and demo results as examples to evaluate" (The typesafe-ai agent skill and Claude Code plugin) |
Where to look next
Closest recipe by task type:
| Task | Start here |
|---|---|
| Routing / intent | Intent routing, Cookbook: Self-consistency — choices |
| Extraction | Cookbook: Pre-parsed value extraction, Cookbook: Date extraction, Cookbook: SDE cascade |
| Ranking / reranking | Cookbook: Re-ranking, Composite scoring |
| Guardrails | Cookbook: Guardrails for LLMs, Cookbook: Classifying RAG passages |
| Dedup / record matching | Cookbook: Knowledge graph entity alignment |
| Classification with confidence | Cookbook: Classification using confidence, Cookbook: Hierarchical classification |
| Function calling / tool use | Cookbook: Function calling |
| Search inside a document | Cookbook: Line-by-line search |
| Verifying an LLM's claims | Cookbook: Double-checking citations |
| Agent context management | Cookbook: Skill suggestion |
| Document structure | Cookbook: Structure recovery (autoformat) |
| Features for a classical ML model | Cookbook: Autoresearch feature discovery |
| Cost and batching evidence | Cookbook: Parallel questions |
| Everything else | Cookbooks overview, Use-case map by industry |
Related
- Consult guide: could Jev help this project? — not sure Jev fits the project at all? Run the consult procedure first (community tier)
- How to build software with System One — the seven-step design workflow this playbook operationalizes
- Patterns overview — the four architectural patterns
- The typesafe-ai agent skill and Claude Code plugin — TypeSafe's own instructions to coding agents
- Workflow evals: how TypeSafe measures Jev — evidence that the workflow shape beats a single prompt
- AI primer: why calibrated decision models — why the probabilities are calibrated
- Smart home assistant demo walkthrough — fan-out plus an LLM fallback in a running app
Sources
- wiki/concepts/system-one.md, wiki/concepts/primitives.md, wiki/concepts/state.md, wiki/concepts/confidence.md, wiki/concepts/how-to-build.md, wiki/concepts/jaggedness-jev-1-13.md, wiki/concepts/use-case-map.md, wiki/concepts/workflow-evals.md
- wiki/reference/http-api.md, wiki/reference/models-and-pricing.md, wiki/reference/rate-limits-and-errors.md, wiki/reference/python-sdk.md, wiki/reference/python-sdk-responses.md, wiki/reference/javascript-sdk.md, wiki/reference/environment-variables.md, wiki/reference/agent-skill.md, wiki/reference/legal-and-data.md
- wiki/patterns/overview.md, wiki/patterns/fan-out.md, wiki/patterns/confidence-routing.md, wiki/patterns/composite-scoring.md
- wiki/cookbooks/overview.md, wiki/cookbooks/parallel-questions.md, wiki/cookbooks/classification-using-confidence.md, wiki/cookbooks/consistency-choice.md, wiki/cookbooks/consistency-noul.md
- wiki/guides/quickstart.md, wiki/guides/choosing-a-primitive.md, wiki/guides/writing-instructions-and-criteria.md
- raw/docs/introduction__quickstart.md (https://docs.typesafe.ai/introduction/quickstart) — console key URL
- raw/docs/agent-skill.md (https://docs.typesafe.ai/agent-skill) — alternate console key URL