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
| Mode | Behavior |
|---|---|
disabled | Off |
observe-only | Record only |
suggest | Recommendations only |
approval-required | HITL before effects |
governed-auto | Execute 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);
}
| Effect | Gatekeeper classification (typical) |
|---|---|
read-only | read |
idempotent / reversible | write |
irreversible | destructive |
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:
- Capability is registered
- Service grants include the capability id
- Gatekeeper policy allows (or approval consumed)
- Tenant context is trusted (framework identity)
- Budgets / rate limits / loop protection permit
- 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 viacreateAgentBackedRunStore. - 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/@Toolapps are unchanged. - Prefer
@Autonomousfor 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