ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

aisuite Coworker 权限与收件箱设计解析:工具目录、风险分级、Unattended 模式与多端收件箱

aisuite Coworker 权限与收件箱设计解析:工具目录、风险分级、Unattended 模式与多端收件箱 aisuite Coworker 权限与收件箱设计解析工具目录、风险分级、Unattended 模式与多端收件箱【免费下载链接】aisuiteSimple, unified interface to multiple Generative AI providers项目地址: https://gitcode.com/GitHub_Trending/ai/aisuite本篇技术指南以aisuite仓库中 Coworker 平台的 PERMISSIONS-AND-INBOX.md 设计文档为核心骨架结合仓库中已落地的 permissions.py、risk.py、inbox.py、unattended.py 等源码实现系统讲解 Coworker 的五层权限决策模型受审工具目录Tool Catalog、风险分级Risk Class、用户本地风险覆盖Override、权限模式Mode与 Unattended 开关。读者读完后将掌握Coworker 如何判断一次工具调用该放行、拒绝还是转人工Unattended 模式下审批与提问如何被路由到 Inbox、Agent 如何挂起/恢复以及多收件箱路由Slack/Telegram 镜像背后的状态机与防竞态契约。背景一份设计文档与它的实现进度该文档标注STATUS: design (draft for review)是与 PERSONAS.md 配套的设计稿——后者将人设Persona声明为可分发的能力包而本文档则定义了人设引用工具所依赖的受审工具目录、风险分类、权限模式、Unattended 开关与 Inbox整套机制。需要特别说明的是文档虽标记为设计草稿但仓库中的关键实现已经落地风险分级已从硬编码集合重构为独立的 risk.pyRiskClass枚举 classify()Inbox 完整实现在 inbox.pyInboxStore、InboxItem、inbox_approver、reconcile_on_resumeUnattended 会话开关实现在 unattended.pyUnattendedRegistry权限引擎在 permissions.pyMode枚举 PermissionEngine.evaluate()对应测试见 test_inbox.py、test_unattended.py、test_inbox_routing.py、test_gateway_inbox_reply.py。文章后续会标明哪些是已实现事实引用具体文件路径、哪些仍是设计提案以设计提出文档规划措辞区分。五个组成部件与它们如何组合成一次决策文档将整套机制拆成五个层次每一层恰好是某一决策的一个干净输入层次回答的问题关键内容1. 工具目录Tool Catalog人设能引用什么稳定的id → capability映射2. 风险分级Risk Class这个工具有多大副作用read/write_local/exec/external四类3. 有效风险覆盖Override用户是否本地改判仅用户本地可改人设永远不能携带4. 权限模式Permission Mode多大自主度Plan / Interactive / Custom / Auto5. Unattended 开关在哪里找到你内联 Composer vs. Inbox整扇门收敛为一个函数evaluate(tool, args, mode, attended?) → allow / deny / route**风险分级覆盖后**决定什么算有后果的consequential模式决定有后果的操作是否需要你Unattended 开关决定需要你这件事落在哪里内联还是 Inbox。这五个层次在 permissions.py 中已有对应骨架Mode枚举定义在 L26-L33PermissionEngine.evaluate()定义在 L109-L167Decision数据类allowed/reason/needs_user/rule定义在 L41-L48。evaluate()的判定顺序清晰体现了分层思想只读模式拦截有后果操作 → 写操作做路径作用域校验 → 低风险直接放行 → Auto 全放行 → 白名单/会话记忆 → 任务级常设规则 → Custom 自动放行 → 兜底需要用户。一、受审工具目录Tool Catalog从硬编码导入到清单展开现状agent 代码本身就是目录文档指出当前工具是手工组装的可调用对象每个 agent 在自己的build_tools(context)里硬编码导入agents/code.py从tools/导入files、git、search、shell、todo等工厂主程序还会额外导入directories、plan、subagent并拼上 aisuite 的files/gittoolkit然后拼接成列表。tools/registry.py 只是运行时管线把 callable 包装成 JSON Schema 执行以__name__为键——没有ID → 能力这一层清单没有可引用的稳定标识。从源码看registry 的实现在 tools/registry.py L17-L67ToolRegistry.register()取func.__name__为键优先用显式传入的 schema其次__coworker_schema__属性最后回退到 aisuite 的Tools([func]).tools(formatopenai)自动从 docstring/类型注解生成 JSON Schema。工具的元数据如requires_approval、risk_level则挂在 aisuite 的ToolMetadata上——例如 tools/files.py L105-L111 为read_file声明了categoryfilesystem、risk_levellow、requires_approvalFalse。设计引入catalog.py每个工厂注册为一个 Capability文档提出新增catalog.py把每个现有工厂注册为一条能力CapabilityCapability( id files, # 稳定 IDpersona 的 tools: 引用它 name Files, # 人类可读标签用于同意界面 description Read edit files in the workspace, factory context - [callables],# 现有工厂保持不变 requires [workspace], # 上下文依赖workspace | executor # | connector:x | secret:y risk { read_file: read, write_file: write_local, ... }, )build_tools由此改为根据会话上下文把人设的tools:清单对照目录展开而不再是硬编码导入。Code/Cowork 现有的硬编码列表变成两份清单tools: [files, git, shell, search, todo]。这是一次机械重构——工厂本身不变。三个关键设计决策粒度清单引用能力组而非函数名。manifest 写的是files、shell这种能力组而不是单个函数名单个函数的放行与否仍由风险分级负责v1 中只读文件由 Plan 模式兜底不需要单独的限定词。封闭集合目录仅归平台所有。第三方不能添加目录条目来扩展能力——它们获得的广度来自平台新增受审工具 MCP。这个封闭集合本身就是受审的保证这也是 PERSONAS.md 中人设绝不携带可执行代码硬规则的配套机制。连接器隐含工具包。在人设中声明connectors: [pagerduty]即隐含其工具包无需在tools:下重复列出。目录条目可以声明requires: [connector:pagerduty]于是启用时界面会提示需要连接 PagerDuty并提供设置入口。二、风险分级Risk Class取代硬编码名称集合现状与重构文档指出 permissions.py 原先硬编码WRITE_TOOLS {write_file, replace_in_file, apply_patch, apply_unified_diff}、SHELL_TOOL run_shell外加每个工具一个metadata.requires_approval。从当前源码看这一重构已经落地这些集合被移入 risk.py 作为数据L26-L27并从permissions.pyre-export 以保持向后兼容L16-L23同时新增了RiskClass枚举L18-L22与classify()L39-L53。文档将工具的内在副作用形式化为声明的风险类别类别示例行为readgrep、read_file、git_log、web_search始终放行write_localwrite_file、apply_patch、replace_in_file路径作用域 模式门控execrun_shell本地最高风险模式门控externalsend_message、HTTP POST、连接器写操作、create_automation机器之外的副作用——Unattended Inbox 路由的挂钩点源码印证classify()的判定链引擎读取声明类别而非匹配名称。实际实现中risk.py L39-L53 的classify(tool_name, metadata, overrides)判定顺序为用户本地覆盖Phase 2优先——overrides(tool_name)有返回值就采用按名基础表_BASEWRITE_TOOLS→WRITE_LOCAL、run_shell→EXEC回退到 aisuite 元数据——metadata.requires_approval True→EXTERNAL否则视为READ。is_consequential(risk)L56-L58判定除纯读外都需要权限引擎关注——这正是external成为无人值守 Agent 必须把该操作排队进 Inbox精确挂钩点的原因。文档还提出一个可选的floor 标志对少数真正不可逆的受审工具标记never_unattended_auto——即使宽泛姿态如 Auto也不能在不经过额外显式步骤的情况下自动触发它们。三、有效风险覆盖User-local Override铁律是仅用户本地核心公式effective_risk(tool) user_override ?? catalog_default不可侵犯的铁律覆盖只能由用户编写、只存在于本机。Persona/包永远不能携带覆盖规则。否则恶意 persona 可以把自己的工具降级绕过闸门、自我宣称可信——这正是设计要防的整类攻击。Persona 声明的是它想要什么只有用户决定多信任它。覆盖主要服务于MCP——MCP 的工具无法受审因此默认一律按external处理。匹配规则为server 工具名 glob最具体者胜出[mcp.notion] default read # 我信任这个 server把它所有工具视为只读 [mcp.notion.tools] create_* external # ……除了写操作 delete_* external [mcp.github] * external # 保持保守默认两个方向都允许升级如read → external永远安全且廉价降级才是需要深思熟虑的主动行为。同一机制可以重分级受审的内置工具但既然它们已受审这是高级功能、风险自负MCP 才是覆盖规则的主战场。主要编写路径是审批 UI 而非配置文件。当 MCP 工具浮现时提供Always allow、Trust read-class tools fromnotion等选项——这些选项写入与 TOML 所表达的同一个本地存储。配置文件是可移植/高级用户形态。四、权限模式Permission Mode自主度的天花板Mode ∈ Discuss / Plan两者均只读——READ_ONLY_MODESPlan 额外驱动propose_plan审批· Interactive自动放行读有后果的操作询问——默认· CustomInteractive 自动放行一组配置的工具· Auto全访问但仍受路径作用域约束。这些模式决定自主度天花板。从 permissions.py L26-L33 的枚举与 L109-L167 的evaluate()可以看到各模式在代码中的实际行为READ_ONLY_MODES {DISCUSS, PLAN}L38有后果的操作直接拒绝L120-L123write_local类工具在任何模式下都先做可写根目录路径校验L126-L129_under_writable_root在 L193-L203非有后果只读工具始终放行L132-L133AUTO全放行L136-L137Interactive/Custom 依次检查命令白名单、会话级工具记忆、任务级常设规则、Custom 的auto_allow_toolsL140-L164。文档同时提出泛化 Custom 的auto_allow_tools为风险类感知如自动放行所有write_local而不只是枚举列表。从 config.py L49 看当前配置结构仍是auto_allow: list[str]的枚举形态风险类感知语法列在文档的 Open questions 中。设计还明确放弃了早期提出的按类别的自主授权autonomy grant独立策略层提案——由 Unattended 开关取而代之整体更简单。五、Unattended 模式改变在哪里找到人而非有多大权力Unattended 是一个会话级开关Composer 区域。它不改变自主度天花板那是模式的事——它改变的是人在哪里被找到并让 Agent 得以挂起/恢复一切本会内联提示的内容——审批、提问、通知——改路由到 InboxComposer 被禁用并显示Running unattended in {mode} — questions approvals go to {Inbox}. Toggle off to take back control.开启时是一次一键确认——Run unattended in {mode} → {Inbox}?——因为这是人类交出控制权的时刻值得一次轻微的摩擦停顿。因为 Unattended不授予任何额外权力它在构造上就是安全的Interactive Unattended 意为批准一切只是从手机上批。从源码看unattended.py 的UnattendedRegistryL17-L43只负责持久化会话级标志位set(session_id, True/False)、is_unattended(session_id)、sessions()开启需一键确认被注释明确标注为在 API/GUI 层强制L5-L6。测试 test_unattended.py L11-L19 验证了开关的持久化跨实例重载后仍为True。模式 × Unattended 行为矩阵模式Unattended 行为Auto完全自主运行Inbox 只收到通知 / 卡住 / 完成Interactive每个有后果的操作都停驻在 InboxAgent 挂起直到被回答Custom隔夜值守的甜点组合——自动放行安全的write_local/测试只把external推送/部署/发送路由到 Inboxtest_unattended.py L22-L46 给出了端到端验证Unattended 会话使用inbox_approver作为审批器权限请求变成 Inbox 条目Agent 挂起等待模拟用户 resolve 为allow后审批器返回ApprovalOutcome.ONCEInbox 中留下一条 approval 记录。六、Inbox跨会话的人类注意力队列Inbox 是规范的、跨会话的人类注意力队列详见 PERSONAS.md 的引用。三种条目各自深度链接到源会话Approval审批——带上下文的可操作允许/拒绝Question提问——Agent 继续所需的自由文本回答Notification通知——FYI / 完成链接到工件。Inbox 是记录的主存储store of record消息连接器和未来的移动 App 只是同一批条目的传输层transports。从源码看inbox.py 的InboxItemL61-L87携带了完整字段id、session_id、kindapproval/question/notification/directory/plan见 L25-L29、statepending/resolvedL31-L32、resolution、inbox命名队列L72、visibilityinline/inboxL38-L39、L75、以及用于幂等恢复的tool_call_idL78。add()方法L116-L154实现了按(session_id, tool_call_id)幂等——持久化恢复重新抛出同一提示时复用已有可能已解决的条目而不是重新提示。侧边栏指示器是这条队列的视图而非第二条列表左侧面板的需要关注信号就是同一批 Inbox 条目以面包屑形式呈现在侧边栏——没有单独的 Needs attention 标签页那只会重建一个 Inbox 并立刻漂移。attention 信号某会话有待处理的 Inbox 条目渲染为琥珀色计数向上冒泡三层会话行→ 精确等待你的那个会话上显示徽标点击直达、内联回答Persona 手风琴标题→汇总计数让折叠的persona 也能表达里面有东西需要你而无需展开页脚 Inbox→ 全部的总数全量分诊视图。三者都通过下面的状态机解析——在会话行、在 Inbox、或在 Slack 上回答都在同一个条目上生效。与此正交的是liveness存活会话正在工作或带待唤醒休眠中只显示一个无计数的圆点永远不会进入 attention 计数这样闲置的定时 Agent 不会虚增需要你。Attention 与 liveness 正交两者都与置顶pinning正交详见 PERSONAS.md 的侧边栏 IA 部分。条目状态机防竞态契约每个条目只有一个权威状态pending → resolved只解析一次、幂等、先应答者胜。Agent 只消费第一个解析结果来自任何界面的第二次尝试都是 no-op显示已回答。每个界面应用内、Slack、Composer都反映已解决状态例如 Slack 消息会被编辑为✅ approved by you。这正是多界面回答安全的原因。源码印证inbox.py 的resolve(item_id, resolution)L295-L309在锁内检查item.state STATE_RESOLVED则返回False首次解析后设置resolved_at并_save()随后唤醒等待者。测试 test_inbox.py L26-L33 明确验证resolve(item.id, allow)返回True再次resolve(item.id, deny)返回False且resolution保持allow——先应答者胜。resolve_session()L311-L321在会话删除时关闭其全部待处理条目。wait()L323-L332用asyncio.Event让审批器挂起直到被解析。恢复对账Resume Reconciliation把各界面回答汇回 Composer当用户关闭 Unattended恢复在场时会话必须对账以形成往后单一连贯的对话地点并避免双重回答竞态该会话待处理的 Inbox 条目内联浮现到 Composer 区域用户从此在一个地方回答镜像通道仍可回答——但按条目状态机任何地方回答都解析同一个条目离开期间已回答的条目以内联回放浮现——While you were away: approved deploy to staging, answered 2 questions——让会话读起来连贯用户能看到当时做了什么决定Composer 重新启用后续提示重新内联进行中的条目仍可任一侧回答但只解析一次。源码印证inbox.py 的reconcile_on_resume(session_id)L335-L344返回{pending: [...], recap: [...]}两组数据测试 test_inbox.py L49-L57 验证已回答的 Deploy? 进入recap未回答的 Still pending? 留在pending其他会话的条目不混入。TurnEngine.resume()engine.py L149-L165配合实现了持久化恢复重放尾部 assistant 消息中未回答的 tool-call提示回调找到已解析的 Inbox 条目后直接返回、不重复提示已回答的调用被跳过、不重复执行。七、多收件箱路由Multi-inbox RoutingInbox 命名队列 投递绑定delivery binding应用内始终存在规范存储可选镜像到 Slack 频道 / Telegram 聊天经消息连接器每个 persona 有默认值 每个会话可覆盖Ops 会话 →#ops-coworker个人助手 → Telegram DMSWE 会话 → 仅应用内。绑定是双向的出 审批/提问入 回复/审批按条目 ID 关联——这样 Slack 里的 ✅ approve 能解析正确的待处理动作并唤醒正确的挂起 Agent。这复用了现有连接器网关入站分发 出站send_message steering——Inbox 路由主要是把它们接到一个队列上中间夹着条目状态机。从仓库结构看这条复用路径是成立的连接器网关实现在 connectors/gateway.py仓库另有 test_gateway_inbox_reply.py 与 test_inbox_routing.py 覆盖入站回复/路由场景InboxItem.inbox字段inbox.py L72即为命名队列标识inbox_approver(store, session_id, *, inboxdefault)L348-L368允许按队列投递审批。与现有代码的关系对照清单文档给出了与现有代码的映射关系结合源码可整理如下现有代码位置文档规划的改动permissions.pyplatform/coworker/permissions.pyMode枚举与evaluate()保留用声明式风险类替换WRITE_TOOLS/SHELL_TOOL/requires_approval已落地集合移入 risk.py 并 re-export新增attended?轴route vs. ask把 Custom 的auto_allow_tools泛化为风险类tools/registry.pyplatform/coworker/tools/registry.py保持运行时管线新的catalog.py位于其上层ID、工厂、requires、风险connectors/网关platform/coworker/connectors/gateway.py已有入站分发 send_message steeringInbox 绑定构建其上automation/调度器 /TaskStoreplatform/coworker/automation/scheduler.py、store.py与 self-wake挂起/恢复共享见 PERSONAS.md尚未决的问题Open Questions文档在末尾列出了仍在讨论中的问题可归纳为五组风险类感知的 Custom 配置语法——自动放行类别 X的配置文法尚未定型当前 config.py L49 仍是枚举形态的auto_allow: list[str]Inbox 持久化/去重、已读/未读、过期条目清理恢复回放是临时的系统注释还是 transcript 中的真实回合floor 清单never_unattended_auto——发布时哪些受审工具如果有入选唤醒预算 / 失控检测——仍被搁置见 PERSONAS.md 的 Tabled for later。决策记录Decisions Log要点文档的决策日志沉淀了 2026-06-26 与 06-27 两天的关键决议值得作为架构取舍的快速索引受审工具目录平台所有、封闭集合persona 引用能力 ID连接器隐含其工具MCP 不在目录内。风险分级read/write_local/exec/external取代硬编码名称集合external是 Unattended Inbox 路由挂钩点。覆盖规则effective_risk user_override ?? catalog_default覆盖仅用户本地、人设永不携带按 serverglob 匹配审批 UI 是主要编写路径降级是深思熟虑的方向。放弃分层授权单独的按类别自主授权策略层被砍掉由 Unattended 开关取代。Unattended会话级开关把一切 agent→user 交互重路由到 Inbox 并启用挂起/恢复不改变自主度天花板模式才改变开启需一键确认开启期间 Composer 禁用Custom Unattended 是推荐的隔夜组合。条目状态机单一权威状态pending→resolved幂等先应答者胜每个界面都反映它。恢复对账关闭 Unattended 时把待处理条目内联浮现 离开期间已答条目的内联回放——单一事实来源无双重回答竞态。Inbox 命名队列 投递绑定应用内始终存在 可选 Slack/Telegram 镜像persona 默认 会话覆盖双向、按条目 ID 关联复用连接器网关。侧边栏指示器needs attention 是 Inbox 队列的视图而非新标签页attention 以琥珀色计数从会话→persona 标题→页脚 Inbox 冒泡liveness 是独立的无计数圆点、永不冒泡两者都与置顶正交。小结一条从工具调用到人类注意力的完整链路综合文档设计与仓库实现Coworker 的权限与收件箱机制可以浓缩成一条端到端链路Agent 提出工具调用 →evaluate()依据风险类覆盖后 模式裁决 → 低风险放行 / Auto 全放行 / 有后果且需人时走 approver → 在场时内联提问Unattended 时路由为 Inbox 条目并挂起 → 用户在任意界面应用内/Composer/Slack回答 → 状态机幂等解析一次并唤醒 Agent → 关闭 Unattended 时reconcile_on_resume()把待办与回放汇回 Composer。其中Inbox 门控有后果操作这一设计还附带一个重要的安全语义PERSONAS.md 亦强调如果每个有后果的步骤都在等人无人值守的 Agent 就无法失控——Inbox 在护栏机制之前成为轻量级的人为限速器。对于需要深入阅读的读者建议按顺序研读本文档、PERSONAS.md人设与 Inbox 的关系、IMPLEMENTATION-LEDGER.md分阶段实现进度并对照 inbox.py、risk.py、permissions.py 与 test_inbox.py、test_unattended.py 验证本文所述行为。【免费下载链接】aisuiteSimple, unified interface to multiple Generative AI providers项目地址: https://gitcode.com/GitHub_Trending/ai/aisuite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表