> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentium.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Jev

> Give a chat agent a judgment tool. Claude talks. Jev picks the label, yes/no, or score.

# Jev

## What this toolkit does

Your agent is still Claude or GPT. It talks to the user. It writes the reply.

When it needs a **judgment** — "is this urgent?", "which team?", "how bad is it?" — it calls a Jev tool. Jev is a decision model. It does not write sentences. It returns a label, a yes/no probability, or a score.

Think of it like a calculator, but for decisions:

```
User: "I was charged twice. Fix this ASAP."

Claude: I should ask Jev before I decide.
        → jev_choose({ question: "Which team?", options: ["billing", "tech"], state: "..." })

Jev:    { "choice": "billing", "confidence": 0.92 }

Claude: "This looks like a billing issue. I'll route it to the billing team."
```

You do **not** have to write the questions in your code first. The chat model can invent them at call time (`jev_choose`, `jev_noul`, `jev_score`, `jev_ask`). Or you lock the questions in a **pack** so the model only picks a pack name (`jev_evaluate`).

<Note>
  Need JSON answers and no chat at all? Skip this toolkit. Use [`jev()` as the model](/models/jev) instead: `agent.run(ticket, { questions })`.
</Note>

***

## Setup

```bash theme={null}
npm install @typesafe-ai/sdk
export TYPESAFE_API_KEY="..."
```

***

## The smallest example

Hand the tools to any chat agent. Tell it when to call them.

```typescript theme={null}
import { Agent, anthropic } from "@agentium/core";
import { JevToolkit } from "@agentium/core/toolkits";

const jevTk = new JevToolkit();

const agent = new Agent({
  name: "support",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `You help support agents.
When you need a yes/no, a label, or a rating, call a Jev tool.
Then explain the answer in plain English.`,
  tools: [...jevTk.getTools()],
});

const result = await agent.run("I was charged twice. Fix this ASAP.");
console.log(result.text);
```

That one `new JevToolkit()` adds four tools: `jev_choose`, `jev_noul`, `jev_score`, `jev_ask`.

***

## The four tools (no packs)

Every tool takes **state** — the text (or JSON string) Jev should judge. Usually that is the user's message.

### 1. `jev_choose` — pick one label

"Which bucket does this belong in?"

```typescript theme={null}
const agent = new Agent({
  name: "router",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Read the ticket. Call jev_choose with options billing, technical, other.
Then say which team should own it.`,
  tools: [...new JevToolkit().getTools()],
});

await agent.run("The API returns 500 on /v1/shipments. No one can create orders.");
```

What the model sends:

```json theme={null}
{
  "question": "Which team should own this ticket?",
  "options": ["billing", "technical", "other"],
  "state": "The API returns 500 on /v1/shipments. No one can create orders."
}
```

What Jev sends back (the model reads this JSON):

```json theme={null}
{
  "choice": {
    "type": "choice",
    "choice": "technical",
    "probabilities": { "billing": 0.04, "technical": 0.93, "other": 0.03 },
    "confidence": 0.88
  }
}
```

`choice` is the winning label. `confidence` is how sure Jev is (0–1). Tell the chat model to treat low confidence as "ask a human".

***

### 2. `jev_noul` — yes or no, as a number

**Noul** = how true is this? `0` is no. `1` is yes. `0.5` is a coin flip. Not a boolean.

```typescript theme={null}
const agent = new Agent({
  name: "triage",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Call jev_noul with statement "Does this need a human in under 1 hour?".
If noul is 0.8 or higher, say "page on-call". Otherwise say "put it in the queue".`,
  tools: [...new JevToolkit().getTools()],
});

await agent.run("Checkout has been down for 20 minutes. We are losing orders.");
```

What the model sends:

```json theme={null}
{
  "statement": "Does this need a human in under 1 hour?",
  "state": "Checkout has been down for 20 minutes. We are losing orders."
}
```

What Jev sends back:

```json theme={null}
{
  "noul": {
    "type": "noul",
    "noul": 0.94
  }
}
```

***

### 3. `jev_score` — how much, on a ladder

Lowest rung is index `0`. `score: 3` means "the fourth item", not "3 out of 5".

```typescript theme={null}
const agent = new Agent({
  name: "impact",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Call jev_score with levels none, low, medium, high, critical.
Then say the label (index 0 = none), not the number.`,
  tools: [...new JevToolkit().getTools()],
});

await agent.run("One user cannot export a CSV. Everyone else is fine.");
```

What the model sends:

```json theme={null}
{
  "question": "How severe is the customer impact?",
  "levels": ["none", "low", "medium", "high", "critical"],
  "state": "One user cannot export a CSV. Everyone else is fine."
}
```

What Jev sends back:

```json theme={null}
{
  "score": {
    "type": "score",
    "score": 1,
    "confidence": 0.81
  }
}
```

`1` = `"low"`. Put that in the instructions so the chat model does not say "severity 1/5".

***

### 4. `jev_ask` — several questions in one go

One TypeSafe call. Same state. Cheaper and faster than three separate tools.

```typescript theme={null}
const agent = new Agent({
  name: "inbox",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `For every ticket, call jev_ask once with three questions:
- team: choice billing / technical / other
- urgent: noul "Needs a reply in under 1 hour?"
- severity: score none, low, medium, high, critical
Then write a 3-line summary.`,
  tools: [...new JevToolkit().getTools()],
});

await agent.run("I was charged twice for last month. Please fix this ASAP.");
```

What the model sends as `questions` (a JSON object, usually as a string):

```json theme={null}
{
  "team": {
    "type": "choice",
    "question": "Which team?",
    "options": ["billing", "technical", "other"]
  },
  "urgent": {
    "type": "noul",
    "question": "Needs a reply in under 1 hour?"
  },
  "severity": {
    "type": "score",
    "question": "Customer impact?",
    "levels": ["none", "low", "medium", "high", "critical"]
  }
}
```

What Jev sends back — your keys, not generic names:

```json theme={null}
{
  "team": { "type": "choice", "choice": "billing", "confidence": 0.91 },
  "urgent": { "type": "noul", "noul": 0.86 },
  "severity": { "type": "score", "score": 2, "confidence": 0.74 }
}
```

You can add a hint on each choice label:

```json theme={null}
{
  "team": {
    "type": "choice",
    "question": "Which team?",
    "options": {
      "billing": "Charges, invoices, refunds",
      "technical": "Bugs, outages, API errors",
      "other": null
    }
  }
}
```

***

## Packs — you write the questions, the model only picks a name

If you do not want the chat model inventing questions, put them in **packs**. That adds a fifth tool: `jev_evaluate`.

The model can only pass `pack: "ticket"` and the text. It cannot change the questions inside.

```typescript theme={null}
import { Agent, anthropic, choice, noul, score } from "@agentium/core";
import { JevToolkit } from "@agentium/core/toolkits";

const jevTk = new JevToolkit({
  packs: {
    ticket: {
      category: choice("What is this ticket about?", {
        billing: "Charges, invoices, refunds",
        technical: "Bugs, outages, API errors",
        other: null,
      }),
      urgent: noul("Does this need a response in under 1 hour?"),
      severity: score("How severe is customer impact?", [
        "none",
        "low",
        "medium",
        "high",
        "critical",
      ]),
    },
  },
});

const agent = new Agent({
  name: "support",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `When a ticket arrives, call jev_evaluate with pack "ticket" and the ticket text.
Then tell the human:
- the category
- whether to jump on it (urgent.noul)
- the severity label (0 = none, 4 = critical)`,
  tools: [...jevTk.getTools()],
});

await agent.run("I was charged twice for last month. Please fix this ASAP.");
```

Two packs on one toolkit:

```typescript theme={null}
const jevTk = new JevToolkit({
  packs: {
    ticket: {
      category: choice("What is this about?", {
        billing: null,
        technical: null,
        other: null,
      }),
      urgent: noul("Needs a reply in under 1 hour?"),
    },
    moderation: {
      safe: noul("Is this message safe to send to a customer?"),
      kind: choice("If unsafe, what kind?", {
        spam: null,
        abuse: null,
        pii: null,
        none: "It is safe",
      }),
    },
  },
});
```

```typescript theme={null}
const agent = new Agent({
  name: "care",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Incoming tickets → jev_evaluate pack "ticket".
Outgoing drafts → jev_evaluate pack "moderation".
If moderation.safe.noul is under 0.8, do not send. Rewrite.`,
  tools: [...jevTk.getTools()],
});

await agent.run("Draft a reply to: you people are idiots, refund me or else");
```

You can write a pack as JSON instead of `choice` / `noul` / `score`. Same result:

```typescript theme={null}
const jevTk = new JevToolkit({
  packs: {
    ticket: {
      urgent: { type: "noul", question: "Needs a reply in under an hour?" },
      severity: {
        type: "score",
        question: "Customer impact?",
        levels: ["none", "low", "medium", "high", "critical"],
      },
    },
  },
});
```

***

## More copy-paste examples

### Support inbox

```typescript theme={null}
import { Agent, anthropic } from "@agentium/core";
import { JevToolkit } from "@agentium/core/toolkits";

const agent = new Agent({
  name: "inbox",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `You sort support mail.
Call jev_ask with team (billing/technical/other), urgent (noul), severity (none→critical).
Reply with three bullets. If urgent.noul >= 0.8, start with "PAGE ON-CALL".`,
  tools: [...new JevToolkit().getTools()],
});

for (const ticket of [
  "I was charged twice. Fix this ASAP.",
  "Where do I change the email on my account?",
  "The API returns 500 since 9am. No one can create orders.",
]) {
  const result = await agent.run(ticket);
  console.log("\n---", ticket, "\n", result.text);
}
```

### Refund gate (yes/no only)

```typescript theme={null}
const agent = new Agent({
  name: "refunds",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Call jev_noul: "Does this customer describe a clear double charge we should refund?"
If noul >= 0.85 say "approve refund".
If noul is between 0.4 and 0.85 say "ask for the invoice id".
Below 0.4 say "not a refund".`,
  tools: [...new JevToolkit().getTools()],
});

await agent.run("You billed me twice for March on order #4412. Same amount both times.");
```

### Route + page with a pack

```typescript theme={null}
import { choice, noul } from "@agentium/core";

const jevTk = new JevToolkit({
  packs: {
    route: {
      team: choice("Who owns this?", {
        billing: "Money, invoices, refunds",
        oncall: "Outage or API down",
        success: "How-to, account, product",
      }),
      page: noul("Should we page a human right now?"),
    },
  },
});

const agent = new Agent({
  name: "router",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Always call jev_evaluate pack "route".
Then say: route to {team}. page={yes/no}.`,
  tools: [...jevTk.getTools()],
});

await agent.run("Checkout 500s for every customer since 9am.");
```

### Judge a draft before send

```typescript theme={null}
import { noul, score } from "@agentium/core";

const jevTk = new JevToolkit({
  packs: {
    draft: {
      safe: noul("Is this safe and professional to send to a customer?"),
      tone: score("How warm is the tone?", ["cold", "neutral", "warm", "too casual"]),
    },
  },
});

const agent = new Agent({
  name: "writer",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Write a short reply, then jev_evaluate pack "draft" on YOUR draft.
If safe.noul < 0.8, rewrite once and evaluate again.
If tone.score is 0 (cold), soften it.`,
  tools: [...jevTk.getTools()],
});

await agent.run("Customer: this product is garbage and I want my money back.");
```

### JSON state (richer than a raw string)

Any tool's `state` can be a JSON string. Jev parses `{...}` / `[...]`.

```typescript theme={null}
const agent = new Agent({
  name: "triage",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `Call jev_ask. Pass state as JSON with subject, body, plan, similarTicketsToday.`,
  tools: [...new JevToolkit().getTools()],
});

await agent.run(`Triage this ticket JSON:
${JSON.stringify({
  subject: "API 500s since 9am",
  body: "No one can create orders.",
  plan: "enterprise",
  similarTicketsToday: 40,
})}`);
```

### Chat agent + Jev + another toolkit

Jev is just another tool. Slack / HTTP / your own tools still work.

```typescript theme={null}
import { SlackToolkit, JevToolkit } from "@agentium/core/toolkits";
import { choice, noul } from "@agentium/core";

const jevTk = new JevToolkit({
  packs: {
    ticket: {
      team: choice("Which Slack channel?", {
        billing: "#billing",
        eng: "#eng-oncall",
        general: "#support",
      }),
      page: noul("Should we @here?"),
    },
  },
});

const slack = new SlackToolkit({ botToken: process.env.SLACK_BOT_TOKEN });

const agent = new Agent({
  name: "desk",
  model: anthropic("claude-sonnet-4-6"),
  instructions: `1. jev_evaluate pack "ticket" on the user text.
2. Post a one-line summary to the channel Jev picked.
3. If page.noul >= 0.8, mention @here.`,
  tools: [...jevTk.getTools(), ...slack.getTools()],
});

await agent.run("Payments webhook failing. Customers cannot check out.");
```

***

## Config (all optional)

```typescript theme={null}
new JevToolkit({
  apiKey: process.env.TYPESAFE_API_KEY, // or set TYPESAFE_API_KEY
  baseURL: process.env.TYPESAFE_BASE_URL, // default https://api.typesafe.ai
  model: "jev-latest", // or pin "jev-1.13.0"
  packs: { /* named question sets → adds jev_evaluate */ },
});
```

<ParamField body="apiKey" type="string">
  TypeSafe key. Falls back to `TYPESAFE_API_KEY`.
</ParamField>

<ParamField body="baseURL" type="string">
  API root. Falls back to `TYPESAFE_BASE_URL`.
</ParamField>

<ParamField body="model" type="string" default="jev-latest">
  `jev-latest` (today `jev-1.13.0`) or a pin. Same ids as [`jev()`](/models/jev).
</ParamField>

<ParamField body="packs" type="object">
  Named question sets. Adds `jev_evaluate`. Write them with `choice` / `noul` / `score`, or as `{ type, question, options | levels }`.
</ParamField>

***

## How to read an answer

| Tool                       | Field                | Meaning                                 |
| -------------------------- | -------------------- | --------------------------------------- |
| `jev_choose`               | `.choice.choice`     | The label Jev picked                    |
| `jev_choose`               | `.choice.confidence` | 0–1, how sure                           |
| `jev_noul`                 | `.noul.noul`         | 0 = no, 1 = yes                         |
| `jev_score`                | `.score.score`       | Index into your `levels` (0 = first)    |
| `jev_ask` / `jev_evaluate` | `.[yourKey]`         | Same three shapes, under **your** names |

`state` is text, or JSON if it starts with `{` or `[`. Bad JSON stays text.

Unknown pack name: `Unknown pack "x". Available: ticket, moderation` — it does not throw.

***

## Toolkit vs `jev()` as the model

|                      | This toolkit             | [`model: jev()`](/models/jev)     |
| -------------------- | ------------------------ | --------------------------------- |
| Who talks            | Claude / GPT             | Nobody. You get JSON.             |
| Who writes questions | The model, or your packs | Your code on `run({ questions })` |
| Other tools          | Yes (Slack, HTTP, …)     | Only closed-set names, empty args |
| `Agent.deep()`       | Fine                     | Do not                            |

***

## See also

* [Jev examples](/examples/jev)
* [Jev as `Agent.model`](/models/jev) — same three helpers, no chat
* [Jev as an eval judge](/eval/jev) — score a chat agent in `@agentium/eval`
* [Docs index](/)
* [TypeSafe introduction](https://docs.typesafe.ai/introduction)
* [TypeSafe JS SDK](https://docs.typesafe.ai/sdk/javascript)
