语音代理可以听起来很流畅、响应迅速,但仍可能在一句话中引发信任危机:“不要再给我打电话了。”

如果该请求仅更新了短信路径,你的代理可能明天还会继续拨打。如果它只更新了通话记录,你的后续工作流可能仍在继续发送短信。对于正在交付 AI 拨号器、收件箱代理、调度机器人或多步骤外展工作流的构建者来说,同意不再是一个静态复选框。它是运行时状态。

这就是 AI 同意账本 的用武之地。它为每个代理操作提供一个简单规则:在联系、丰富、记录或升级某个人之前,从一个持久位置检查最新的同意状态。

本指南展示如何设计该账本,而不让你的产品变成合规迷宫。

这是技术架构指导,不是法律建议。如果你的工作流涉及受监管的外展、健康、金融、就业或敏感个人数据,请咨询合格的法律审查员。

为什么 AI 代理让同意更难管理

传统应用通常在可预测的时刻请求许可:注册、新闻通讯订阅、Cookie 横幅、电话号码捕获或账单同意。

AI 代理模糊了这一界限。

生产中的代理可能:

  • 接听来电
  • 总结语音邮件
  • 发送后续链接短信
  • 安排另一次通话
  • 丰富 CRM 记录
  • 触发下周的活动步骤
  • 将案例转交给人工
  • 在工具调用失败后重试
  • 从语音切换到短信或电子邮件

每个步骤本身可能都有效。当一个渠道的同意发生变化而工作流的其余部分未察觉时,风险就出现了。

常见的失败模式很简单:

  1. 用户在他面前的渠道中撤销了许可。
  2. 代理将消息记录为对话文本。
  3. 另一个工作流继续运行,因为它从未检查过撤销状态。

这不是 LLM 的问题,而是状态管理的问题。

什么是 AI 同意账本?

AI 同意账本 是一个仅追加的权限事件记录,加上一个快速读取模型,用来回答一个问题:

这个特定的代理现在是否被允许为这个人执行这个特定的操作?

该账本应跨渠道、身份、代理、工作流、工具和时间跟踪同意与撤销。

它不仅仅是一个 subscribed: true 列。

一个有用的账本存储:

  • 同意属于谁
  • 它适用于哪个渠道
  • 它涵盖什么目的
  • 何时授予或撤销
  • 是如何捕获的
  • 哪个工作流或代理对其进行了操作
  • 支持该决定的证据是什么
  • 撤销是应停止一个渠道还是所有相关外展

对于 AI 工作流,最重要的部分不是日志,而是在操作前的策略检查。

团队最常忽略的同意接触点

首先映射代理可以创建、使用或更改权限状态的每一个位置。

1. 语音撤销

用户可能会说:

  • “不要再给我打电话。”
  • “把我从名单上删除。”
  • “不要再联系这个号码。”
  • “只给我发邮件。”
  • “改用短信联系我。”
  • “我没有要求这个。”

不要把这些短语埋在转录文本中。把它们提取为候选同意事件。

语音撤销之所以棘手,是因为代理可能在长时间对话中听到它。你需要一个单独的分类器或确定性短语层来监视权限变化,而不仅仅是最终摘要。

2. 短信关键词与自然语言

大多数团队首先处理 STOPUNSUBSCRIBESTART。这是必要的,但用户并不总是使用确切的关键词。

你还需要捕获自然语言:

  • “请不要再给我发短信”
  • “打错号码了”
  • “改打我办公室的电话”
  • “不感兴趣,把我删除”

确切关键词可以是确定性的。自然语言可以分类,然后在不确定时路由到确认或人工审核。

3. 电子邮件和通知偏好

代理可能会从原本基于语音的工作流中起草或发送电子邮件。如果电子邮件有单独的同意目的,账本必须知道这一点。

同意不应仅仅因为代理有另一个可用工具而悄无声息地扩展。

4. CRM 导入与过时名单

许多 AI 工作流始于导入的联系人。该导入可能包含旧的同意标志、部分渠道历史,或根本没有证据。

将导入的许可视为较低信任度,直到验证为止。存储来源和时间戳,以便代理可以选择更安全的模式。

5. 人工覆盖

人工需要覆盖路径,但覆盖应是带有理由的明确事件,而不是隐形的管理员编辑。

一个好的覆盖记录应回答:

  • 谁更改了状态
  • 他们为什么更改
  • 他们审查了什么证据
  • 更改是临时的还是永久的

实用的同意数据模型

保持写入模型为仅追加。从中构建一个单独的当前状态视图。

这是一个紧凑的 TypeScript 风格模型:

type ConsentChannel = "voice" | "sms" | "email" | "push" | "in_app";
type ConsentPurpose =
  | "support_followup"
  | "appointment_reminder"
  | "marketing_outreach"
  | "transactional_update"
  | "recording"
  | "data_enrichment";

type ConsentEventType =
  | "granted"
  | "revoked"
  | "limited"
  | "confirmed"
  | "expired"
  | "human_override";

type ConsentEvent = {
  id: string;
  tenantId: string;
  subjectId: string;          // person/customer/contact
  normalizedContact: string;  // phone/email/device id hash
  channel: ConsentChannel;
  purpose: ConsentPurpose;
  eventType: ConsentEventType;
  scope: "single_channel" | "all_channels" | "all_outreach";
  source: "voice_agent" | "sms_keyword" | "web_form" | "crm_import" | "human";
  evidenceRef: string;        // transcript span, form id, message id, admin note
  confidence: number;         // 0..1 for AI-extracted events
  workflowRunId?: string;
  agentId?: string;
  createdAt: string;
  expiresAt?: string;
};

Enter fullscreen mode Exit fullscreen mode

一些细节很重要:

  • scope 可防止“停止打电话”意外变成“停止一切”,除非策略要求。
  • purpose 可防止预约提醒的同意变成促销的同意。
  • evidenceRef 允许你解释系统做出该决定的原因。
  • confidence 允许不确定的 AI 提取事件暂停,而不是盲目行动。

每个代理操作所需的运行时检查

在任何状态更改或联系操作之前,向账本请求决策。

type ConsentDecision = {
  allowed: boolean;
  reason: string;
  matchedEventIds: string[];
  requiresHumanReview?: boolean;
  safeAlternative?: "do_not_contact" | "in_app_only" | "ask_for_confirmation";
};

async function canAgentAct(input: {
  tenantId: string;
  subjectId: string;
  channel: ConsentChannel;
  purpose: ConsentPurpose;
  action: "call" | "text" | "email" | "record" | "enrich";
  workflowRunId: string;
}): Promise<ConsentDecision> {
  const state = await consentStore.currentState(input);

  if (state.hasGlobalRevocation) {
    return {
      allowed: false,
      reason: "Subject revoked all outreach",
      matchedEventIds: state.blockingEvents,
      safeAlternative: "do_not_contact",
    };
  }

  if (state.channelRevoked) {
    return {
      allowed: false,
      reason: `${input.channel} permission was revoked`,
      matchedEventIds: state.blockingEvents,
      safeAlternative: "in_app_only",
    };
  }

  if (!state.hasPurposeGrant) {
    return {
      allowed: false,
      reason: `No active consent for ${input.purpose}`,
      matchedEventIds: [],
      safeAlternative: "ask_for_confirmation",
    };
  }

  return { allowed: true, reason: "Active consent found", matchedEventIds: state.grantEvents };
};

Enter fullscreen mode Exit fullscreen mode

代理不应是最终权威。它可以请求操作。后端决定该操作是否被允许。

如何从语音转录中处理撤销

语音代理需要在转录周围有一个小型管道。

第 1 步:捕获转录片段

不要只存储完整的转录 blob。存储带时间码的片段。

{
  "span_id": "span_928",
  "speaker": "user",
  "text": "Please stop calling this number. Texts too.",
  "start_ms": 184200,
  "end_ms": 188700
}

Enter fullscreen mode Exit fullscreen mode

第 2 步:提取同意意图

对明显短语使用确定性规则,对较软的语言使用 LLM 分类器。

{
  "intent": "revocation",
  "channels": ["voice", "sms"],
  "scope": "all_outreach",
  "confidence": 0.94,
  "evidence_span_id": "span_928"
}

Enter fullscreen mode Exit fullscreen mode

第 3 步:应用置信度策略

示例策略:

  • 置信度 >= 0.90:立即写入撤销事件
  • 置信度 0.65 - 0.89:暂停工作流并请求人工审核
  • 置信度 < 0.65:存储为信号,不自动更改状态

对于撤销,宁可误报也不要继续不受欢迎的联系。对于新的同意授予,要更严格。

第 4 步:停止活动工作流

账本写入应发出一个事件,取消或暂停相关工作流。

await eventBus.publish("consent.revoked", {
  tenantId,
  subjectId,
  channels: ["voice", "sms"],
  scope: "all_outreach",
  evidenceRef: "call_123:span_928",
});

Enter fullscreen mode Exit fullscreen mode

然后工作者应停止未来步骤:

on("consent.revoked", async (event) => {
  await workflowStore.pauseRuns({
    tenantId: event.tenantId,
    subjectId: event.subjectId,
    affectedChannels: event.channels,
    reason: "consent_revoked",
  });
});

Enter fullscreen mode Exit fullscreen mode

如果撤销没有取消计划任务,账本就变成了日记,而不是控制系统。

设计渠道感知规则

并非每个权限事件都意味着相同的事情。

使用策略矩阵:

用户陈述 建议范围 默认操作
“不要再给我发短信” 仅短信 阻止短信,允许其他已获许可的渠道
“不要再给我打电话” 仅语音 阻止通话,允许其他已获许可的渠道
“不要联系我” 所有外展 阻止语音、短信、电子邮件、推送
“打错号码了” 联系方式 阻止该电话号码,标记身份质量
“改用邮件联系我” 渠道偏好 阻止当前渠道,要求检查电子邮件许可
“不要记录这个” 记录目的 停止记录,仅在工作流允许时继续

此矩阵应存在于代码或策略配置中,而不是在提示中。

账本在架构中的位置

一个简单的生产流程如下:

  1. 代理提出一个操作。
  2. 运行时策略检查工具权限。
  3. 同意账本检查用户权限。
  4. 速率限制器检查预算和频率。
  5. 工具执行器执行操作。
  6. 审计日志记录决策和证据。

这个顺序很重要。不要先调用工具再检查同意。

对于语音代理,在两个方向上都放置账本:

  • 在拨出电话或短信之前
  • 在实时通话中出现撤销时
  • 在通话后处理摘要时
  • 在活动重试之前
  • 在人工交接后续之前

可观测性:记录什么

每个同意决策都应产生一个小收据。

{
  "decision_id": "cd_456",
  "workflow_run_id": "run_789",
  "agent_id": "voice_followup_agent",
  "subject_id": "contact_123",
  "requested_action": "sms.followup",
  "purpose": "appointment_reminder",
  "allowed": false,
  "reason": "sms permission revoked",
  "matched_event_ids": ["ce_111"],
  "created_at": "2026-07-30T06:08:00Z"
}

Enter fullscreen mode Exit fullscreen mode

这有助于调试和用户信任。当有人询问代理为什么联系或不联系他们时,你会有一个具体的答案。

跟踪这些指标:

  • 按渠道检测到的撤销
  • 需要审核的不确定同意事件
  • 被阻止的代理操作
  • 撤销后暂停的工作流
  • 从撤销到工作流取消的时间
  • 有导入同意但无证据的联系人
  • 假阳性和假阴性审核结果

关键指标不是“发送了多少条消息”,而是“有多少操作被正确允许、阻止或暂停”。

常见错误

错误 1:将提示视为策略

系统提示可以告诉代理尊重同意。它无法在重试、工作线程、队列和集成中强制执行同意。

策略属于后端检查。

错误 2:仅存储当前状态

单个 can_contact=false 标志隐藏了决策发生的原因。你需要事件历史用于审计、调试和安全恢复。

错误 3:忽略目的

发送登录码的权限不是发送促销序列的权限。将同意与目的绑定。

错误 4:让计划任务绕过检查

排队任务应在执行时检查同意,而不仅仅是在安排时。同意可能在安排和发送之间发生变化。

错误 5:跨渠道扩展同意

如果用户为提醒提供电话号码,不要假设代理可以永远拨打、发送短信、丰富、记录和发送电子邮件。使扩展明确。

小型实施检查清单

用作首次检查:

  • [ ] 列出代理联系、记录、丰富或更新个人的每一个操作。
  • [ ] 为每个操作定义目的。
  • [ ] 创建仅追加的同意事件。
  • [ ] 构建当前状态读取模型。
  • [ ] 在工具执行前添加后端同意检查。
  • [ ] 从语音和短信中提取撤销。
  • [ ] 撤销后暂停活动工作流。
  • [ ] 存储证据引用,而不仅仅是标志。
  • [ ] 为不确定的 AI 提取事件添加人工审核。
  • [ ] 记录同意决策收据。
  • [ ] 在撤销后测试排队任务。
  • [ ] 单独审查导入的 CRM 同意。

测试账本

根据真实失败模式创建测试用例。

it("blocks scheduled SMS after spoken all-channel revocation", async () => {
  await ledger.append({
    subjectId: "c1",
    channel: "voice",
    purpose: "marketing_outreach",
    eventType: "revoked",
    scope: "all_outreach",
    source: "voice_agent",
    confidence: 0.96,
    evidenceRef: "call_1:span_9",
  });

  const decision = await canAgentAct({
    tenantId: "t1",
    subjectId: "c1",
    channel: "sms",
    purpose: "marketing_outreach",
    action: "text",
    workflowRunId: "run_1",
  });

  expect(decision.allowed).toBe(false);
  expect(decision.reason).toContain("revoked");
});

Enter fullscreen mode Exit fullscreen mode

还要测试:

  • 短信 STOP 在策略规定所有外展时阻止未来的语音。
  • “改用邮件联系我” 不会发送邮件,除非存在邮件许可。
  • 导入的 CRM 同意过期或需要验证。
  • 低置信度撤销会暂停工作流。
  • 人工覆盖会创建事件和收据。
  • 重试的工作线程在执行前重新检查同意。

这如何与更广泛的 AI 架构连接

同意账本最好与其他运行时控制一起工作:

  • 运行时策略 决定工具调用是否安全。
  • 速率限制 控制频率和支出。
  • 租户隔离 防止跨客户状态泄露。
  • 输出来源 存储生成的决策的证据。
  • 审批门 暂停有风险的操作以供审核。

同意是更大系统中的一个边界。但这是用户立即理解的边界。如果他们说停止,系统就应该停止。

常见问题

什么是 AI 同意账本?

AI 同意账本是一个仅追加的同意和撤销事件记录,加上一个运行时读取模型,用于决定 AI 代理是否可以联系、记录、丰富或为某人采取行动。

这仅适用于语音代理吗?

不是。语音代理使问题变得明显,但相同的模式适用于短信代理、电子邮件代理、支持副驾驶、CRM 自动化、通知系统以及使用个人数据的 AI 工作流。

LLM 可以决定是否存在同意吗?

LLM 可以帮助分类“请不要打扰我”这样的混乱语言,但它不应该是最终的执行层。写入候选事件,应用置信度规则,并让后端策略做出决定。

一个渠道的撤销是否应停止所有渠道?

这取决于用户陈述、产品策略和法律背景。“不要再给我发短信”可能是渠道特定的。“不要联系我”通常应停止所有外展。在策略矩阵中对此进行编码。

排队任务是否需要再次检查同意?

是的。昨天安排的任务今天可能变得无效。每个排队的联系操作应在执行前立即检查最新的同意状态。

我应该首先构建什么?

从撤销开始。捕获短信关键词、口头停止短语、外出操作前的当前状态检查,以及工作流取消。然后添加目的级授予、审核队列和更丰富的证据收据。

最终思考

AI 代理使工作流更快,但速度使同意错误代价更高。最安全的模式很简单:代理可以提议联系,但账本决定联系是否被允许。

如果一个人在一个地方撤销了许可,每个相关工作流都应在下一次工具调用运行之前听到它。