Typography

学习笔记

Deep Agents 学习06 Permissions

发布于 # Deep Agents

当 Agent 开始真正碰文件系统时,“它能不能读”“它能不能写”“它到底能写到哪里”就不能再靠口头约定。权限规则的意义,在于把这些边界前置成一层清晰配置,让访问范围在工具真正执行前就已经被框定下来。

为什么值得关注

文件权限控制看起来像细节配置,实际却是让 Agent 从“默认开放”走向“按边界执行”的第一步。它不负责解决所有安全问题,但它会非常直接地影响 Agent 能否越界访问、能否误改敏感文件,以及不同角色的代理之间是否真的做到了隔离。

权限系统的核心价值是:


权限控制作用于什么

权限规则只作用于内置文件系统工具:

不作用于什么

权限规则不会自动覆盖以下能力:

这点必须记清楚。

因为这意味着:

一个实用判断


基本用法

create_deep_agent(...) 里传 permissions=[...] 即可。

from deepagents import create_deep_agent, FilesystemPermission

agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(
            operations=["write"],
            paths=["/**"],
            mode="deny",
        ),
    ],
)

这个例子表示:


权限规则的核心结构

每条 FilesystemPermission 由三个部分组成:

1. operations

类型:

["read"]
["write"]
["read", "write"]

含义:

2. paths

这是路径匹配规则,支持 glob 风格。

例如:

常见能力包括:

3. mode

可选值:

作用是指定:

默认值是 allow,但实际使用中建议显式写出,避免歧义。


规则匹配机制

权限系统采用的是:

这三点非常重要。

这意味着什么

权限系统不是“最严格优先”,而是“先匹配先生效”。

因此:

一个简单理解方式

权限规则像防火墙规则:


最常见的几种权限设计模式

真正写权限规则时,很多人最容易卡住的不是语法,而是“到底应该从什么边界开始设”。最实用的办法通常不是从空白开始凭感觉设计,而是先套用几种常见模式,再按业务风险做增减。


模式一:把 Agent 限制在某个工作目录

这是最常见、也最适合作为默认起点的一种方式。它的思路很直接:先承认 Agent 需要读写文件,但把活动范围明确压缩到一个工作区里。

目标:

agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/workspace/**"],
            mode="allow",
        ),
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/**"],
            mode="deny",
        ),
    ],
)

这个模式适合什么

如果你的核心诉求是“Agent 可以工作,但不能在整个文件系统里乱跑”,那这个模式几乎总是最先该考虑的基线。典型场景包括:

这是一种非常推荐的默认基线

如果你不知道怎么开始做权限控制,就先用“只开放一个工作区目录”这个模式。


模式二:保护特定敏感文件

第二类需求通常不是“全关”或“全开”,而是大部分工作区可以正常使用,但某些位置必须单独加锁。这时就需要把敏感路径从允许范围里显式剥出来。

目标:

agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/workspace/.env", "/workspace/examples/**"],
            mode="deny",
        ),
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/workspace/**"],
            mode="allow",
        ),
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/**"],
            mode="deny",
        ),
    ],
)

这个模式适合什么

如果你的项目里已经有少量明确的高风险文件或目录,这种写法往往比“缩小整个工作区”更实用。典型情况包括:

一个经验规则

.env、密钥目录、配置模板等位置,优先默认 deny,而不是依赖 Agent 自觉不去碰。


模式三:记忆只读

到了多轮任务或多用户场景,权限控制的重点往往不再只是代码目录,而会转向“哪些知识可以查,哪些知识不能被改”。这时,把记忆区做成只读会比简单的目录白名单更有价值。

目标:

agent = create_deep_agent(
    model=model,
    backend=CompositeBackend(
        default=StateBackend(),
        routes={
            "/memories/": StoreBackend(
                namespace=lambda rt: (rt.server_info.user.identity,),
            ),
            "/policies/": StoreBackend(
                namespace=lambda rt: (rt.context.org_id,),
            ),
        },
    ),
    permissions=[
        FilesystemPermission(
            operations=["write"],
            paths=["/memories/**", "/policies/**"],
            mode="deny",
        ),
    ],
)

这个模式适合什么

如果你把记忆看成制度、知识沉淀或共享政策,而不是 Agent 的私人草稿本,那么这一层只读限制就会非常必要。常见场景包括:

为什么这个模式很重要

很多系统里,记忆并不等于“可以随便改的笔记本”。

有些记忆更像:

这些内容应该允许读取,但不应该允许 Agent 随意写入。


模式四:拒绝全部访问

还有一种思路更适合安全要求很高的系统:先什么都不给,再逐步开放。它读起来最“保守”,但在很多高约束场景里反而是最容易建立确定边界的办法。

目标:

agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/**"],
            mode="deny",
        ),
    ],
)

这个模式适合什么

适合:

这种做法的优点


规则顺序为什么是核心风险点

权限系统最容易踩坑的地方,不是 allowdeny 本身,而是你以为更严格的规则会自动优先。实际上这里没有“最严格优先”这种保护机制,只有按顺序命中,所以规则顺序本身就是安全边界的一部分。

因为权限是“第一条命中即生效”,所以规则顺序必须从“更具体”到“更宽泛”。

正确写法

permissions=[
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/workspace/.env"],
        mode="deny",
    ),
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/workspace/**"],
        mode="allow",
    ),
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/**"],
        mode="deny",
    ),
]

含义:

  1. 先特殊拒绝 .env
  2. 再允许整个工作区
  3. 最后拒绝其他所有路径

错误写法

permissions=[
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/workspace/**"],
        mode="allow",
    ),
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/workspace/.env"],
        mode="deny",
    ),
    FilesystemPermission(
        operations=["read", "write"],
        paths=["/**"],
        mode="deny",
    ),
]

这个写法的问题是:

一条实用规则

永远把:

按这个顺序排。


子代理权限

子代理默认继承父 Agent 的权限。

这意味着:

给子代理单独设置权限

如果希望子代理拥有不同权限,可以在子代理定义里显式设置 permissions

注意:

示例

agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/workspace/**"],
            mode="allow",
        ),
        FilesystemPermission(
            operations=["read", "write"],
            paths=["/**"],
            mode="deny",
        ),
    ],
    subagents=[
        {
            "name": "auditor",
            "description": "Read-only code reviewer",
            "system_prompt": "Review the code for issues.",
            "permissions": [
                FilesystemPermission(
                    operations=["write"],
                    paths=["/**"],
                    mode="deny",
                ),
                FilesystemPermission(
                    operations=["read"],
                    paths=["/workspace/**"],
                    mode="allow",
                ),
                FilesystemPermission(
                    operations=["read"],
                    paths=["/**"],
                    mode="deny",
                ),
            ],
        }
    ],
)

这个例子表达了什么

父 Agent:

子代理 auditor

适合的典型角色拆分

这类分权设计在复杂 Agent 系统里非常实用。


CompositeBackend 配合时的限制

如果 backend 是 CompositeBackend,而它的默认 backend 是 sandbox,那么权限规则会有一个额外限制:

为什么会这样

因为 sandbox 支持 execute,而 execute 可以运行任意命令。

这意味着:

因此系统要求:

一个可行例子

from deepagents.backends import CompositeBackend

composite = CompositeBackend(
    default=sandbox,
    routes={"/memories/": memories_backend},
)

agent = create_deep_agent(
    model=model,
    backend=composite,
    permissions=[
        FilesystemPermission(
            operations=["write"],
            paths=["/memories/**"],
            mode="deny",
        ),
    ],
)

这里可以工作,是因为:

不可行例子

agent = create_deep_agent(
    model=model,
    backend=composite,
    permissions=[
        FilesystemPermission(
            operations=["write"],
            paths=["/workspace/**"],
            mode="deny",
        ),
    ],
)

或:

agent = create_deep_agent(
    model=model,
    backend=composite,
    permissions=[
        FilesystemPermission(
            operations=["read"],
            paths=["/**"],
            mode="deny",
        ),
    ],
)

这类配置会报 NotImplementedError

一条必须记住的规则

如果默认 backend 是 sandbox:


permissions 和 backend policy hooks 的区别

这两个机制很容易混淆,但用途完全不同。

permissions 适合做什么

适合:

特点:

backend policy hooks / wrapper 适合做什么

适合:

特点:

一个实用判断标准

如果你问的是:

如果你问的是:


常见权限设计套路

套路一:工作区白名单

目标:

适合:

套路二:敏感文件黑名单

目标:

适合:

套路三:长期记忆只读

目标:

适合:

套路四:子代理分权

目标:

适合:


常见错误与排查

规则看起来对,但就是不生效

最常见原因:

解决:

以为没匹配规则就默认拒绝

这是错误理解。

真实行为是:

解决:

以为 permissions 能限制自定义工具

这是错误理解。

真实情况是:

解决:

以为 permissions 能限制 sandbox 的 shell 访问

这也是错误理解。

因为:

解决:

子代理权限设置后行为和父代理不一致

原因:

解决:

CompositeBackend + sandbox 下报 NotImplementedError

原因:

解决:


验收标准

可以用下面的标准判断权限配置是否合理:


推荐实操顺序

建议按这个顺序配置权限:

  1. 先明确 Agent 需要访问哪些目录
  2. 先写最小工作区 allow 规则
  3. 再补敏感路径 deny 规则
  4. 最后补全局 deny 规则
  5. 如果有子代理,再按角色拆分权限
  6. 如果有 sandbox 或自定义工具,再单独设计额外安全机制

关键要点


写在最后

文件权限控制的本质,是把 Agent 的文件访问能力从“默认开放”收敛成“按路径和操作精确授权”。

可以把整个权限系统理解成三层:

当这三层设计清楚后,Agent 的文件系统访问才真正进入可控状态。