Skip to content
System One

Real-time control with System One models

A model inside a control loop has to answer before the next frame. Jev returns a typed decision rather than text, and TypeSafe reports end to end latency of 70ms to 500ms, which puts a semantic judgment inside the budget of a game tick or an interface response.

The problem

A control loop has a budget. A game at 60 frames per second has 16 milliseconds. An interface that responds to what the user is doing has maybe 200 before the response stops feeling connected to the action. An LLM that thinks for four seconds and writes a paragraph cannot participate in either.

What those loops need is a small decision, repeatedly, on the current situation. Which of these four moves, and is it safe to make right now. TypeSafe’s use-case map puts it as decisions made faster than human perception, fast enough to play games or to sit inside a UI, and quotes 150ms for that. TypeSafe’s launch post gives a range of 70ms to 500ms end to end. Both figures are the vendor’s own measurements, and neither appears on the models page alongside the published price and rate limits, so budget against your own numbers from your own region.

The awkward part is that Jev takes text only: a string, a JSON object, or an array of text. There is no vision input. Your code has to turn the frame, the scene or the screen into a description before asking anything, and the quality of that description sets the ceiling on everything after it. typesafe-mario and jev-drone both work this way, serialising a live situation into text and reading back a typed action.

What the state looks like

Your serialiser runs every tick. Send the entities that matter to the decision, already resolved into positions and relations code computed, and nothing else.

{
  "player": { "x": 412, "y": 96, "on_ground": true, "facing": "right" },
  "ahead": [
    { "kind": "gap", "distance_px": 64, "width_px": 96 },
    { "kind": "goomba", "distance_px": 210, "moving": "toward" }
  ],
  "overhead": "clear",
  "recent_action": "run_right"
}

Note what is already numeric. The distances were computed in code, not estimated by the model. Jev judges what the situation means, and everything measurable arrives pre-measured.

The questions you ask

One request per tick, carrying every question the loop might need.

import { choice, noul, TypeSafeClient } from "@typesafe-ai/sdk";

const client = new TypeSafeClient();

const response = await client.systemOne({
  state: frame,
  questions: {
    action: choice("What should the player do right now?", {
      run_right: "Keep moving right along the ground",
      jump_right: "Jump forward to clear something ahead",
      stop: "Stop and wait for the hazard to pass",
      back_off: "Move left away from the hazard",
    }),
    risky: noul("Would acting now most likely cost the player a life?"),
  },
});

const { choice: action, confidence } = response.answers.action;
const risk = response.answers.risky.noul;

The choice carries the decision, returning the winning option, a probability for every option, and a confidence value from 0 to 1. The noul, a yes/no question whose answer is one probability, acts as a brake that is independent of which action won. Read both from response.answers, which is where the JavaScript SDK puts every answer.

Send the speculative questions too. TypeSafe’s fan-out pattern states there is no speed cost for additional questions, so a loop can ask about situations that have not arisen yet and discard the answers it does not need. That is what keeps the request count at one per tick instead of one per question.

Decision policy

The loop runs whether or not an answer has arrived, so the policy has to include what happens when it has not.

const FLOOR = 0.55;
const BRAKE = 0.7;

function apply(action: string, confidence: number, risk: number) {
  if (risk > BRAKE) return safeDefault();       // the brake overrides the pick
  if (confidence < FLOOR) return lastGoodAction();
  return action;
}

Your code owns the fallback, so a slow or failed request degrades into a scripted default rather than a freeze. It also owns the rate: asking on a state change or every nth tick beats asking every frame. The brake is a separate question from the action on purpose: a confident wrong move still gets stopped.

Treat the model’s answer as advice to a controller you already trust. TypeSafe documents a three-band policy, acting automatically on high confidence, confirming or gathering more in the middle, and refusing to act at the bottom, with worked thresholds from a 0.5 floor up to 0.9 for anything destructive. In a physical system the bottom band means the safe default, not a prompt to a human, and the thresholds are yours to set for your domain. The confidence-gated action recipe has the pattern, and Jev speed and pricing covers what the latency budget actually buys you.

When not to use this

Anything you can compute. Distance to the gap, time to collision, whether a trajectory clears an obstacle: that is physics, and it belongs in the same code that built the state. Jev is weak at arithmetic and does not count reliably, so “how many enemies are on screen” should come from your entity list.

Numeric representations read worse than semantic ones. A colour as a hex value performs worse than the same colour named in English, which matters when you are deciding what a serialiser should emit. Name things.

Hard real-time control is out of scope entirely. A loop that must hold a deadline every cycle cannot depend on a network request, whatever the median latency is. Keep Jev on the advisory layer above a deterministic controller that can run alone.

Watch the state size. Serialising the whole scene rather than the part the decision needs costs accuracy, and long states cost latency too. And if any of that text comes from a player, treat it as hostile: state is data, and Jev has no default defence against instructions written into it.

FAQ

How fast is it really?

TypeSafe reports 70ms to 500ms end to end and quotes 150ms in its own real-time examples. Those are the vendor’s measurements, and they do not include your network path, your serialiser, or queueing under load. Measure the full round trip from your own infrastructure before designing a loop around a number, and build the fallback path first.

Can it see the screen?

No. Jev takes text only, as a string, a JSON object, or an array of text. Anything visual has to be described by your code first, which in a game means reading the entity list you already have. That constraint often helps, because it forces the measurable parts into code where they are exact.

What happens when a request is slow or fails?

Your loop keeps running on the last good decision or a scripted default, and the late answer gets discarded rather than applied to a state that has moved on. Stamp each request with the tick it describes and drop anything that arrives stale. The SDK exposes error types and a retry policy, but in a loop, failing fast beats retrying.

Does asking more questions slow the tick down?

Not meaningfully. TypeSafe’s fan-out pattern states there is no speed cost for additional questions, which is why a real-time loop should batch everything it might need into the single request it was going to make anyway. Cost scales with the state you send rather than the questions you ask, and that state is small here.

Examples in the wild

More real-time and agents use cases