一、为什么“能查到员工”不等于“能修改员工”?
这是企业 Agent 接入中非常典型的一类问题。
用户先说:
帮我查一下张三的员工资料。
Agent 很快返回了正确结果。
接着用户说:
把他的备注改成“负责华东区渠道”。
这时却可能出现几种完全不同的反馈:
- 找不到“修改员工”工具;
- 当前没有调用该工具的权限;
- 操作需要审批;
- 当前账号无权修改该员工;
- 请求已经发出,但业务系统返回失败;
- 在后台聊天入口里可以修改,换到本地 Agent 后却不可以。
表面上看,它们都是“不能改”。实际上,它们分别可能发生在能力声明、路由裁剪、写工具开放、审批、身份绑定和业务权限等不同层次。
最容易产生的误判是:
查询成功
≠ 当前路由已经开放写工具
≠ 写操作不需要审批
≠ 当前身份拥有业务修改权限
≠ 目标对象此刻允许被修改
所以,正确的排查方式不是继续修改提示词,让模型“更大胆一点”,而是先找到请求被哪一道闸门拒绝。
二、先把“权限”拆开:它至少包含七个问题
企业后台里的 Agent 写操作,不应该由一个简单的 can_write=true 决定。
一条比较完整的链路,至少要依次回答七个问题:
- 业务系统有没有把这个接口声明为可供 Agent 使用的能力?
- 当前 route 是否选择了对应工具源、放行它的 scope,并把
permission设为允许写入的readwrite或full? - 当前 Client、Agent Session、route 和 audience 的交集是否允许进入这个工作区?
- 当前路由是否启用了本地 Agent 的直接工具面?
- 如果它是写操作,是否按精确
operationId加入写工具白名单? - ACC 声明或路由附加策略是否要求审批?
- 业务系统在真正执行时,是否仍然认可当前用户、租户、角色和目标对象?
可以把它理解为一条逐层收窄的通道:
业务能力声明
∩ route 工具源、scope 与 permission
∩ Client / Session / audience
∩ Agent Direct 开关
∩ 精确写工具白名单
∩ ACC 与额外审批规则
∩ 业务系统实时授权
= 本次真正允许执行的写操作
三、第一层:业务侧到底有没有正确声明“修改”能力?
很多项目只把查询接口接入了 Agent,却误以为相近的修改接口会自动出现。
实际上,查询员工和修改员工通常是两个独立 operation:
staff_list 查询员工
staff_get 查询员工详情
staff_update_profile 修改员工资料
写操作需要在业务侧 OpenAPI / ACC 声明中拥有稳定的 operationId、参数 schema、scope、主体要求、风险和审批语义。例如:
operationId: staff_update_profile
x-agent-capability:
version: 1
enabled: true
scope: tenant.staff.write
subject:
required: true
risk:
level: medium
approval:
required: false
execution:
readonly: false
这段声明表达的是:它是一个需要业务主体的写能力,业务侧 ACC 当前没有要求固定审批。
但它并不代表“任何 Agent 都能直接调用”。后面仍要经过路由、写工具白名单、身份和业务系统权限检查。
这一层常见的问题包括:
- 写接口没有
x-agent-capability.enabled=true; - 写接口和查询接口误用了同一个
operationId; operationId改名后,中枢配置仍然引用旧名字;- 参数 schema 不完整,Agent 无法构造合法请求;
- 把写接口错误声明成只读;
- scope 与路由中配置的 allow 不一致;
- 接口要求主体,但当前请求没有可信主体。
因此,第一步应该查看中枢从工具源实际解析到了什么,而不是只看业务后端“确实有这个接口”。
四、第二层:route 有没有放行正确的工具源、scope 和写权限?
业务系统声明了一项能力,不代表它会自动出现在所有 Agent 场景中。
中枢通常还会在 route 中选择工具源,并通过 tools.sources[].allow 限定当前场景需要的 scope。例如一个员工助手路由可以只开放:
permission: readwrite
tools:
sources:
- provider: business-system
allow:
- tenant.staff.read
- tenant.staff.write
如果 route 只放行了 tenant.staff.read,那么员工查询可以正常工作,tenant.staff.write 下的修改能力却不会进入当前能力面。
这不是模型权限不足,而是路由在装配工具时已经把它裁掉了。
还要同时检查 route 自身的 permission。在 BailingHub v0.5.0 的 Agent Client 直接工具面中,写工具只有在 permission 为 readwrite 或 full 时才会保留;否则即使 scope 和 write_tools 都正确,查询仍可能可见,写工具却会被过滤。
排查时重点确认:
- 当前会话实际命中了哪个 route;
- route 绑定的是不是预期工具源;
allow中是否包含写工具对应 scope;- route 的
permission是否为readwrite或full; - 多工具源中是否存在重复
operationId; - 修改配置后,运行时使用的能力版本是否已经刷新。
五、第三层:本地 Agent 还要经过独立的直接工具面
当编排发生在本地 Agent 时,中枢不能因为它完成过网页授权,就把 route 中所有写工具全部交给本地运行时。
BailingHub Agent Client v1 会继续对 Client、Agent Session、route、audience 和 tools.agent_direct 求交集。路由需要显式启用本地 Agent 的直接工具面:
tools:
agent_direct:
enabled: true
如果这个开关没有启用,本地 Agent 不能通过 Agent Client Runtime 直接调用业务工具。
此外,route、Agent Client、Agent Session 与 audience 必须同时允许,本地 Agent 才能在正确工作区发现能力。
所以,“已经授权成功”只能证明客户端拿到了一个有效 Agent Session,不等于这个 Session 自动拥有中枢中的全部路由和工具。
六、第四层:只读工具和写工具的开放方式并不相同
这是“能查不能改”最常见的直接原因。
在 BailingHub v0.5.0 的公开 Agent Client 语义中:
- 只读工具仍要通过工具源、scope、route、audience 和主体等边界,但不需要逐项加入
write_tools; - 写工具必须按精确
operationId加入tools.agent_direct.write_tools; - 写工具白名单不允许使用
*; - 列入
write_tools只表示“允许本地 Agent 尝试调用”,不表示绕过审批或业务权限。
例如:
tools:
agent_direct:
enabled: true
write_tools:
- staff_update_profile
如果漏掉了 staff_update_profile,员工查询仍可能正常,修改员工则会在直接工具面被隐藏或拒绝。
为什么不允许给写工具填一个 *?
因为工具源会持续变化。今天只有“修改员工备注”,下周可能新增“停用员工”“批量调整角色”“重置登录凭据”。如果通配符自动接纳未来新增写操作,一次普通配置就可能在无人审查的情况下扩大权限。
精确白名单看起来多了一步,却能把“未来新增接口”从默认放行变成默认拒绝。
七、第五层:允许调用,不等于不需要审批
另一个常见误解是:
我已经把工具放进
write_tools了,为什么还提示审批?
因为“是否可调用”和“是否需要审批”是两件事。
BailingHub v0.5.0 的公开语义是:写工具是否需要审批,默认继承业务侧 ACC 中的以下声明:
risk.level;approval.required;approval.when。
其中 approval.when 可以根据本次真实参数决定是否审批。例如普通资料修改直接执行,但批量修改数量超过阈值时进入审批。
route 还可以通过 force_approval_tools 对某些已开放写工具额外收紧:
tools:
agent_direct:
enabled: true
write_tools:
- staff_update_profile
- staff_disable
force_approval_tools:
- staff_disable
这里表示:staff_disable 无论 ACC 原本是否要求审批,当前 route 都额外要求审批。
但这项配置只能收紧,不能放宽:
- ACC 明确要求审批的工具,route 不能把它改成免审批;
- 高风险或命中条件审批的操作,不能被本地插件绕过;
- 本地 Agent 不应该再实现一套相互冲突的“是否审批”判断。
如果同一个员工修改操作在网页聊天里不审批、在本地 Agent 里却审批,应该检查两边是否命中同一 route、同一 ACC 版本,以及本地 route 是否把该 operation 加入了 force_approval_tools,而不是直接认定业务声明失效。
八、第六层:Agent 拿到的是可信业务身份,不是万能后台账号
本地 Agent 完成网页授权后,并不是获得业务系统的账号密码,也不是复制浏览器 Cookie。
更合理的方式是:业务授权页根据自己的服务端登录态,确认当前用户与租户,再把一个可信业务主体绑定给 Agent Session。后续中枢在调用业务工具时携带这个主体,业务系统据此重新做权限判断。
因此,下面几种情况都可能导致“查询可以,修改不行”:
- 当前业务角色只有查看员工权限,没有编辑权限;
- 当前用户属于门店 A,却试图修改门店 B 的员工;
- 用户授权以后被管理员撤销了编辑权限;
- 员工已经离职、冻结或进入不可修改状态;
- 目标字段只有更高角色可以修改;
- 授权时绑定的租户或业务主体不完整;
- 业务接口只验中枢签名,却没有校验代表谁操作。
最后一种尤其危险。中枢签名只能证明“请求来自可信中枢”,不能证明“任意业务用户都有权限修改任意对象”。
业务系统必须在执行当下重新回答:
这个主体是谁?
属于哪个租户?
当前拥有什么角色和权限?
目标对象是否属于其可管理范围?
对象当前状态是否允许这项变更?
这也是为什么 Agent Session 尚未过期,业务侧仍然可以拒绝一次写操作:权限判断必须以实时业务事实为准。
九、为什么后台聊天入口能改,本地 Agent 却不能改?
这种现象不应简单归因于“本地 Agent 能力更弱”。更常见的原因是两条链路的配置并没有真正对齐。
后台嵌入聊天和本地 Agent 可以复用同一个 ACC 能力事实源、同一套业务身份与审批语义,但它们的入口边界仍然可能不同:
| 对比项 | 后台嵌入聊天 | 本地 Agent Client |
|---|---|---|
| 入口身份 | 业务后台当前可信票据/主体 | 网页授权后绑定的 Agent Session 主体 |
| 路由范围 | 聊天入口所绑定的 route | Client 与 Session 共同允许的 route |
| 编排位置 | 中枢既有聊天链路 | 本地 Agent 负责规划 |
| 工具开放 | route 的既有工具治理 | 还需经过 tools.agent_direct |
| 写操作 | 依 route 与 ACC 规则执行 | 还必须进入精确 write_tools |
| 最终授权 | 业务系统实时校验 | 同样由业务系统实时校验 |
排查时可以用同一个业务用户、同一个租户、同一个目标员工和同一个 operation 做对照,逐项核对:
- 是否命中同一个 route;
- 本地 Client / Session 是否允许该 route;
- 本地 route 是否启用
agent_direct; - 写工具是否精确列入
write_tools; force_approval_tools是否额外收紧;- 两边传到业务系统的可信主体是否一致;
- 业务系统实际返回的领域错误是否一致。
这样才能定位差异,而不是用更强的 Prompt 掩盖配置问题。
十、按症状定位:先看失败发生在哪一层
| 用户看到的现象 | 更可能的层次 | 优先检查 |
|---|---|---|
| Agent 完全不知道有“修改员工”能力 | 声明或路由装配 | ACC enabled、operationId、route 工具源、scope allow |
| 网页聊天有工具,本地 Agent 没有 | Agent Client 直接工具面 | Client/Session route、audience、agent_direct.enabled、write_tools |
| 查询正常,所有修改都提示无权调用 | route 写权限或写工具白名单 | route 的 permission 是否为 readwrite / full,精确 operationId 是否进入 write_tools,是否仍引用旧名称 |
| 修改工具可见,但调用后进入等待 | 审批层 | ACC risk / required / when、force_approval_tools |
| 审批通过后仍被拒绝 | 身份或业务授权 | 主体、租户、角色、对象范围、实时权限 |
| HTTP 200,但 Agent 说修改失败 | 业务领域结果 | 响应 envelope、业务 code、错误信息映射 |
| A 门店可改,B 门店不可改 | 租户和对象授权 | 当前授权主体、门店归属、目标对象范围 |
| 修改偶尔成功、偶尔重复 | 幂等与恢复 | invocation/job 标识、超时续查、业务幂等键 |
一条实用原则是:
工具不可见,先查能力装配;工具可见但不可调用,查写白名单与路由;进入等待,查审批;已经送达业务系统,查实时业务权限和领域状态。
十一、不要用高风险操作做第一次写权限验收
第一次验收不要选择退款、删除订单、停用账号、修改库存或财务入账,而应选择低风险、可识别、可回滚的操作,例如:
- 修改开发测试员工的内部备注;
- 给测试客户增加一个普通标签;
- 创建一张未提交草稿;
- 新建一条内部测试工单。
以修改员工备注为例,可以按下面流程验收:
- 确认环境:只使用开发测试租户与测试员工;
- 读取基线:先查询员工详情,记录原备注;
- 设置唯一值:改成带时间或验收编号的可识别文本;
- 观察能力:确认 Agent 搜索到了正确的读、写工具;
- 观察治理:确认是否按 ACC 与 route 规则进入审批;
- 执行修改:只提交一次,记录同一轮 run 与工具 job;
- 再次读取:用只读工具确认业务对象确实变化;
- 检查审计:确认能看到工具、审批、状态与业务结果摘要;
- 执行回滚:把测试字段恢复为原值;
- 再次确认:验证回滚成功且没有产生重复写入。
这套流程验证的不是“模型会不会说已完成”,而是从权限开放到业务落库是否形成了闭环。
十二、正向测试通过以后,还要做哪些负向测试?
安全能力不能只验证“允许时能成功”,还要验证“不允许时确实失败”。
至少建议覆盖以下场景:
1. 从 write_tools 移除目标 operation
预期:本地 Agent 不应继续直接调用该写工具,即使模型记得它的名字。
2. 从 route allow 移除写 scope
预期:该能力应从当前 route 的授权工具面消失。
3. 尝试使用 * 开放全部写工具
预期:配置应被拒绝,而不是静默扩权。
4. 将工具加入 force_approval_tools
预期:它应额外进入审批;移除后则回到 ACC 自己的审批语义,而不是默认永久免审批。
5. 撤销业务用户的编辑权限
预期:即使 Agent Session 仍然有效,业务系统也应在执行时拒绝修改。
6. 使用跨租户或无权管理的对象 ID
预期:业务系统 fail-closed,不能因为中枢签名有效就放行。
7. 修改 ACC 为条件审批
预期:未命中条件时按声明执行,命中真实参数阈值时进入审批;不能由本地 Agent 自行解释条件。
8. 模拟超时后继续查询原任务
预期:客户端优先恢复或查询原 invocation/job,不应该立即重新提交同一个副作用。
负向测试能证明权限边界真正生效,而不只是刚好在一次正向演示中没有出错。
十三、排查时应该保留什么审计信息?
如果后台只显示“失败”,维护者仍然很难判断是哪一层拒绝。
一条安全而有用的执行轨迹,至少应该回答:
- 当前使用哪个 Client、Agent Session、工作区和 route;
- 能力 revision 是否与预期一致;
- 目标 operation 是否进入当前授权能力面;
- 它是只读还是写操作;
- 是否命中
write_tools; - 审批来自 ACC 还是 route 额外收紧;
- 中枢是否把请求送达业务系统;
- 业务系统返回成功、拒绝还是结果未知;
- 当前调用的 run、invocation 和 job 标识是什么。
但普通排查页面不应该直接暴露:
- Agent access token;
- 业务 Cookie 和密码;
- Tool Provider Secret;
- 模型 API Key;
- 完整敏感参数值;
- 业务接口原始响应正文;
- 模型隐藏思维链。
审计的目标是定位“哪一道闸门作出了什么决定”,不是把所有秘密复制一份。
十四、BailingHub v0.5.0 在这里解决了什么?
BailingHub v0.5.0 已公开的 Agent Client v1,把本地 Agent 的规划与中枢治理分开:本地 Agent 负责理解目标、选择工具和多步编排,中枢继续负责身份重验、能力裁剪、审批、工具执行与审计。
与“能查不能改”直接相关的公开边界包括:
- 工作区能力取 Client、Agent Session、route audience 与
tools.agent_direct等边界的交集; - route 工具源及其 allow scope 会先裁剪能力面;
- route 的
permission还必须允许写入,即readwrite或full; write_tools只接受精确的写工具operationId;- 只读工具不需要逐项加入
write_tools; - 写工具白名单不允许使用
*; - 审批默认继承业务侧 ACC;
force_approval_tools只能额外收紧,不能降低 ACC 的要求;- 工具执行时仍使用可信业务主体,业务系统需要完成最终实时授权。
这套设计没有把“是否能修改”交给模型,也没有要求本地插件再维护一套业务审批规则。
它做的是把能力发现、写操作开放、审批和业务授权分成可解释的边界,让一次拒绝可以被定位,而不是只剩一句模糊的“没有权限”。
常见问题
1. 为什么只读工具不用加入 write_tools?
因为 write_tools 专门用于显式开放本地 Agent 可调用的写操作。只读工具仍然受工具源、route allow、audience、主体和其他治理边界约束,并不是默认向所有人开放。
2. 把写工具加入 write_tools 后,就一定不会审批了吗?
不会。write_tools 解决“能否尝试调用”,审批默认由 ACC 的风险与审批声明决定,route 还可以通过 force_approval_tools 进一步收紧。
3. 可以用 * 一次开放所有写工具吗?
不可以。BailingHub v0.5.0 的公开配置要求使用精确 operationId,避免工具源未来新增写接口时自动扩大权限。
4. ACC 写了不需要审批,为什么本地 Agent 仍然审批?
先检查当前 route 是否把该 operation 加入了 force_approval_tools,以及两边是否使用同一 route 和同一版能力声明。额外强制审批可以收紧 ACC,但不能放宽 ACC。
5. 中枢已经允许调用,业务系统为什么还能拒绝?
因为中枢只完成通用能力与治理检查。业务系统仍需根据实时用户、租户、角色、对象归属和业务状态作最终裁决。
6. 网页后台登录用户和本地 Agent 是同一个身份吗?
应由业务授权页根据服务端登录态派生并绑定可信主体。它不是把账号密码或 Cookie 交给本地 Agent,而是让后续工具调用可以代表经过授权的业务用户。
7. 为什么不能只在本地插件里配置“无需审批”?
因为客户端运行在用户设备上,不能成为审批事实源。审批应复用业务侧 ACC 与中枢治理,客户端只负责正确展示、等待和恢复原调用。
结语:写操作不是“给模型多一点权限”,而是把每一道边界对齐
当 AI Agent 只能回答问题时,“能不能调用接口”可能只是一个产品开关。
当它开始修改员工、商品、订单、库存或工单时,权限必须变成一条可验证的链路:
- 业务侧明确声明可供 Agent 使用的能力;
- route 只开放当前场景需要的工具源与 scope,并明确允许写入的 permission;
- Client、Session 和 audience 限制可进入的工作区;
- 本地 Agent 的直接工具面被显式启用;
- 写操作按精确 operationId 加入白名单;
- 是否审批继续服从 ACC,route 只能额外收紧;
- 业务系统根据可信主体和实时业务事实作最终裁决;
- 每次调用留下可追查但不泄露秘密的执行证据。
所以,“AI 能查询却不能修改”不一定说明系统做错了。
它更像是在提醒我们:查询能力和写能力之间,本来就应该有一条更严格、更清楚的边界。
真正需要修复的,不是让 Agent 无条件获得更多权限,而是找出哪一层与预期不一致,并让相同的业务身份、能力声明、审批语义和执行结果在不同入口中保持可解释的一致性。
项目与延伸阅读: