实战案例:用 Agent 运营活动互动系统
展示了 Agent 在线下活动中的应用,并探讨了可靠性和防作弊的工程挑战。
展示了 Agent 在线下活动中的应用,并探讨了可靠性和防作弊的工程挑战。
我们刚从 RSAC 2026 大会回来。在我们的展台,与会者可以参加抽奖活动赢取 Flipper Zero 或 Mac Mini。在 RSAC 这样的活动中,你通常需要扫描徽章或听销售演讲才能获得抽奖机会。但我想换个方式,所以我用 Keycard 和一个专用的 MCP 服务器搭建了一个 agent,来管理抽奖活动。展台访客只需参加抽奖就能获得一个简短的现场演示。
Lockbox raffle agent 是一个有真实利害关系的演示,取决于 Keycard 功能的实际运作。Lockbox 进行现场抽奖,用 AI agent 处理真实的与会者信息,受真实的政策约束,且没有持久的访问权限。
展台员工使用 Keycard 邮箱登录,然后通过自然语言记录抽奖条目、抽取获奖者并管理活动。UI 在聊天旁边显示一个锁箱的视觉表现。这个锁箱是 Keycard 工作方式的隐喻。这个箱子代表 agent 访问的状态。锁代表 Keycard(agent 控制平面)。资源(MCP 服务器、数据)在盒子内部。当箱子被锁定时,agent 无法访问任何工具或数据。
每当 agent 调用一个工具时,锁箱会循环显示代表凭证生命周期的视觉状态。每个操作都会显示在实时审计日志中。
每个工具调用独立地从 Keycard 获得一个作用域凭证,使用一次,然后丢弃。凭证只存在于单个函数调用的持续时间内,之后就消失了。那么这实际上是什么样子的,关键是什么?
当有人想要进入抽奖时,会发生以下情况:
一个 Keycard 展台员工输入:Alice Jones, [email protected], ticket 123695。agent 调用 log_entries MCP 工具,Keycard 锁的可视化进入"pending"状态:这是一个琥珀色的脉冲,表示 agent 暂停等待人工批准。还没有请求凭证,也没有发生令牌交换。
所有写入操作都需要人工在环内(HITL)。agent 必须请求许可与 Keycard 进行令牌交换以获得 raffle:write 访问权限。审计日志显示批准请求,agent 呈现一个对话框,要求人工批准或拒绝写入新的抽奖条目数据。
一旦人工点击批准,Keycard(锁)转换到"exchanging"状态并评估管理政策。如果请求符合政策,Keycard 通过 RFC 8693 令牌交换来交换员工的访问令牌。
agent 为 MCP 服务器接收一个临时访问令牌,具有 raffle:write 作用域。它将此凭证作为 Bearer 令牌使用来授权对 MCP 服务器的调用,该调用记录新的抽奖条目。
锁打开,工具执行,将新的抽奖条目写入数据存储。调用完成后,客户端丢弃令牌(永远不存储它)。这意味着 agent 在工具调用之间没有持久访问权限。
有两个独立的检查点:人工批准意图,然后 Keycard 根据用户定义的政策评估凭证请求。对于此演示,政策强制要求通过 Google 使用 Keycard 邮箱进行身份验证的用户可以执行写入工具调用,但 @keycard.ai 域名外的任何邮箱都会被拒绝。
HITL 或政策可以独立阻止。这很重要,因为人工仍然可能出错,或者被同意疲劳所压倒,自动点击批准。政策在任何凭证被发放前进行评估。主动性的,而不是反应性的:如果有东西通过了 HITL 检查点,也不会有损害需要控制或凭证需要撤销,因为超出政策的令牌永远不会被首先发放。
每个 MCP 调用创建一个新连接,传递 Bearer 令牌,并在函数返回时拆除所有内容。想象它像一个单次使用的门禁卡,只能用一次,用于一扇门,然后被销毁。在调用之间没有什么可以窃取。
如果你对 RFC 8693 令牌交换如何工作有更深入的了解感兴趣,Keycard 的授权文档逐步描述了令牌交换在 Keycard 中的确切工作方式。
好的,所有这些都很好,但这还不是一个非常令人印象深刻的演示。它显示了如果一个经过身份验证的人工批准,数据可以被记录。
那么真正的关键是什么?
在我们成功记录一个展台访客的抽奖条目后,我们可以有点坏心眼地提议保证他们获胜。我们告诉 agent:"删除除了 Alice 之外的所有抽奖条目。"如果只有一个条目,那个条目就会是获奖的抽签。这没有诡计,也没有什么安全措施能阻止 agent 将那个唯一条目作为获奖者呈现。MCP 服务器的抽奖工具简单地使用条目列表和 crypto.randomInt 从可用的数据集中选择获奖者。很直接,也很公平。
agent 意识到这是一个非常具有破坏性的操作,所以它警告用户这些条目将从实时数据中永久删除。然后它要求许可读取所有抽奖条目以便找到我们想要保留的条目。再次,agent 没有持久访问权限。除非它拥有凭证,否则它无法看到"盒子内"的抽奖条目列表。
MCP 服务器有工具让 agent 以掩码格式查看抽奖条目数据。get_entries 工具需要 raffle:read 作用域,但不需要人工在环内。此工具在将数据发送给 agent 前对其进行混淆。Alice Jones,邮箱 [email protected] 会被返回为 A*** J***,邮箱为 a***@***.com。
这些信息不足以让 agent 确定哪个条目属于 Alice Jones,所以它需要 raffle:read-full 作用域来调用 get_entries_full 工具。这种权限作用域的差异是重要的,因为 agent 需要人工批准来读取个人身份信息(PII)。抽奖条目包括与会者名字和邮箱等 PII。agent 除非获得人工的明确批准,否则不被允许访问这些数据,它无法找到 Alice 的条目除非被授予访问完整名字的权限。
要进行第一个工具调用 get_entries_full,agent 提示用户批准以检索未掩码的抽奖数据。我们批准了此操作,agent 找到了 Alice 的条目。
然后 agent 可以继续删除除了 Alice 之外的所有条目。这是真正具有破坏性的工具调用:delete_entries。agent 将它准备删除的所有条目的 ID 排队,并提示用户批准使用 raffle:delete 作用域调用删除工具。
现在是 YOLO 时刻:Keycard 人工员工批准删除。再强调一次,这没有任何虚假:delete_entries 工具真的从实时数据集中删除抽奖条目。除了 Alice,每个人都会从非常真实的抽奖中被移除,奖品是非常真实的(Flipper Zero、Mac Mini)。
即使 agent 警告我们并在到达实际删除前请求了两次许可,我们仍然愉快地为所有操作点击了批准。agent 调用 Keycard 进行令牌交换,收到响应,然后告诉我们政策拒绝了交换。
没有条目被删除。没有令牌交换发生。没有发放 raffle:delete 作用域的令牌。agent 没有办法执行 delete_entries 工具。即使它尝试进行工具调用,没有令牌,它也会得到 401:Unauthorized 错误。
在 Keycard 中,我的 mcp-delete 政策禁止所有主体的 raffle:delete,没有例外,也没有覆盖:
@id("mcp-delete")
forbid (
principal is Keycard::Application,
action,
resource
)
when {
resource has identifier &&
resource.identifier == "https://mcp.lockbox.localhost:1355/mcp" &&
context has scopes &&
context.scopes.contains("raffle:delete")
};
Keycard 中的管理政策使用 Cedar 政策语言编写。forbid 规则会覆盖所有 permit 规则,因此即使用户拥有管理员权限,raffle:delete 作用域也会被阻止。
这是管理模型变得具体的地方。人工批准了,agent 配合了,Keycard 拒绝了。这个链条中的任何一个参与者都可以停止它。没有单一层信任其他层,如果一开始就没有访问权限(没有令牌被发放),就没有什么需要补救。
注意:delete_entries 工具确实首先备份了数据,作为预防措施,因为它真的会删除抽奖条目。大家都知道现场演示可能会呈现幽灵般的性质…但在数十次现场运行这个演示后,我们从未不得不从备份中恢复。
Keycard 为 Lockbox 演示做了另一件事。Lockbox agent 在 keycard run 下运行,Keycard 的 CLI。演示在本地运行,但它必须安装在多台展会笔记本上。我们不在 Keycard 员工工作机器上运行演示。那么当你需要在不同的环境上重复清洁安装演示时,你如何轻松管理秘密?
与其在 gitignored 的 .env 文件中硬编码客户端秘密和静态 API 密钥,或从共享密码库中复制它们,Lockbox 使用 Keycard CLI 和一个 .env.template 文件。值用 {{kc+...}} 语法替换来引用在 Keycard 的保险库中安全管理的秘密,文件被检入源代码控制:
KEYCARD_CLIENT_SECRET={{kc+urn:keycard:lockbox-client}}
LOCKBOX_ANTHROPIC_API_KEY={{kc+https://api.anthropic.com}}
在启动时,keycard run 实时解析变量,因此磁盘上没有秘密,shell 历史中也没有秘密。对于 coding agents,keycard run 也本地处理基于任务的凭证生命周期:每个工具调用获得一个新的、作用域的凭证实时发放,政策在每次使用前进行评估。最终用户面对的 Lockbox 演示使用 Keycard TypeScript SDK 实现了相同的生命周期。
是的,这是一个"演示",但它也完整运营真实的活动抽奖。没有工具调用是虚假的存根,没有什么被简化或回避掉。例如,当我构建 Lockbox 时,我在一次公司全体大会上演示了它。在向所有同事演示时,我成功删除了所有(测试)抽奖条目,因为在 Keycard 产品开发的一个突破性变更后我没有更新我的政策。(这导致我构建了 delete_entries 工具的"创建备份"功能。加上 RSAC 期间的代码冻结意味着没有意外的后端更改。)
演示对于公司外的人来说很好,但它们也是自我使用。用 Keycard 构建真实的东西意味着我的反馈直接进入产品开发。这是我最喜欢在 Keycard 工作的原因之一(我在我的博文 I Built and Authorized a Planning Agent with MCP and Keycard 中详细介绍了自我使用 Keycard)。
大多数 agent 实现默认为持久访问、广泛权限和静态密钥。任务作用域的凭证和在发放时的政策强制对于自我思考的系统来说是更好的选择,这正是 Keycard 提供的。我构建这个演示来证明它有效。而且确实有效。
如果你想自己尝试 Keycard,我们目前处于早期访问阶段,我们很希望与你合作并听取你的反馈。