@nexusaiframework/runtime
v0.2.0
Published
Durable, model-agnostic runtime for AI agents: persistent runs, human approval gates, pre-flight budgets, OpenTelemetry export, deterministic replay, and the open OAR run format. Zero runtime dependencies.
Maintainers
Readme
Nexus
Every AI agent run should be a file you can audit, replay, and resume.
🌐 nexus-agent-runs.netlify.app, where the OAR schemas are hosted too (run · step).
Nexus is two things:
- The Open Agent Run (OAR) format: a small, vendor-neutral, versioned spec (spec/SPEC.md) for durable agent runs: the state machine, the messages, the approvals, the costs, and an append-only audit journal. Public domain (CC0). Every framework today traps this data in proprietary shapes; OAR sets it free.
- The reference runtime: a durable, model-agnostic agent runtime with zero runtime dependencies that produces and consumes that format.
Every agent framework can call an LLM in a loop. Almost none of them answer the questions that actually matter in production:
- What happens when the process crashes mid-run? → Nexus runs are persistent and resumable.
- How do I stop an agent from doing something dangerous? → Approval gates pause the run until a human approves, denies, edits the arguments before they run, or answers the call directly.
- How do I stop an agent from burning $400 overnight? → Per-run token and USD budgets halt execution, checked before each model call.
- What exactly did the agent do? → Every LLM call, tool execution, and decision is journaled, and the whole run exports as OpenTelemetry spans to Datadog, Grafana, or Langfuse.
- Which customer did this run cost money for? → Tag a run and per-run cost becomes per-customer cost.
- How do I debug or regression-test agent behavior? → Any finished run replays deterministically from its own record, offline, for $0.00, and can be re-run against a new model or prompt with divergences reported.
- What about my MCP servers? → Point Nexus at any unmodified MCP server (stdio or HTTP) and its tools get durability, approvals, and budgets wrapped around them. Or expose Nexus itself as an MCP server so Claude or Cursor can drive durable runs.
- Which model vendor am I married to? → None. Anthropic, OpenAI, and Ollama (local) adapters ship in the box with token streaming and cancellation; the provider interface is ~10 lines.
Nexus is the reliability layer, not another orchestration DSL. Agents are a definition (provider + model + prompt + tools); the runtime makes their execution durable, governable, and observable.
npm install # zero runtime dependencies: TypeScript in, plain Node out
npm test # 171 tests
npm run demo # full lifecycle offline, no API keys neededQuick start
import {
NexusRuntime, FileStorage, AnthropicProvider,
defineAgent, defineTool,
} from '@nexusai/runtime';
const sendEmail = defineTool({
name: 'send_email',
description: 'Send an email',
parameters: {
type: 'object',
properties: { to: { type: 'string' }, subject: { type: 'string' }, body: { type: 'string' } },
required: ['to', 'subject', 'body'],
},
requiresApproval: true, // ← human must approve before this ever executes
async execute({ to }) { /* ... */ return `sent to ${to}`; },
});
const runtime = new NexusRuntime({ storage: new FileStorage('.nexus') });
runtime.register(defineAgent({
name: 'assistant',
provider: new AnthropicProvider(), // or OpenAIProvider / OllamaProvider
model: 'claude-sonnet-4-5',
systemPrompt: 'You are a helpful operations assistant.',
tools: [sendEmail],
maxTurns: 10,
budget: { maxUsd: 0.50, maxTotalTokens: 100_000 }, // ← hard caps, enforced per run
}));
const run = await runtime.run('assistant', 'Email the team a status update.');
if (run.status === 'waiting_approval') {
// ...maybe hours later, maybe from a different process entirely:
const finished = await runtime.approve(run.id);
console.log(finished.result);
}Every run is a persistent state machine: running → waiting_approval → completed | failed | halted_budget | halted_max_turns | cancelled. Kill the process at any point and runtime.resume(runId) picks up where it left off. Tool results already recorded are never re-executed on resume.
CLI
nexus run assistant "Summarize today's tickets" --tag customerId=acme # start + follow, tagged
nexus runs --status waiting_approval --tag customerId=acme # filter by what needs a human, and by tag
nexus approve run_abc123 call_xyz # unblock it (from any process)
nexus approve run_abc123 call_xyz --edit '{"amountUsd":200}' # correct the args, then run
nexus respond run_abc123 call_xyz --message "the balance is $412" # answer without running the tool
nexus deny run_abc123 call_xyz --reason "wrong recipient"
nexus resume run_abc123 # continue after crash/restart
nexus runs --status waiting_approval --stale 30m # approvals past their SLA
nexus cancel-all --agent support # kill switch: stop everything
nexus show run_abc123 --steps # full audit journal
nexus serve --token $NEXUS_DASHBOARD_TOKEN # dashboard + JSON API
nexus mcp # expose the runtime as an MCP serverThe CLI loads nexus.config.mjs (see examples/nexus.config.mjs), which exports a configured NexusRuntime.
Approvals that fit how teams actually work
// Policy rules decide the easy cases; humans decide the rest.
runtime.register(defineAgent({
name: 'support', provider, model, tools,
approvalPolicy: ({ toolName, arguments: args }) =>
toolName === 'issue_refund' && Number(args.amountUsd) < 50 ? 'approve' : 'ask',
}));
// Every decision, human or policy, lands in the audit journal as decidedBy.
// A throwing policy fails safe to 'ask', never open.
// Four ways to resolve a paused call, all journaled:
await runtime.approve(runId, callId); // let it run
await runtime.approve(runId, callId, { amountUsd: 200 }); // edit the args first, then run
await runtime.deny(runId, callId, 'wrong account'); // block it; the agent sees the denial
await runtime.respond(runId, callId, 'the balance is $412'); // answer directly; the tool never runs
// Deliver approval requests and run outcomes where people live:
import { attachWebhook } from '@nexusai/runtime';
attachWebhook(runtime, { url: process.env.SLACK_WEBHOOK_URL!, format: 'slack' });
attachWebhook(runtime, { url: 'https://ops.internal/hooks/nexus' }); // structured JSON
// And a self-hosted dashboard (zero deps, one process):
// nexus serve --token $NEXUS_DASHBOARD_TOKEN → http://127.0.0.1:3838
// Live run list, transcripts, journals, and approve/edit/deny/respond buttons.
// The token is a shared-secret gate; still keep it behind your SSO/proxy.Operating it in production
The library is the easy 20%. These are the runtime-level pieces that make an agent safe to actually run unattended (the perimeter, SSO, SIEM, secrets, egress, is still yours):
// A kill switch on-call can hit in one command:
await runtime.cancelAll(); // stop every non-terminal run (cross-process)
await runtime.cancelAll({ agent: 'support' }); // or just one agent
runtime.disableAgent('support', 'incident #42'); // refuse new runs until re-enabled
// Notice when approvals pile up (the quiet way HITL agents die):
import { watchStaleApprovals } from '@nexusai/runtime';
watchStaleApprovals(runtime, {
olderThanMs: 30 * 60_000, // 30-minute SLA
onStale: (run) => pagerduty.trigger(run.id, run.waitingMs),
});
// nexus runs --status waiting_approval --stale 30m // the SLA-breach view from the CLI
// Scrub PII from the audit journal before it ships to your SIEM. The live run
// record keeps the real content (the runtime needs it to resume); only the
// journal copy is redacted, so pair with encryption-at-rest on your storage.
const runtime = new NexusRuntime({
storage,
redactStep: (type, detail) => redactPii(detail),
});The honest split: Nexus gives you the audit journal, the human gates, per-run budgets, the kill switch, and stale-approval signals. You bring SSO/RBAC (an IdP + proxy in front of nexus serve), immutable log retention (ship the journal to your SIEM), scoped short-lived credentials (your tools fetch them server-side; the agent never holds a raw token), and network-egress control. Nexus is the governance core, not the whole perimeter.
Durable MCP
MCP standardized how agents reach tools; Nexus makes those tool calls safe to run unattended, with no changes to the server:
import { McpClient, mcpTools } from '@nexusai/runtime';
const client = await McpClient.connect({
command: 'npx', args: ['-y', '@your/mcp-server'], // stdio…
// …or url: 'https://mcp.example.com/mcp' // …or Streamable HTTP
});
const tools = await mcpTools(client, {
requiresApproval: ['issue_refund', 'delete_record'], // policy in one line
namespace: 'crm',
});
runtime.register(defineAgent({ name: 'support', provider, model, tools, budget: { maxUsd: 1 } }));Every MCP call is journaled, budget-metered, resumable after a crash, and, where you say so, held for human approval. npx tsx examples/02-durable-mcp.ts demos the whole flow offline. The client is zero-dependency JSON-RPC over stdio or Streamable HTTP (SSE responses included).
Replay: turn production runs into test fixtures
A finished OAR run contains everything needed to re-execute it without the network:
import { replayRun } from '@nexusai/runtime';
// exact replay: verifies the record reproduces byte-for-byte, offline, $0.00
const report = await replayRun(run);
report.identical; // true
// regression replay: recorded tools + a LIVE candidate model or new prompt
const regression = await replayRun(run, {
provider: new AnthropicProvider(),
model: 'claude-sonnet-5',
systemPrompt: newPrompt,
});
regression.divergences; // where the new behavior differs from productionnexus replay <runId> does the exact-replay check from the CLI. Yesterday's incident becomes today's regression test.
Why this exists
The gap is real and specific. As of mid-2026:
- LangGraph, CrewAI, Mastra, VoltAgent checkpoint at step boundaries but none enforce token/USD budgets natively.
- Temporal, Restate, Inngest, DBOS offer serious durable execution, by running a server cluster, a licensed binary, or your Postgres, and none of them meter model spend either.
- MCP standardized how agents reach tools, but its own roadmap still lists durability, retry semantics, and enterprise controls as open problems.
- Approval-gateway products are appearing (they validate the need), but they proxy traffic; they don't make execution durable.
Nexus takes the position that one small, self-hosted, MIT-licensed library should give you all three (durability, human approval, and spend control) with nothing to deploy and no vendor to trust. node:fs or SQLite for persistence, fetch for providers, zero runtime dependencies.
Storage
| Adapter | Durability | Requirements |
|---|---|---|
| MemoryStorage | none (tests/dev) | none |
| FileStorage | JSON + append-only JSONL journal | any Node ≥ 20 |
| SqliteStorage | single SQLite file | Node ≥ 22 (built-in node:sqlite), import from @nexusai/runtime/sqlite |
| PostgresStorage | shared database, multi-node | bring any pg-compatible client, import from @nexusai/runtime/postgres |
The Storage interface is 5 methods; Redis/S3 adapters are straightforward contributions. PostgresStorage imports no driver of its own: you pass a pg Pool (or anything with a query method), so the zero-dependency guarantee holds.
Observability
Every run exports as OpenTelemetry spans following the GenAI semantic conventions, zero dependencies, over OTLP/HTTP:
import { attachOtel } from '@nexusai/runtime';
attachOtel(runtime, { endpoint: 'http://localhost:4318' });
// invoke_agent → chat → execute_tool spans, with gen_ai.usage token counts,
// land in Datadog, Grafana, Langfuse, or any OTLP backend. No vendor SDK.
// Prompt/argument content is off by default (the convention's privacy default);
// pass { captureContent: true } to include it.The runtime is also an EventEmitter: run:start, llm:call, llm:response, llm:token (streaming), tool:start, tool:end, approval:required, retry, run:complete, run:failed, run:halted. Wire them to your own logger or metrics. The step journal (nexus show <id> --steps) is the permanent audit record.
Cost attribution with tags
await runtime.run('support', 'Refund order #4417', { tags: { customerId: 'acme', tier: 'enterprise' } });
// Tags ride through the record, journal, webhooks, and OTel spans.
const acme = await runtime.listRuns({ tags: { customerId: 'acme' } });
const spend = acme.reduce((sum, run) => sum + run.usage.costUsd, 0); // per-customer cost, not per-invoiceNexus as an MCP server
Expose the runtime's controls as MCP tools so Claude, Cursor, or any MCP client can drive durable runs:
nexus mcp # serves run/list/get/approve/deny/respond/resume/cancel over stdioThe OAR ecosystem
The format is a real standard, not just our storage shape:
- Validators ship in the box:
validateRun,validateStep,validateJournalcheck any producer's documents against the spec, zero dependencies, no JSON-Schema engine required. - A Python reader (
python/, stdlib only) is a second, independent implementation:python -m oar validate <run.json> <steps.jsonl>andpython -m oar timeline .... Two implementations of one public-domain format is what makes it portable.
Status & roadmap
Today (v0.2, working and tested): the OAR format spec with JSON Schemas, conformance tests, in-box validators, and an independent Python reader; durable run loop; approval gates with policy rules plus edit-before-approve and respond; pre-flight token/USD budgets; token streaming and in-flight cancellation; OpenTelemetry export; run tags for cost attribution; Slack/webhook notifications; self-hosted dashboard (nexus serve, optional token auth); Nexus-as-MCP-server (nexus mcp); the operability kit (kill switch via cancelAll/disableAgent, stale-approval SLA watcher, journal redaction hook); retries with backoff; crash resume; deterministic + regression replay; MCP client adapter (stdio + Streamable HTTP); three provider adapters + scriptable mock; four storage adapters (memory, file, SQLite, Postgres); CLI; 171 tests; CI. Not yet published to npm; install from source.
Next (in order):
- Standalone
oarCLI (oar validate <file>) wrapping the shipped validators for use without installing the runtime. - Publish the Python reader to PyPI.
- Track the MCP 2026-07-28 revision (stateless core, Tasks extension) in the client adapter once it is final.
- Redis and S3 storage adapters.
Things we will not claim until they're real: a hosted cloud, "zero-copy" anything, adoption numbers we cannot cite.
Contributing
See CONTRIBUTING.md. npm test must pass; new behavior needs a test.
