A support request arrives. Your application needs to choose a department, identify the topics, and decide whether to send it to a specialist. The specialist may then spend several minutes gathering information, call a tool, and wait for approval. Somewhere in that sequence, a browser disconnects or a worker restarts.

These are two distinct runtime problems: making a bounded judgment, and preserving the work that follows it. Anvia now has explicit APIs for both, through typed decisions in Core, the new Jev adapter, and experimental durable agent execution.

A decision has its own contract

With @anvia/core/decision, an application supplies state and typed questions, then receives typed answers. It can call this operation directly, without creating an agent or starting a conversation.

The contract supports four question shapes:

  • choice() selects one option from a supplied set.
  • multiLabel() selects labels using independent probabilities and an explicit threshold.
  • score() rates input against an ordered rubric.
  • check() returns the estimated probability of yes for a question.

@anvia/jev implements that contract using TypeSafe's official SDK. Jev handles narrow judgments such as classification, routing, tagging, and verification. Your application supplies the possible destinations and decides what to do with the result.

pnpm add @anvia/core@^1.9.0 @anvia/jev@^0.2.0

Here is a department decision for an incoming support message:

import { choice, decide } from "@anvia/core/decision";
import { JevClient, JEV_LATEST } from "@anvia/jev";

const client = new JevClient({
  apiKey: process.env.TYPESAFE_API_KEY,
});

const result = await decide({
  model: client.decisionModel({ modelId: JEV_LATEST }),
  state: { message: "Please refund my duplicate payment." },
  questions: {
    department: choice({
      instructions: "Which department should handle this request?",
      options: {
        billing: "Payments, invoices, and refunds",
        technical: "Product bugs and technical support",
        general: "Other requests",
      },
    }),
  },
});

const department = result.answers.department.choice;
// Inferred as "billing" | "technical" | "general".
console.log(department, result.answers.department.confidence);

Several question types can share one request. A router can choose a department, attach independent topic labels, and score urgency from the same state. The adapter preserves normalized token usage and the original provider response alongside the typed answers.

Core validates the questions and model capabilities before calling the provider, then checks the returned answers, allowed options, probabilities, rubric bounds, and usage. The Jev adapter also checks that returned score legends match the rubric sent in the request. decide() supports cancellation and opt-in retries; decideBatch() runs bounded concurrent work and returns results in input order.

These checks establish the shape of a result. Model judgments can still be wrong. Confidence and probabilities remain provider estimates; applications should measure their routing policy on representative cases and define what happens when a decision is uncertain or unavailable. Permissions and business rules stay in application code.

Preserve the execution that follows

Once a request reaches an agent, the application needs a record of what actually happened. Conversation memory alone cannot tell a restarted worker whether a tool completed, whether an approval is still pending, or which events a disconnected client has already seen.

@anvia/durable journals submissions, completed model responses, tool results, interactions, and progress events in SQLite. On startup, the runtime can discover unfinished work and recover from its committed checkpoints.

pnpm add @anvia/durable@^0.2.1 @anvia/core@^1.9.0 @anvia/openai zod

SQLite support requires Node.js 22.16 or newer. Create an agent and register it with the runtime:

import { Agent } from "@anvia/core/agent";
import { DurableRuntime } from "@anvia/durable";
import { SqliteDurableStore } from "@anvia/durable/sqlite";
import { OpenAIClient } from "@anvia/openai";

const client = new OpenAIClient({
  apiKey: process.env.OPENAI_API_KEY!,
});
const agent = new Agent({
  id: "support",
  model: client.completionModel({
    modelId: "gpt-5.6",
    api: "responses",
  }),
  instructions: "Help resolve the customer's support request.",
});

const runtime = await DurableRuntime.open({
  store: new SqliteDurableStore("./support-runs.sqlite"),
  agents: [{ agent, version: "1", stream: true }],
});

try {
  await runtime.resume();
  const run = await runtime.submit({
    agentId: agent.id,
    sessionId: "customer-42",
    requestId: "support-request-123",
    prompt: "Explain how to resolve a duplicate payment.",
  });

  for await (const event of run.stream()) {
    console.log(event.type, event.data);
  }
  const outcome = await run.result();
  if (outcome.type === "response") console.log(outcome.output);
} finally {
  await runtime.close();
}

In a server, keep the runtime alive at application scope. A client disconnect closes its subscription; cancelling the underlying work is a separate action.

Submissions are deduplicated by session and request ID. Completed model and tool checkpoints are reused during recovery. Pending approvals and questions remain available after a restart, and explicit cancellations are preserved.

Streaming is part of the journal

Durable 0.2 adds opt-in persisted model streaming. A registration with stream: true records model deltas before subscribers read them. Clients can reconnect using the last event cursor they successfully applied.

Partial output belongs to a specific model attempt. If an interrupted operation starts another attempt, the client should replace the previous partial output. Only a completed, validated model response becomes a reusable model checkpoint. A restart can require another provider request, with another charge, even when some text was already shown.

Tool recovery is also explicit. Tools default to manual reconciliation when execution is interrupted. Read-only tools can use safe recovery; an idempotent policy requires the external service to deduplicate requests using the operation ID. The runtime cannot infer whether a payment or ticket creation completed outside its database.

Admit new agents without restarting existing work

Durable 0.2.1 adds runtime.registerAgents() and runtime.unregisterAgent(). A host can register new immutable agent configurations while other runs continue, then unload an idle configuration while retaining its journal history.

Registration validates the whole batch before adding it. Existing IDs cannot be overwritten, and removal is blocked while unfinished runs or settling attempts still need that agent. Registrations are process-local, so the host must restore the matching implementations before recovering their work after a restart.

The package also supports persisted task graphs, custom tasks, child ownership, timers, signals, and explicit effect recovery. It remains experimental, with one runtime owner per local SQLite database. Durable graph data is exposed through its APIs; Studio visualization and durable Core Pipeline execution remain future work.

Start with the boundary your application needs

Use typed decisions where an application needs a bounded judgment. Use durable execution where agent work must survive a process or connection lifetime. A support router can use both: application code applies the selected route, then submits work to the corresponding durable agent.

The decision call itself does not become a durable checkpoint merely because its output is passed to an agent. Persist the applied route where your application needs it, and keep recovery policy explicit for any external effects.

Read the decision API guide, the Jev guide, and the durable execution guide. The release notes cover Core 1.9.0, Jev 0.2.0, and Durable 0.2.1.

Keep up with Anvia.

Occasional releases, practical agent-engineering notes, and updates from Studio and Lens.

Anvia