← All posts

Jev Is in the Palette. The Runtime Didn't Notice.

Last week I said I was glad Jev entered the market, and that a bounded-decision model would slot into FloMorphic without the engine learning anything. FloMorphic v0.3.9 ships a Jev node. It went in without any major change, and here is how it works.

Last week I wrote that I was genuinely glad Jev entered the market. Jev is TypeSafe's model that cannot answer outside the options you give it. Its thesis has been in FloMorphic since the first day: the model proposes, the graph decides. Until Jev, though, I mostly explained it with a diagram. Now I can show it with a node.

FloMorphic v0.3.9 is out, tagged v0.3.9 and latest, and Jev is in the palette as a builtin node. You drag it onto the canvas next to the LLM, MCP and HTTP nodes, write your questions, and the answers become ports you can wire.

In that post I also made a claim that is easy to write and costly to get wrong: a new kind of decider slots into FloMorphic without the runtime learning anything. It held: the Jev node went in without any major change to the engine. This post is about why that was possible, and what it looks like on a real flow.

Install it with the script from the getting-started repo:

curl -fsSL https://raw.githubusercontent.com/FloMorphic/getting-started/main/install.sh | bash

I'll say it again: we already had this

Here is how an LLM node in FloMorphic has always decided.

You write a prompt that describes the situation. You bind a few functions to the node: approve, escalate, ask_for_documents. Each function becomes an outbound port on the canvas, and you draw an edge from each port to whatever should happen next. At run time the model reads the situation and picks one. Only the edge behind that choice fires.

The model picks the route. It can't invent one. If it picks nothing that matches, the exception port fires, and that is a visible edge too, not a silent fall-through.

Jev works the same way one layer down. You hand it a block of state and a few typed questions: which category?, how urgent?, has this customer written before? Each question has answers you declared ahead of time. Jev returns a calibrated probability for every answer and can't produce one you didn't list.

Put them side by side and they are the same idea:

LLM nodeJev node
Who writes the optionsyou, as bound functionsyou, as a question's answers
What each option becomes on the canvasa porta port
What the model does at run timepicks one functionpicks one answer per question, with a probability
Can it send the flow somewhere nobody drew?nono
Does it think first?yes: reads, calls tools, iteratesno: one fast read

In the node's settings drawer, an LLM function and a Jev answer are literally the same row: a name that becomes the port and a description the model reads to decide whether the situation matches. The code that turns those rows into ports is shared between the two nodes. I didn't adapt it for Jev. The rows already had the same shape.


Why a graph was ready for this

This is not a lucky coincidence in our design. It follows from what a graph is.

A graph is made of routes. Every node that has more than one way out holds a decision, whether you wrote it as a rule, a script, or a person clicking approve. So routing was never something bolted onto FloMorphic. It is the thing the runtime does. A node finishes, it names which of its ways out to take, and the runtime starts whatever sits at the end of those edges. That is the whole contract.

Once routing is the contract, the question who decides which way to go stays open by design. A rule node decides from an expression. Your backend decides from its reply. An LLM node decides from the function the model picked. Same enforcement, different decider. Jev is one more decider, and it needed no special path through the engine because deciding which way to go was never the engine's job to hard-code.


How the Jev node works

Say you have a support-triage flow: a ticket arrives and something has to decide where it goes.

You drop in one Jev node and tell it what to read from the flow's context: the customer, the subject, the message. Then you write three questions:

  • category: billing, technical, sales, or other
  • urgency: low, medium or high. This one is data only: it routes nothing, and its answer is stored for later steps to read.
  • repeat: has this customer written about it before? yes or no

Every answer of a question that routes becomes a port on the canvas, labelled with its question (category.billing, repeat.yes), so two questions that both have a high answer never share an edge. Add the exception port and you have the full set of ways out, all visible before anything runs.

I ran a real ticket through it: "I was charged twice, and I already emailed about this last week."

   ticket ──►  ┌────────────┐── category.billing ──►  refund desk        ✔ fired
               │    Jev     │── repeat.yes ────────►  flag to team lead  ✔ fired
               │  decision  │── category.technical      (skipped)
               └────────────┘── category.sales          (skipped)
                              ── category.other          (skipped)
                              ── exception               (skipped)

One call answered all three questions in under two seconds for one credit. The ticket was billing, it was a repeat, and urgency came back high. Two branches fired at the same moment and the other four were skipped, and the runtime's log names every one of them.

The flow's context now holds the full answer: the chosen option for each question, the probability for every option, the confidence, and the cost. A rule node placed after the Jev node can read that confidence and send anything under your threshold to a human. That threshold lives on the canvas where a compliance officer can read it, and changing it doesn't need a deploy.

Three small classifier steps became one node and one call, and that changes what you reach for when you design.


Where "none of these" moved

One thing I only understood by building it.

On an LLM node, "none of these" is a run-time event. The model might answer in prose or call nothing, and the exception port catches it.

Jev can't answer "none of these." It always places the state in one of your options, and when none of them really fits, it tends to look more confident, not less. So the no-match case has to be drawn earlier, as an option: that is why other sits in the category list above. It is the same branch as before, but now you draw it when you design the flow, where before the model triggered it at run time. The exception port on a Jev node shrinks to what it should be: the service failed, or an answer came back under your confidence floor.

The lesson carries over to every bounded model: a calibrated number doesn't remove the need for a no-match route. It only tells you where to draw that route.


The part I find beautiful

Here is what I most wanted to say with this release.

A flow drawn on a canvas is a plan. On its own it is static: every branch is there, and none of them is taken yet. What makes it alive is that at run time something reads the real situation and tells the graph where to go next. With a Jev node or an LLM node, that something is a model.

So the model is issuing commands to the process at run time: go to billing, and flag it as a repeat. But it can only give commands the graph already understands, along routes a person drew, reviewed and versioned. It steers, and it can't leave the road.

I think this is the right shape for how agents, and the industries adopting them, will make decisions and set direction:

  • The judgment is live. It reacts to the actual ticket, claim, alert or shipment, not to a rule someone guessed at last year.
  • The direction is designed. The set of possible next steps is decided by the people accountable for it, before the run, where everyone can see it.
  • The record is automatic. Every decision lands in the flow's context: what was seen, which options existed, what was chosen, how sure the model was, and where the process went next.

An insurer triaging claims, a security team deciding which alert deserves a human, a bank routing a credit request, a support desk sorting ten thousand tickets a day: in all of them the model decides and the graph directs. They are the same pattern with different nouns.


Which one to reach for

The Jev node doesn't replace the LLM node. It makes clearer what each one is for.

The LLM node is the corridor. It reads documents, weighs conflicting evidence, calls tools, takes a second turn, and then chooses a port. Bounding where that reasoning can lead is what makes it safe to let the model be as clever as it can.

The Jev node is the junction. The options are known, no open-ended reasoning is needed, and what you want is speed, low cost, several answers at once, and a confidence number you can route on.

Both are bounded and neither can invent a route. The difference is whether the model thinks first, and whether you get one answer or a distribution.

The model bounds the answer. The graph bounds the process. v0.3.9 puts both in the palette.


Get it: curl -fsSL https://raw.githubusercontent.com/FloMorphic/getting-started/main/install.sh | bash, or follow the getting-started repo. The Jev node ships in the builtin plugins alongside the LLM, MCP and HTTP nodes; the full changelog, including the new flow import/export over REST and MCP, is in that same repo. The triage run described above is one I actually ran; the numbers are from its log.