Human-in-the-loop

AI Act Art. 14 requires effective human oversight for high-risk agents. The HITL primitive makes this a runtime invariant: a tool suspends the run, the loop persists state via @fuze-ai/agent-suspend-store, an overseer approves or denies, and the loop resumes from the suspension point.

What you'll build: a tool that pauses the run, captures an overseer's decision, and resumes, with a replay-protected resume token. Prerequisites: First agent so you have a working runAgent baseline. Next: write a Cerbos policy to govern which tools require approval.

Flow

code
   Agent           Suspend Store        Overseer            Resume
   -----           -------------        --------            ------
     |                   |                  |                  |
     | Suspend(reason)   |                  |                  |
     |------------------>|                  |                  |
     |                   | save             |                  |
     |                   | SuspendedRun     |                  |
     |                   |                  |                  |
     | resumeToken       |                  |                  |
     |<------------------|                  |                  |
     |  status=suspended |                  |                  |
     |                   |                  |                  |
     |                   | evidence panel   |                  |
     |                   +----------------->|                  |
     |                   |                  | review + decide  |
     |                   |                  |                  |
     |                   |                  | submit decision  |
     |                   |                  +----------------->|
     |                   |                  |                  |
     |                   |   consume nonce (replay-protected)  |
     |                   |<------------------------------------|
     |                   |                  |                  |
     | resumeAgent()     |                  |                  |
     |<------------------------------------+|                  |
     | spans continue    |                  |                  |
     | from suspend pt   |                  |                  |
     v                   v                  v                  v

When required

The framework requires HITL when any of the following holds:

  • producesArt22Decision: true
  • annexIIIDomain is non-'none'
  • a tool declares requiresOversight: true

If required and absent, the run halts with fuze.run.missing_oversight=true.

Define an oversight tool

ts
import { z } from 'zod'
import {
  defineTool,
  Suspend,
  type ThreatBoundary,
} from '@fuze-ai/agent'

const threatBoundary: ThreatBoundary = {
  trustedCallers: ['agent-loop'],
  observesSecrets: false,
  egressDomains: 'none',
  readsFilesystem: false,
  writesFilesystem: false,
}

export const requestApproval = defineTool.public({
  name: 'request_approval',
  description: 'pauses the run for human approval of the proposed action',
  input: z.object({
    action: z.string(),
    rationale: z.string(),
  }),
  output: z.object({
    approved: z.boolean(),
    overseer: z.string(),
    decidedAt: z.string(),
  }),
  threatBoundary,
  retention: {
    id: 'oversight.v1',
    hashTtlDays: 365,
    fullContentTtlDays: 365,
    decisionTtlDays: 365,
  },
  requiresOversight: true,
  run: async (input, ctx) => {
    return Suspend({
      reason: 'human_approval_required',
      payload: { action: input.action, rationale: input.rationale },
      resumeSchema: z.object({
        approved: z.boolean(),
        overseer: z.string(),
        decidedAt: z.string(),
      }),
    })
  },
})

Suspend returns a typed control-flow result that the loop recognizes. The loop persists run state to the suspend store, returns a resumeToken, and exits with status: 'suspended'.

Resume

The overseer service receives the suspend payload, presents the decision UI, and resumes:

ts
import { resumeAgent } from '@fuze-ai/agent'

const resumed = await resumeAgent(
  { definition: agent, policy, evidenceSink: (r) => records.push(r) },
  {
    resumeToken: result.resumeToken!,
    resumeValue: {
      approved: true,
      overseer: 'overseer:dpo@example.org',
      decidedAt: new Date().toISOString(),
    },
  },
)

The token is single-use and bound to the run; reuse is rejected with fuze.suspend.replay=true.

Evidence

Resumption emits a human.oversight.decision span:

json
{
  "span": "human.oversight.decision",
  "attributes": {
    "human.oversight.principal": "overseer:dpo@example.org",
    "human.oversight.decision": "approved",
    "human.oversight.decided_at": "2026-04-30T12:34:56Z"
  }
}

evaluateApproval produces this span. Auditors verify AI Act Art. 14 compliance by querying the chain for human.oversight.decision spans on each high-risk run.

Next: write Cerbos policies.