AI 智能体不再只是回答问题。它们在审批贷款申请、分诊患者入院表单、计算工资、决定谁被标记进入欺诈审查。当其中某一次判断出错时,责任落在部署者身上。
监管已经跟上了。第一个硬性截止日期在 2026 年 8 月,如果你正在构建涉及金融服务、医疗、招聘、或任何错误输出会对真实的人造成真实后果的领域的智能体,合规倒计时已经开始。
三项法规指向同一项义务:必须有人能够监督、干预并覆盖影响人类的 AI 驱动决策。Agent SDK 今天就提供了把这些控制接入你的智能体所需的基础组件。
| 法规 | 生效时间 | 适用对象 | 核心要求 |
|---|---|---|---|
| EU AI Act(欧盟人工智能法案),第 14 条 | 2026 年 8 月(高风险义务) | 任何为欧盟居民提供高风险 AI 系统的提供者或部署者,无论公司位于何处。 | 人类监督,并具备干预和覆盖的能力。需保留监督行动的审计轨迹。 |
| 科罗拉多州 ADMT 法案(SB26-189) | 2027 年 1 月 | 在科罗拉多州开展业务的任何开发者或部署者,包括对科罗拉多州居民做出重大决定(consequential decisions)的州外公司。 | 当受监管的 ADMT 对重大决定产生实质影响时,受监管的开发者/部署者必须提供文档、信息披露、消费者权利流程,以及有意义的人工审查/复议机制。 |
| NIST AI RMF(GOVERN 1) | 自愿性框架,被美国监管机构引用 | 任何开发或部署 AI 系统的组织(自愿采用,但美国联邦机构越来越期待其实施)。 | 与风险相称的人工监督,并对监督控制措施进行文档化记录。 |
共同点在于:如果你的智能体做出或影响了对人们产生实质影响的决定(信贷、就业、医疗、安全),你需要在模型的建议与行动的执行之间设置一个可审查的门控环节。
下面是使用 @openrouter/agent 满足这些要求的 5 种模式,基于 HITL 工具 cookbook(其中介绍了 SDK 的具体机制)构建。这里我们介绍的是在其之上叠加的合规模式。
注意: 本文提供的是工程实践模式,并非法律建议。请咨询法律顾问,以确定哪些法规适用于您的具体用例和司法辖区。
把这份内容交给你的智能体
想让你的编程智能体来实现这些?复制下面的提示词:
I need to add regulatory-compliant human-in-the-loop controls to my AI agent using the OpenRouter Agent SDK.
Inspect my codebase to identify which tools and actions are high-risk (financial, PII, legal, or safety-critical), then infer the appropriate risk tiers and implement a compliance layer using the Agent SDK HITL tools.
The compliance layer should:
1. Mark high-risk tools with requireApproval or onToolCalled gates based on my risk classification.
2. Log every oversight event (tool invocation, human decision, timestamp, reviewer ID) to my audit backend.
3. Add timeout-based escalation: if no human responds within the deadline, escalate to a supervisor or reject the action.
4. Stamp each human decision with reviewer identity and timestamp via onResponseReceived.
5. Persist conversation state with a StateAccessor backed by my chosen storage so audit records survive restarts.
Consult these pages for current SDK shapes and patterns:
- HITL tools reference: https://openrouter.ai/docs/sdks/typescript/call-model/tools#human-in-the-loop-hitl-tools
- Tool Approval & State: https://openrouter.ai/docs/sdks/typescript/call-model/approval-and-state
- callModel API reference: https://openrouter.ai/docs/sdks/typescript/call-model/api-reference
Do not hard-code secrets. Use environment variables for API keys and database credentials.
1. 按风险等级为工具分类
法规要求对具有 重大影响 的操作进行人工审查。首先将你的工具划分为不同等级:
| 等级 | 示例操作 | 管控方式 |
|---|---|---|
| 高风险 | 金融交易、PII(个人身份信息)处理、访问权限决策、医疗建议 | 带强制暂停的 HITL(人工介入)工具(return null) |
| 中风险 | 批量邮件、内容审核、数据导出 | 带条件谓词的 requireApproval |
| 低风险 | 搜索、只读查询、格式化 | 无需审核关卡 |
import { OpenRouter, tool } from '@openrouter/agent';
import { z } from 'zod';
// High-risk: always pauses for human review
const processCreditDecision = tool({
name: 'process_credit_decision',
description: 'Issue or deny a credit application',
inputSchema: z.object({
applicationId: z.string(),
recommendedAction: z.enum(['approve', 'deny', 'refer']),
riskScore: z.number(),
applicantName: z.string(),
}),
outputSchema: z.object({
decision: z.enum(['approved', 'denied', 'referred']),
reviewerId: z.string(),
reviewedAt: z.number(),
justification: z.string(),
}),
onToolCalled: async () => {
// Always escalate to human. No auto-resolve path for high-risk.
return null;
},
});
对于中等风险的工具,使用基于上下文进行把关的条件谓词:
const sendBulkEmail = tool({
name: 'send_bulk_email',
description: 'Send email to a recipient list',
inputSchema: z.object({
recipients: z.array(z.string().email()),
subject: z.string(),
body: z.string(),
}),
outputSchema: z.object({ sent: z.boolean(), count: z.number() }),
requireApproval: (params) => {
// Gate kicks in above 50 recipients
return params.recipients.length > 50;
},
execute: async (params) => {
await sendEmails(params);
return { sent: true, count: params.recipients.length };
},
});
2. 为每个监督事件添加审计日志
法规要求你证明人工监督确实发生了。这意味着要记录谁审查了什么、何时审查、以及做出了什么决定。将此逻辑接入 onResponseReceived:
import { tool } from '@openrouter/agent';
import { z } from 'zod';
const auditSchema = z.object({
decision: z.enum(['approved', 'denied', 'referred']),
reviewerId: z.string(),
justification: z.string(),
});
const processCreditDecision = tool({
name: 'process_credit_decision',
description: 'Issue or deny a credit application',
inputSchema: z.object({
applicationId: z.string(),
recommendedAction: z.enum(['approve', 'deny', 'refer']),
riskScore: z.number(),
applicantName: z.string(),
}),
outputSchema: z.object({
decision: z.enum(['approved', 'denied', 'referred']),
reviewerId: z.string(),
reviewedAt: z.number(),
justification: z.string(),
}),
onToolCalled: async (input) => {
// Log the escalation event itself
await writeAuditLog({
event: 'escalated_to_human',
toolName: 'process_credit_decision',
input,
timestamp: Date.now(),
});
return null;
},
onResponseReceived: async (raw) => {
const parsed = auditSchema.parse(raw);
const reviewedAt = Date.now();
// Write the immutable audit record
await writeAuditLog({
event: 'human_decision_recorded',
toolName: 'process_credit_decision',
reviewerId: parsed.reviewerId,
decision: parsed.decision,
justification: parsed.justification,
reviewedAt,
});
return { ...parsed, reviewedAt };
},
});
writeAuditLog 函数应写入只追加(append-only)存储。一个最小化的接口:
interface AuditEntry {
event: string;
toolName: string;
timestamp?: number;
reviewerId?: string;
decision?: string;
justification?: string;
input?: unknown;
reviewedAt?: number;
escalatedTo?: string;
}
async function writeAuditLog(entry: AuditEntry): Promise<void> {
// Write to your audit backend: Postgres, S3, Datadog, Splunk, etc.
// The record must be append-only and tamper-evident for compliance.
await db.insertInto('audit_log').values({
...entry,
timestamp: entry.timestamp ?? Date.now(),
id: crypto.randomUUID(),
}).execute();
}
EU AI Act 第 12 条(记录保存)要求高风险系统在其运行生命周期内保留日志。将审计日志存储在持久化的只追加存储中,并根据你的法规要求配置相应的保留策略。
3. 实现基于超时的升级机制
一个无人响应的人工审核关卡比没有关卡更糟糕。法规期望系统能够处理审查者无响应的情况。实现一个超时机制,默认情况下要么上报给主管,要么直接拒绝该操作。
这个模式运行在 callModel 循环之外,位于任何轮询过期待审核事项的服务中:
interface PendingReview {
conversationId: string;
callId: string;
toolName: string;
createdAt: number;
assignedTo: string;
}
const REVIEW_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
async function escalateStaleReviews(
pendingReviews: PendingReview[],
): Promise<void> {
const now = Date.now();
for (const review of pendingReviews) {
const elapsed = now - review.createdAt;
if (elapsed < REVIEW_TIMEOUT_MS) continue;
await writeAuditLog({
event: 'review_timeout_escalated',
toolName: review.toolName,
reviewerId: review.assignedTo,
timestamp: now,
});
// Option A: Escalate to supervisor
await assignToSupervisor(review);
// Option B: Default-deny and resume the agent with a rejection
// await resumeWithDenial(review);
}
}
选择哪个方案取决于你的风险偏好。对于需要符合欧盟 AI 法案的高风险系统,默认拒绝(方案 B)更安全:没有明确的人工批准,操作永远不会执行。对于延迟会带来运营成本的较低风险系统,升级给主管(方案 A)既能保持监督链,又能让流程继续推进。
4. 用持久化存储支撑你的 StateAccessor
内存中的状态在进程重启后会消失。出于合规要求,你的 StateAccessor 必须使用持久化存储,以便待审查项、对话历史和审计上下文能在崩溃、部署和横向扩展中得以保留。
import type { ConversationState, StateAccessor, Tool } from '@openrouter/agent';
function createDurableStateAccessor<TTools extends readonly Tool[]>(
conversationId: string,
): StateAccessor<TTools> {
return {
load: async () => {
const row = await db
.selectFrom('conversation_state')
.where('id', '=', conversationId)
.selectAll()
.executeTakeFirst();
if (!row) return null;
return JSON.parse(row.state) as ConversationState<TTools>;
},
save: async (state) => {
await db
.insertInto('conversation_state')
.values({
id: conversationId,
state: JSON.stringify(state),
updated_at: new Date(),
})
.onConflict((oc) =>
oc.column('id').doUpdateSet({
state: JSON.stringify(state),
updated_at: new Date(),
}),
)
.execute();
},
};
}
每当状态转换为 'awaiting_hitl' 或 'awaiting_approval' 时,待审查项都会被持久化。你的升级服务(第 3 步)会查询这张表来找出过期的审查。
5. 将所有部分串联起来
完整流程如下:分类、把关、记录、超时、恢复。这里假设使用第 1-2 步中的 processCreditDecision 和 sendBulkEmail、第 2 步的 writeAuditLog,以及第 4 步的 createDurableStateAccessor。
import { OpenRouter } from '@openrouter/agent';
// processCreditDecision, sendBulkEmail defined in steps 1-2
// createDurableStateAccessor defined in step 4
const openrouter = new OpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const tools = [processCreditDecision, sendBulkEmail] as const;
const conversationId = `conv-${crypto.randomUUID()}`;
const state = createDurableStateAccessor<typeof tools>(conversationId);
// Initial request
const result = openrouter.callModel({
model: 'openai/gpt-4o',
input: 'Review application APP-2024-001 and issue a credit decision',
tools,
state,
});
// Wait for the call to complete (or pause for human review)
const snapshot = await result.getState();
if (snapshot?.status === 'awaiting_hitl' || snapshot?.status === 'awaiting_approval') {
const pending = snapshot.pendingToolCalls ?? [];
// Surface to your review UI, queue, or notification system.
// 'awaiting_hitl' fires for onToolCalled tools (processCreditDecision).
// 'awaiting_approval' fires for requireApproval tools (sendBulkEmail).
// Both resume via function_call_output here; see approval-and-state docs
// for the approveToolCalls/rejectToolCalls alternative for requireApproval tools.
for (const call of pending) {
await createPendingReview({
conversationId,
callId: call.id,
toolName: call.name,
createdAt: Date.now(),
assignedTo: getReviewerForTool(call.name),
arguments: call.arguments,
});
}
}
当审核者响应时(通过你的管理后台、Slack 操作、队列消费者等):
// Retrieve the pending call from your review queue (by conversationId, callId, etc.)
const pendingCall = await getPendingReview(conversationId);
// Human supplies their decision
const humanDecision = {
decision: 'approved' as const,
reviewerId: 'reviewer-jane-smith',
justification: 'Risk score within policy limits, verified income docs',
};
const resumed = openrouter.callModel({
model: 'openai/gpt-4o',
input: [
{
type: 'function_call_output',
callId: pendingCall.callId,
output: JSON.stringify(humanDecision),
},
],
tools,
state,
});
const text = await resumed.getText();
onResponseReceived 钩子被触发,盖印审计记录,模型接收到经过验证的决策。
今天就开始构建
欧盟 AI 法案的高风险义务将于 2026 年 8 月生效。科罗拉多州的 ADMT 法 于 2027 年 1 月 1 日生效。NIST AI RMF 是自愿性的,但越来越多地被美国联邦机构引用为基线要求。只需一套实现(风险分类、审计日志、超时升级、持久化状态)即可同时满足这三个框架。
Agent SDK 负责处理暂停执行、跨重启持久化状态、按照 schema 校验人类响应,以及干净地恢复运行。你的工作是把它接入你的审查工作流和审计存储。
关于相关治理控制(预算上限、数据保留策略、模型限制),请参见 Guardrails。
完整的 SDK 参考和可运行的示例:HITL 工具文档。
常见问题
欧盟 AI 法案第 14 条有什么要求?
第 14 条要求高风险 AI 系统必须包含人类监督措施。人类必须能够理解系统的能力、监控其运行、解读其输出,并能够干预或否决决策。审计日志保留要求则属于第 12 条(记录保存)和第 9 条(风险管理)。
欧盟 AI 法案何时生效?
《人工智能法案》于 2024 年 8 月生效,但高风险义务(包括第 14 条的人工监督)自 2026 年 8 月起适用。这是被归类为高风险的系统必须证明其具备合规监督控制的最后期限。
科罗拉多州的 ADMT 法何时生效?
科罗拉多州的《自动化决策技术》法(SB26-189)一般于 2027 年 1 月 1 日生效,并适用于在该日期或之后做出的重大决策。科罗拉多州总检察长的规则制定页面持续跟踪实施细节。
科罗拉多州的 ADMT 法是否适用于科罗拉多州以外的公司?
适用。该法适用于任何“在科罗拉多州开展业务”的开发者或部署者,而不仅仅是在该州设立总部的公司。如果你部署的 ADMT 对有关科罗拉多州居民的重大决策(就业、金融、住房、保险、医疗、教育、基本政府服务)产生实质性影响,你很可能受该法约束。这与《科罗拉多州隐私法》的管辖模式相同,后者涵盖在科罗拉多州开展业务或以商业产品或服务面向科罗拉多州居民的实体。执法通过《科罗拉多州消费者保护法》进行(违法行为被视为欺骗性商业行为)。
什么是 AI 智能体的人机协同(HITL,human-in-the-loop)?
HITL(人在回路)是指在 AI 智能体执行其提议的操作之前,由人工进行审核并批准(或拒绝)。在 Agent SDK 中,这是通过 onToolCalled(暂停执行并等待人工输入)和 requireApproval(根据参数有条件地把关工具执行)来实现的。
AI agents aren’t just answering questions anymore. They’re approving loan applications, triaging patient intake forms, running payroll calculations, deciding who gets flagged for fraud review. When one of those calls goes wrong, the liability sits with the deployer.
Regulators caught up. The first hard deadline lands in August 2026, and if you’re building agents that touch financial services, healthcare, hiring, or any domain where a wrong output has real consequences for a real person, the compliance clock is already running.
Three regulations converge on the same obligation: a human must be able to oversee, intervene in, and override AI-driven decisions that affect people. The Agent SDK has the primitives to wire these controls into your agent today.
| Regulation | Effective | Who it applies to | Core requirement |
|---|---|---|---|
| EU AI Act, Article 14 | Aug 2026 (high-risk obligations) | Any provider or deployer of high-risk AI systems serving EU residents, regardless of where the company is based. | Human oversight with ability to intervene and override. Audit trail of oversight actions. |
| Colorado ADMT Law (SB26-189) | Jan 2027 | Any developer or deployer doing business in Colorado, including companies outside Colorado that make consequential decisions about Colorado residents. | Covered developers/deployers must provide documentation, disclosures, consumer rights processes, and meaningful human review/reconsideration where covered ADMT materially influences consequential decisions. |
| NIST AI RMF (GOVERN 1) | Voluntary, referenced by US regulators | Any organization developing or deploying AI systems (voluntary, but increasingly expected by US federal agencies). | Human oversight proportional to risk. Documentation of oversight controls. |
The common thread: if your agent makes or influences decisions that materially affect people (credit, employment, healthcare, safety), you need a reviewable gate between the model’s recommendation and the action’s execution.
Below are 5 patterns that satisfy those requirements using @openrouter/agent, building on the HITL tools cookbook (which covers the SDK mechanics). Here we cover the compliance patterns you bolt on top.
Note: This post provides engineering patterns, not legal advice. Consult legal counsel to determine which regulations apply to your specific use case and jurisdiction.
Give this to your agent
Want your coding agent to implement this? Copy the prompt below:
I need to add regulatory-compliant human-in-the-loop controls to my AI agent using the OpenRouter Agent SDK.
Inspect my codebase to identify which tools and actions are high-risk (financial, PII, legal, or safety-critical), then infer the appropriate risk tiers and implement a compliance layer using the Agent SDK HITL tools.
The compliance layer should:
1. Mark high-risk tools with requireApproval or onToolCalled gates based on my risk classification.
2. Log every oversight event (tool invocation, human decision, timestamp, reviewer ID) to my audit backend.
3. Add timeout-based escalation: if no human responds within the deadline, escalate to a supervisor or reject the action.
4. Stamp each human decision with reviewer identity and timestamp via onResponseReceived.
5. Persist conversation state with a StateAccessor backed by my chosen storage so audit records survive restarts.
Consult these pages for current SDK shapes and patterns:
- HITL tools reference: https://openrouter.ai/docs/sdks/typescript/call-model/tools#human-in-the-loop-hitl-tools
- Tool Approval & State: https://openrouter.ai/docs/sdks/typescript/call-model/approval-and-state
- callModel API reference: https://openrouter.ai/docs/sdks/typescript/call-model/api-reference
Do not hard-code secrets. Use environment variables for API keys and database credentials.
1. Classify your tools by risk tier
Regulations require human review on actions that are consequential. Start by splitting your tools into tiers:
| Tier | Example actions | Control |
|---|---|---|
| High-risk | Financial transactions, PII processing, access decisions, medical recommendations | HITL tool with mandatory pause (return null) |
| Medium-risk | Bulk emails, content moderation, data exports | requireApproval with conditional predicate |
| Low-risk | Search, read-only queries, formatting | No gate needed |
import { OpenRouter, tool } from '@openrouter/agent';
import { z } from 'zod';
// High-risk: always pauses for human review
const processCreditDecision = tool({
name: 'process_credit_decision',
description: 'Issue or deny a credit application',
inputSchema: z.object({
applicationId: z.string(),
recommendedAction: z.enum(['approve', 'deny', 'refer']),
riskScore: z.number(),
applicantName: z.string(),
}),
outputSchema: z.object({
decision: z.enum(['approved', 'denied', 'referred']),
reviewerId: z.string(),
reviewedAt: z.number(),
justification: z.string(),
}),
onToolCalled: async () => {
// Always escalate to human. No auto-resolve path for high-risk.
return null;
},
});
For medium-risk tools, use a conditional predicate that gates on context:
const sendBulkEmail = tool({
name: 'send_bulk_email',
description: 'Send email to a recipient list',
inputSchema: z.object({
recipients: z.array(z.string().email()),
subject: z.string(),
body: z.string(),
}),
outputSchema: z.object({ sent: z.boolean(), count: z.number() }),
requireApproval: (params) => {
// Gate kicks in above 50 recipients
return params.recipients.length > 50;
},
execute: async (params) => {
await sendEmails(params);
return { sent: true, count: params.recipients.length };
},
});
2. Add audit logging to every oversight event
Regulations require you to prove that human oversight happened. That means logging who reviewed what, when, and what they decided. Wire this into onResponseReceived:
import { tool } from '@openrouter/agent';
import { z } from 'zod';
const auditSchema = z.object({
decision: z.enum(['approved', 'denied', 'referred']),
reviewerId: z.string(),
justification: z.string(),
});
const processCreditDecision = tool({
name: 'process_credit_decision',
description: 'Issue or deny a credit application',
inputSchema: z.object({
applicationId: z.string(),
recommendedAction: z.enum(['approve', 'deny', 'refer']),
riskScore: z.number(),
applicantName: z.string(),
}),
outputSchema: z.object({
decision: z.enum(['approved', 'denied', 'referred']),
reviewerId: z.string(),
reviewedAt: z.number(),
justification: z.string(),
}),
onToolCalled: async (input) => {
// Log the escalation event itself
await writeAuditLog({
event: 'escalated_to_human',
toolName: 'process_credit_decision',
input,
timestamp: Date.now(),
});
return null;
},
onResponseReceived: async (raw) => {
const parsed = auditSchema.parse(raw);
const reviewedAt = Date.now();
// Write the immutable audit record
await writeAuditLog({
event: 'human_decision_recorded',
toolName: 'process_credit_decision',
reviewerId: parsed.reviewerId,
decision: parsed.decision,
justification: parsed.justification,
reviewedAt,
});
return { ...parsed, reviewedAt };
},
});
The writeAuditLog function should write to append-only storage. A minimal interface:
interface AuditEntry {
event: string;
toolName: string;
timestamp?: number;
reviewerId?: string;
decision?: string;
justification?: string;
input?: unknown;
reviewedAt?: number;
escalatedTo?: string;
}
async function writeAuditLog(entry: AuditEntry): Promise<void> {
// Write to your audit backend: Postgres, S3, Datadog, Splunk, etc.
// The record must be append-only and tamper-evident for compliance.
await db.insertInto('audit_log').values({
...entry,
timestamp: entry.timestamp ?? Date.now(),
id: crypto.randomUUID(),
}).execute();
}
EU AI Act Article 12 (Record-Keeping) requires that high-risk systems maintain logs for their operational lifetime. Store audit logs in durable, append-only storage with retention policies that match your regulatory requirements.
3. Implement timeout-based escalation
A human review gate that nobody responds to is worse than no gate at all. Regulations expect the system to handle unresponsive reviewers. Implement a timeout that either escalates to a supervisor or rejects the action by default.
This pattern runs outside the callModel loop, in whatever service polls for stale pending reviews:
interface PendingReview {
conversationId: string;
callId: string;
toolName: string;
createdAt: number;
assignedTo: string;
}
const REVIEW_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
async function escalateStaleReviews(
pendingReviews: PendingReview[],
): Promise<void> {
const now = Date.now();
for (const review of pendingReviews) {
const elapsed = now - review.createdAt;
if (elapsed < REVIEW_TIMEOUT_MS) continue;
await writeAuditLog({
event: 'review_timeout_escalated',
toolName: review.toolName,
reviewerId: review.assignedTo,
timestamp: now,
});
// Option A: Escalate to supervisor
await assignToSupervisor(review);
// Option B: Default-deny and resume the agent with a rejection
// await resumeWithDenial(review);
}
}
Which option to pick depends on your risk appetite. For EU AI Act compliance with high-risk systems, default-deny (Option B) is safer: the action never executes without explicit human approval. For lower-risk systems where delays have operational cost, escalation to a supervisor (Option A) keeps things moving while preserving the oversight chain.
4. Back your StateAccessor with durable storage
In-memory state disappears on process restart. For compliance, your StateAccessor must use durable storage so that pending reviews, conversation history, and audit context survive crashes, deploys, and horizontal scaling.
import type { ConversationState, StateAccessor, Tool } from '@openrouter/agent';
function createDurableStateAccessor<TTools extends readonly Tool[]>(
conversationId: string,
): StateAccessor<TTools> {
return {
load: async () => {
const row = await db
.selectFrom('conversation_state')
.where('id', '=', conversationId)
.selectAll()
.executeTakeFirst();
if (!row) return null;
return JSON.parse(row.state) as ConversationState<TTools>;
},
save: async (state) => {
await db
.insertInto('conversation_state')
.values({
id: conversationId,
state: JSON.stringify(state),
updated_at: new Date(),
})
.onConflict((oc) =>
oc.column('id').doUpdateSet({
state: JSON.stringify(state),
updated_at: new Date(),
}),
)
.execute();
},
};
}
Every time state transitions to 'awaiting_hitl' or 'awaiting_approval', the pending review is persisted. Your escalation service (step 3) queries this table to find stale reviews.
5. Wire it all together
Here’s the complete flow: classify, gate, log, timeout, resume. This assumes processCreditDecision and sendBulkEmail from steps 1-2, writeAuditLog from step 2, and createDurableStateAccessor from step 4.
import { OpenRouter } from '@openrouter/agent';
// processCreditDecision, sendBulkEmail defined in steps 1-2
// createDurableStateAccessor defined in step 4
const openrouter = new OpenRouter({
apiKey: process.env.OPENROUTER_API_KEY,
});
const tools = [processCreditDecision, sendBulkEmail] as const;
const conversationId = `conv-${crypto.randomUUID()}`;
const state = createDurableStateAccessor<typeof tools>(conversationId);
// Initial request
const result = openrouter.callModel({
model: 'openai/gpt-4o',
input: 'Review application APP-2024-001 and issue a credit decision',
tools,
state,
});
// Wait for the call to complete (or pause for human review)
const snapshot = await result.getState();
if (snapshot?.status === 'awaiting_hitl' || snapshot?.status === 'awaiting_approval') {
const pending = snapshot.pendingToolCalls ?? [];
// Surface to your review UI, queue, or notification system.
// 'awaiting_hitl' fires for onToolCalled tools (processCreditDecision).
// 'awaiting_approval' fires for requireApproval tools (sendBulkEmail).
// Both resume via function_call_output here; see approval-and-state docs
// for the approveToolCalls/rejectToolCalls alternative for requireApproval tools.
for (const call of pending) {
await createPendingReview({
conversationId,
callId: call.id,
toolName: call.name,
createdAt: Date.now(),
assignedTo: getReviewerForTool(call.name),
arguments: call.arguments,
});
}
}
When the reviewer responds (through your admin UI, Slack action, queue consumer, etc.):
// Retrieve the pending call from your review queue (by conversationId, callId, etc.)
const pendingCall = await getPendingReview(conversationId);
// Human supplies their decision
const humanDecision = {
decision: 'approved' as const,
reviewerId: 'reviewer-jane-smith',
justification: 'Risk score within policy limits, verified income docs',
};
const resumed = openrouter.callModel({
model: 'openai/gpt-4o',
input: [
{
type: 'function_call_output',
callId: pendingCall.callId,
output: JSON.stringify(humanDecision),
},
],
tools,
state,
});
const text = await resumed.getText();
The onResponseReceived hook fires, stamps the audit record, and the model receives the validated decision.
Start building today
EU AI Act high-risk obligations land August 2026. Colorado’s ADMT law takes effect January 1, 2027. NIST AI RMF is voluntary but increasingly referenced by US federal agencies as the baseline expectation. One implementation (risk classification, audit logging, timeout escalation, durable state) satisfies all three frameworks.
The Agent SDK handles pausing execution, persisting state across restarts, validating human responses against schemas, and resuming cleanly. Your job is to wire it into your review workflows and audit storage.
For related governance controls (budget caps, data retention policies, model restrictions), see Guardrails.
Full SDK reference and working examples: HITL tools documentation.
FAQ
What does EU AI Act Article 14 require?
Article 14 mandates that high-risk AI systems include human oversight measures. Humans must be able to understand the system’s capabilities, monitor its operation, interpret outputs, and intervene or override decisions. Audit log retention requirements fall under Article 12 (Record-Keeping) and Article 9 (Risk Management).
When does the EU AI Act take effect?
The AI Act entered into force August 2024, but the high-risk obligations (including Article 14 human oversight) apply starting August 2026. That’s the deadline for systems classified as high-risk to demonstrate compliant oversight controls.
When does Colorado’s ADMT law take effect?
Colorado’s Automated Decision-Making Technology law (SB26-189) generally takes effect January 1, 2027 and applies to consequential decisions made on or after that date. The Colorado AG’s rulemaking page tracks implementation details.
Does Colorado’s ADMT law apply to companies outside Colorado?
Yes. The law applies to any developer or deployer “doing business in” Colorado, not just companies headquartered there. If you deploy ADMT that materially influences consequential decisions (employment, finance, housing, insurance, healthcare, education, essential government services) about Colorado residents, you’re likely subject to the law. This follows the same jurisdictional pattern as the Colorado Privacy Act, which covers entities that conduct business in Colorado or target Colorado residents with commercial products or services. Enforcement runs through the Colorado Consumer Protection Act (violations are treated as deceptive trade practices).
What is human-in-the-loop (HITL) for AI agents?
HITL means a human reviews and approves (or rejects) an AI agent’s proposed action before it executes. In the Agent SDK, this is implemented through onToolCalled (which pauses execution and waits for human input) and requireApproval (which conditionally gates tool execution based on parameters).