Tutorials

Jev Tutorial: Build Your First Decision Workflow

Jev AI Guide Editorial Team
Sep 18, 20264 min readUpdated Sep 22, 2026

The API guide covered a single Jev call. A real workflow is more than one call: it combines decisions, a confidence policy, fallbacks, and logging. This tutorial builds that loop for support routing, and the same skeleton scales to agent routing and tool selection.

What we are building

            incoming ticket


          Jev: state + questions
   (department Choice + urgency Noul)

      ┌────────────┼────────────┐
      ▼            ▼            ▼
  confident    uncertain      very low
  (>= 0.85)   (0.6–0.85)     (< 0.6)
      │            │            │
      ▼            ▼            ▼
  auto-route   ask user      escalate
              to clarify    to a human

The goal is to automate the confident majority of routing decisions, spend a cheap interaction on the uncertain middle, and keep humans for the tail.

Step 1: Define the questions

Start from the answer space, not the prompt. Support departments are a fixed, known set, and that closed set is what makes this a decision-model problem rather than a generation problem. We ask two questions in one call: a Choice for the department and a Noul for urgency. Because every question is evaluated in parallel against the same state, the second one is essentially free.

const DEPARTMENTS = {
	billing: "Payment or subscription issues",
	technical: "Bugs or integration problems",
	sales: "Pricing or account questions",
} as const;

type Department = keyof typeof DEPARTMENTS;

type RouteResponse = {
	model: string;
	answers: {
		department: {
			type: "choice";
			choice: Department;
			confidence: number;
			probabilities: Record<Department, number>;
		};
		is_urgent: {
			type: "noul";
			noul: number;
		};
	};
	usage: { input_tokens: number; output_tokens: number };
};

Typing the response keeps every downstream branch honest. You cannot route to a department that does not exist.

Step 2: Call Jev

async function classify(ticket: string): Promise<RouteResponse> {
	const response = await fetch("https://api.typesafe.ai/v1/systemone", {
		method: "POST",
		headers: {
			Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`,
			"Content-Type": "application/json",
		},
		body: JSON.stringify({
			state: `Support ticket: ${ticket}`,
			model: "jev-latest",
			questions: {
				department: {
					type: "choice",
					instructions: "Which team should handle this",
					criteria: DEPARTMENTS,
				},
				is_urgent: {
					type: "noul",
					instructions: "The ticket conveys urgency or time-sensitivity",
				},
			},
		}),
	});

	if (!response.ok) {
		throw new Error(`TypeSafe API error: ${response.status}`);
	}

	return response.json();
}

Step 3: Encode the policy

The policy is where the workflow lives. Keep it in one place with named thresholds so you can tune it later without hunting through the code:

const POLICY = {
	autoRoute: 0.85,
	clarify: 0.6,
	urgent: 0.9,
} as const;

type Route =
	| { action: "route"; department: Department; urgent: boolean }
	| { action: "clarify" }
	| { action: "escalate" };

function decide(result: RouteResponse): Route {
	const { department, confidence } = result.answers;
	const urgent = result.answers.is_urgent.noul >= POLICY.urgent;

	if (confidence >= POLICY.autoRoute) {
		return { action: "route", department: department.choice, urgent };
	}

	if (confidence >= POLICY.clarify) {
		return { action: "clarify" };
	}

	return { action: "escalate" };
}

The Noul question is what makes urgency a number you can branch on anywhere — priority queues, SLA selection, notification rules — without a second API call or any prompt parsing.

Step 4: Execute with logging and fallback

Wrap the decision in error handling so a TypeSafe outage degrades to escalation instead of taking the request down with it. Log every decision too; you will want the data when you tune thresholds.

export async function handleTicket(ticket: string): Promise<Route> {
	try {
		const result = await classify(ticket);
		const route = decide(result);

		console.log(
			JSON.stringify({
				ticket,
				decision: result.answers.department.choice,
				confidence: result.answers.department.confidence,
				urgency: result.answers.is_urgent.noul,
				action: route.action,
			}),
		);

		return route;
	} catch (error) {
		console.error("decision failed, escalating", error);
		return { action: "escalate" };
	}
}

That is the complete loop: classify, branch, act, log, fail safe.

Step 5: Watch the probabilities, not just the answer

Once deployed, inspect the full probabilities distribution, especially on misroutes:

  • A flat distribution means the ticket genuinely does not fit your options. Maybe you are missing a department.
  • Two close top probabilities (billing 0.48 against technical 0.44, say) means the input is ambiguous, and the clarify branch is doing its job.
  • A confident wrong answer usually means the state is missing context, or two criteria descriptions overlap.

One calibration detail from the docs is worth internalizing: probabilities are calibrated across groups of predictions. That makes them good for setting thresholds, and never a guarantee that one specific answer is right. It is also why the escalation branch exists.

The same pattern, three products

Rework the skeleton and you have the other core use cases:

  • Agent routing — the options become your agents ("Kubernetes Agent", "Coding Agent", "Research Agent") and the state becomes the user request.
  • Tool selection — the options become your tools (kubectl, web-search, calculator) and the state becomes the task the agent is on.
  • Guardrails — a Noul question ("Does this output violate the policy?") gates every response before it ships.

One integration, many workflows.

Try it

The Jev Agent playground ships interactive versions of these scenarios — routing, tool selection, and evaluation — so you can watch probabilities and confidence respond to your own inputs before writing code. The official TypeSafe playground is the fastest way to try the raw primitives.

What to read next

// Related articles