Direct Classification
Some questions have typed answers. Is this claim supported by the source? How severe is this complaint? Which queue does this ticket belong in? A chat model answers those in prose, so the call site ends up with a prompt asking for JSON, a parser, and a fallback for when the parse fails.
A classifier answers them as numbers instead, and the decision stays in your code:
use everruns::Classifier;use everruns_integrations_typesafe::TypeSafeClassifier;
let classifier = Classifier::new(TypeSafeClassifier::from_env()?);let spam = classifier .probability("Is this message spam?", "Claim your prize now!") .await?;if spam > 0.9 { println!("quarantined");}This is the counterpart to direct model calls: the same shape, a different contract.
Model | Classifier | |
|---|---|---|
| you send | messages | state plus typed questions |
| you get back | text | calibrated numbers |
| decides the outcome | the model’s words | your threshold, in your code |
| streams | yes | no — one round trip |
Three primitives
Section titled “Three primitives”A judgment asks one or more questions about the same state. Each is one of three shapes:
use everruns::Classifier;
let answers = classifier .about("I've been on hold for two hours and my card was charged twice.") .noul("urgent", "Does this convey urgency?") .score( "severity", "How severe is the problem the writer describes?", [ "A minor annoyance", "A real problem with their account", "Serious harm requiring immediate action", ], ) .choice( "queue", "Which team should handle this message?", ["billing", "technical", "sales"], ) .send() .await?;
let urgent: f64 = answers.probability("urgent")?;let queue: &str = answers.selected("queue")?;noul— whether something holds, as the probability of yes. A value near 0.5 means yes and no are near-equally likely, not “medium”.choice— exactly one option from your set, with the distribution behind it. Needs at least two options.score— a position along levels you define, lowest first. Needs at least two levels.
Questions in one call are answered in parallel inside a single request, so asking five costs one round trip, not five.
Ids are yours; instructions are the model’s
Section titled “Ids are yours; instructions are the model’s”The id labels the answer for your code and is never sent to the model. A question whose meaning lives in its id asks nothing:
// Wrong: the model never sees "is_the_joke_funny"..noul("is_the_joke_funny", "?")
// Right: the instructions carry the question..noul("funny", "Would a general audience laugh at this joke?")The same applies to score levels. Describe concrete situations — “A minor annoyance” reads on its own where “2 out of 5” does not.
Read the tail, not the average
Section titled “Read the tail, not the average”For “is there any serious hit here” rules, read the probability mass at or above a level rather than the weighted score. Something probably fine but possibly awful must not average into fine:
// Not: answers.score("severity")? > 1.5let serious = answers.tail("severity", 2)?;if serious > 0.3 { println!("escalated");}Offline by default
Section titled “Offline by default”Classifier::simulated needs no credentials and no network, so judgments are
testable the same way agents and completions are:
use everruns::Classifier;
let p = Classifier::simulated(0.93) .probability("Does this convey urgency?", "Two hours on hold.") .await?;assert!(p > 0.9);Credentials
Section titled “Credentials”TypeSafeClassifier::from_env() reads your application’s own
TYPESAFE_API_KEY. The classifier itself comes from the integration crate, so
add it alongside everruns:
everruns = "0.22"everruns-integrations-typesafe = { version = "0.1", default-features = false }default-features = false leaves out the hosted connector catalog, which only
the platform needs. everruns itself stays vendor-free: Classifier::new
takes any ClassifierService, exactly as Model::new takes any provider.
A deployment running the Everruns platform configures a separate
UTILITY_TYPESAFE_API_KEY for its guardrails — a
different account from the one an embedding application holds.
Giving an agent the classifier
Section titled “Giving an agent the classifier”Everything above is agentless: your code asks, your code decides. The other half is letting an agent classify as part of its own work — checking a claim against a source before citing it, rating a draft before sending it.
Jev is the same classifier as a capability, so an agent gets it as a tool:
use everruns::{Agent, Engine, Model, OpenAI};use everruns_integrations_typesafe::Jev;
let agent = Agent::builder() .name("reviewer") .instructions( "You review copy. When asked how something reads, measure it with \ jev_evaluate and report the numbers rather than judging by eye.", ) .model(Model::new("gpt-5.6-terra", OpenAI::from_env()?)) .capability(Jev::new(std::env::var("TYPESAFE_API_KEY")?)) .build()?;
let session = Engine::new().create(agent);let turn = session .run("Rate this subject line for pushiness: 'Act now before it is too late'") .await?;The agent calls jev_evaluate, writing its own questions about whatever it is
looking at, and gets the same calibrated numbers back. It is the identical tool
the hosted TypeSafe integration gives platform
agents — same name, same schema — so behavior matches whether you embed the
Framework or run on Everruns.
Which one to reach for:
| you decide | the agent decides | |
|---|---|---|
| who asks | your code writes the questions | the model writes the questions |
| use | Classifier | the Jev capability |
| good for | a policy check, a routing rule, a gate | verification inside a longer task |
Add the dependency alongside everruns:
everruns-integrations-typesafe = { version = "0.1", default-features = false }default-features = false leaves out the hosted connector catalog, which only
the platform needs.
What stays with an agent
Section titled “What stays with an agent”A judgment owns no session, no history, and no workspace, and runs no tools. Reach for an agent as soon as the work needs any of those. Typed output guarantees the interface, not the truth: validate thresholds against your own data and consequences.
Runnable: direct_classification.rs
and agent_classification.rs.