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.
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
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.
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'.
The overseer service receives the suspend payload, presents the decision UI, and resumes:
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.
Resumption emits a human.oversight.decision span:
{
"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.