Solution Architecture13 October 20267 min read

Jev: The Decision Layer — Part 3 of 10

State in, signal out, code acts

A decision model is only safe inside a boundary your application controls. Part 3 sets out the five layers of that boundary, from preparing minimised state to recording what actually happened, with a worked server-side example and a failure-handling design that never loses a case.

#Jev#Decision Models#AI Architecture#Production Readiness#Solution Architecture
Unstructured signals pass through a transparent decision boundary and emerge as controlled branching, scoring and binary outputs.

The tempting architecture is two lines long: send the report to the model and do whatever comes back. It works in a demo. In production it fails in all the ways that matter. Personal data goes where it does not need to go, a confident wrong answer becomes an action, and when the service is slow the report quietly disappears.

The architecture that survives production has explicit stages, and only one of them involves the model. Everything else belongs to the application, can be tested without calling the model, and keeps working when the model does not.

This part walks through those stages for Kloofvale's Fix-It routing. Kloofvale Local Municipality is fictional, and its systems and figures are illustrative. This is an independent CloudNala analysis; product details are as documented on 13 October 2026.

State in, signal out, code actsKLOOFVALE'S APPLICATIONFix-It report arrives by WhatsApp or webValidate, redact and build minimised stateDecision adapter asks the versioned questionsPolicy and thresholds decide the pathRoute to team queueTriage review queueSafe fallbackDecision and outcome evidenceOUTSIDE THE BOUNDARYJev decisionendpointversioned questions in,typed answers out
Everything inside the dashed boundary belongs to Kloofvale and can be tested without calling the model. The decision service sits outside it and is reached through one adapter. Policy, not the model, chooses between routing, review and the fallback, and every path ends in the same evidence record.

Layer 1: collect and prepare state

The application gathers only the facts the decision needs. For Kloofvale that is the report text, the channel it came through, the ward if the resident chose one, and a flag saying whether a location pin was shared. It is not the resident's name, phone number or account history, even though the case system holds all three and the API would happily accept them.

Good state is:

  • relevant to the question being asked;
  • current, not a stale copy from last night's batch;
  • minimised, holding the fewest fields that give a good answer;
  • valid against a schema, so malformed input is caught before it costs anything;
  • traceable, so every field can be followed back to its source;
  • clean of secrets and of personal information the decision does not need.

Minimisation is not only a privacy measure. Every field you add is something the model can be distracted or manipulated by, and something you must now include in your evaluation set. TypeSafe's notes on Jev 1.13 also warn about "context rot" when state grows long. Less state is usually a better decision as well as a safer one.

Layer 2: ask the bounded question

The question defines the contract, so version it like code. Changing one word in an option's description can change behaviour even though the JSON shape is identical. Kloofvale's adapter stores, for every call:

  • the question identifier and version, such as route-v3;
  • the criteria or rubric version;
  • the model identifier;
  • the timestamp;
  • a correlation identifier linking the call to the Fix-It case.

Layer 3: interpret the signal

The answer is evidence for the next step, not the next step itself. Kloofvale's pilot policy reads:

IF route is not "other"
AND probability of the chosen route >= 0.85
AND needs_human < 0.20
AND a location is present
AND the state passed schema validation
THEN place the report in that team's queue
ELSE place it in the triage review queue

That policy belongs in deterministic code or a governed rules engine. It must be testable with recorded answers and no live model call at all. When a reviewer later asks why a report went to the water depot, the answer should be a rule and a version number, not "the AI decided".

Layer 4: act through authorised services

The action layer is the one that already exists. It checks who is acting, what they are allowed to do, the current state of the case and whether this action has already happened. A high-probability answer never bypasses any of that. At Kloofvale, the routing service writes to team queues through the same internal API the call-centre staff use, with the same permissions and the same audit log.

Idempotency matters here more than people expect. If the adapter retries a call after a timeout and both attempts eventually succeed, the report must still land in one queue once, not twice.

Layer 5: record the outcome

The system needs two records, not one:

  1. What the model answered and why the application took the path it took.
  2. What actually happened afterwards: did the water team accept the report, reroute it, or close it as a duplicate?

Without the second record, the team can measure how often the model agrees with reviewers, but not whether the decision layer is improving anything residents care about. Kloofvale's case system already records when a team closes a case, so linking that back through the correlation identifier was a small change with a large payoff.

Test the boundary without the model

Because every layer except the second belongs to Kloofvale, almost all of the testing can happen without a live call. The team keeps a folder of recorded responses: clear cases, close calls, a water answer at 0.84 that should fall just short of the threshold, a missing needs_human field, an empty body, a 529. The policy and the adapter are tested against those recordings on every build, quickly and for free.

That split also clarifies what each kind of test proves. The recorded tests prove that the application does the right thing with any answer it might receive, including bad ones. They say nothing about whether the model's answers are any good. That is a separate question, answered by evaluation against labelled reports, which Part 7 covers. Teams that blur the two end up with either a well-tested application around an unmeasured model or a well-measured model inside an untested application.

A minimal server-side example

The example below shows the shape of Kloofvale's adapter call using the Jev AI endpoint. It is illustrative, not production code. The key stays on the server, the call has a hard timeout, and the response is treated as a signal that policy then interprets.

type Route = "roads" | "water" | "electricity" | "other";

type Outcome =
  | { action: "route"; queue: Exclude<Route, "other"> }
  | { action: "review"; reason: string };

async function decideRoute(state: object, hasLocation: boolean): Promise<Outcome> {
  const response = await fetch("https://thejevai.com/v1/systemone", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.JEV_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "typesafe/jev-1.13",
      state,
      questions: {
        route: {
          type: "choice",
          instructions: "Which municipal team should handle this service report?",
          criteria: {
            roads: "Potholes, road markings, damaged or obstructed road surfaces",
            water: "Leaks, burst pipes, water interruptions or sewage overflows",
            electricity: "Power interruptions, meters, streetlights or exposed cables",
            other: "Refuse, parks or anything no approved team covers",
          },
        },
        needs_human: {
          type: "noul",
          instructions: "Does this report need a person to triage it?",
        },
      },
    }),
    signal: AbortSignal.timeout(3000),
  }).catch(() => null);

  if (!response?.ok) return { action: "review", reason: "decision_unavailable" };

  const body = await response.json();
  const answers = body.result?.answers ?? body.answers;
  if (!answers?.route || !answers?.needs_human) {
    return { action: "review", reason: "invalid_response" };
  }

  const route = answers.route;
  const needsHuman: number = answers.needs_human.noul;

  if (
    hasLocation &&
    route.choice !== "other" &&
    route.probabilities[route.choice] >= 0.85 &&
    needsHuman < 0.2
  ) {
    return { action: "route", queue: route.choice };
  }
  return { action: "review", reason: "uncertain" };
}

Two notes on that code. First, the line body.result?.answers ?? body.answers is deliberate. On 13 October 2026, Jev AI's documentation described answers as sitting inside a result object, while its own examples, and all of TypeSafe's, showed answers at the top level. Verify the actual response contract before you build, and replace the fallback with proper schema validation. Second, TypeSafe's own API uses a different endpoint (https://api.typesafe.ai/v1/systemone) and model name (jev-latest). The two are not interchangeable without a configuration change, which is one reason to keep the call inside a single adapter.

Production code still needs full response-schema validation, secret management and rotation, redaction, retries, circuit breaking, idempotency, auditing and tests. The example is the shape, not the finished thing.

Failure handling

Both Jev AI and TypeSafe document the same error responses: 401 for an unauthorised request, 422 for a validation error, 429 when you hit a rate limit and 529 when the service is overloaded. Each needs a different response, and only two of them should ever be retried.

Every failure has somewhere to goADAPTERCalls Jevwith a 3 secondtimeout200, answers validApply policy and thresholds429 rate limit, 529 overloadedBack off and retry, then review422 validation errorFix the request, do not retry401 unauthorisedAlert, check key and configTimeout or malformed replySend to triage reviewEvery path ends somewhere visible: no Fix-It report is silently dropped
The adapter treats each outcome of the call differently. Only temporary failures are retried, within a budget. Errors that a retry cannot fix raise an alert. Whatever happens, the report ends up in a queue a person can see, so a burst main is never lost because a service was busy.

Kloofvale's adapter applies:

  • a timeout on every call;
  • exponential backoff with jitter for 429 and 529 only, within a retry budget;
  • no retry for 422, because sending the same invalid request again will fail again, and an alert so someone fixes it;
  • an immediate alert for 401, because it means a key or configuration problem;
  • a circuit breaker, so that a struggling service is not hammered with retries;
  • a safe fallback that puts the report in the triage queue;
  • duplicate protection at the action layer;
  • alerts based on business impact, such as "triage queue growing faster than staff can clear it", not only on HTTP status.

The principle is simple. If the decision service is unavailable, a burst main must not disappear, a payment must not run without its control, and a resident's report must not be silently discarded. When everything fails, Kloofvale is back where it started: two people reading reports. That is a slower system, not a broken one.

Key points

Only one layer calls the model. Preparation, policy, action and evidence all belong to your application.

Minimised state is safer and often more accurate. Send what the decision needs, nothing more.

Version questions like code. A wording change is a behaviour change.

Policy turns signals into actions. Keep it deterministic and testable without the model.

Every failure has a destination. Retry only what is temporary, and make sure nothing is lost.

Previous: Part 2, Choice, Score and Noul. Next: Part 4, Jev is not an agent. All parts are on the series page.

How CloudNala can help

We design and review the boundary around AI decisions: the state builder, the adapter, the policy layer, the failure paths and the evidence record. The aim is an architecture where the model can be wrong, slow or unavailable without the business process failing, and where every routed case can be traced back to a rule and a version.


Work with CloudNala

CloudNala helps organisations move from technology ambition to practical execution across cloud, AI, data, platform engineering and digital services.

Whether you are exploring AI, modernising your cloud environment, building a public-sector digital service, or turning an idea into a working MVP, we can help you shape the roadmap and deliver the next step.

Book an AI Decision Review or write to us at consult@cloudnala.co.za