Documentation•Reference

Autonomous Application Runtime

HazelJS AAR makes governed autonomy a native option for ordinary @Injectable() services.

Autonomy does not mean unrestricted. LLMs are optional. Decisions propose; Gatekeeper authorizes. Side effects require explicit grants. Replays are dry-run by default. Durable execution does not claim exactly-once for arbitrary external effects. Existing apps keep working with negligible overhead when AAR is unused.

What it is not

  • Not a second @Agent / LLM loop
  • Not a replacement for Gatekeeper (AAR uses Gatekeeper)
  • Not Skillgate (Skillgate curates which APIs become tools)
  • Not Decision Runtime alone (optional Decision Plane can propose; AAR still authorizes)
  • Not roadmap @AutonomousOptimization (HTTP self-tuning — separate, unimplemented)

Architecture

@Service @Autonomous
  → @Observe → AutonomousRuntime
    → decision engine (rules /​ optional Decision Plane)
      → AgentGatekeeper
        → HITL when required
          → @Capability method
            → durable /​ memory run + timeline
Observation ≠ Decision ≠ Policy ≠ Authorization ≠ Execution

Install

npm install @hazeljs/​autonomous @hazeljs/​agent-gatekeeper
# optional peers: @hazeljs/​agent @hazeljs/​agent-vm @hazeljs/​decision
hazel add autonomous --setup

Quick start

import { Injectable, Autonomous, Observe, Capability, HazelModule } from '@hazeljs/​core';
import { AutonomousModule, AutonomousRuntime } from '@hazeljs/​autonomous';

@Injectable()
@Autonomous({
  id: 'order-operations',
  mode: 'governed-auto',
  capabilities: ['orders.retry'],
  objective: 'Maintain reliable order processing',
})
export class OrderOperationsService {
  @Observe('orders.failed')
  onFailure(event: { orderId: string }) {
    return event;
  }

  @Capability({
    id: 'orders.retry',
    effect: 'idempotent',
    approval: 'auto',
    idempotencyKey: true,
  })
  retryOrder(input: { orderId: string }) {
    return this.orders.retry(input);
  }
}

@HazelModule({
  imports: [AutonomousModule.forRoot({ observability: { enabled: true } })],
  providers: [OrderOperationsService],
})
export class AppModule {}
const runtime = container.resolve(AutonomousRuntime);
const [runId] = await runtime.emit('orders.failed', { orderId: 'ORD-1' }, { tenantId: 't1' });
/​/​ approval-required mode:
await runtime.resume(runId!, { approved: true, actor: 'ops' });

Modes

ModeBehavior
disabledOff
observe-onlyRecord only
suggestRecommendations only
approval-requiredHITL before effects
governed-autoExecute granted + Gatekeeper-allowed capabilities

Default capability grants: none.

Capabilities & effects

@Capability({
  id: 'orders.retry',
  effect: 'idempotent',
  approval: 'auto',
  idempotencyKey: true,
})
retryOrder(input: { orderId: string }) {
  return this.orders.retry(input.orderId);
}
EffectGatekeeper classification (typical)
read-onlyread
idempotent / reversiblewrite
irreversibledestructive

Irreversible capabilities cannot auto-retry without idempotencyKey or reconcile (HAZEL_AUTONOMOUS_RETRY_UNSAFE).

Security (default deny)

A capability executes only when all of the following hold server-side:

  1. Capability is registered
  2. Service grants include the capability id
  3. Gatekeeper policy allows (or approval consumed)
  4. Tenant context is trusted (framework identity)
  5. Budgets / rate limits / loop protection permit
  6. Effect / simulation barriers permit

Model-produced permission claims are ignored.

Approvals

Approvals bind capability id, argument digest, tenant, and expiry. Argument changes invalidate prior approval (argumentsHash / Gatekeeper fingerprint).

Tenant isolation

Pass resolveTenantId (or trusted request context). It overrides any tenantId inside observation payloads.

Dry-run / simulation

simulation: true and dry-run replay never invoke live capability side effects. Use simulate() for fixtures; evaluateOutcome() returns recommendations only (never auto-weakens policy).

Secrets

Observation payloads are redacted before timeline/audit persistence by default (redactPayload).

Durability & recovery

  • Use Agent createDurableRunStore / SQL for production durability via createAgentBackedRunStore.
  • Memory store is for tests and local demos.
  • Uncertain effects after crash enter reconcile — do not blindly re-issue irreversible operations.
  • Inspect with runtime.inspect, timeline, diff, listRuns, recover.

Loop protection

Depth, cooldown, eventId dedupe, and per-run action ceilings. Override per service or globally via loopProtection / AutonomousModule options.

CLI

hazel add autonomous --setup
hazel agent runs list
hazel agent runs inspect <run-id>
hazel agent runs timeline <run-id>
hazel agent runs replay <run-id> --dry-run
hazel agent runs diff <run-a> <run-b>

Migration

  • Existing @Agent / @Tool apps are unchanged.
  • Prefer @Autonomous for ordinary services that should observe domain events without an LLM loop.
  • Do not confuse with NestJS-style DIY assemblies of bus + workflow + AuthZ + HITL — AAR is one control plane over Agent OS primitives. See competitive DX notes in the monorepo guide autonomous-vs-nestjs.md.

Production checklist

  • Explicit capability grants only
  • Gatekeeper in enforce + default deny
  • Trusted tenant resolver
  • Durable run store (not memory) in multi-replica
  • Irreversible caps have reconcile / idempotency
  • Replay only with --dry-run