一、为什么"已经授权成功"还不够?
假设一位运营人员同时管理两个门店:
text
门店 A:开发调试门店
门店 B:正式运营门店
他希望在本地智能体里完成这些任务:
- 查询门店 A 的员工和商品;
- 修改门店 A 的一项测试数据;
- 切换到门店 B 查询真实订单;
- 必要时撤销门店 A 的授权,但保留门店 B;
- 第二天重新打开客户端时,仍能分清两个连接。
如果系统只考虑"能不能授权一次",很容易变成下面这样:
text
本地 Agent
-> 一个全局 Token
-> 一个当前 tenant_id
-> 所有对话和工具共用
这套方案在单账号演示中可能看不出问题,但进入多账号场景后会立刻暴露风险:
- 当前 Token 到底属于哪个账号?
- 切换门店时,旧对话是否也跟着换了身份?
- 模型能不能自己修改
tenant_id? - 同一个账号重新授权,是替换旧连接还是再增加一条?
- 删除本地配置以后,服务端授权是否仍然有效?
- 一个连接失效,会不会把其他账号一起退出?
所以,多账号能力不是在设置页增加一个下拉框。
它本质上是一个身份生命周期问题。
二、用户切换的不是"网址",而是完整业务身份
很多系统会把多个门店部署在不同路径下:
text
https://example.com/tenant/store-a/
https://example.com/tenant/store-b/
于是最直觉的实现是,让用户在客户端里填写不同的业务网址。
但网址只能帮助定位入口,不能独立证明当前业务身份。
一条真正可执行的业务身份至少包含:
text
业务系统
+ 当前账号
+ 当前租户或门店
+ 当前角色与实时权限
+ 允许进入的中枢工作区
+ 对应的 Agent Session
+ 本地安全凭据
+ 可撤销状态
其中任何一项变化,都可能改变本次操作是否允许成立。
因此,一个本地连接不能只保存:
json
{
"name": "门店 A",
"url": "https://example.com/tenant/store-a/"
}
更合理的抽象应该是:
text
一个连接
= 一次由业务系统确认的可信身份授权
+ 一组非秘密连接元数据
+ 一份由操作系统保护的可撤销凭据
用户看到的"门店 A""测试环境""客户 CRM"只是本地别名,不是权限事实。
权限事实仍然来自业务系统当前登录态、中枢绑定结果和执行时的实时校验。
三、四种看起来省事、实际上很危险的实现
1. 让模型填写 user_id 或 tenant_id
例如把工具设计成:
json
{
"tenant_id": "store_a",
"user_id": "user_001",
"action": "employee.update"
}
如果服务端相信这些字段,那么模型只需要改一个字符串,就可能尝试访问另一个租户。
模型可以提供业务对象和用户意图,但不能生成自己的行动主体。
2. 给所有门店共用一个超级管理员 Token
这样确实容易调用接口,却把业务系统原有的用户、角色和租户隔离全部压扁成一个高权限入口。
一台电脑丢失、一份日志泄露或一次错误工具调用,都可能影响所有门店。
3. 同一个别名每次授权都静默覆盖
用户原本连接的是测试门店,重新授权时浏览器里登录的却是正式门店。
如果客户端不比较授权后的真实身份,直接覆盖 default,下一轮任务就可能在用户没有意识到的情况下切换业务主体。
4. 切换账号时复用原来的长对话
旧对话中可能已经包含:
- 门店 A 的员工编号;
- 门店 A 的订单结果;
- 门店 A 可用的工具;
- 针对门店 A 的知识与记忆;
- 一项尚未完成的写操作或审批。
如果把同一条对话直接切换到门店 B,模型很可能继续引用旧对象。
多身份系统最怕的并不是"无法连接",而是连接成功后发生安静的上下文串用。
四、更合理的入口:一个公开连接配置,浏览器决定真实登录身份
对通用智能体插件来说,开发者当然需要配置自己的中枢,而不是写死某一家业务系统。
最终用户可以填写或导入这样的非秘密配置:
text
hubUrl=https://hub.example.com
clientAppId=merchant-agent
workspace=operations
connectionName=default
这里不应该包含:
- 业务账号密码;
- 浏览器 Cookie;
- Tool Provider Secret;
- 数据库地址;
- 超级管理员 Token;
- 写死的某个门店用户 ID。
当用户点击"添加连接"时,客户端发起一次临时授权请求,并打开系统浏览器:
text
本地 Agent
-> 中枢创建一次性授权请求
-> 系统浏览器打开业务授权入口
-> 业务系统读取当前登录态
-> 用户确认当前账号、租户、设备与允许范围
-> 返回一次性授权码
-> 客户端完成 PKCE 兑换
-> 建立一个可撤销 Agent Session
这种方式有两个重要好处:
第一,用户不需要把业务密码交给插件。
第二,授权的是浏览器当前真实登录身份,而不是客户端猜测出来的门店地址。
OAuth 原生应用最佳实践建议通过外部浏览器完成授权,并要求公共原生客户端使用 PKCE,避免应用直接接触用户凭据,也降低授权码被截获后重放的风险。参见 RFC 8252。
需要强调的是,PKCE 只保护授权过程。
最终"当前是谁、属于哪个租户、拥有什么业务权限",仍然必须由业务系统服务端派生。
五、同一个统一授权入口,怎样添加第二个账号?
通用插件不应该要求开发者为每个门店硬编码一个授权地址。
更合理的体验是:
- 用户在客户端点击"添加连接";
- 系统打开同一个公开授权入口;
- 业务系统根据当前浏览器登录状态识别账号;
- 如果一个账号能访问多个门店,业务授权页由业务系统让用户选择;
- 用户确认后,客户端获得这次授权对应的真实身份摘要;
- SDK 决定替换现有连接,还是创建一条新连接。
如果用户想换账号,可以在业务授权页面退出当前账号,再用另一个账号登录。
这和很多 CLI 的浏览器授权体验类似:
text
入口保持不变
真正授权哪个账号
取决于浏览器中登录并确认的是谁
对于不同业务系统,中枢可以配置不同 Client App;对于同一个业务系统下的多个门店,不需要让本地插件理解每一家 SaaS 的私有 URL 规则。
门店选择、首次改密、账号退出和业务登录都属于业务系统自己的授权门户。
通用 SDK 只负责发起授权、接收结果和管理连接生命周期。
六、同身份重新授权与不同身份授权,处理规则不能一样
授权完成后,客户端不应该只看用户输入的连接名称,而应该比较服务端返回的稳定身份绑定。
可以遵循下面这组规则。
情况一:同一绑定、同一业务身份
例如用户原来已经授权门店 A,Token 即将过期,于是重新完成授权。
合理结果是:
text
更新门店 A 的 Session 和凭据
保留门店 A 的本地别名
旧凭据失效
不增加重复连接
情况二:同一绑定、不同业务身份
例如 default 原来代表门店 A,重新授权时浏览器里登录的是门店 B。
合理结果是:
text
保留门店 A
为门店 B 创建一个新的可识别别名
提示用户新身份已经加入
不静默覆盖门店 A
情况三:相同显示名称,但真实身份不同
两个业务系统都可能显示"管理员"或"总店"。
本地别名可以自动追加短标识,例如:
text
总店
总店-2
但真正用于隔离的不能是显示名称,而应是服务端返回的稳定主体标识、租户标识和绑定关系。
情况四:授权中途取消或失败
用户关闭浏览器、拒绝授权或网络超时,不应该破坏已有连接。
旧连接继续保持原状态,新连接只有在完整兑换成功并安全保存凭据后才进入可用状态。
这是一条很重要的失败原则:
添加新身份失败,不能顺手把现有身份登出。
七、连接切换必须由用户明确触发,模型不能自己换号
多连接建立以后,本地智能体可以提供:
text
connections list
connections add
connections use <name>
connections remove <name>
connections revoke <name>
桌面客户端也可以把它们做成连接管理页面。
但有一条边界必须固定:
模型可以提醒用户当前连接不匹配,但不能自行从门店 A 切换到门店 B。
原因很简单。
用户说"帮我查一下今天的订单",并不代表模型有权决定查哪个租户。
如果当前上下文存在歧义,智能体应该明确展示:
text
当前连接:开发调试门店
目标动作:查询今日订单
需要换账号时,由用户点击或执行明确的切换命令。
这比让模型根据自然语言猜"他说的大概是正式门店"可靠得多。
八、切换连接只影响新会话,旧会话应继续绑定原身份
连接选择和对话上下文不能混成一个全局变量。
推荐规则是:
text
当前默认连接
-> 只决定下一条新会话使用哪个身份
已经开始的会话
-> 固定绑定创建时的连接、主体与工作区
例如:
text
09:00 创建会话 A,绑定门店 A
09:10 用户把默认连接切换为门店 B
09:12 创建会话 B,绑定门店 B
09:15 继续回复会话 A,仍然只能使用门店 A
这样可以避免一条正在等待审批或工具结果的任务,在中途突然更换业务身份。
如果用户确实希望用门店 B 重新处理相同问题,应该创建一条新会话,并重新建立该身份下的能力和知识上下文。
九、身份隔离以后,工具、知识和记忆也要重新投影
切换身份并不只是换一份凭据。
不同租户可能具有不同的:
- 工具授权范围;
- 写操作白名单;
- 审批规则;
- 业务知识库;
- 页面上下文;
- 用户角色;
- 对象可见范围。
因此,新会话开始时,本地 Agent 应根据当前连接向中枢获取这次身份对应的运行上下文:
text
当前业务主体
-> 允许进入的 workspace / route
-> 当前可见的能力目录
-> 本轮相关工具
-> 相关知识与公开记忆
-> 风险、审批和审计规则
模型不能把门店 A 搜索过的内部工具列表永久缓存,再拿到门店 B 继续使用。
即使两个门店使用相同的接口名称,执行时也要重新验证当前 Session、路由、能力范围和业务权限。
十、凭据不能明文放进插件配置文件
多账号以后,凭据数量会增加。
如果每个连接都把 refresh token 写进 JSON、.env 或插件配置,用户复制配置、提交仓库、发送截图时就可能一起泄露。
更合理的做法是把数据分成两类。
可以保存在普通配置中的非秘密元数据
text
hubUrl
clientAppId
workspace
connectionName
当前选择的连接名称
非敏感身份显示摘要
必须进入系统安全存储的秘密
text
access token
refresh token
凭据版本
与刷新和撤销有关的秘密状态
在 macOS 上,可以使用 Keychain Services 保存小型秘密。Apple 官方将 Keychain 定位为应用安全保存密码、密钥等小型敏感数据的机制,参见 Keychain Services。
在 Windows 上,可以使用当前用户作用域的 DPAPI。默认情况下,CryptProtectData 保护的数据只能由同一台机器上的同一用户账户解密;它不应该被当作跨机器同步方案,参见 Microsoft DPAPI 文档。
这也意味着:
- 连接配置可以导出;
- 业务凭据不应该跟着明文导出;
- 换电脑后通常需要重新授权;
- 不同系统用户之间不应自动共享连接;
- 安全存储不可用时应失败关闭,而不是退回明文保存。
跨平台支持不是简单地"Windows 也能运行 Node.js"。
只要 Agent Session 需要长期恢复,就必须把 Windows、macOS 和 Linux 的凭据存储边界一并考虑。
十一、本地删除和服务端撤销不是一回事
连接管理至少需要区分两个动作。
删除本地连接
它表示:
text
本机不再保存这条连接和凭据
适合用于清理本地环境,但不一定能证明服务端 Session 已经失效。
撤销远端授权
它表示:
text
中枢或业务系统把这条 Agent Session 标记为不可继续使用
适合设备丢失、员工离职、权限收回或用户主动解除授权。
更完整的"移除连接"流程通常是:
- 请求服务端撤销对应 Session;
- 服务端明确返回撤销结果;
- 清除本地安全凭据;
- 从连接注册表删除本地元数据;
- 如果删除的是当前默认连接,明确选择一个剩余连接或回到未连接状态。
如果远端撤销失败,客户端不应该假装已经全部完成。
它可以清楚提示:
text
本地凭据已清除
远端撤销尚未确认
请在中枢后台检查设备授权
对于企业系统,"界面里看不见了"和"凭据已经失效"是两个不同事实。
十二、用两个门店走一遍完整流程
假设本地智能体已经连接开发调试门店。
第一步:查看当前连接
text
当前:开发调试门店
状态:可用
工作区:门店运营
用户发起:
查询当前门店的一位员工,并修改测试备注。
本地 Agent 在开发调试门店身份下完成查询和允许的可回滚写操作,中枢记录会话、工具调用和审计轨迹。
第二步:添加第二个门店
用户点击"添加连接"。
浏览器打开统一授权入口。用户退出原账号,登录另一个真实账号,并在业务授权页确认正式运营门店。
客户端识别这是不同业务身份,于是保留原连接并增加:
text
开发调试门店
正式运营门店
第三步:明确切换
用户选择"正式运营门店"。
客户端提示:
text
已将正式运营门店设为新会话默认连接
现有会话不会改变身份
接下来新建的对话使用正式运营门店;之前的测试对话仍固定在开发调试门店。
第四步:单独撤销测试门店
用户撤销"开发调试门店"。
服务端 Session 失效,本地安全凭据被清理;正式运营门店继续保持可用。
这个流程真正证明的不是"客户端有一个下拉框",而是:
text
不同身份能够共存
+ 切换由用户明确触发
+ 对话不会跨身份漂移
+ 一个身份可以单独撤销
+ 另一个身份不受影响
十三、BailingHub 在这套架构里负责什么?
BailingHub v0.5.0 已经公开 Agent Client v1 的基础接入边界,包括:
- 外部浏览器授权与 PKCE;
- 可撤销 Agent Session;
- 本地 Agent 编排使用的 Runtime;
- route、工具范围与 ACC 审批继承;
- Conversation、Agent Run、工具调用和审计轨迹;
- 宿主无关的 Agent Client SDK;
- DeepSeek Harness 等宿主通过独立插件接入。
这些公开能力解决的是"本地 Agent 怎样在不接触业务账号密码和 Cookie 的前提下,获得一个受治理业务身份"。
当系统继续扩展到多账号、多门店与多业务身份时,应继续保持相同边界:
| 组件 | 负责什么 |
|---|---|
| 本地 Agent / Desktop | 用户交互、明确选择连接、本地推理和多步编排 |
| 通用 SDK / 插件 | 浏览器授权、连接注册、凭据安全存储、刷新、切换与撤销 |
| BailingHub 中枢 | Client App、允许路由、身份绑定、能力投影、审批、执行与审计 |
| 业务系统 | 登录页面、账号与门店选择、可信身份派生、实时权限和最终业务裁决 |
这里不应该出现一个"专门为某家 SaaS 写死门店 URL"的通用插件。
不同业务系统只需要接入统一 Agent Auth 方法,并由自己的授权页面处理登录和业务主体选择。
ACC 在其中描述工具风险、审批和执行语义,但不承担账号登录、租户选择或本地凭据存储。
十四、开发者接入多业务身份时的检查清单
授权入口
- 客户端打开的是公开统一授权入口;
- 业务账号密码只输入业务系统页面;
- 使用系统浏览器和 PKCE;
- 授权页面清楚展示当前账号、租户、设备和范围;
- 用户可以退出并换号;
- 多门店选择由业务系统根据当前账号权限提供。
身份与连接
- 同一连接保存稳定身份绑定,不依赖显示名称;
- 同身份重新授权会替换旧凭据,而不是制造重复连接;
- 不同身份授权会共存,不静默覆盖;
- 模型不能自行切换连接;
- 切换默认连接只影响新会话;
- 旧会话继续固定原身份和工作区。
凭据与撤销
- 秘密不进入 JSON、
.env、日志和导出配置; - macOS 使用 Keychain 或等价安全存储;
- Windows 使用当前用户作用域 DPAPI 或等价安全存储;
- 安全存储不可用时失败关闭;
- 本地删除和远端撤销状态能够区分;
- 一个连接撤销不会清除其他连接。
业务执行
- 每轮新会话按当前身份重新投影工具、知识和规则;
-
user_id、tenant_id不由模型自由填写; - 中枢重新校验 Session、route、工具与审批;
- 业务系统保留实时权限和对象归属的最终裁决;
- 后台能按 Conversation、Run 和工具步骤还原行动;
- 日志不记录 Token、Cookie 和未脱敏业务数据。
常见问题
1. 一个本地 AI Agent 可以连接多个业务系统吗?
可以。不同中枢或不同 Client App 可以形成独立公开绑定;同一绑定下也可以存在多个经过业务系统确认的身份。关键是每条连接必须独立存储、独立刷新、独立撤销,不能共用一个高权限 Token。
2. 切换门店时需要重新填写中枢地址吗?
通常不需要。同一个业务系统可以使用统一授权入口,由浏览器当前登录账号和业务授权页决定具体门店。更换中枢或接入完全不同的业务系统时,才需要增加另一组公开连接配置。
3. 可以让模型自动选择最合适的门店吗?
不建议。模型可以发现当前连接可能不匹配并请求用户确认,但身份切换应由用户明确触发。否则一句含糊的"查今天订单"就可能访问错误租户。
4. 同一个账号拥有多个门店怎么办?
业务系统授权页应根据该账号的真实权限列出可选门店,用户确认其中一个或一个明确范围。客户端不应该通过猜测 URL 或让模型填写门店编号来决定。
5. 关闭本地客户端算退出业务系统吗?
不一定。关闭程序通常只结束本地进程;可恢复 Agent Session 仍可能存在。需要解除授权时,应执行明确的远端撤销并清理本地安全凭据。
6. Windows 客户端为什么不能直接把 Token 写进配置文件?
因为 Windows 能运行插件不等于具备安全的长期凭据恢复。需要使用 DPAPI 等操作系统能力,把凭据绑定到当前用户或设备,并在保护上下文不可用时失败关闭。
7. 多账号功能属于 ACC 吗?
不属于 ACC Core。ACC 关注动作风险、审批和执行结果等治理语义;账号登录、连接管理、凭据存储和租户切换属于 Agent Client、中枢与业务系统的工程职责。
8. 多身份共存是否代表用户可以跨租户操作?
不是。共存只表示本机保存了多条独立授权。每次会话仍然绑定其中一个身份,业务系统继续按当前主体、角色和对象归属作最终授权。
结语:多账号的关键不是"切得快",而是"永远不会悄悄切错"
本地 AI Agent 开始操作真实业务系统以后,多门店、多租户和多账号几乎一定会出现。
真正可靠的方案,不是把更多 Token 塞进配置文件,也不是让模型自己猜应该使用哪个租户。
它应该满足:
text
一个公开授权入口
+ 浏览器确认真实业务身份
+ 同身份重授权安全替换
+ 不同身份独立共存
+ 用户显式切换
+ 旧会话身份不漂移
+ 工具和知识按身份重新投影
+ 凭据进入操作系统安全存储
+ 每条连接可以单独撤销
+ 中枢和业务系统共同保留审计与最终授权
当这些边界成立以后,本地智能体才真正具备从"连接一个后台"走向"管理多个业务身份"的基础。
如果你正在为商城、CRM、ERP、工单或 SaaS 后台开发本地 Agent,不妨先验证两条真实身份:
同一个公开绑定,授权两个业务账号,确认它们可以共存、明确切换、会话隔离并单独撤销。
这比先做一个漂亮的账号下拉框,更接近系统是否真的安全可用。
项目与延伸阅读: