HazelJS Decision Package
@hazeljs/decision is the native Decision Runtime for HazelJS Agent OS — bounded, typed, auditable judgment as a first-class primitive.
It is not a thin wrapper around Jev, OpenAI, Gemini, or another external model. The default provider is hazel-agent: an evidence → candidates → judge → critic → confidence pipeline built from HazelJS primitives. External providers are optional adapters; every result still passes policy and Gatekeeper.
Models propose. Agents evaluate. Critics challenge. Confidence informs. Policies govern. Skillgate classifies. Gatekeeper authorizes. Durable Kernel executes. Agent VM isolates effects. Agent OS observes.
Reasoning ≠ Decision ≠ Policy ≠ Authorization ≠ Execution. Confidence is evidence for policy routing, never permission to mutate the world. High confidence never bypasses Skillgate / Agent Gatekeeper.
Quick Reference
- Purpose: Bounded choice over a closed set (
choices: [...] as const) with risk routing, confidence, deterministic policy, Gatekeeper auth, durable HITL, and optional capability execution. - When to use: Incident remediation, refund paths, ticket routing, fraud/risk triage, “needs human?” gates — anywhere an agent must pick from a known set instead of free-form LLM JSON.
- Key APIs:
createDecisionRuntime,decide,resumeFromHuman,evaluate,DecisionLab/createDecisionLab,createGovernedDecisionRuntime,DecisionModule/DecisionService,HazelDecisionAgent,MockDecisionProvider,createOpenAiDecisionProvider/createGeminiDecisionProvider/createJevDecisionProvider/createLocalDecisionProvider,createEnsembleDecisionProvider,DecisionCache,DecisionHistory,fitCalibrationFromHistory,projectDecisionFlow,createCostAwareRouter,@Decision. - Dependencies: Peer
@hazeljs/agent. Optional peers:@hazeljs/agent-gatekeeper,@hazeljs/skillgate,@hazeljs/audit,@hazeljs/ai,@hazeljs/core,@hazeljs/guardrails,@hazeljs/observability,@opentelemetry/api. - Common mistakes: Treating confidence as authorization; letting models invent choices outside the closed set; skipping Gatekeeper when
execute: true; auto-feeding history into prompts; assuming calibration authorizes; expecting Flow projection to replaceDecisionRuntime.
Installation
npm install @hazeljs/decision @hazeljs/agent
# recommended for production execution:
npm install @hazeljs/agent-gatekeeper @hazeljs/skillgate
# optional:
npm install @hazeljs/ai @hazeljs/audit @hazeljs/cli
Or:
hazel add decision
Minimal example
import { createDecisionRuntime } from '@hazeljs/decision';
const decisions = createDecisionRuntime();
const result = await decisions.decide({
objective: 'Choose the safest production remediation',
state: {
errorRate: 0.38,
previousErrorRate: 0.01,
deploymentAgeMinutes: 4,
failedHealthChecks: 8,
},
choices: ['retry', 'rollback', 'escalate', 'ignore'] as const,
});
// result.decision: 'retry' | 'rollback' | 'escalate' | 'ignore'
Architecture
APPLICATION
│
▼
decisions.decide({ objective, state, choices, risk, … })
│
▼
DECISION RUNTIME
│
▼
COMPLEXITY / RISK ROUTER
├── fast → judge
├── standard → evidence → judge → confidence
├── deliberate → evidence → candidates → judge → critic → confidence
├── human-required → … → HITL
└── rules → DNA / hybrid rules path
│
▼
DETERMINISTIC POLICY (allow | critique | review | deny | …)
│
▼
GATEKEEPER (authorization) ← only if execute: true
│
▼
DURABLE CHECKPOINTS + receipts (@hazeljs/agent)
│
▼
CAPABILITY HANDLER (optional; never without auth)
flowchart TD App[Application] --> Decide["decide()"] Decide --> Router[ComplexityRouter] Router --> Provider[DecisionProvider] Provider --> Conf[Confidence] Conf --> Policy[PolicyEvaluator] Policy -->|allow| GK[Gatekeeper] Policy -->|review| HITL[HumanTask] Policy -->|deny| Stop[Denied] GK -->|allow| Cap[CapabilityHandler] GK -->|deny| Stop
What this package reuses (does not reimplement)
| Concern | Primitive |
|---|---|
| Agent Runtime / DNA / HITL / checkpoints | @hazeljs/agent |
| Call-time authorization | @hazeljs/agent-gatekeeper |
| Skill risk class (floor only) | @hazeljs/skillgate |
| Effect isolation | Host effectGate / @hazeljs/agent-vm (documented, not duplicated) |
| Spans | Optional OTel tracer hazeljs |
Skillgate curates and classifies skills. Gatekeeper authorizes each invocation. The decision runtime never collapses those layers.
decide() request shape
await decisions.decide({
name: 'incident-remediation', // optional DNA / registry key
objective: 'Choose the safest remediation',
state: incident, // data — prompt-injection resistant
choices: ['retry', 'rollback', 'escalate', 'ignore'] as const,
risk: 'high', // or { level, impact, reversible }
strategy: 'auto', // fast | standard | deliberate | human-required | rules
provider: 'hazel-agent', // or mock / ensemble / openai / …
policy: { confidence: { high: 0.95, medium: 0.7 } },
execute: false, // true → Gatekeeper + capability after allow
cache: false, // opt-in; requires runtime cache
calibrate: false, // opt-in reporting remap
context: { tenantId: 'acme', agentId: 'incident-agent' },
});
Result (selected fields)
| Field | Meaning |
|---|---|
decision | Typed choice from choices |
confidence / confidenceDetail | Score + type (heuristic | ensemble | calibrated …); never auth |
evidence / candidates / critic | Pipeline artifacts |
strategy | Resolved execution strategy |
policy.outcome | allow | deny | critique | review | … |
execution | Whether capability was authorized / invoked |
hitl | Human task id / status when review required |
provenance.stages | Stage names for Lab / audit |
status | Run machine status (COMPLETED, WAITING_FOR_HUMAN, DENIED, …) |
Providers
| Provider | Role |
|---|---|
hazel-agent | Default. Native offline pipeline; optional LLM judge/critic via generateObject |
mock | Test double (MockDecisionProvider) |
auto | Router hook (defaults toward hazel-agent / cost profiles) |
ensemble | Majority / weighted / unanimous / risk-sensitive fan-out |
openai / gemini / jev / local | Thin adapters via create*DecisionProvider — still governed |
import {
createGovernedDecisionRuntime,
createOpenAiDecisionProvider,
createJevDecisionProvider,
} from '@hazeljs/decision';
const decisions = createGovernedDecisionRuntime({
generateObject: aiService, // @hazeljs/ai or duck-typed
agentRuntime,
auditService,
gatekeeper,
});
decisions.providers.register(createOpenAiDecisionProvider(aiService));
decisions.providers.register(createJevDecisionProvider({ decide: jevDecide }));
On model failure, hazel-agent falls back to the heuristic judge/critic (never to “allow”). Invalid choices outside the closed set are rejected.
Complexity / risk router
| Risk | Default strategy | Stages |
|---|---|---|
low | fast (small choice sets) | judge |
medium | standard | evidence → judge → confidence |
high | deliberate | + candidates + critic |
critical | human-required | + HITL |
Risk is trusted configuration. Models cannot lower it. Skillgate skill class may raise the risk floor only.
Confidence & policy
interface DecisionConfidence {
value: number; // [0, 1]
type: 'provider' | 'heuristic' | 'ensemble' | 'calibrated' | 'self-reported';
calibrated: boolean;
components?: {
candidateSeparation?: number;
criticAgreement?: number;
evidenceCoverage?: number;
providerConfidence?: number;
};
}
Default confidence bands (overridable via DNA / request policy):
| Band | Policy action |
|---|---|
≥ high (default 0.95) | allow (eligible for Gatekeeper) |
≥ medium (default 0.7) | critique |
| < medium | review (HITL) |
Even with confidence 0.999 and policy allow, Gatekeeper can deny. See package example high-confidence-denied.ts.
Decision DNA
Optional field on Agent DNA (format: 'hazeljs.agent.dna'):
exportAgentDna({
name: 'incident-agent',
tools: [],
decisions: {
'incident-remediation': {
version: '3',
objective: 'Choose the safest remediation',
choices: ['retry', 'rollback', 'escalate', 'ignore'],
risk: { level: 'high' },
confidence: { high: 0.95, medium: 0.7 },
evidence: { required: ['error-rate-change'], projections: [...] },
scoring: { rollback: [{ evidenceKey: 'error-rate-change', weight: 0.4 }] },
execution: {
rollback: { capability: 'deployment.rollback' },
},
},
},
});
Register with runtime.registry.register(...) or registry.registerFromDna(dna). DNA without decisions remains valid.
HITL & durable execution
Policy review / strategy human-required creates a HumanTask and checkpoints WAITING_FOR_HUMAN via @hazeljs/agent stores.
await decisions.resumeFromHuman({
decisionId,
runId,
action: 'override', // approve | reject | override
decision: 'escalate',
actor: 'ops-lead',
reason: 'Production rollback currently prohibited',
execute: true,
});
Overrides are audited. Resume loads the execution receipt and will not double-invoke the capability. Replay never re-executes side effects.
Decision Lab
import { createDecisionRuntime, createDecisionLab } from '@hazeljs/decision';
const lab = createDecisionLab(createDecisionRuntime());
const run = await lab.run({ objective, state, choices: [...] as const, risk: 'high' });
// run.graph — pipeline nodes for UI
const cmp = await lab.compare(request, ['hazel-agent', 'mock']);
// cmp.executionForbidden === true
Agent Office hosts the control plane at /office/decisions (Run, Compare, Flow graph, History, Calibration).
Ensemble, cache, history, routing
import {
createEnsembleDecisionProvider,
DecisionCache,
DecisionHistory,
createCostAwareRouter,
} from '@hazeljs/decision';
runtime.providers.register(
createEnsembleDecisionProvider({
providers: [hazel, openai],
strategy: 'majority', // weighted | unanimous | risk-sensitive
})
);
const runtime = createDecisionRuntime({
cache: new DecisionCache({ ttlMs: 30_000 }),
history: new DecisionHistory(),
});
await runtime.decide({ ..., cache: true }); // opt-in only; never caches side effects
runtime.providers.setRouterHook(
createCostAwareRouter([
{ name: 'local', costTier: 'economy' },
{ name: 'hazel-agent', costTier: 'balanced' },
{ name: 'openai', costTier: 'premium', minRisk: 'high' },
])
);
history.trends(); // aggregates for Lab — never auto-injected into prompts
Flow graph projection
import { projectDecisionFlow, buildDecisionFlowDefinition } from '@hazeljs/decision';
const graph = projectDecisionFlow({ name: 'incident-remediation', risk: 'high' });
const stub = buildDecisionFlowDefinition(graph); // optional @hazeljs/flow registration
DecisionRuntime remains the authority. Flow projection is scaffolding / UI, not a second decision engine.
Calibration (opt-in)
import { fitCalibrationFromHistory, DecisionHistory } from '@hazeljs/decision';
const history = new DecisionHistory();
const runtime = createDecisionRuntime({ history });
// decide + evaluate({ expectedDecision }) …
runtime.setCalibration(fitCalibrationFromHistory(history));
await runtime.decide({ ..., calibrate: true }); // reporting only
Calibrated scores never authorize execution and are never auto-fed into prompts.
Module / DI
import { DecisionModule, DecisionService, createGovernedDecisionRuntime } from '@hazeljs/decision';
DecisionModule.forRoot({ generateObject, agentRuntime, gatekeeper, auditService });
// or without DI:
const decisions = createGovernedDecisionRuntime({ generateObject, gatekeeper });
DecisionService.decide(...) delegates to the wired DecisionRuntime.
Observability
In-process metrics include:
hazeljs_decisions_totalhazeljs_decision_durationhazeljs_decision_confidencehazeljs_decision_critic_totalhazeljs_decision_review_totalhazeljs_decision_override_totalhazeljs_decision_denied_totalhazeljs_decision_execution_totalhazeljs_decision_failures_total
Optional OTel span: decision.run (tracer hazeljs). Raw state is not recorded by default.
Security
- State is data (prompt-injection resistant).
- Choices are a closed set — invalid model output is rejected.
- Risk / policy / capabilities are not model-writable.
- Cross-tenant evidence is keyed by decision id +
tenantIdon provenance. - Replay never re-executes side effects.
- Missing Gatekeeper ⇒ execution denied when
execute: true. - History / calibration never auto-inject into prompts.
CLI
hazel decision run --risk high
hazel decision compare --providers hazel-agent,mock
hazel decision flow --risk critical
hazel decision lab
Examples (package)
npx tsx examples/incident-remediation.ts
npx tsx examples/refund-hitl.ts
npx tsx examples/high-confidence-denied.ts
npx tsx examples/prompt-injection.ts
npx tsx examples/decision-lab.ts
npx tsx examples/flow-and-calibration.ts
npx tsx examples/ensemble.ts
Progressive complexity
decide()-
- provider
-
- risk + confidence
-
- critic + policy
-
- Gatekeeper + durable HITL
-
- Decision DNA + Lab + evaluation / calibration
Level 1 users are not forced into Level 6 concepts.
Related
- Guide: Decision Runtime
- Agent OS guide
- Skillgate — which APIs become skills
- Agent Gatekeeper — per-invocation authorize
- Agent package — DNA, HITL, durable runs
- Flow package — durable graphs (optional projection target)
Intentionally never
- Automatic prompt injection of historical outcomes
- Confidence as authorization
- Collapsing Skillgate + Gatekeeper into the decision model