Deep Agents 파일시스템 권한

Deep Agents 파일시스템 권한 (Permissions)

딥 에이전트가 어떤 파일과 디렉터리를 읽거나 쓸 수 있는지를 선언형 권한 규칙으로 통제할 수 있어요. 규칙 목록을 permissions=에 넘기면 에이전트의 내장 파일시스템 도구가 그 규칙을 따라요. 탐색 도구에는 몇 가지 구체적인 동작 방식이 정해져 있으니, 규칙 구조와 평가 순서부터 차근차근 보시죠.

출처: 공식문서

이 기능은 deepagents>=0.5.2 버전이 필요해요. 그리고 권한은 내장 파일시스템 도구(ls, read_file, glob, grep, write_file, edit_file, delete)에만 적용돼요. 파일시스템에 접근하는 커스텀 도구나 MCP 도구는 적용 대상이 아니고, execute 도구로 임의 명령 실행을 지원하는 샌드박스 백엔드에도 적용되지 않아요.

경로 기반의 allow/deny 규칙이 필요할 때 permissions를 쓰고, 커스텀 밸리데이션 로직(레이트 리밋, 감사 로깅, 콘텐츠 검사)이나 커스텀 도구 통제가 필요하면 백엔드 정책 훅(backend policy hooks)을 쓰세요.

기본 사용법

create_deep_agentFilesystemPermission 규칙 목록을 넘겨요. 규칙은 선언된 순서대로 평가되고, 첫 번째로 매칭되는 규칙이 승리해요. 매칭되는 규칙이 없으면 해당 동작은 허용돼요(관대한 기본값).

from deepagents import FilesystemPermission, create_deep_agent

# 읽기 전용 에이전트: 모든 쓰기 거부
agent = create_deep_agent(
    model=model,
    backend=backend,
    permissions=[
        FilesystemPermission(
            operations=["write"],
            paths=["/**"],
            mode="deny",
        ),
    ],
)

규칙 구조

FilesystemPermission은 세 가지 필드를 가져요.

필드 타입 설명
operations list["read" | "write"] 규칙이 적용되는 동작. "read"ls, read_file, glob, grep을 다루고, "write"write_file, edit_file, delete를 다뤄요.
paths list[str] 파일 경로를 매칭하는 glob 패턴 (예: ["/workspace/**"]). **는 재귀 매칭, {a,b}는 대안(alternation)을 지원해요.
mode "allow" | "deny" | "interrupt" 매칭되는 동작을 허용·거부·또는 인간 승인을 위해 일시 정지할지 여부. 기본값은 "allow"예요.

규칙은 첫 번째 매칭 우선(first-match-wins) 방식으로 평가돼요. 첫 번째 규칙 중 operationspaths가 현재 호출에 맞는 것이 결과를 결정하고, 매칭되는 규칙이 없으면 (관대한 기본값으로) 허용돼요.

인간 승인을 위한 일시 정지

"interrupt" 모드는 **deepagents>=0.6.8**이 필요해요. mode="interrupt"로 설정하면 매칭되는 동작을 허용하거나 거부하는 대신 인간 승인을 위해 일시 정지해요.

에이전트가 interrupt 모드 규칙과 매칭되는 경로에서 내장 쓰기 도구(write_file, edit_file, delete)를 호출하면, 도구를 실행하는 대신 create_deep_agent가 human-in-the-loop 인터럽트를 발생시켜요. 검토자가 그 호출을 승인하거나, 수정하거나, 거부할 수 있죠.

from deepagents import FilesystemPermission, create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver

agent = create_deep_agent(
    model=model,
    permissions=[
        # /secrets 아래에 뭔가 쓰기 전에 승인을 받는다.
        FilesystemPermission(
            operations=["write"],
            paths=["/secrets/**"],
            mode="interrupt",
        ),
    ],
    # interrupt 모드는 일시 정지·재개를 위해 체크포인터가 필요하다.
    checkpointer=InMemorySaver(),
)

interrupt 모드 규칙은 에이전트의 human-in-the-loop 미들웨어에 자동으로 연결되고, 여러분이 넘기는 interrupt_on과 합쳐져요. 그래서 도구 호출 인터럽트와 같은 방식으로 처리하고 재개할 수 있어요.

delete는 디렉터리를 통째로 지우는 게 전부예요. 대상과 그 모든 하위 경로에 write 권한을 확인하고, 하나라도 거부되면 트리 일부를 지우는 대신 전체 동작을 거부하죠. 이미 존재하는 빈 디렉터리에도 이 보수적인 검사를 그대로 적용해요. 일반 파일 삭제는 정확 매칭(exact-match)으로 동작하는데, deletewrite_file, edit_file처럼 같은 방식으로 대상을 해석하기 때문이에요. **deepagents>=0.7.3**이 필요하죠.

interrupt 패턴은 리터럴 선행 세그먼트(예: /secrets/** 또는 /projects/*/secrets/**)로 고정(anchor)하는 게 좋아요. 벌크 도구(ls, glob, grep, 디렉터리 delete)는 검색 하위 트리가 규칙의 고정된 접두사와 겹칠 수 있을 때 인터럽트를 발화하기 때문이에요. 그래서 / ** /secrets처럼 완전히 고정되지 않은 패턴은 보수적으로 과발화(over-fire)돼요.

예시

워크스페이스 디렉터리로 격리

/workspace/ 아래에서만 읽기·쓰기를 허용하고 그 외는 모두 거부:

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 = 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",
        ),
    ],
)

읽기 전용 메모리

에이전트가 메모리 파일을 읽을 수는 있지만 수정할 수는 없게 해요. 앱 코드로만 갱신돼야 하는 조직 전역 정책이나 공유 지식 베이스에 유용해요.

from deepagents.backends import CompositeBackend, StateBackend, StoreBackend

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",
        ),
    ],
)

모든 접근 거부

모든 읽기·쓰기를 차단해요. 더 구체적인 allow 규칙을 그 위에 겹겹이 쌓을 수 있는 보수적 기준선이에요.

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

규칙 순서 (Rule ordering)

첫 번째 매칭 우선이기 때문에 규칙 순서가 중요해요. 더 구체적인 규칙을 더 넓은 규칙보다 앞에 두세요.

# 올바른 예: .env 거부 → 워크스페이스 허용 → 그 외 모두 거부
correct_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",
    ),
]

# 버그: /workspace/**가 .env를 먼저 매칭해서 deny가 발화되지 않는다
incorrect_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",
    ),
]

서브에이전트 권한

서브에이전트는 기본적으로 부모 에이전트의 권한을 상속해요. 서브에이전트에게 다른 권한을 주려면 그 스펙(spec)에서 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",
                ),
            ],
        }
    ],
)

복합 백엔드 (Composite backends)

CompositeBackend를 샌드박스 기본값과 함께 쓸 때는 모든 권한 경로가 알려진 라우트 접두사 아래에 스코프되어야 해요. 샌드박스는 임의 명령 실행을 지원하므로, 경로 기반 제한만으로는 셸 명령을 통한 파일시스템 접근을 막을 수 없거든요. 권한을 라우트별 백엔드에 스코프하면 이 충돌을 피할 수 있어요.

from deepagents.backends import CompositeBackend

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

# 동작: 권한이 /memories/ 라우트에 스코프됨
agent = create_deep_agent(
    model=model,
    backend=composite,
    permissions=[
        FilesystemPermission(
            operations=["write"],
            paths=["/memories/**"],
            mode="deny",
        ),
    ],
)

어떤 라우트에도 속하지 않는 경로를 포함한 권한은 NotImplementedError를 던져요.

# NotImplementedError 발생: /workspace/**가 샌드박스 기본값에 걸린다
try:
    create_deep_agent(
        model=model,
        backend=composite,
        permissions=[
            FilesystemPermission(
                operations=["write"],
                paths=["/workspace/**"],
                mode="deny",
            ),
        ],
    )
except NotImplementedError:
    pass

# 이것도 발생: /**가 라우트와 기본값 모두를 덮는다
try:
    create_deep_agent(
        model=model,
        backend=composite,
        permissions=[
            FilesystemPermission(
                operations=["read"],
                paths=["/**"],
                mode="deny",
            ),
        ],
    )
except NotImplementedError:
    pass

## 더 알아보기 (Learn more)

- [인간 승인을 위한 일시 정지 (Human-in-the-loop)](https://docs.langchain.com/oss/python/deepagents/human-in-the-loop)
- [백엔드 정책 훅 (Backends)](https://docs.langchain.com/oss/python/deepagents/backends)
- [샌드박스 (Sandboxes)](https://docs.langchain.com/oss/python/deepagents/sandboxes)