Jev is the small decision model everything in this crate defers to. It does not see raw pixels. It is handed a screen already turned into a short list of candidate elements and a small, closed question ("which of these should I click, if any"), and it answers with a choice and a confidence. Nothing here guesses on its own: every click, every field, every "this is done" comes from Jev's answer.
JevRuntime, in src/agentic/runtime.rs, is the one place in this crate that
talks to Jev. The doc comment on the crate calls it "the one door to Jev",
and the code backs that up: every other module (the flow runtime, the task
controller, RunGoal, ResolveIntent) asks Jev by calling
JevRuntime::evaluate (indirectly, through the loops built on it), never by
holding a client of its own. That matters for three reasons: the budget is
charged in one place, secrets are masked in one place, and the debug journal
sees every exchange, with nothing able to slip past it.
A JevRuntime is built once, from the module's private configuration, with
JevRuntime::configure:
let runtime = JevRuntime::configure(&JevConfig {
provider: JevProvider::TinyHumansOpenRouter,
model: Some("jev-latest".to_owned()),
..JevConfig::default()
})?;The configuration (JevConfig, in tinycomputer-bus) names:
| Field | What it controls |
|---|---|
provider |
Which decision model, through which API. See below. |
api_key |
The credential for that provider. Never printed; JevRuntime's Debug implementation shows "[configured]" in its place. |
endpoint_url |
An exact endpoint to use instead of the provider's own route, checked against an allow-list (see below). |
model |
The Jev model or alias to ask for. Defaults to the provider's JevProvider::default_model(): "jev-latest", "openjev" for OpenJEV, and the fixed "levanto-sage" for Sage. |
timeout_ms / max_retries |
Per-attempt HTTP timeout (10 seconds by default, ATTEMPT_TIMEOUT) and how many transient retries the client makes: four by default (RETRY), waiting 1, 2, 4, then 8 seconds, since live a gateway's brief 502s ended runs after the client's own 0.3 s of waiting. Sage ignores both. |
sdk_name |
Attribution sent only to the TinyHumans proxy, so it knows which host is calling. |
fast |
Sage only: score each choice in one pass rather than one per option. |
JevRuntime::configuration() returns the non-secret summary
(JevConfiguration: provider, model, endpoint override, fast), which the
module serves as Describe's Capabilities.decision_model.
Once built, a JevRuntime is cheap to clone: cloning shares the same
underlying HTTP client, the same pending-confirmation table, and the same
journal handle. The module clones it per call rather than rebuilding it.
Five providers are recognised, each with one approved endpoint that
JevRuntime::configure checks endpoint_url against before it will use it:
| Provider | Wire name | Approved endpoint |
|---|---|---|
TypeSafe, TypeSafe's own System One API, the default |
type_safe |
https://api.typesafe.ai/v1/systemone |
OpenRouter, OpenRouter's Jev-compatible decisions API |
open_router |
https://openrouter.ai/api/alpha/decisions |
TinyHumansOpenRouter, Tiny Humans' authenticated OpenRouter proxy, the only one that takes an sdk_name |
tiny_humans_open_router |
https://api.tinyhumans.ai/agent-integrations/openrouter/systemone |
OpenJev, OpenJEV's public System One API |
open_jev (or openjev) |
https://api.openjev.sh/v1/systemone |
Sage, Levanto Sage in place of Jev |
sage |
https://sage.levanto.ai/ (trailing slash optional) |
Passing an endpoint_url that is not the provider's own approved route
returns a JEV_INVALID_CONFIG error rather than silently sending credentials
somewhere unexpected. tinyinference-decisions has no Tiny Humans proxy route
for OpenJEV or Sage, so none is approved. In tests, endpoints on
http://127.0.0.1:* are also accepted, so a scripted Jev server can stand in
for the real one.
The sage provider builds a runtime that answers the same loops with Levanto
Sage instead of Jev; JevRuntime::sage(api_key, fast) builds the same one
directly, for measuring one decision model against the other behind
identical code. Every call still goes through JevRuntime::evaluate below,
exactly as a Jev-backed runtime's would. See sage.md.
JevRuntime::configure itself can fail (a bad key, an unapproved endpoint)
before anything runs. Once running, every Jev call funnels its failure
through the same small set of error codes:
| Code | Cause |
|---|---|
JEV_AUTHENTICATION |
The key was rejected. |
JEV_RATE_LIMITED |
Too many requests too fast. |
JEV_TIMEOUT |
The provider did not answer in time. |
JEV_INVALID_RESPONSE |
The reply could not be parsed, or answered a question that was not asked. |
JEV_PROVIDER_FAILED |
Anything else. |
JevRuntime::evaluate takes an optional step label (used only for the
journal) and an EvaluationRequest (Jev's own request shape, from
tinyinference_decisions), and returns either an EvaluationResult or an
EvaluationFailure. Every caller in this crate builds that request the same
way it builds any other: state (the goal, the screen's app and window, recent
actions), and one or more Questions, each a closed choice with named
criteria.
The function itself is short, because most of the interesting work (turning
a screen into candidates, turning candidates into a question, gating the
answer against a confidence threshold) lives in the modules that call it
(agentic::screen, agentic::policy, and, for whole flows, agentic::flow;
see goals-and-intents.md for the two loops that live
directly in agentic/ (resolve.rs, goal.rs), and the flow module's own docs for the rest).
evaluate itself does exactly two things: call the client, and hand the
request and outcome to the journal, win or lose.
async fn evaluate(
&self,
step: Option<&str>,
request: &EvaluationRequest,
) -> std::result::Result<EvaluationResult, EvaluationFailure> {
let outcome = self.client.evaluate(request).await;
self.journal.exchange(step, request, outcome.as_ref());
outcome
}Because the journal write happens here and nowhere else, a caller that
somehow bypassed evaluate (there is no supported way to) would also bypass
the journal. That is the enforcement mechanism for "one door": there is
exactly one function in this crate that can produce a Jev answer, and it
always logs.
A decision asks its framings all at once, each on an HTTP/1.1 connection of
its own, and opening one to the gateway costs a handshake: live, a task's
first decision took 330 ms more a call (the median over 84 runs) than the
task's later decisions of the same size. JevRuntime::warm(votes) opens them
ahead of time. A step's first turn asks its judging and grounding's opening
together (FIRST_TURN, 2), each in votes framings (at most 9,
MAX_VOTES), so the warm-up sends one small evaluation for each of those
calls, all at once: a single yes/no question about a one-line state, through
evaluate like any other call, so each is journaled under the step
warm-up. The answers are dropped, calls still out after 10 s
(WARM_TIMEOUT) are given up on, and Sage, whose calls take seconds, is not
warmed. Idle connections stay in the client's pool for 90 s, longer than a
plan takes to draft.
The task controller warms while a task's plan is drafted
(FlowRunner::warm), so its first turn finds the connections open; the task
never waits for the warm-up. A task whose budget caps its Jev calls
(max_model_calls) is not warmed: the warm-up's calls would not count
against the cap.
A JevRuntime carries a journal handle (see journal.md) that
starts a new run each time one of the top-level entry points
(resolve_intent, run_goal, run_flow) begins. JevRuntime::begin_run(kind, label) opens (or continues) a run in the journal and returns a new runtime
pointed at it. JevRuntime::journaled_as(run_id) lets a caller, such as the
task controller, which runs one task across several separate flow runs, keep
every run of one task in the same journal file, by giving them all the same
run id.
JevRuntime::with_journal(dir) switches the journal on unconditionally,
overriding the TINYCOMPUTER_JEV_JOURNAL environment variable that
JevRuntime::configure otherwise reads. journal_dir() reports where the
current run is writing, if it is writing anywhere.
RunGoal can pause in front of an action Jev judged hard to undo, and hand
the caller a one-use confirmation id rather than performing it. JevRuntime
holds the table of these pending actions (pending: Arc<Mutex<HashMap<String, PendingRun>>>) so that a later RunGoal call carrying a GoalContinuation
can find the exact action it is being asked to approve or decline. Entries
older than ten minutes are dropped, and the table is capped at 32 pending
actions at a time, past which a new confirmation is refused rather than
silently evicting an older one a caller might still be about to answer. See
goals-and-intents.md for what a caller does with a
confirmation id.
crates/tinycomputer-engine/src/agentic/runtime.rs,JevRuntime,evaluate, provider configuration;pending.rs, confirmation handles.crates/tinycomputer-bus/src/agentic/,JevConfig,JevProvider, and the other payload types that cross the bus.docs/technical/jev-harness.md, the full Jev stack, one decision end to end, and the levers that change its latency.