Forms rather than prompts: what a System One model contributes to chat assistants and generated dashboards

In an Agentic UI, a model sits between the user and the application. Each message triggers at least one model call, and that call has to choose a tool, populate its arguments, and settle on a widget to render. When an LLM handles this step, the wait is often measured in seconds and every invocation adds cost. For an interface, neither is acceptable.

Jev goes another way. Its vendor TypeSafe describes it as the first model of a class they call System One. Rather than producing text, such a model answers predefined questions by picking from predefined options, and it does so in roughly 100 milliseconds. I received an early preview and want to summarize below what Jev can offer Agentic UI.

📂 Source code

Two branches live in the repository. The first, main, puts Angular, CopilotKit, AG-UI, and Jev together. The second, console-chat, keeps Jev on the console alone and serves as a quick overview.

What Jev Actually Is

Text generation happens token by token when an LLM receives a prompt. Whenever the application needs a decision out of that, it must request structured output, then parse and validate whatever comes back. Jev removes those steps entirely. You give it a state — the conversation history, for example — along with a collection of typed questions. For every question it hands back one of the answers you defined, plus a probability attached to each option.

Picture a questionnaire and you have the right mental model. A form is passed to the model together with the text it has to judge, and the model ticks exactly one box per question. There is no room to write in the margins. Your own code then reads those ticked boxes and decides the next move: invoke a tool, render a widget, or ask the user something further.

A questionnaire on a clipboard. A note shows the user message "Did I already book my flight to HAM?". Three questions are ticked: the tool is bookings, the departure city is NOT_DEFINED, and the destination is Hamburg.

Two consequences follow from this setup. Answers arrive typed, so what reaches your code always matches a value your code anticipates. And within one request, every question is resolved independently and in parallel. TypeSafe states that extending the questionnaire has hardly any impact on response time.

The Example Application

What I built is a compact flight assistant with two views. From the chat, users request flights or look up their bookings. Jev picks the tool and supplies its arguments, the server executes the tool, and the result streams back through AG-UI. CopilotKit renders each flight as a widget on the client:

Chat view: the user asks "Did I book HAM yet?". The assistant calls the tool findBookings and shows the booked flight from Graz to Hamburg as a widget.

The dashboard goes further. A user describes which tiles should appear, and Jev answers a bigger questionnaire about that description: which tiles are wanted, for which route, how many entries, in what order. The server then invokes the necessary tools and ships the dashboard to the client as A2UI:

Dashboard view: a text description leads to six tiles with flight tables, delay charts, rental cars, and hotels. A status line shows that Jev needed 162 ms of processing for the request.

Getting Started

npm is how the SDK is distributed:

npm install @typesafe-ai/sdk

TypeSafe also ships a skill that familiarizes coding agents with the question types, the patterns, and the best practices. Installing it for Claude Code happens as a plugin, while for other agents the skills CLI handles it:

# Claude Code
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai

# Other agents
npx skills add typesafe-ai/skills --skill typesafe-ai

Defining the Questionnaire

Three questions make up the questionnaire for our chat — one to select the tool, two more to determine its arguments:

import { choice } from '@typesafe-ai/sdk';

[...]

const CITIES = {
  'Berlin': null, 'Bremen': null, 'Dresden': null, 'Frankfurt': null,
  'Graz': null, 'Hamburg': null, 'Innsbruck': null, 'Linz': null,
  'London': null, 'München': null, 'Paris': null, 'Rome': null,
  'Salzburg': null, 'Stuttgart': null, 'Wien': null, 'Zürich': null,
} as const;

const CITY_OPTIONS = {
  ...CITIES,
  'NOT_DEFINED': 'No place is named for this end of the journey.',
  'NOT_SUPPORTED': 'A place is named, but it is not one of the cities listed here.',
} as const;

const CITY_NAMES = Object.keys(CITIES);

const QUESTIONS = {
  tool: choice(
    'A traveller writes to an assistant that searches flights and lists the flights ' +
      'they have booked. `conversation` is the exchange so far; the last entry is the ' +
      'new message. What do they want now?',
    {
      flights: 'To travel somewhere, or to see connections between two places.',
      bookings: 'To see the flights they have already booked: their own reservations.',
      none:
        'Neither: a greeting, a thank-you, or something this assistant cannot do, ' +
        'such as hotels or trains.',
    },
  ),

  from: choice(
    'Which city does the traveller now name as the start of the journey, the place departed ' +
      'from? The last entry of `conversation` is the new message; earlier entries count only ' +
      'when the new message builds on them, as a return flight does.',
    CITY_OPTIONS,
  ),

  to: choice(
    'Which city does the traveller now name as the end of the journey, the destination? ' +
      'The last entry of `conversation` is the new message; earlier entries count only when ' +
      'the new message builds on them, as a return flight does.',
    CITY_OPTIONS,
  ),
};

Every question here is a choice. It receives the question text, plus an object describing the possible answers: the keys are the options and the values are their descriptions. Where a key is self-explanatory, as with the city names, null suffices. The word conversation appearing in the question texts refers to the property of the same name on the state that we hand over afterwards.

Because the possible answers have to be fixed in advance, the two city questions each need two extra options. Messages that mention no city at all are covered by NOT_DEFINED. Cities the airline doesn't fly to are covered by NOT_SUPPORTED. Skip those options and the model would be forced to tick one of the 16 cities even for a message that names none.

A question currently accepts at most 255 options. Anything larger requires some inventiveness; I return to that point near the end.

Further Question Types

Two more question types exist alongside choice:

  • noul poses a yes/no question and reports the probability that the answer is yes.
  • score rates the state on ordered levels that you define yourself — the urgency of a request ranging from "can wait" to "act now," for instance. A result can also fall between two levels.

For the dashboard, one noul per tile type determines which tiles the description is asking for:

import { noul } from '@typesafe-ai/sdk';

[...]

const QUESTIONS = {
  hotels: noul(`Do they ask for hotels?`),
  [...]
};

Asking Jev

The state and all questions travel in a single call:

import { TypeSafeClient } from '@typesafe-ai/sdk';

[...]

const client = new TypeSafeClient();
const state = { conversation };
const result = await client.systemOne({ state, questions: QUESTIONS });

The API key is read from the environment variable TYPESAFE_API_KEY. Since the key must remain secret, this call has to run on the server.

Any JSON structure can serve as the state. In this case it holds only the conversation — an array containing the user's and the assistant's messages. Keeping that history lets users refer back to the flight that was just under discussion. If someone searches flights from Graz to Hamburg and then asks about the return flight, the meaning is obvious: Hamburg becomes the origin and Graz the destination. The question texts above prepare Jev for exactly this situation by stating that earlier entries matter only when the new message builds on them.

NOTE

Agentic UI with Angular

If you don’t just want to connect an agent but embed Agentic UI into a scalable architecture:
In my book Agentic UI with Angular, I cover the underlying patterns and trade-offs in depth.

Cover of the eBook Agentic UI with Angular

More about the eBook →

Reading the Result

Asked Did I already book my flight to HAM?, Jev came back with the following:

{
  "model": "jev-1.13.0",
  "answers": {
    "tool": {
      "type": "choice",
      "choice": "bookings",
      "confidence": 0.99,
      "probabilities": {
        "flights": 0,
        "none": 0,
        "bookings": 1
      }
    },
    "from": {
      "type": "choice",
      "choice": "NOT_DEFINED",
      "confidence": 0.92,
      "probabilities": {
        "Paris": 0,
        "Salzburg": 0,
        [...]
        "NOT_DEFINED": 0.93,
        [...]
      }
    },
    "to": {
      "type": "choice",
      "choice": "Hamburg",
      "confidence": 0.99,
      "probabilities": {
        [...]
        "Hamburg": 1,
        "NOT_SUPPORTED": 0,
        [...]
      }
    }
  },
  "usage": {
    "input_tokens": 891,
    "output_tokens": 365
  }
}

One entry per question appears in the answers object, mirroring the questionnaire. Three pieces of information sit inside each entry:

  • choice is the selected option.
  • probabilities holds a value for every option.
  • confidence is a number between 0 and 1 that summarizes how clearly one option stands out from the others.

Here, Jev selects the tool bookings and maps the airport code HAM to Hamburg. No departure city is named in the message, so from comes back as NOT_DEFINED. The code uses these answers to invoke the bookings tool and filter for flights to Hamburg.

Control flow benefits from the confidence value. When it drops below a threshold, the application can ask the user to confirm or clarify. And thanks to as const in the questionnaire, the result is fully typed: result.answers.to.choice is a union of the city names and the two special options, not merely a string. The usage section reports how many tokens the request consumed.

Comparison

I measured duration and cost against OpenAI's Luna. To do that, I ported the application to Luna (see the openai branch), sent the requests shown in the screenshots three times to each model, and took the best run for the comparison.

Luna was chosen because it is capable enough for this use case. According to TypeSafe, Jev can even match the larger Terra.

Chat: Luna Chat: Jev Dashboard: Luna Dashboard: Jev
Model gpt-5.6-luna jev-1.13.0 gpt-5.6-luna jev-1.13.0
Round trip 1,069 ms 716 ms 3,207 ms 1,078 ms
Processing at the provider 708 ms 107 ms 2,590 ms 204 ms
Network 361 ms 609 ms 617 ms 874 ms
Input tokens 856 937 7,494 7,260
Output tokens 23 370 326 4,982
Cost per request $0.000199 $0.000039 $0.000542 $0.000305

Duration

Provider-side processing time is the most striking figure. Jev needs 107 ms for the chat request where Luna takes 708 ms. For the dashboard the numbers are 204 ms against 2,590 ms. That makes Jev over 6 times faster in the first case and over 12 times faster in the second.

The round trip shows a narrower gap, since Jev's total is dominated by the network. In my measurements the network portion was larger for TypeSafe than for OpenAI, though that share depends on my location and on where the endpoints are hosted — it says little about the model itself.

Repeat runs surfaced another difference. Across all six runs, Jev's processing time remained between 107 and 204 ms. Luna ranged from 708 to 1,683 ms on the chat request, and one dashboard run took nearly 13 seconds.

Cost

The calculation is based on both vendors' list prices as of September 20, 2026, the day the measurements were taken. Prices are in USD per million tokens:

Model Input Cached input Output
gpt-5.6-luna $0.20 $0.02 $1.20
jev-1.13.0 $0.042 n/a $0.00

For the chat request, Jev costs roughly a fifth. On the dashboard, Jev lands at a little more than half of Luna's price — and there Luna already profits from prompt caching, since 7,491 of its 7,494 input tokens came from the cache.

Jev reports considerably more output tokens than Luna. Those never reach the bill, because TypeSafe charges for input tokens only.

Three runs per request is a spot check rather than a benchmark, but the tendency is unmistakable.

Interested in production-ready Agentic UI architectures?

In my workshop, we dive into AG-UI, A2UI, MCP Apps, HITL patterns, and modern Angular architectures for real-world agentic systems.

Workshop: Agentic AI with Angular – AG-UI, A2UI, MCP Apps & HITL patterns

See all details →

Assessment and Outlook

Performance and Cost

Both dimensions look very promising for Agentic UI. Processing times of 100 to 200 ms mean the model no longer dictates how responsive an application feels. I also expect the model class Jev popularized to suit local execution well — in the browser, for instance — which would eliminate the network share too.

What Remains Your Job

A simplification runs through the examples above: widget and tile selection are coupled directly to the respective tool calls. Decoupling them would be possible, but with Jev that turns into programming work. The same holds for mapping values between tool results and widgets or follow-up tool calls — that code is yours to write. An LLM, by contrast, takes on such tasks with little effort on our side.

Further Use Cases

Agentic UI is just one possible field. The documentation lists several more:

  • Routing: classify incoming requests and hand each one to plain code, a specialized LLM, or a human.
  • Guardrails: screen the messages going into and coming out of an LLM application.
  • Reranking for RAG: judge retrieved passages before they reach the answering model.
  • Verification: check citations or extracted fields against the source document.
  • Classification at scale: sort tickets, documents, or product data and moderate content.

Thinking in Classes

Working with questionnaires and predefined options — which are classes in the end — takes adjustment. Certain use cases and their implementation demand a different mindset, particularly because not everything can be expressed as a class.

Jev's documentation illustrates a few approaches. Dates, for example, can be broken into their parts — year, month, day — with separate questions determining each part and code reassembling them. A hierarchical route also works: ask for the continent first, then the country, and finally the airport.

Another approach applies when the desired value is already present in the message, such as a number or a quantity. There you can pre-filter the message with a regular expression and present the values you find as options.

Outlook: Polyglot and Hybrid Approaches

A polyglot setup offers another answer for values that resist classification. It pairs Jev with a very lean language model such as Gemini Nano, which is demonstrably strong at extraction tasks. I can picture both running in the browser.

A hybrid of the two worlds is likewise an exciting prospect, with the advantage that developers wouldn't have to juggle two kinds of models and mediate between them.

So — trend or game changer? For the decisions inside an Agentic UI, I see game-changer potential. The chosen tool and its arguments arrive typed, with probabilities, after 100 to 200 ms of processing. Free-form values and generated text still call for a language model. That is why the combination of both worlds strikes me as the most interesting prospect.