ACAgentic Craft

Foundation~30 minHazelJS

Safe Writes, Approvals, and HITL

Add a refund tool the right way: side-effect classes, requiresApproval, PolicyEngine, and durableSuspend so humans can approve without hanging workers.

Authors
editorial-team
Published
Last reviewed
Progress is stored locally in this browser.

Direct answer

Reads can be autonomous. Irreversible writes need a human in the loop on HazelJS: requiresApproval: true, usually a PolicyEngine rule, and for production waits durableSuspend + approveAndResume.

Pattern: /patterns/human-approval-gate. Guide: /guides/add-human-approval-without-breaking-the-run.

Side-effect classes (memorize these)

ClassExamplesDefault posture
ReadlookupOrder, searchAutonomous, still validate args
Reversible writecreate draft ticketOptional approval; idempotent ids
Irreversiblerefund, delete, prod deployAlways approval + durable suspend

Step 1Add a refund tool (do not auto-run it)

typescript
@Tool({
  name: 'processRefund',
  description: 'Refund an order — requires human approval',
  requiresApproval: true,
  parameters: [
    { name: 'orderId', type: 'string', required: true },
    { name: 'amount', type: 'number', required: true },
  ],
})
async processRefund(input: { orderId: string; amount: number }) {
  // Adapter must be idempotent (same key → same outcome)
  return { ok: true, ...input, status: 'refunded' };
}

Update the system prompt: “Propose refunds only when the user asks. Never invent amounts.”

Step 2Reinforce with PolicyEngine

typescript
import {
  AgentRuntime,
  PolicyEngine,
  defaultPiiMaskPolicies,
} from '@hazeljs/agent';

const runtime = new AgentRuntime({
  llmProvider,
  policyEngine: new PolicyEngine([
    ...defaultPiiMaskPolicies(),
    {
      id: 'refund-needs-approval',
      tool: 'processRefund',
      effect: 'require_approval',
      priority: 20,
    },
  ]),
});

Prompts remind; policy enforces.

Step 3Durable wait (production shape)

Holding an HTTP worker open while a human decides is a bad idea. Prefer:

typescript
import { createDurableRunStore, AgentRuntime } from '@hazeljs/agent';

const store = createDurableRunStore('.hazel/runs');

const runtime = new AgentRuntime({
  llmProvider,
  durableSuspend: true,
  runRepository: store.runRepository,
  checkpointService: store.checkpointService,
  humanTaskService: store.humanTaskService,
  policyEngine, // from step 2
});

const waiting = await runtime.execute(
  'ops-desk',
  'Please refund ORD-1001 for 42.00'
);

// …process may restart; human reviews in your UI…
await runtime.approveAndResume(waiting.executionId, {
  approved: true,
  approvedBy: 'ops@example.com',
});

Glossary: /glossary/hitl, /glossary/durable-execution.

Step 4Idempotency (minimum bar)

Before enabling refunds in any shared environment:

Checkpoint

  • Reads vs writes are explicit in tool design
  • processRefund has requiresApproval: true
  • You understand suspend → human → approveAndResume

What to do next

Lock behavior with tests: /learn/agentic-development/golden-tests-with-describe-agent

Artifact: Ops Desk with lookupOrder + processRefund(requiresApproval)

Sources

Related