Lesson 03 · TypeSafe Jev — System One models

Composing questions & real patterns

One ticket, one call — but now three judgements at once: which team (Choice), how urgent (Score), is the customer angry (Noul). Then we combine them in code, name the three patterns you'll reuse forever, and finish by pointing Jev at your own product.

~12 min Skill + application Builds on: Lesson 02
The leap from Lesson 02 questions is a map. Put three differently-typed questions in it and Jev answers all three in parallel against the same state — one round trip, barely more latency. Then your code fuses them into a decision.

1. Three questions, one call

Same ticket as before. We add a Score (urgency 1–5) and a Noul (is the customer angry?) beside the Choice (department):

{
  "model": "jev-latest",
  "state": "Help! My payouts have been failing for 3 days. This is unacceptable.",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this ticket?",
      "criteria": {
        "billing":   "Payments, invoicing, refunds",
        "technical": "Bugs, outages, integrations",
        "sales":     "Pricing, upgrades, new accounts"
      }
    },
    "priority": {
      "type": "score",
      "instructions": "How urgent is this ticket?",
      "criteria": {
        "1": "Trivial, no time pressure",
        "3": "Normal, handle this week",
        "5": "Critical, revenue or outage now"
      }
    },
    "is_angry": {
      "type": "noul",
      "instructions": "The customer is angry or frustrated."
    }
  }
}

The response mirrors the request — one answer per key, each with its own shape:

{
  "model": "jev-1.13.0",
  "answers": {
    "department": { "type": "choice", "choice": "technical",
                    "probabilities": { "billing": 0.08, "technical": 0.85, "sales": 0.07 },
                    "confidence": 0.82 },
    "priority":   { "type": "score", "score": "5",
                    "probabilities": { "1": 0.02, "3": 0.19, "5": 0.79 },
                    "confidence": 0.79 },
    "is_angry":   { "type": "noul", "probability": 0.88 }
  },
  "usage": { "input_tokens": 341, "output_tokens": 63 }
}
Note the shapes differ by type Choice → choice + probabilities + confidence. Score → score (a level key) + probabilities + confidence. Noul → a bare probability, no confidence — the number is the signal.

2. Type the union, then fuse in code

Extend Lesson 02's types with Score and Noul, as a discriminated union on type:

interface ChoiceAnswer { type: "choice"; choice: string;
  probabilities: Record<string, number>; confidence: number; }
interface ScoreAnswer  { type: "score";  score: string;
  probabilities: Record<string, number>; confidence: number; }
interface NoulAnswer   { type: "noul";   probability: number; }

type JevAnswer = ChoiceAnswer | ScoreAnswer | NoulAnswer;
interface JevResponse { model: string;
  answers: Record<string, JevAnswer>; usage: { input_tokens: number; output_tokens: number }; }

Now the fusion — this is the part the model never does for you. Small judgements in; one business decision out:

const CONFIDENCE = 0.75;
const ANGRY = 0.7;

function decide(res: JevResponse) {
  // In strict code, narrow on a.type; here we know the keys.
  const dept    = res.answers.department as ChoiceAnswer;
  const urgency = res.answers.priority   as ScoreAnswer;
  const angry   = res.answers.is_angry   as NoulAnswer;

  const score = Number(urgency.score);          // "5" -> 5
  const fastLane = score >= 4 || angry.probability >= ANGRY;

  // Low confidence on the routing? A human triages.
  if (dept.confidence < CONFIDENCE) {
    return { action: "escalate", fastLane, score, top: dept.choice };
  }
  return { action: "auto", team: dept.choice, fastLane, score };
}

// -> { action: "auto", team: "technical", fastLane: true, score: 5 }
The rule, again Never ask one giant "what should I do with this ticket?" question. Ask several atomic ones and write the if yourself. That's what keeps the system testable, tunable, and reliable — each threshold is a knob you own.

3. The three patterns you'll reuse

Almost every Jev integration is one of these — often several stacked in a single call:

PatternPrimitiveWhat it does
RoutingChoiceSend an item down one of N known paths: team, queue, workflow branch, which tool an agent calls next.
Gating / moderationNoulA yes/no checkpoint before an action: is this spam? safe to auto-send? does it need review? Gate on the 0–1 value.
PrioritisationScoreRank items by urgency/quality/risk so code can sort a queue or trigger a fast lane.

4. Three Jev-shaped problems in your product

You wanted to know where this fits your real work. Take your instructor SaaS that messages parents over WhatsApp. Run each idea through the four-question test from Lesson 01 — all three score high:

① Route incoming parent replies — Choice
A parent WhatsApps back. Classify intent: billing_query · reschedule · complaint · general. High confidence → fire the matching template automatically; low → drop into the instructor's inbox. Code consumes it, fixed set, gate on confidence, high volume — 4/4.
② Safe-to-auto-send gate — Noul
Before auto-sending a billing reminder or an AI-drafted note, ask: "This message is appropriate and non-sensitive to send to a parent." Below, say, 0.9 → hold for human review. A cheap safety net on every outbound message. Binary judgement feeding an if — textbook Noul.
③ Prioritise the instructor's follow-up queue — Score
Score each unread parent message or overdue account on urgency 1–5 (angry parent + failed payment = 5). Sort the instructor's dashboard by it. Ordered rating that code sorts on — Score's whole job.
And where it's NOT the tool Writing the reply to the parent, drafting the observation note, summarising a thread for the instructor — those are words for a human, so an LLM does them. Classic combo: Jev decides whether/where/how-urgent, the LLM writes the text, Jev's Noul then gates whether it auto-sends.

Check yourself

Retrieval, not recognition — answer before revealing.

Q1. Putting three questions in one call:

Q2. A Noul answer gives you:

Q3. You combine the three answers:

Q4. A Score primitive is best for:

I'm your teacher — ask me anything. Good next questions: "Let's build use-case ① end to end for my app", "How do I test/tune these thresholds?", or "How does cost scale if I add more questions?"

Primary source to read next: docs.typesafe.ai — the Patterns & Confidence pages (you now have the vocabulary to read them fast).

Reference: Glossary & cheat-sheet · Prev: Lesson 02 · Next: pick a use case above and let's build it for real.