推出 actor 感知策略时可能会出什么问题?
actor 感知策略会根据是谁或什么在执行操作来改变应用的响应。做得好时,它能让人获得顺畅体验,为合法 agent 提供结构化路径,并且只在真正重要的地方增加阻力。做得不好时,它会把真实用户挡在门外,破坏客户依赖的集成,并制造没人能解释的支持工单。
差异很少在于策略逻辑本身,而在于推出方式。(关于策略模型本身,参见 为人类和 AI agent 设计应用级策略。) 本指南描述了团队可以针对任何 route 采用的分阶段流程: 观察,预览,小范围强制,然后扩展。每个阶段都有明确的退出标准和快速回退方式。
三种模式
可以把每条策略理解为在每个 route 上以三种模式之一运行:
- 观察。 分类结果会被记录下来。应用的响应与没有这些结果时完全一致。
- 预览。 策略会被评估并记录其决策,但不会被应用。你可以看到本来会发生什么。
- 强制执行。 决策会应用到真实请求上。
模式属于 route 和策略的组合,而不是整个应用。你的文档页面可以处于强制执行状态,而结账流程仍在观察。
第 1 步: 观察,直到你知道基线
从每个 route 都处于观察模式开始。至少收集:
- route 和 method,
- actor 类别(Human, Human + AI, Agent 或 Unknown)及其依据(verified, declared, inferred 或 unknown), 如 我们对应用流量的分类体系 所定义,
- response 状态,以及该 route 的一个简单结果标记,例如 "account created" 或 "search returned results"。
运行足够长的时间,以覆盖正常的每周周期以及期间发布的任何版本。然后为你关心的每个 route 记录基线: 类别构成、Unknown 比例,以及各类别的结果率。后续所有数据都要与这些数值对比。
退出标准: 你可以针对每个候选 route 描述谁在使用它,以及结果如何随类别而变化。
第 2 步: 将策略写成数据
当策略以数据形式存在,而不是散落在各处的 if 语句中时,它们更容易审查、测试和回滚。
type ActorClass = "human" | "human_ai" | "agent" | "unknown";
type Basis = "verified" | "declared" | "inferred" | "unknown";
type Action = "allow" | "adapt" | "challenge" | "review" | "limit";
type Mode = "observe" | "preview" | "enforce";
interface Rule {
actor: ActorClass | "*";
basis?: Basis[];
action: Action;
}
interface RoutePolicy {
route: string;
mode: Mode;
enforcePercent: number;
rules: Rule[];
fallback: Action;
}
export const policies: RoutePolicy[] = [
{
route: "GET /api/catalog",
mode: "preview",
enforcePercent: 0,
rules: [
{ actor: "agent", basis: ["verified"], action: "allow" },
{ actor: "agent", basis: ["declared", "inferred"], action: "limit" },
],
fallback: "allow",
},
];每条策略都有一个明确的 fallback。这是用于 Unknown 结果、超时以及任何规则未覆盖情况的 action。刻意选择它,是整个流程中最重要的安全决策。
第 3 步: 在一个地方进行评估
统一的 evaluation 函数可以让不同模式下的行为保持一致。
export function decide(policy: RoutePolicy, actor: ActorClass, basis: Basis): Action {
for (const rule of policy.rules) {
const actorMatch = rule.actor === "*" || rule.actor === actor;
const basisMatch = !rule.basis || rule.basis.includes(basis);
if (actorMatch && basisMatch) return rule.action;
}
return policy.fallback;
}然后应用模式:
export function applyPolicy(policy: RoutePolicy, actor: ActorClass, basis: Basis, requestKey: string) {
const decision = decide(policy, actor, basis);
const inSlice = bucket(requestKey) < policy.enforcePercent;
const enforced = policy.mode === "enforce" && inSlice;
record({ route: policy.route, mode: policy.mode, actor, basis, decision, enforced });
return enforced ? decision : "allow";
}
function bucket(key: string): number {
let h = 0;
for (const c of key) h = (h * 31 + c.charCodeAt(0)) >>> 0;
return h % 100;
}stable bucket 的含义是同一个 session 或 client 会始终留在强制切片内或切片外,这样用户不会看到请求之间的行为突然变化。
第 4 步: 在强制执行前如何测量误报?
将一个 route 切换到预览。此时每个请求都会记录它本来会得到的决策。你会在这里了解策略是否正确。
误报是指策略本来会限制,但实际上应该被允许的请求。你不能只通过分类结果来测量,因为分类本身正是你正在测试的内容。应当用独立信号来衡量合法使用:
- 请求完成了正常的成功流程,例如已登录用户保存了工作,
- client 是已知合作伙伴集成,或是你已经约定要服务的已验证 operator,
- 支持工单或用户报告提到了受影响的 route,
- 对一部分本应被限制的请求进行人工审查。
手动审查一个样本。阅读二十个真实的、策略本来会 challenge 的请求,是发现规则过宽的最快方法。
退出标准: 本应被限制的集合主要由你确实打算限制的流量构成,并且你能为发现的每一类误报给出解释。
第 5 步: 对一小部分流量执行强制
将该 route 切换为带有一个很小 enforcePercent 的强制执行。切片要足够小,以便错误只影响少量请求,同时也要足够大,能在一两天内产生可测信号。
关注与基线相同的指标:
- 该 route 上各类别的结果率,
- error 率和 challenge 完成率,
- 提到该 route 的支持量,
- 延迟,因为 evaluation 位于请求路径上。
如果强制切片中的 Human 流量结果相较于其余流量下降,就要在扩展前停止并调查。
第 6 步: 逐步扩展
按步骤增加切片,例如从一小部分到四分之一,再到一半,最后到全部流量,并在每一步停留足够长时间以观察一个正常周期。若前面的阶段做得好,扩展过程会很平稳。这正是目标。
选择回退和故障行为
提前决定当证据缺失或 evaluation 无法运行时会发生什么。
- Fail open 表示请求会按没有 actor 上下文时的方式继续执行。适用于低风险 route,例如公开页面、文档和只读目录 endpoint。
- Fail closed 表示会应用你现有的更强检查,例如重新认证或 review 队列。适用于你已经具备此类检查的高影响操作。
不要让 timeout 或 error 产生 Human 结果。缺失证据属于 Unknown,而 Unknown 采用该 route 的 fallback。我们关于 为什么在分类 AI 流量时 Unknown 是一个有效结果 的文章解释了这一规则为何重要。
async function actorWithTimeout(evaluate: () => Promise<{ actor: ActorClass; basis: Basis }>) {
const timeout = new Promise<{ actor: ActorClass; basis: Basis }>((resolve) =>
setTimeout(() => resolve({ actor: "unknown", basis: "unknown" }), 150),
);
return Promise.race([evaluate(), timeout]);
}如何快速回滚策略?
回滚应该是一次配置变更,而不是一次 deploy。将策略保存在团队可以快速编辑的存储中,并让以下操作立即生效:
- 将某个 route 恢复为预览,这样会停止强制执行,同时保留测量。
- 将
enforcePercent设为零。 - 禁用单条规则,而不影响其他规则。
记录每次策略变更,以及变更者和时间。当结果发生变化时,第一个问题永远是: 什么变了。
避免对个人做出具有后果的决策
actor 分类描述的是关于一次交互的证据。它们不是对某个人的判断。不要把推断出的类别作为影响某人就业、信贷、教育、住房或基本服务获取的唯一依据。对于这些流程,应使用 actor 上下文来选择一个流程,例如路由到人工审查,而不是据此直接决定结果。
在强制执行前先沟通
会影响自动化 client 的策略,也会影响运行这些 client 的人和公司。适当沟通可以减少大量支持工作:
- 公布自动化访问规则。 说明哪些 route 开放、适用哪些限制,以及 client 如何使用带签名的请求表明身份,方法是 HTTP Message Signatures (RFC 9421) 和 IETF Web Bot Auth protocol draft。
- 提前告知合作伙伴和集成方。 如果某个已知集成在预览期间落入受限切片,请在强制执行前联系其负责人。
- 返回有用的响应。 当策略限制或 challenge 某个请求时,请返回清晰的 status code,例如
429 Too Many Requests配合Retry-After,并提供指向文档的链接。使用 Problem Details 格式的机器可读 body 可以为自动化 client 提供结构化原因。设计良好的 agent 在理解原因后会进行调整。 - 把证据交给支持团队。 当收到关于被阻止请求的工单时,支持团队应能够在不询问工程的情况下查到 route、类别、依据和策略决策。
会提前宣布变更并进行解释的团队,遇到的意外通常远少于悄悄切换规则的团队,而能够适应变化的 client 会随着时间更容易被识别。
每个 route 的检查清单
- 已记录类别构成、Unknown 比例和结果的基线。
- 策略已写成数据,并带有明确 fallback。
- 已运行预览,已审查样本,已解释误报类别。
- 已在小切片上开始强制执行,并监控指标。
- 已选择故障行为: fail open 或 fail closed。
- 在扩展前已测试回滚。
Proof of Actor 在这里的作用
Proof of Actor 正是围绕这个流程设计的。观察是默认状态,每个结果都携带其依据,而 Route 策略由你来定义,并在你自己的代码中按 route 进行强制执行。Detect, Understand, Route: 按这个顺序。
如果你想和我们的团队一起在自己的应用上完成一次推出,请 申请提前访问。若要了解端到端的决策流程,请阅读 Proof of Actor 如何工作。
来源
- RFC 6585: Additional HTTP Status Codes, Section 4:
429 Too Many Requests状态码 - RFC 9110: HTTP Semantics, Retry-After: 告知 client 何时重试
- RFC 9457: Problem Details for HTTP APIs: 机器可读错误正文
- RFC 9421: HTTP Message Signatures: 自动化 client 如何为请求签名
- draft-ietf-webbotauth-httpsig-protocol: 用于已签名自动化流量的 IETF Web Bot Auth 草案(截至 2026 年 11 月仍在进行中)