很多团队第一次把 AI agent 接进真实系统时,凭证问题看起来还不大。一个短请求、一次工具调用、几十秒内结束,环境变量里塞个 token,似乎就够了。真正的麻烦通常出现在系统开始跑长任务之后:凌晨 2 点的同步任务做到第 47 分钟,合作方 API token 过期;浏览器 runner 正准备提交表单,SSO session 被统一轮转;某个 MCP 代理因为短期证书失效,把整条执行链卡在半路。
这时最危险的反应往往是“先让它续上”。如果系统为了追求恢复率,把 refresh token、长期密钥或者人工 re-auth 流程直接暴露给模型,问题并没有被解决,只是从可恢复故障变成了权限边界被打穿的事故。
真正要设计的不是“401 后再试一次”,而是凭证生命周期和 agent 生命周期如何对齐。否则你会发现:模型确实还在工作,但系统已经不知道它现在拿着的到底是旧权限、新权限,还是一个本不该出现在当前租户上下文里的 capability。
建议配合 AI agent 凭证委托与 Token Vault Proxy、AI agent Policy Engine 规则分层、AI agent 多执行环境路由 和 AI agent Harness 崩溃恢复与 Wake 流程 一起看。
先给结论:不是所有凭证都该“自动续”
| 凭证类型 | 常见寿命 | 能否自动续签 | 系统应该怎么做 |
|---|---|---|---|
| 短期访问 token | 分钟到小时 | 可以,但必须在 proxy/vault 内完成 | 刷新 capability 句柄,不把 token 明文交给 agent |
| 人工登录态 / SSO session | 小时到天 | 通常不能无感自动续 | 进入 needs_reauth,暂停高风险动作并触发人工接力 |
| 合作方 API key | 天到月 | 不该由 agent 自行续 | 用版本化 secret 引用替换,不让模型接触长期秘钥 |
| 临时签发的 scoped credential | 很短 | 可以重新签发 | 旧 scope 失效后,必须重新过 policy 和租户校验 |
如果系统一律把“过期”看成“再试一次”,它迟早会在权限升级、租户串线或副作用重放上出事故。
401 不是设计,过期状态必须先被结构化
多数恢复失败不是出在续签本身,而是系统只看到了 HTTP 401,却没看见 401 背后的语义差别:
- 是 access token 到期,还是 refresh credential 本身失效
- 是租户切换导致 scope 变了,还是用户撤销了授权
- 是 runner 本地缓存了旧 token,还是 vault 已经完成轮转但句柄没更新
更稳的做法,是把“凭证异常”抬成显式状态,而不是散落在工具日志里:
{
"runId": "run_2981",
"tool": "partner.crm.sync",
"authState": {
"status": "expired",
"credentialHandle": "cred_partner_crm_v12",
"scopeVersion": "tenant-a.standard",
"recoverability": "requires_reauth",
"sideEffectGate": "freeze"
}
}
一旦 recoverability 被结构化,调度器、proxy、人工 review 和用户界面才能说同一种话。否则前端只会显示“失败,请重试”,而后台根本不知道这次重试是不是已经越过了原本的权限边界。
续签动作应该发生在系统里,不应该发生在 prompt 里
真正生产可用的流程里,agent 拿到的应该是 capability,而不是裸 token。也就是说,模型最多知道“我现在可以发起一次 Git push”或“我有权限读取 CRM 某个范围的数据”,但不知道那串真正能完成动作的秘密是什么。
这件事听起来像安全洁癖,实际上是恢复能力的前提。因为只有这样,secret rotation 才能在不惊动模型的情况下完成:
- vault 更新长期密钥版本
- proxy 把旧 capability handle 映射到新 token
- 旧 session 收到
credential_version_superseded事件 - 后续工具调用重新以新 handle 执行
如果 token 是直接进了模型上下文,rotation 一旦发生,系统只能在三种坏选择里挑一种:继续用旧 token 硬撞、让模型向用户索要新 token,或者把整个 run 作废重来。
真正难的是“半路过期”,不是“启动前没授权”
启动前缺权限,至少还容易处理。半路过期才会把系统逼到角落里,因为这时 run 往往已经积累了上下文、草稿、中间结果,甚至拿到了准备提交的副作用。
设计上至少要分清三层:
- 可继续读取的动作:例如本地草稿整理、已有证据排序、低风险分析。
- 必须冻结的动作:例如发邮件、写外部数据库、执行 Git push、提交工单。
- 必须等待人工接力的动作:例如 scope 升级、组织级授权、涉及账单或客户数据的跨系统写入。
也就是说,credential expiry recovery 不是简单的 yes/no,而是一种有边界的降级。系统应该允许 run 进入 degraded_read_only,而不是一刀切让整条任务死掉;但它也不能为了“尽量不中断”就让所有副作用继续执行。
Re-auth handoff 要像任务移交,不要像报错弹窗
很多产品在 token 过期后只会弹一句“请重新登录”。对单页应用还勉强说得过去,对长任务 agent 来说几乎没有帮助。因为用户真正关心的不是“我是不是掉线了”,而是:
- 当前任务做到哪一步了
- 哪些结果已经安全保留
- 哪些动作因为权限问题被暂停
- 重新授权后系统会从哪一层继续,而不是全部重做
所以更好的 re-auth handoff 应该是一份结构化移交单,而不是一个无上下文的按钮。至少要包含:
- 当前 run 和 session 标识
- 已完成步骤与已冻结副作用
- 需要补的权限范围
- 恢复窗口,例如 30 分钟内恢复可原位继续,超时后转人工复核
这也是为什么 credential recovery 不该被藏在某个 SDK 的自动重试里。只要用户和操作员看不见 handoff envelope,他们就无法判断这次“重新授权”到底是在继续任务,还是在重跑任务。
一个常见事故:统一轮转做对了,系统恢复仍然做错了
一支团队把企业客户的第三方工单 token 统一接到了 vault,安全上看是进步。但上线后第一周,夜间 agent 仍出现大面积失败。原因不是 vault 没更新,而是 runner 在任务启动时把 capability 解析成了本地缓存 token,后续 50 分钟都没有再询问 proxy。结果是:
- vault 中 secret 已成功轮转
- proxy 已经知道新的 credential version
- 正在运行的旧 session 还在持续使用旧 token
- 系统自动重试 3 次后触发合作方风控,整个租户被临时封禁
最后修复不是“把重试次数调低”,而是把 capability 解析改成分层:
- 本地只缓存极短期 access token
- 每次进入高风险工具调用前重新做一次 handle resolve
- session log 记录
credential_version_seen - 一旦发现
superseded,直接冻结写操作并要求重新确认
这个例子很典型。很多团队以为 secret rotation 做完就结束了,实际上 rotation 只解决了“秘钥如何替换”,没有解决“正在运行的任务如何意识到世界已经变了”。
如果你现在要先补一层,优先补这三件事
第一,把 credential error 从原始报错里抬出来,变成调度器和前端都能理解的状态。
第二,让所有续签和轮转动作都发生在 vault/proxy 侧,agent 只拿 capability,不拿裸 secret。
第三,给长任务设计 re-auth handoff,把“权限恢复”做成一次可继续的任务移交,而不是一次模糊报错。
系统真正要守住的,不是某次 token 过期后还能不能马上跑通,而是权限生命周期是否仍由系统控制。如果凭证一过期,你就只能把秘密暴露给模型或者让用户手工补洞,那说明这套 agent 还没真的进入生产级。
延伸阅读:


