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_agent에 FilesystemPermission 규칙 목록을 넘겨요. 규칙은 선언된 순서대로 평가되고, 첫 번째로 매칭되는 규칙이 승리해요. 매칭되는 규칙이 없으면 해당 동작은 허용돼요(관대한 기본값).
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) 방식으로 평가돼요. 첫 번째 규칙 중 operations와 paths가 현재 호출에 맞는 것이 결과를 결정하고, 매칭되는 규칙이 없으면 (관대한 기본값으로) 허용돼요.
인간 승인을 위한 일시 정지
"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)으로 동작하는데, delete가 write_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)