DocumentationReference

HazelJS Agent OS

Agent OS is the production lifecycle layer around HazelJS agents. The Agent Runtime is the kernel; Agent OS is how you loop, govern, recover, observe, package, and test agents that ship.

Status: Available on npm — @hazeljs/agent, @hazeljs/testing, @hazeljs/benchmark, and @hazeljs/inspector.

Dual narrative

AudienceStory
App buildersAI-native TypeScript backend — HTTP, DI, agents, RAG, workflows
Agent platform teamsAgent OS — production lifecycle for agents that ship

Capability map

PillarAPI
Confidence loopoptions.loop — plan → execute → critique → validate
State machineAgentState + onState / onStateChange (RETRYING / BLOCKED)
Visual timelinegetTimeline · Inspector SSE · optional FileTimelineStore
Testing@hazeljs/testingdescribeAgent
Time travelTimeTravelDebugger / runtime.getTimeTravel()
Benchmarks@hazeljs/benchmark + hazel benchmark
Contractsoptions.contract (+ fallback agent)
PoliciesPolicyEngine — allow / deny / mask / require_approval
Recoveryoptions.recovery
Cost routingCostOptimizer / options.costRoute
SkillsopenApiToSkills / createSkillInvoker
Memory graphAgentMemoryGraph + GraphRAG bridge
EvolutionevolveSystemPrompt / runEvolutionLoop
SimulatorrunAgentSimulator
Knowledge freshnessassessKnowledgeFreshness
Agent DNAexportAgentDna / hotReloadDna / installAgentPackage
Digital twinrunDigitalTwin / shouldRunCanary
ConsensusrunConsensus
GovernanceGovernanceGate / options.governance

Execute with Agent OS options

await runtime.execute('support-desk', message, {
  loop: {
    maxIterations: 4,
    successScore: 90,
    stages: ['plan', 'execute', 'critique', 'validate'],
  },
  contract: {
    name: 'support-desk-slo',
    maxLatencyMs: 30_000,
    fallbackAgent: 'safe-desk',
  },
  recovery: {
    maxRetries: 2,
    fallbackAgent: 'safe-desk',
    steps: ['retry', 'fallback_agent', 'fail'],
  },
  costRoute: { qualityBias: 0.35, maxCostUsd: 0.05 },
  governance: {
    action: 'agent.execute',
    roles: ['agent:run'],
    residency: 'eu',
    compliancePacks: ['soc2'],
  },
});

Wire policies and governance on the runtime (or via AgentModule.forRoot):

import {
  AgentModule,
  PolicyEngine,
  defaultPiiMaskPolicies,
  GovernanceGate,
  defaultAgentGovernance,
  FileTimelineStore,
  CostOptimizer,
} from '@hazeljs/agent';

AgentModule.forRoot({
  runtime: {
    llmProvider,
    policyEngine: new PolicyEngine([
      ...defaultPiiMaskPolicies(),
      {
        id: 'refund-needs-approval',
        tool: 'processRefund',
        effect: 'require_approval',
        priority: 20,
      },
    ]),
    governanceGate: new GovernanceGate(defaultAgentGovernance()),
    costOptimizer: new CostOptimizer(),
    timelineStore: new FileTimelineStore('./.hazel/timeline.jsonl'),
  },
});

Register @Agent classes as @Service() providers so Inspector can list them and Run / Timeline work against AgentService.

DNA & marketplace

What is Agent DNA?

Agent DNA is a portable JSON snapshot of an agent definition — the parts that usually live as tribal knowledge across prompts, tool lists, and policy rules. Instead of “copy this class and hope,” you export a versioned artifact (format: hazeljs.agent.dna) you can review, store, share, and apply to a live runtime.

A DNA document typically includes:

FieldPurpose
name / versionIdentity and semver of the packaged agent
systemPrompt / modelBehavior and model preference
tools[]Tool names, descriptions, requiresApproval
policies[]Declarative policy rules (e.g. refund HITL)
contracts[]Optional SLO / I/O contracts bundled with the agent
exportedAtWhen the snapshot was produced

DNA is configuration + contract, not a full binary of your TypeScript handlers. Tool implementations still live in your app (or get wired via dynamic skill handlers). DNA tells the runtime which tools exist, which need approval, and which policies/prompts to apply.

Why it matters

Without DNA, shipping an agent change often means a full app redeploy. With DNA you can:

  1. Export the current definition for review / audit / backup
  2. Hot-reload prompt, model, metadata, and policies on a running agent
  3. Install a package from disk (CLI or API) into a live AgentRuntime
  4. Wrap DNA in a marketplace package (readme, keywords) for sharing

Export DNA

import { exportAgentDna, serializeDna, toMarketplacePackage, saveMarketplacePackage } from '@hazeljs/agent';

const dna = exportAgentDna({
  name: 'support-desk',
  description: 'Nordhelm Commerce support desk',
  systemPrompt,
  model: 'gpt-4o-mini',
  tools: [
    { name: 'lookupOrder', description: 'Look up order' },
    { name: 'processRefund', description: 'Process refund', requiresApproval: true },
  ],
  policies: [
    {
      id: 'refund-hitl',
      tool: 'processRefund',
      effect: 'require_approval',
      priority: 20,
      reason: 'Refunds require human approval',
    },
  ],
  contracts: [{ name: 'support-desk-slo', maxLatencyMs: 30_000, fallbackAgent: 'safe-desk' }],
  version: '1.0.0',
});

// Persist raw DNA
fs.writeFileSync('./dna/support-desk.dna.json', serializeDna(dna));

// Or wrap as a marketplace package
const pkg = toMarketplacePackage(dna, {
  readme: 'Nordhelm Commerce support desk agent',
  keywords: ['support', 'ecommerce', 'agent-os'],
});
saveMarketplacePackage(pkg, './dna/support-desk.marketplace.json');

In the Nordhelm starter: GET /api/support/dna and npm run dna:export.

Hot-reload and install

hotReloadDna patches a registered agent in place (system prompt, description, model, metadata, policies, dynamic tools). The agent must already exist on the runtime — DNA does not create a blank agent from nothing.

// From JSON string or object
runtime.hotReloadDna(JSON.stringify(dna));

// From a marketplace / DNA file on disk
import { installAgentPackage } from '@hazeljs/agent';
installAgentPackage(runtime, './dna/support-desk.marketplace.json');

// CLI (when published):
// hazel agent install ./support-desk.dna.json

What hot-reload updates today

  • systemPrompt, description, model, metadata (incl. dnaVersion)
  • Policy engine rules when policies are present
  • Dynamic tool registrations when tools include handlers / DNA skill wiring

What still requires a deploy

  • Changing TypeScript tool implementation bodies
  • Adding brand-new agent classes that were never registered

Marketplace package shape

{
  name: '@hazeljs/support-desk-agent',
  version: '1.0.0',
  description: '...',
  dna: { format: 'hazeljs.agent.dna', /* ... */ },
  readme: '...',
  keywords: ['hazeljs', 'agent', 'dna']
}

Install path works from files today. A hosted catalog is optional product work — the format and installAgentPackage / CLI path are the Agent OS surface.


Digital twin (canary)

What is a digital twin?

A digital twin (canary) runs a shadow agent alongside your primary agent on the same input, then compares outputs. The caller always gets the primary result. The twin is for observation: drift detection, safer rollouts, and comparing a conservative fallback (e.g. safe-desk) against the full desk (support-desk).

This is not A/B routing of user traffic. It is shadow execution + compare.

Why it matters

Without a twinWith runDigitalTwin
You change a prompt and hopeYou compare old vs new (or full vs safe) on live inputs
Twin failures can break the requestswallowTwinErrors: true keeps primary healthy
“Looks similar” is vibesJaccard similarity + explicit divergences[]

Typical uses:

  1. Canary a prompt / DNA change — primary = current, twin = candidate
  2. Conservative shadow — primary = full support desk, twin = lookup-only safe desk
  3. Sampled production traffic — use shouldRunCanary(0.1) so only ~10% of requests pay the twin cost

API

import { runDigitalTwin, shouldRunCanary } from '@hazeljs/agent';

if (shouldRunCanary(0.1)) {
  const { primary, twin, compare } = await runDigitalTwin({
    runPrimary: () => runtime.execute('support-desk', message, { /* Agent OS options */ }),
    runTwin: () => runtime.execute('safe-desk', message),
    matchThreshold: 0.4,      // default 0.85 — lower = looser match
    swallowTwinErrors: true, // twin errors become divergences, not thrown
  });

  // Always return primary to the user
  return { response: primary.response, twin: compare };
}

// Non-canary path: primary only
return runtime.execute('support-desk', message);

What compare contains

FieldMeaning
primaryOutput / twinOutputResponse strings compared
similarityJaccard token similarity 0..1
matchsimilarity >= matchThreshold and no twin error
primaryDurationMs / twinDurationMsLatency of each run
divergencesReasons such as similarity_below_0.4, twin_error:…, text_diff_soft_match

Primary runs first; twin runs after. Twin latency does not replace primary latency in the user-facing result, but total wall time is primary + twin unless you parallelize inside your own wrappers.

Nordhelm example

curl -s localhost:3040/api/support/chat \
  -H 'content-type: application/json' \
  -d '{"message":"Status of ORD-1003","canary":true}' | jq .twin

The starter’s canary: true flag calls runDigitalTwin with support-desk as primary and safe-desk as twin — useful to see when the conservative agent would answer differently (e.g. no refunds).

Tips

  • Start with a lower matchThreshold for free-form support answers; raise it for deterministic tool-heavy flows.
  • Log compare.divergences and similarity to metrics — treat twin as a sensor, not a gate, until you trust it.
  • Pair with DNA: hot-reload a candidate prompt onto a twin agent name, canary against primary, then promote DNA to primary when similarity stays high.

Observe

EndpointDescription
GET /__hazel/agents/:name/streamSSE live timeline
GET /__hazel/agents/:name/timelineJSON replay
POST /__hazel/agents/:name/runRun from Inspector UI
runtime.getTimeline({ agentName: 'support-desk' });

Test

import { describeAgent, runAgentSuite, expectTools } from '@hazeljs/testing';

const suite = describeAgent('Support Desk', ({ test }) => {
  test('tracks ORD-1001', async ({ run }) => {
    const r = await run('Where is my package for ORD-1001?');
    expectTools(r, ['trackShipment'], 0.5);
  });
});

See @hazeljs/testing.

Showcase: Nordhelm Support Desk

Concrete production example — e-commerce support with orders, tracking, refunds (HITL), loop, policies, contracts, recovery, cost, governance, DNA, twin, and Inspector.

Repo folder: hazeljs-agent-os-starter (sibling to hazeljs in the monorepo workspace).

cd hazeljs-agent-os-starter
npm install
npm run demo          # three tickets, DemoLLM (no API key)
npm run dev           # http://localhost:3040
curl -s localhost:3040/api/support/chat \
  -H 'content-type: application/json' \
  -d '{"message":"Where is my package for ORD-1001?"}'

curl -s localhost:3040/api/support/chat \
  -H 'content-type: application/json' \
  -d '{"message":"I want a refund for ORD-1002","canary":true}'

Open /__hazel for Agents → Run / Timeline. Sample orders: ORD-1001 (shipped), ORD-1002 (refund), ORD-1003 (processing).