Release May 25, 2026 · 8 min read

ElsiumAI 0.16.0: The Critical Five — Fluent VAG, Pause/Resume, and Auto-Replay

The previous release shipped the primitives. This one makes them feel native. Five focused changes turn defineAgent from “run an LLM with tools” into a runtime that verifies, pauses, replays, and gates — with no extra boilerplate.

Each change is small on its own. Together they close the gap between “the building blocks exist” and “the agent does the right thing by default.”


The five

ChangeWhat it gives you
C — Fluent verificationagent.withVerifier(...).withRetryPolicy({ maxAttempts }) wraps every run()/generate() in a repair loop. Immutable, zero-overhead if you don’t opt in.
A — Pause + resumeagent.runResumable() + resume(token). Tool throws pauseAgent(), runtime snapshots state to a StateStore, resumes hours or days later.
B — Auto-record + replayEvery agent.run() auto-records. agent.replayFrom(traceId, { fromStep, overrides }) swaps a midpoint prompt and re-executes the tail.
E — Auto-approval gateDestructive tools auto-gate by default (requireApproval: 'auto'). Denials short-circuit before the handler runs.
D — Stream wrap-upagent_end now carries stopReason alongside the result — one terminal event with everything to close out the run.

Plus six spec-compliance polish items so the public demos match the published API verbatim. More on that at the end.


1. Fluent verification on the agent

VAG (runWithVerification) shipped in 0.15.0 as a standalone function. You wrapped your generate call in a closure, passed validators, and got back a tagged outcome. Useful, but every call site needed the same boilerplate.

In 0.16.0, the same engine is available as a fluent API on any agent. Chain .withVerifier(...) and .withRetryPolicy(...) and every subsequent run() or generate() loops generate → validate → repair-or-abort under the hood:

import { defineAgent, schemaValidator, judgeValidator } from '@elsium-ai/agents'

const verified = defineAgent({ name: 'extract', system: '...', model: 'claude-sonnet-4-6' }, deps)
  .withVerifier(schemaValidator(InvoiceSchema))            // alias of zodValidator — reads better in chains
  .withVerifier(
    judgeValidator({
      rubric: 'Summary must cite at least one source from the input.',
      judge: async (rubric, value) => callJudgeLLM(rubric, value),
      threshold: 0.7,
    }),
  )
  .withRetryPolicy({ maxAttempts: 3 })

const result = await verified.run('Extract the invoice from ')

withVerifier and withRetryPolicy return a new agent — they don’t mutate the base. You can fork the same base agent into multiple verification configurations safely. Agents without verifiers attached behave identically to 0.15.0 — zero overhead unless you opt in.

Two new validator factories ship to match the spec naming:


2. Pause + resume across restarts

This one is the biggest behavior change. A tool handler can now throw pauseAgent(reason, context) and the agent runtime catches the signal, snapshots { messages, originalInput, pausedAt } to a StateStore, and returns { status: 'paused', resumeToken }. The token round-trips through your own queue / DB / Slack thread and feeds back into agent.resume(token) — possibly days later, possibly on a different process:

import { createInMemoryStateStore, pauseAgent } from '@elsium-ai/core'
import { defineAgent } from '@elsium-ai/agents'
import { defineTool } from '@elsium-ai/tools'

const reviewTool = defineTool({
  name: 'request_human_review',
  input: z.object({ amount: z.number() }),
  sideEffectLevel: 'destructive',
  handler: async (i) => {
    pauseAgent('reviewer approval needed', { amount: i.amount })
    return { /* unreachable — pauseAgent throws */ }
  },
})

const store = createInMemoryStateStore()
const agent = defineAgent({ name: 'ops', system: '...', tools: [reviewTool] }, deps)

const outcome = await agent.runResumable('approve refund of $1,200', {}, { stateStore: store })
if (outcome.status === 'paused') {
  // persist outcome.resumeToken in your queue
  // hours or days later:
  const final = await agent.resume(outcome.resumeToken, {
    stateStore: store,
    followUpMessage: { role: 'user', content: 'approved by reviewer@example.com' },
  })
}

The framework ships createInMemoryStateStore for prototyping. For production, supply a durable adapter implementing save / load / delete / list (Redis, Postgres, SQLite, S3 — your call). MVP scope: snapshots are taken only at explicit pauseAgent() boundaries, not on every iteration. Full crash-recovery across restarts is a planned follow-up.


3. Auto-record + agent.replayFrom

0.15.0 shipped the createTraceRecorder + replayFrom primitives in @elsium-ai/testing. They worked, but you had to wire the recorder manually for every step.

In 0.16.0, every agent.run() automatically attaches a TraceRecorder and records each LLM iteration as a step (llm:iter_N). Traces stay in an in-memory ring buffer on the agent (default cap 100). Each agent gets getTrace, listTraces, and replayFrom for free:

const result = await agent.run('Summarize Q1 earnings call')

const trace = agent.getTrace(result.traceId)
console.log('steps:', trace?.steps.map((s) => s.key))

// Time-travel: re-run from any step with a swapped prompt
const replay = await agent.replayFrom(result.traceId, {
  fromStep: 'llm:iter_1',
  overrides: {
    'llm:iter_1': { prompt: 'You are a tougher summarizer. Cut filler.' },
  },
})

{ prompt: '...' } is a new shorthand that translates to a { kind: 'transform', input: (req) => ({ ...req, system: '...' }) } override against the system field. The verbose form still works for arbitrary request transforms.

The replay primitives moved from @elsium-ai/testing into @elsium-ai/core so the agent runtime can use them without breaking the package DAG. @elsium-ai/testing keeps the same exports via re-export — no breaking change for existing callers. MVP scope: records only LLM iterations, not individual tool calls; in-memory store only.


4. Auto-approval gate for destructive tools

The 0.15.0 tool contracts let you declare sideEffectLevel: 'destructive' on a tool. The framework knew the level — but didn’t do anything with it by default.

In 0.16.0, that changes. defineTool accepts a new requireApproval: 'auto' | 'always' | 'never' option (default 'auto'). When set to 'auto', destructive-level tools call context.requestApproval before invoking the handler. Denials short-circuit and return { success: false, approvalDenied: true, approvalReason } without ever running the handler:

import { defineTool } from '@elsium-ai/tools'

const transfer = defineTool({
  name: 'transferFunds',
  input: TransferSchema,
  sideEffectLevel: 'destructive',
  // requireApproval defaults to 'auto' → destructive tools auto-gate
  handler: async (i) => bank.transfer(i),
})

await transfer.execute(input, {
  requestApproval: async (req) => ({
    status: req.input.amount > 1_000 ? 'rejected' : 'approved',
    reason: 'amount cap exceeded',
  }),
})

Approval is skipped automatically when dryRun: true. Set requireApproval: 'always' to gate every call regardless of side-effect level (good for PII reads); 'never' opts out entirely (good for test-only tools). Tools without a requestApproval handler on the context log a warning and proceed — wire it explicitly to enforce.


5. Complete agent stream wrap-up

Small but load-bearing. The agent_end stream event now carries stopReason alongside result, making it a true terminal “final” event with everything a consumer needs to close out the run — message, usage, toolCalls, traceId, stopReason. Combined with the new spec-named aliases (below), a typical UI loop becomes a single switch:

for await (const event of agent.stream('Plan a 3-day trip to Lisbon')) {
  switch (event.type) {
    case 'thinking':    sidebar.write(event.text); break
    case 'token':       stdout.write(event.text); break
    case 'tool_call':   console.log('→', event.toolCall.name, event.toolCall.arguments); break
    case 'tool_result': console.log('←', event.result.data); break
    case 'final':       saveAgentResult(event.result); break  // includes stopReason
  }
}

The granular variants (text_delta, thinking_start/delta/end, tool_call_start/delta/end, agent_end) still fire alongside — better when you need partial state during streaming (e.g. progressive UI rendering as the assistant types). Both styles coexist; no breaking changes.


Spec-compliance polish

Six small ergonomic gaps closed so the published spec demos work verbatim:

None of these add new capability — they make the existing capability spell better.


Putting it together

A production agent that uses every 0.16.0 surface looks like this:

const refundAgent = defineAgent(
  { name: 'refund-ops', system: '...', tools: [lookupOrder, issueRefund, requestReview] },
  deps,
)
  .withVerifier(schemaValidator(RefundDecisionSchema))
  .withVerifier(judgeValidator({ rubric: 'Decision must cite policy section', judge, threshold: 0.7 }))
  .withRetryPolicy({ maxAttempts: 3 })

const outcome = await refundAgent.runResumable(
  'Refund cust 42 for order #1729',
  {},
  {
    stateStore,
    requestApproval: async (req) => slack.askApproval(req),
  },
)

if (outcome.status === 'paused') {
  await persistResumeToken(outcome.resumeToken)
  return
}

// Auto-recorded; if QA wants to see what would have happened with a stricter system prompt:
const replay = await refundAgent.replayFrom(outcome.traceId, {
  fromStep: 'llm:iter_2',
  overrides: { 'llm:iter_2': { prompt: 'Be more skeptical. Reject by default.' } },
})

The agent verifies its output, pauses on destructive operations, gates approvals at the tool level, and remembers every iteration so you can rewind. All chained from defineAgent with no separate infrastructure.


Getting Started

npm install elsium-ai@0.16.0

Full documentation: Pause + Resume · Auto-record + Replay · Tool Contracts · Auto-approval · Fluent verification

If you missed the previous release, the 0.15.0 post covers the primitives this release is built on — capability tokens, signed execution proofs, CARG cascade routing, and the rest.


ElsiumAI is MIT licensed and open source. Created by Eric Utrera (@ebutrera9103).

E

Eric Utrera

Creator of ElsiumAI. Building production AI infrastructure.