Jev (TypeSafe)
In plain terms
Jev is a decision model. You send state (the facts) and typed questions. You get answers your code canif / switch on.
It does not write emails, stream tokens, or fill free-form tool arguments. Do not pair it with Agent.deep().
Use it three ways:
The smallest working example
questions object on every run() if the decision set changes. Same agent, different questions.
Setup
- SDK
- Environment
@typesafe-ai/sdk is an optional peer of @agentium/core. Install it before the first generate() / run(). Without it you get:The three question helpers
Every question is a name you pick (category, urgent, …) mapped to one of these three helpers. Names become keys on the answer object.
@typesafe-ai/sdk when it is installed, and otherwise build the same wire-format objects ({ type, instructions, criteria }). You can import them even if the SDK is not installed yet.
choice(instructions, criteria) — pick exactly one label
Use when the answer is a closed list: team, category, route, label.
string
required
The question Jev answers. Write it as something a human would decide. Be specific:
"What is this support ticket about?" is better than "category".Record<string, string | null>
required
The allowed labels. Keys are what you get back in
answer.choice. Values are extra descriptions, or null if the key is already clear."billing": "Charges, invoices, refunds"— label plus hint"other": null— label only, no extra text
When to use choice vs noul vs score
When to use choice vs noul vs score
- choice — one of a named set (
billing/technical/other). - noul — yes or no, as a probability (
0.91= probably yes). - score — how much, on an ordered rubric (
none→critical). Index0is the lowest level.
noul(instructions?, criteria?) — how true is this?
Noul = probability that the statement is true. 0 is no. 1 is yes. 0.5 is a coin flip.
string
The yes/no statement. Phrase it so true means the thing you care about.Good:
"Does this need a human response in under 1 hour?"Bad: "urgency" — Jev does not know what that word means here.object
Optional hints for what yes and no mean.
When questions come from
structuredOutput, a noul is flattened to a boolean: noul >= 0.5 → true. On a normal run({ questions }) you get the raw 0–1 number so you can pick your own threshold.score(instructions, levels) — rate on an ordered rubric
Use when the answer is how much, not which label. Index 0 is the lowest level.
string
required
What to rate. Same rule as
choice: write a real question.string[]
required
Ordered rubric. First item is 0. Last item is highest. Need at least two levels.Short labels work (
"none", "low", "high"). Longer text on each level is better — Jev uses that text to decide.When questions come from z.number().min(1).max(5), Agentium builds levels "1" … "5" and later adds min back so result.structured.severity is 1–5, not 0–4.jev(modelId?, config?)
string
default:"jev-latest"
TypeSafe model id or alias.
JevConfig
Optional. See every field below.
JevConfig
string
TypeSafe API key. If omitted, uses
TYPESAFE_API_KEY.You can also pass a per-run key: agent.run(input, { apiKey, questions }). That only swaps the key for that call.string
API root. Falls back to
TYPESAFE_BASE_URL, then https://api.typesafe.ai.Use this for a proxy or a self-hosted TypeSafe endpoint.Record<string, Question>
Default questions if a run does not pass its own. Build them with
choice, noul, and score.agent.run(input, { questions }) wins and replaces these entirely (no merge).temperature, topP, stop, and reasoning.effort are ignored. Jev is not a sampler.
agent.run(input, { questions })
This is the normal way to ask Jev.
string | ContentPart[]
The state — the facts Jev judges. Usually a string (a ticket, a message, a JSON blob).
- Plain text → sent as that string
- Text that parses as JSON (
{...}or[...]) → sent as the parsed object - Earlier turns in the same session → packed as
{ input, history }so Jev still sees the thread
Record<string, Question>
Named
choice / noul / score questions for this run only. Wins over jev(model, { questions }). Ignored by chat models (openai, anthropic, …).Keys are yours. Use names you want to read later (category, urgent).agent.stream(input, { questions }) takes the same options. Jev still makes one systemOne call, then yields the JSON as a single text chunk and finish. There is no token stream.
Different questions per call
jev().
Where questions come from
Jev will not invent a prompt.generate() picks the first source that has at least one question:
No questions, no mappable schema, no tools → throws:
{} does not count. Priority falls through to the next source.
What run() gives back
result.text is a JSON string of TypeSafe answers (unless you used structuredOutput — see below).
Typical raw answers:
State — what Jev actually sees
The last user message becomes TypeSafestate.
JSON parse failures stay as text. System / assistant / earlier user turns land in
history.
You can send structured state yourself:
structuredOutput as questions
If you do not pass questions, a Zod object on the agent becomes questions:
.describe("…") becomes the question text. Without it, the field name is the question.
max >= min, and 2–32 levels. z.number() without .min() / .max() throws.
These cannot become questions:
- free-form
z.string() - open
z.object({ ... }) - dates
- unbounded numbers
- arrays
run({ questions }) still wins over structuredOutput. If you pass both, Jev asks the run questions and result.text is the raw TypeSafe object (not flattened). Skip questions if you want result.structured to parse.Tools
Jev cannot fillsend_email({ body: "..." }). It can only pick a name.
Auto __tool__
If there are no run / constructor / schema questions, but the agent has tools, Agentium asks one choice:
none. Descriptions come from each tool’s description.
If the pick is a registered tool (not none), finishReason is "tool_calls" and the existing loop runs that tool with {} arguments.
A named choice that matches a tool
Constructor / run questions are not replaced by auto__tool__. But if a choice value equals a registered tool name and is not none, that tool still runs.
Models and price
Input is billed at $0.042 / million tokens. Output is free. TypeSafe evaluates every question in a request in parallel against the same state — adding questions barely changes latency. See TypeSafe models.
Errors you will actually see
More examples
Ticket triage (run a few tickets)
Branch in your code, not in a prompt
Moderation defaults on the constructor
JSON state for a richer ticket
What not to do
Agent.deep({ model: jev() })— the deep harness (skills, subagents, filesystem) expects a chat model- Open-ended chat, reflection-as-prose, or free-form tool args
- Using Jev as a drop-in swap for
anthropic()withoutquestionsorstructuredOutput - Treating
scoreas “points out of N” — it is a 0-based index - Expecting
run({ questions })to merge with constructor questions — it replaces them - Passing
jev()tollmJudgeorAgentJudgeEval— those need a chat model. Usecustom()+ Jev
See also
- Jev examples
- Jev as an eval judge — score a chat agent with
noul/score/choice - JevToolkit — same three primitives as tools on a chat agent
- RunOpts.questions
- Docs index
- TypeSafe introduction
- TypeSafe JS SDK
- TypeSafe models