Framework Guide

EU AI Act Compliance for OpenAI Agents SDK

Wrap your OpenAI Agents SDK tools with guard(). You get a hash-chained audit log, side-effect compensation, loop detection, and per-run token/step/time limits. Your Agent and Runner code stays the same.

Your role under the EU AI Act

When you build on OpenAI's models, OpenAI is the Provider of the underlying model. You are the Deployer of the AI system you build on top. The two roles have different obligation stacks. If you fine-tune, or if you place the system on the EU market under your name, you become a Provider too — see deployer vs provider.

Fuze is a component supplier under Article 25(4). Fuze tooling does not certify your AI system. You remain solely responsible for your compliance posture; see Terms §6.

1. Install

npm install fuze-ai

fuze-ai ships as a single TypeScript package. No OpenAI Agents SDK adapter is required — guard() is framework-agnostic and works with any async function the OpenAI SDK can call as a tool.

2. Wrap your tools

import { guard } from 'fuze-ai'
import { Agent, tool } from '@openai/agents'
import { z } from 'zod'

// Wrap your async functions with guard().
// guard() takes a function and returns the same function — instrumented.
const lookupCustomer = guard(
  async function lookupCustomer(id: string) {
    return await db.customers.find(id)
  },
  // Per-run resource limits (tokens / steps / wall-clock — never USD).
  {
    resourceLimits: {
      maxTokensPerRun: 200_000,
      maxSteps: 25,
      maxWallClockMs: 60_000,
    },
  },
)

// Mark irreversible actions as side effects, with a compensation
// function. If the run is killed mid-step, Fuze runs compensate().
const approveCredit = guard(
  async function approveCredit(id: string, amount: number) {
    return await creditSystem.approve(id, amount)
  },
  {
    sideEffect: true,
    compensate: async (id, amount) => creditSystem.revoke(id, amount),
  },
)

// Hand the wrapped functions to the OpenAI Agents SDK as tools.
// Your Agent / Runner code stays exactly the same.
const agent = new Agent({
  name: 'credit-bot',
  model: 'gpt-4o',
  instructions: 'Process credit applications…',
  tools: [
    tool({
      name: 'lookup_customer',
      description: 'Find a customer by ID',
      parameters: z.object({ id: z.string() }),
      execute: ({ id }) => lookupCustomer(id),
    }),
    tool({
      name: 'approve_credit',
      description: 'Approve a credit application',
      parameters: z.object({ id: z.string(), amount: z.number() }),
      execute: ({ id, amount }) => approveCredit(id, amount),
    }),
  ],
})

That's the entire integration. guard()takes a function and returns the same function with instrumentation attached. The OpenAI SDK doesn't know or care.

3. Verify the audit trail

Every wrapped call writes one JSONL record to ~/.fuze/audit.jsonl. Records are HMAC-SHA256 hash-chained: each record carries the previous record's hash plus its own. Tampering with one record breaks every record after it.

import { verifyChain, readAudit } from 'fuze-ai'

// Read the JSONL audit trail produced by guard().
// Default location: ~/.fuze/audit.jsonl
const records = await readAudit()

// Verify the HMAC hash chain. Returns true if untouched,
// false (with index) if any record was edited or removed.
const result = verifyChain(records)
console.log(result.ok, result.firstMismatchIndex)

// Each record has: stepId, runId, stepNumber, toolName,
// argsHash (sha256, first 16 chars), tokensIn, tokensOut,
// latencyMs, error?, prevHash, hash, signature, sequence.

Arguments are SHA-256 hashed (first 16 chars stored), not raw — so audit logs don't leak PII even when wrapped tools process personal data. Set logPii: true per-call if you need raw arguments and have a lawful basis.

What Fuze actually does, and what it doesn't

Fuze sees the tool-call boundary. Every wrapped function emits a span; spans chain. Honest scope:

  • Tool-call audit log — every call recorded with stepId, runId, toolName, argsHash, tokensIn, tokensOut, latencyMs, error.
  • 3-layer loop detection — iteration cap, repeated tool calls (sliding-window dedup on funcName:argsHash), no-progress detection. Detects loops at the tool-call level. It does not understand OpenAI Agents' internal handoff graph.
  • Per-run resource limits — tokens, steps, wall-clock. Enforced shared across all guarded calls in the run.
  • Side-effect compensation — the compensate callback runs if the run is killed mid-step. You write the rollback; Fuze schedules it.
  • Kill switch — the daemon can return kill on a step-start event; the wrapped function refuses to run and logs it as a guard event.

Fuze does nottrack cost in dollars (we deliberately don't — see the budget docs); per-handoff or per-agent budgets do not exist; cross-run drift detection is not implemented; there is no model-vendor-specific integration with OpenAI's tracing or billing surfaces.

Mapping to AI Act articles

  • Article 12 (record-keeping):the JSONL hash-chained log is the substrate. Retention is your call; the SDK doesn't prune.
  • Article 14 (human oversight): sideEffect: true + compensate give you a defensible kill-and-rollback path. Plug your own approval UI in front of the wrapped function for explicit consent before high-impact tools run.
  • Article 15 (robustness): loop detection and resource limits short-circuit runaway runs.
  • Article 50 (transparency to users): not covered by guard(). You disclose AI-system presence to your users separately.

When to consider Fuze Agent instead

If you're starting fresh and want compliance baked into the runtime — typed tools with explicit data classification, Ed25519-signed run-roots, replay-protected HITL approvals, EU-residency as a type — consider building on Fuze Agent directly instead of bolting guard() onto OpenAI Agents. Same evidence pipeline, stricter type system, more opinionated runtime.

If you have an existing OpenAI Agents codebase, stay with guard(). The integration is one-line-per-tool and doesn't require rewriting your agents.