Turn natural language, an LLM prompt, or the code that runs one into a Jev decision
Jev is TypeSafe's System One model. It evaluates typed questions against a state
and returns probabilities, not prose. Code owns control flow. The input is one of
three things: a requirement in natural language with no prompt yet, an LLM prompt
(system prompt, template, or prompt-and-parse step), or the code that runs one today,
with its parser and the branches on its output. Converting any of them means moving
each judgment into its own question, moving every rule code can compute into code,
and leaving generation with a generative model. The bundled references distill the
TypeSafe docs; when live docs are reachable, https://docs.typesafe.ai/llms.txt is the
source of truth for the API contract.
What you deliver
Produce the conversion package. It has five parts, in this order, every time:
- Decision contract (five lines): the decision the code takes, the evidence
unit (what one request judges), the consumer of each answer, the unknown path
(what happens when nothing fits or evidence is missing), and the success check.
- Split table: every instruction in the original prompt mapped to one of
choice, noul, score, code, keep-llm, or drop, with the question id or
the code rule it became. Nothing in the prompt goes unaccounted for.
- Request JSON in the native shape
{"model", "state", "questions"}, saved to
a file and validated with prompt2jev validate --strict before it is shown.
- Composition code, as a file that runs. Generate it from the validated request
with
prompt2jev code request.json --lang <lang> --output <file>, then replace the
return in decide() with the branching from part 1. The file has imports, the
constants block (every threshold, weight, and hard rule), the questions, one call
that sends the request, decide(), and an entry point; it compiles, and it runs
on the example state. Language rule: Python gets the official typesafe_sdk
(--lang python); JavaScript and TypeScript projects get @typesafe-ai/sdk
(--lang javascript, an ES module saved as .mjs; the SDK ships its own
types); any other language, or a Python environment that cannot
install packages, sends the HTTP request directly, with --lang python-stdlib
or --lang curl as the reference (see api.md). When the
user asks only for the request, part 3 is the deliverable and this part is one
line on how to send it.
- Assumptions and fixtures: what was assumed, what the user should confirm,
and the test cases to run before automating (positive, negative, ambiguous,
missing evidence, adversarial).
When the user asks for a script, a module, a program, or "code I can run", the
answer is the path of the file from part 4 plus the output of running it: the real
output when a key is present and the user approved a paid call, otherwise the
--dry-run request and the exact command to run once a key exists.
Step 0: read the prompt, dig before asking
Collect before designing: the prompt text, every placeholder and where its value
comes from, the parser or schema that reads the output, the code that branches on
each output field, existing enums or label lists, and any tests or labeled
examples. In a codebase, grep for the prompt string, the output field names, and
the API call that sends it.
Ask the user only when a gap changes the shape of a question: the label set, what
the code does with an answer, or the stakes of a wrong answer. Ask one question at
a time. A gap that does not change the shape becomes a stated assumption in part 5.
Never stop with nothing delivered; a validated draft with assumptions beats a
question.
Step 1: split the prompt into judgments
Read the prompt sentence by sentence and classify each instruction:
| Instruction in the prompt |
Becomes |
Why |
| "X must be one of: a, b, c" |
choice with those options plus a fallback |
One option from a known set |
| "Set flag if the message ..." |
noul |
One yes/no proposition |
| "Rate ... from 1 to 5", "how severe" |
score with described levels |
A position on a spectrum |
| "If A and B then ..." |
one question per condition, combined in code |
Compound conditions hide judgments |
| Arithmetic, comparisons, dates, counting, thresholds |
code |
Jev is not a calculator |
| "Extract the invoice number" |
candidates found in code, then choice over them |
Select, never generate |
| "Summarize", "write a reply", "explain" |
keep-llm |
Generation stays with a generative model |
| "Return JSON only", output format rules |
drop |
The answer is already typed |
| Policy text, reference material, examples |
state fields or structured criteria |
Evidence and boundaries, not questions |
"Escalate if a refund is requested and the amount is over 100 and the purchase is
older than 30 days" becomes one noul (refund requested) plus two comparisons in
code. "Priority is p0 when the plan is enterprise and urgency is at least 4" is
entirely code once urgency is a score.
Step 2: build the state
state is a JSON object with a named field for each input the questions need and
nothing else. Placeholders in the prompt become fields. Reference material the
prompt inlined (a refund policy, a rubric the model compares against) becomes a
field. Values the code already knows (plan, amount, dates) go in only if a question
needs them; otherwise they stay in code. Keep untrusted content (tickets, pages,
pasted text) in fields separate from trusted policy. Jev does not see the
conversation, so each request must be self-contained.
Mind the size: the state plus the longest question must stay under 32k tokens, and
everything together under 64k. Filter in code first; unrelated detail lowers
accuracy.
Step 3: write the questions
Rules for every question:
- One judgment per question. Split "angry and asking for a refund" into two.
- The id is not sent to the model. Write the full question in
instructions.
- Point at state fields with backticked paths: "Does
ticket.text ask for a refund?"
- Put boundary cases in
criteria, and keep criteria aligned with instructions.
- Use an object for
instructions when the question needs data or focus:
{"question": "...", "focus": "Classify the primary request only."}.
Per type:
- Choice: list every option, describe each so neighbours separate, and add a
fallback such as
other or not_stated when the list may not cover every input.
Use {"what", "not_for", "examples"} objects for options that get confused.
- Score: 2 to 10 levels, each a concrete situation ("Broken, but a workaround
exists"), never bare numbers or degree words. One dimension per Score. Give a
rare extreme its own level.
- Noul: one proposition, phrased so a high value means yes, no negations. Add
criteria.true and criteria.false when the boundary is subtle. A Noul near 0.5
means unsure, not "medium".
A Choice is relative (which option is best); a Noul is absolute (does this hold).
Use both when "best candidate" and "any acceptable candidate" are different
questions.
Step 4: one request
Put every independent question in one request, including speculative ones whose
answers only matter on some branches (bug severity when the category might not be
bug). Questions run in parallel and cannot see one another's answers. Make a
second request only when its state depends on a first answer: fetching more
evidence, building new state, or choosing the next options.
Step 5: compose in code
Start from the generated file (part 4). Its constants block holds MODEL, the
confidence floor, and the Noul thresholds; its read_choice, read_score, and
read_noul helpers turn each answer into a value plus a band. Keep every new
threshold or weight in that block so a reviewer can read the whole decision in one
place. Then, in decide():
- Gate on confidence with three bands. Route low-confidence answers to a person or
a reasoning model. Raise the acting threshold for costly actions and lower it for
cheap, reversible ones.
- Threshold
noul values in code; a Noul has no separate confidence. Do not reuse
a Choice confidence threshold for a Noul.
- Combine Scores with weights you own; normalize by the top level index. Keep hard
exclusions as separate rules, not small negative weights.
- Read speculative answers only on the branch they belong to.
- Log the response's
model field. Pin a versioned id once thresholds are tuned.
See composition.md for the generated file's anatomy,
the SDK call shapes, the HTTP shape for other languages, and a full routing example.
Step 6: validate, generate, run
python3 <skill-dir>/scripts/prompt2jev.py validate request.json --strict
python3 <skill-dir>/scripts/prompt2jev.py code request.json --lang python --output decide.py
python3 -m py_compile decide.py # after editing decide()
python3 <skill-dir>/scripts/prompt2jev.py run request.json --dry-run
python3 decide.py # live: TYPESAFE_API_KEY in the environment
validate checks the contract and lints against the rules above (fallback option,
numeric levels, compound or negated Nouls, math in a question, unreferenced state
fields, oversized state). The lint is heuristic: fix every warning, or when a
question is right as written (a closed set such as months needs no fallback; "terms
and conditions" is one phrase), suppress that code with --allow CODE and say why
in part 5. code writes the runnable file for the language (python,
python-stdlib, javascript, curl). It checks the contract and prints lint
findings first, so a request that breaks the contract never becomes code; run it
after validate --strict so lint warnings are already gone. With a key present and
the user's approval, run
the file once on the example state and paste its real output, then run a small
labeled sample, tune thresholds on it, and evaluate on held-out cases. Write
fixtures for each branch, including inputs where the model should abstain.
Run
Resolve <skill-dir> to the folder containing this file. The script needs only
Python 3.10+. After uv tool install or pipx install of the repository,
prompt2jev is on PATH and replaces python3 <skill-dir>/scripts/prompt2jev.py.
prompt2jev setup # which keys are present; prints no values
prompt2jev template classify-route > request.json # start from an archetype
prompt2jev validate request.json --strict
prompt2jev code request.json --lang python --output decide.py # or --lang javascript --output decide.mjs, python-stdlib, curl
prompt2jev run request.json --dry-run
prompt2jev run request.json # live call: TYPESAFE_API_KEY, model jev-latest
prompt2jev run request.json --provider openrouter # OPENROUTER_API_KEY, model typesafe/jev-1.13
Keys come from the process environment only; never paste one into chat, a request
file, a generated script, or a repository. A live call costs money and sends the
state to the provider, so confirm before the first one. Without a key, stop at a
validated request plus the generated file and point the user to
https://console.typesafe.ai/keys. Do not simulate Jev output: an output shown to the
user is the output of a command that ran.
Archetypes: classify-route, checklist-guardrail, rubric-composite,
extract-select, verify-claim. Each is a validated request to copy and edit.
References
| Need |
Read |
| Worked conversion, start to finish |
playbook.md |
| Rules and anti-patterns per primitive, known failure modes |
question-design.md |
| Request and response fields, endpoints, models, limits, errors, HTTP from any language |
api.md |
| The generated file's anatomy, confidence bands, weights, fan-out, SDK code in Python and JS |
composition.md |
| Before and after pairs for common prompt shapes |
examples.md |
Red flags
Stop and rework if you notice any of these:
- A question asks for a number, a count, a date comparison, or a sum.
- A Score's levels are
["1", "2", "3"] or "low / medium / high".
- A Noul contains "and", or is phrased so that yes means the bad thing is absent.
- A Choice has no fallback and the inputs are open-ended.
- One request per question, or a second request whose state did not change.
- A summary or reply is being requested from Jev.
- Thresholds scattered through the code, or a Noul threshold applied to confidence.
- The package is missing a part, or the request was shown before it was validated.
- The user asked for a script and received a fragment, pseudo-code, or JSON only.
- A file that was never compiled, or an "output" that no command produced.
1---2name: prompt2jev3description: Use when a user asks to convert an LLM prompt, system prompt, prompt template, or prompt-and-parse step (classifier, router, judge, grader, extractor, guardrail) into TypeSafe Jev decisions, points at the code that runs one (an LLM call that returns labels, scores, booleans, or JSON fields, with its parser and the branches on its output) and wants it replaced with typed questions, describes a decision requirement in natural language with no prompt yet, or asks for a Python script, TypeScript module, or other program that calls Jev. Also use when a request mentions Jev, TypeSafe, System One, "turn this prompt into questions", or "make this decision structured".4license: MIT5---67# Turn natural language, an LLM prompt, or the code that runs one into a Jev decision89Jev is TypeSafe's System One model. It evaluates typed questions against a `state`10and returns probabilities, not prose. Code owns control flow. The input is one of11three things: a requirement in natural language with no prompt yet, an LLM prompt12(system prompt, template, or prompt-and-parse step), or the code that runs one today,13with its parser and the branches on its output. Converting any of them means moving14each judgment into its own question, moving every rule code can compute into code,15and leaving generation with a generative model. The bundled references distill the16TypeSafe docs; when live docs are reachable, https://docs.typesafe.ai/llms.txt is the17source of truth for the API contract.1819## What you deliver2021Produce the conversion package. It has five parts, in this order, every time:22231. **Decision contract** (five lines): the decision the code takes, the evidence24 unit (what one request judges), the consumer of each answer, the unknown path25 (what happens when nothing fits or evidence is missing), and the success check.262. **Split table**: every instruction in the original prompt mapped to one of27 `choice`, `noul`, `score`, `code`, `keep-llm`, or `drop`, with the question id or28 the code rule it became. Nothing in the prompt goes unaccounted for.293. **Request JSON** in the native shape `{"model", "state", "questions"}`, saved to30 a file and validated with `prompt2jev validate --strict` before it is shown.314. **Composition code, as a file that runs.** Generate it from the validated request32 with `prompt2jev code request.json --lang <lang> --output <file>`, then replace the33 `return` in `decide()` with the branching from part 1. The file has imports, the34 constants block (every threshold, weight, and hard rule), the questions, one call35 that sends the request, `decide()`, and an entry point; it compiles, and it runs36 on the example state. Language rule: Python gets the official `typesafe_sdk`37 (`--lang python`); JavaScript and TypeScript projects get `@typesafe-ai/sdk`38 (`--lang javascript`, an ES module saved as `.mjs`; the SDK ships its own39 types); any other language, or a Python environment that cannot40 install packages, sends the HTTP request directly, with `--lang python-stdlib`41 or `--lang curl` as the reference (see [api.md](references/api.md)). When the42 user asks only for the request, part 3 is the deliverable and this part is one43 line on how to send it.445. **Assumptions and fixtures**: what was assumed, what the user should confirm,45 and the test cases to run before automating (positive, negative, ambiguous,46 missing evidence, adversarial).4748When the user asks for a script, a module, a program, or "code I can run", the49answer is the path of the file from part 4 plus the output of running it: the real50output when a key is present and the user approved a paid call, otherwise the51`--dry-run` request and the exact command to run once a key exists.5253## Step 0: read the prompt, dig before asking5455Collect before designing: the prompt text, every placeholder and where its value56comes from, the parser or schema that reads the output, the code that branches on57each output field, existing enums or label lists, and any tests or labeled58examples. In a codebase, grep for the prompt string, the output field names, and59the API call that sends it.6061Ask the user only when a gap changes the shape of a question: the label set, what62the code does with an answer, or the stakes of a wrong answer. Ask one question at63a time. A gap that does not change the shape becomes a stated assumption in part 5.64Never stop with nothing delivered; a validated draft with assumptions beats a65question.6667## Step 1: split the prompt into judgments6869Read the prompt sentence by sentence and classify each instruction:7071| Instruction in the prompt | Becomes | Why |72|---|---|---|73| "X must be one of: a, b, c" | `choice` with those options plus a fallback | One option from a known set |74| "Set flag if the message ..." | `noul` | One yes/no proposition |75| "Rate ... from 1 to 5", "how severe" | `score` with described levels | A position on a spectrum |76| "If A and B then ..." | one question per condition, combined in `code` | Compound conditions hide judgments |77| Arithmetic, comparisons, dates, counting, thresholds | `code` | Jev is not a calculator |78| "Extract the invoice number" | candidates found in `code`, then `choice` over them | Select, never generate |79| "Summarize", "write a reply", "explain" | `keep-llm` | Generation stays with a generative model |80| "Return JSON only", output format rules | `drop` | The answer is already typed |81| Policy text, reference material, examples | `state` fields or structured `criteria` | Evidence and boundaries, not questions |8283"Escalate if a refund is requested and the amount is over 100 and the purchase is84older than 30 days" becomes one `noul` (refund requested) plus two comparisons in85code. "Priority is p0 when the plan is enterprise and urgency is at least 4" is86entirely code once urgency is a `score`.8788## Step 2: build the state8990`state` is a JSON object with a named field for each input the questions need and91nothing else. Placeholders in the prompt become fields. Reference material the92prompt inlined (a refund policy, a rubric the model compares against) becomes a93field. Values the code already knows (plan, amount, dates) go in only if a question94needs them; otherwise they stay in code. Keep untrusted content (tickets, pages,95pasted text) in fields separate from trusted policy. Jev does not see the96conversation, so each request must be self-contained.9798Mind the size: the state plus the longest question must stay under 32k tokens, and99everything together under 64k. Filter in code first; unrelated detail lowers100accuracy.101102## Step 3: write the questions103104Rules for every question:105106- One judgment per question. Split "angry and asking for a refund" into two.107- The id is not sent to the model. Write the full question in `instructions`.108- Point at state fields with backticked paths: "Does `ticket.text` ask for a refund?"109- Put boundary cases in `criteria`, and keep `criteria` aligned with `instructions`.110- Use an object for `instructions` when the question needs data or focus:111 `{"question": "...", "focus": "Classify the primary request only."}`.112113Per type:114115- **Choice**: list every option, describe each so neighbours separate, and add a116 fallback such as `other` or `not_stated` when the list may not cover every input.117 Use `{"what", "not_for", "examples"}` objects for options that get confused.118- **Score**: 2 to 10 levels, each a concrete situation ("Broken, but a workaround119 exists"), never bare numbers or degree words. One dimension per Score. Give a120 rare extreme its own level.121- **Noul**: one proposition, phrased so a high value means yes, no negations. Add122 `criteria.true` and `criteria.false` when the boundary is subtle. A Noul near 0.5123 means unsure, not "medium".124125A Choice is relative (which option is best); a Noul is absolute (does this hold).126Use both when "best candidate" and "any acceptable candidate" are different127questions.128129## Step 4: one request130131Put every independent question in one request, including speculative ones whose132answers only matter on some branches (bug severity when the category might not be133bug). Questions run in parallel and cannot see one another's answers. Make a134second request only when its state depends on a first answer: fetching more135evidence, building new state, or choosing the next options.136137## Step 5: compose in code138139Start from the generated file (part 4). Its constants block holds `MODEL`, the140confidence floor, and the Noul thresholds; its `read_choice`, `read_score`, and141`read_noul` helpers turn each answer into a value plus a band. Keep every new142threshold or weight in that block so a reviewer can read the whole decision in one143place. Then, in `decide()`:144145- Gate on confidence with three bands. Route low-confidence answers to a person or146 a reasoning model. Raise the acting threshold for costly actions and lower it for147 cheap, reversible ones.148- Threshold `noul` values in code; a Noul has no separate confidence. Do not reuse149 a Choice confidence threshold for a Noul.150- Combine Scores with weights you own; normalize by the top level index. Keep hard151 exclusions as separate rules, not small negative weights.152- Read speculative answers only on the branch they belong to.153- Log the response's `model` field. Pin a versioned id once thresholds are tuned.154155See [composition.md](references/composition.md) for the generated file's anatomy,156the SDK call shapes, the HTTP shape for other languages, and a full routing example.157158## Step 6: validate, generate, run159160```bash161python3 <skill-dir>/scripts/prompt2jev.py validate request.json --strict162python3 <skill-dir>/scripts/prompt2jev.py code request.json --lang python --output decide.py163python3 -m py_compile decide.py # after editing decide()164python3 <skill-dir>/scripts/prompt2jev.py run request.json --dry-run165python3 decide.py # live: TYPESAFE_API_KEY in the environment166```167168`validate` checks the contract and lints against the rules above (fallback option,169numeric levels, compound or negated Nouls, math in a question, unreferenced state170fields, oversized state). The lint is heuristic: fix every warning, or when a171question is right as written (a closed set such as months needs no fallback; "terms172and conditions" is one phrase), suppress that code with `--allow CODE` and say why173in part 5. `code` writes the runnable file for the language (`python`,174`python-stdlib`, `javascript`, `curl`). It checks the contract and prints lint175findings first, so a request that breaks the contract never becomes code; run it176after `validate --strict` so lint warnings are already gone. With a key present and177the user's approval, run178the file once on the example state and paste its real output, then run a small179labeled sample, tune thresholds on it, and evaluate on held-out cases. Write180fixtures for each branch, including inputs where the model should abstain.181182## Run183184Resolve `<skill-dir>` to the folder containing this file. The script needs only185Python 3.10+. After `uv tool install` or `pipx install` of the repository,186`prompt2jev` is on PATH and replaces `python3 <skill-dir>/scripts/prompt2jev.py`.187188```bash189prompt2jev setup # which keys are present; prints no values190prompt2jev template classify-route > request.json # start from an archetype191prompt2jev validate request.json --strict192prompt2jev code request.json --lang python --output decide.py # or --lang javascript --output decide.mjs, python-stdlib, curl193prompt2jev run request.json --dry-run194prompt2jev run request.json # live call: TYPESAFE_API_KEY, model jev-latest195prompt2jev run request.json --provider openrouter # OPENROUTER_API_KEY, model typesafe/jev-1.13196```197198Keys come from the process environment only; never paste one into chat, a request199file, a generated script, or a repository. A live call costs money and sends the200state to the provider, so confirm before the first one. Without a key, stop at a201validated request plus the generated file and point the user to202https://console.typesafe.ai/keys. Do not simulate Jev output: an output shown to the203user is the output of a command that ran.204205Archetypes: `classify-route`, `checklist-guardrail`, `rubric-composite`,206`extract-select`, `verify-claim`. Each is a validated request to copy and edit.207208## References209210| Need | Read |211|---|---|212| Worked conversion, start to finish | [playbook.md](references/playbook.md) |213| Rules and anti-patterns per primitive, known failure modes | [question-design.md](references/question-design.md) |214| Request and response fields, endpoints, models, limits, errors, HTTP from any language | [api.md](references/api.md) |215| The generated file's anatomy, confidence bands, weights, fan-out, SDK code in Python and JS | [composition.md](references/composition.md) |216| Before and after pairs for common prompt shapes | [examples.md](references/examples.md) |217218## Red flags219220Stop and rework if you notice any of these:221222- A question asks for a number, a count, a date comparison, or a sum.223- A Score's levels are `["1", "2", "3"]` or "low / medium / high".224- A Noul contains "and", or is phrased so that yes means the bad thing is absent.225- A Choice has no fallback and the inputs are open-ended.226- One request per question, or a second request whose state did not change.227- A summary or reply is being requested from Jev.228- Thresholds scattered through the code, or a Noul threshold applied to confidence.229- The package is missing a part, or the request was shown before it was validated.230- The user asked for a script and received a fragment, pseudo-code, or JSON only.231- A file that was never compiled, or an "output" that no command produced.