기능에이전트

권한

에이전트가 쓸 수 있는 도구, 사람의 승인, 에이전트를 관리하는 사람과 자격 연결

에이전트의 권한은 세 층이다. 프로필이 정하는 도구와 권한 모드, 사람이 답하는 권한 요청, 그리고 에이전트를 관리하고 자격 증명 이름을 연결하는 사람.

도구: 프로필이 정한다

세션은 프로필의 tools 안의 도구만 쓴다. 프로필이 막은 내장 도구(deny)는 tools에 없다. 밖의 도구는 권한 요청 없이 거절되고, 모델은 오류 tool_result를 받는다. 무엇을 막는지는 프로필 고르기에 있다. 아래 대본은 로컬 프로필이 막은 WebFetch를 부른다(로컬 프로필의 deny는 ["NotebookEdit","WebFetch","WebSearch"]다).

created=$(curl -sS "$API/v1/sessions" \
  -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "$(jq -nc --arg m 'GATE-SPEC {"id":"outside","steps":[{"tool":"WebFetch","input":{"url":"https://example.com","prompt":"summarize"}}],"final":"done"}' \
    '{profile_id: "claude-coding-local", repository_id: "sample-app", message: $m}')")
SID=$(echo "$created" | jq -r .session_id)
until [ "$(curl -sS "$API/v1/sessions/$SID/turns/1" -H "Authorization: Bearer $KEY" | jq -r .status)" = completed ]; do
  sleep 2
done
curl -sSN --max-time 5 "$API/v1/sessions/$SID/events" -H "Authorization: Bearer $KEY" \
  | awk '/^event: tool_result/ { getline; sub(/^data: /, ""); print }' \
  | jq -c '.data | {is_error, content}' || true
curl: (28) Operation timed out after 5006 milliseconds with 4482 bytes received
{"is_error":true,"content":"<tool_use_error>Error: No such tool available: WebFetch. WebFetch is disabled for this session, in subagents as well as here.</tool_use_error>"}

curl: (28)은 --max-time이 스트림을 끊은 것이다.

release로 만든 세션은 그보다 좁게, release에 고정된 도구만 쓴다. 카드의 capabilities.tools(null이면 release를 만들 때 프로필이 막지 않은 도구 전부)에서 forbidden을 뺀 목록이고, release 응답의 effective_tools.tools가 그 목록이다. 카드에 Read만 적으면 세션은 Read만 쓰고, 다른 내장 도구는 위와 같이 거절된다. release 없이 만든 세션은 프로필의 도구를 모두 쓴다.

막는 곳은 둘이고 범위가 다르다.

프로필 deny카드 capabilities.forbidden
누가 정하나운영자 프로필은 owner, 워크스페이스 프로필은 owner·admin에이전트 관리자
미치는 곳그 프로필 버전으로 도는 모든 세션과 release그 에이전트의 release로 만든 세션
받는 이름내장 도구 이름만도구 이름
바꾸면새 프로필 버전. 그 버전으로 만든 release와 세션부터새 release부터

카드는 프로필이 막은 도구를 열 수 없다. 프로필이 막은 도구를 카드의 capabilities.tools에 적으면 release를 만들 때 거절된다.

이전에는 카드의 tools·forbidden이 release에 고정만 되고 실행에는 적용되지 않았다. 지금은 그 전에 만든 release로 이미 진행 중인 세션도 release의 도구로 좁혀진다. 세션의 실행 환경이 다시 뜰 때(마지막 턴이 끝난 뒤 입력 없이 30분이 지나 실행 환경이 내려간 다음의 메시지, 또는 일시 정지 뒤 재개) 적용된다. 카드의 tool_policy에 적은 내장 도구 값(allow·ask·deny)은 release에 고정되지만 아직 실행에 적용되지 않는다. 실행 전에 묻는지는 아래 권한 모드가 정한다.

카드 tool_policy의 내장 도구 기본값은 allow이고 MCP 도구 기본값은 ask다. 내장 도구를 실행 전에 사람에게 묻게 하는 것은 이 값이 아니라 권한 모드다. 내장 도구를 아예 못 쓰게 하려면 프로필 deny나 카드 forbidden으로 막는다.

권한 모드

프로필의 permission_mode가 도구를 실행하기 전에 사람에게 묻는지 정한다. 세션 상세의 runtime.permission_mode에 나온다.

모드동작
default세션 작업 공간 안의 파일 쓰기·편집, git·gh 명령 전체와 sed, 작업 공간 안 mkdir, bun·npm·pnpm·yarn의 test·run은 묻지 않고 실행한다. &&·;·|로 이은 명령은 각 부분이 이 범위일 때만 통과하고, 한 부분이라도 밖이면 전체가 권한 요청이 된다. 작업 공간 밖 쓰기(symlink와 ..를 풀어 본 경로 기준), 비밀값·.git·.claude 경로, sed -i, 파일을 쓰거나 프로그램을 돌리는 git --output·--ext-diff, 다른 저장소를 가리키는 git -C, git add -f, gh auth, curl 같은 그 밖의 네트워크 도구는 실행 전에 권한 요청을 만든다. git·gh는 옵션과 상관없이 묻지 않는다. 막는 것은 권한 요청이 아니라 작업 공간 컨테이너 격리, egress authorizer(push 대상·토큰 범위), GitHub 브랜치 보호다. 상태를 바꾸지 않는 조회(ls, cat, grep, find 등)는 엔진이 작업 공간 안에서 묻지 않고 실행한다. 작업 공간 밖 경로나 find -exec·-delete는 엔진이 묻고, 비밀 파일 이름(.env, 키, .git/config)을 줄에 적은 cat·head·tail·grep·rg와 숨김 파일까지 뒤지는 rg -u·--hidden류는 이 목록이 묻는다(이름만 보므로 cat .e*나 grep -r은 못 잡는다). 로컬 프로필의 모드다
acceptEdits, dontAskClaude Code의 같은 이름 모드로 돌고, default와 같은 작업 공간 사전 허용과 승인 목록을 더한다. 이 목록의 비밀값·.git·.claude 경로는 acceptEdits에서도 묻는다
planClaude Code의 같은 이름 모드로 돈다. 사전 허용은 없다
auto사람 대신 모델 분류기가 호출마다 허용·거부를 정한다. 분류기를 쓸 수 없으면 거부한다. 설치 설정 없이 쓸 수 있다. 내장 GitHub 도구 중 push, PR 쓰기, 저장소 읽기는 예외로 분류기를 거치지 않고 서버가 판정한다(push 승인, release의 도구 목록, 세션 grant). 리뷰 제출과 workflow는 분류기를 거친다. 플랫폼이 분류기에 행동 단위 규칙을 더한다. 아래 허용 목록의 push·PR·저장소 읽기는 이 예외를 받지 못하는 옛 worker에서만 분류기가 보는 규칙이다. 허용: 세션 작업 공간의 commit과 플랫폼 push 흐름으로 세션 자기 저장소의 세션 전용 작업 브랜치(agent/<session-id>)에 push하기, 그 브랜치에서 PR 열기와 이 세션이 연 PR의 제목·본문 수정·코멘트·닫기·다시 열기, 허용된 저장소 목록 보기와 읽기용으로 붙이기. 이 허용에 들지 않는 것: 기본·보호 브랜치나 플랫폼이 만들지 않은 브랜치 push, force push, PR 머지·승인, 리뷰 제출, 이 세션이 열지 않은 PR의 수정·코멘트·닫기, 다른 저장소(세션에 붙인 저장소 포함) push, 비밀값. 저장소 설정·브랜치 보호·webhook·deploy key·Actions secret 변경, GitHub의 브랜치·저장소 삭제, PR 머지는 어떤 경로로든 거부 쪽으로 둔다
bypassPermissions판정 없이 모든 호출을 실행한다. 이 모드의 프로필은 API에 PLATFORM_ALLOW_UNATTENDED_PERMISSIONS=true가 없으면 빠지고, 워크스페이스 프로필에서는 owner·admin만 고를 수 있다

모드는 카탈로그에만 있고, 세션 요청으로 바꿀 수 없다. 콘솔에서 새로 만드는 프로필은 바탕이 허용하면 auto에서 시작한다. 이미 default로 만든 프로필은 자동으로 옮기지 않는다. push와 PR 도구는 최신 worker에서 plan을 뺀 모드에서 엔진이 묻지 않는다(plan은 지금처럼 엔진이 묻는다). Bash 같은 엔진 도구까지 그 에이전트가 묻지 않게 하려면 auto 버전을 만들고 에이전트의 새 release가 그 버전을 쓰게 한다. 운영자 프로필이면 owner가 상세의 새 버전에서 권한 모드를 auto로 저장한다. 워크스페이스 프로필은 바탕보다 넓은 모드를 고를 수 없으므로, 바탕이 auto를 허용하면 수정에서 auto로 저장하고, 바탕이 default이면 auto를 허용하는 바탕(위의 auto 운영자 프로필 버전 등) 위에 새 워크스페이스 프로필을 만든다. 이미 도는 세션은 원래 모드로 계속 돈다. push 승인은 모드와 별개다. auto 모드여도 push 승인이 켜져 있거나 첨부 저장소 push 정책이 ask이면 모든 저장소의 push마다 사람에게 묻는다. 에이전트 GitHub 탭을 처음 저장할 때는 push 승인이 꺼진 채로 시작하지만, GitHub 설정이 없는 release(이 설정 전에 만든 release 등)는 켜진 것으로 읽는다. 승인 없이 자동 push하려면 GitHub 탭에서 push 전 사람 승인이 꺼져 있는지 보고 저장한다. 꺼도 승인 없이 가려면 첨부 저장소 push 정책(allow·ask·deny)도 allow여야 한다. 정책이 deny이면 push 자체가 막히고, 그 밖에는 모든 저장소의 push가 이 스위치와 그 정책 중 더 엄격한 쪽을 따른다. 사람 승인을 받으려면 스위치를 켜거나 정책을 ask로 둔다.

승인: 사람만 한다

권한 요청과 질문에 답하려면 sessions:approve scope가 필요하다. worker 안의 에이전트가 가진 자격은 /v1에서 401이라 스스로 승인하지 못한다. 에이전트 key도 sessions:approve를 가질 수 있지만, 그 key로 시작한 세션의 요청은 승인하지 못한다. 답하는 방법은 승인 대기와 재개에 있다.

세션을 볼 수 있다고 그 세션의 요청에 답할 수 있는 것은 아니다. 권한 요청과 질문에 답하고 push·PR을 승인하는 것은 다음 사람만 한다.

  • 세션을 만든 사람(API key가 만든 세션이면 그 key)
  • 그 세션 에이전트의 관리자
  • 워크스페이스 owner·admin

공유로 세션을 보기만 하는 사람이 답하면 403 FORBIDDEN(reason_code: role)이고, 요청은 열린 채로 남아 세션은 needs_input에 머문다. 볼 수 없는 세션은 지금처럼 404다. GET /v1/sessions/{id}/pending-requests의 answerable이 부르는 사람이 답할 수 있는지 알려 주고, 콘솔은 false이면 허용·거부를 이유와 함께 비활성으로 보인다.

에이전트 관리자

에이전트를 만든 사람이 그 에이전트의 관리자가 된다. 관리자는 member여도 그 에이전트의 카드 수정, release, 활성화, 자격 연결, 관리자 지정, 에이전트 key 발급을 할 수 있다. 사람이나 팀을 관리자로 더할 수 있고, 팀이면 그 팀 구성원 모두가 관리자다. 팀을 새로 더하는 것은 팀을 관리하는 owner·admin만 한다. 에이전트 관리자(member)는 팀을 더할 수 없고, 이미 더해진 팀을 빼거나 사람을 더하고 뺄 수는 있다.

에이전트에는 늘 담당 팀이 하나 있고, 바꿀 수는 있어도 비울 수는 없다. 담당 팀의 팀 관리자(lead)는 그 에이전트의 관리자이고, 구성원(member)은 관리하지 않는다. owner·admin이 아니면 자기가 속한 팀만 담당 팀으로 고른다. 담당 팀으로 정해진 팀은 그 에이전트를 보관하거나 담당 팀을 옮기기 전에는 지울 수 없다.

팀 역할은 콘솔의 팀 화면에서 본다. 구성원 표의 팀 역할 칸이 팀 관리자(lead)와 구성원(member)을 나누고, 내가 속한 팀 목록에도 내 역할이 붙는다. owner·admin은 구성원 줄의 역할 바꾸기로 역할을 고르고 저장한다(PATCH /v1/teams/{team_id}/members/{user_id} {"role"}). 담당 팀인 팀을 지우려 하면 콘솔은 삭제하지 않고 그 팀이 담당 팀인 에이전트 이름과 링크를 보인다(409 TEAM_MANAGES_AGENTS). 각 에이전트의 담당 팀을 먼저 바꾼다.

에이전트를 보는 범위는 이렇다.

누가정보(이름·slug·설명·담당 팀·상태·활성 release)설정(카드·release·관리자·자격 연결·GitHub·Slack) 읽기설정 바꾸기
owner·admin읽는다읽는다바꾼다
에이전트 관리자(담당 팀 lead 포함)읽는다읽는다바꾼다
담당 팀의 member읽는다읽는다못 한다(403)
그 밖의 member읽는다(아래 설정이 none이면 못 본다)못 한다(403, 설정이 none이면 404)못 한다(403)

이전에는 member가 관리하지 않는 에이전트를 목록에서 볼 수 없었고, 담당 팀의 member도 설정을 읽지 못했다. 지금은 모든 member가 GET /v1/agents·GET /v1/agents/counts로 워크스페이스의 에이전트를 찾고, 담당 팀의 member는 설정을 읽는다. include=summary의 요약(관리자·자격 연결)은 설정을 읽을 수 있는 에이전트에만 붙는다. 에이전트 설정을 바꾼 receipt도 설정을 읽을 수 있는 사람만 읽는다.

바깥 member가 할 수 있는 일

owner·admin, 에이전트 관리자, 담당 팀 구성원을 그 에이전트의 안쪽이라 하고, 그 밖의 member를 바깥쪽이라 한다. 바깥쪽이 에이전트로 무엇을 할 수 있는지는 워크스페이스 설정 두 개가 정한다. owner·admin만 PATCH /v1/workspaces/{id}/agent-access로 바꾸고, 바꿀 때마다 receipt가 남는다. 지금 값은 GET /v1/auth/me의 workspace.settings에 있다.

설정값바깥 member에게
agent_outsider_accessuse(기본값)정보를 읽고 세션을 시작한다
view정보만 읽는다. 세션 시작은 403 FORBIDDEN(reason_code: role)
none에이전트가 목록에 없고 읽으면 404, 세션 시작은 403
agent_outsider_session_visibilitymanaging_team(기본값)에이전트의 workspace 공유 세션이 담당 팀 밖 구성원에게는 목록·개수·상세·이벤트·사용량 최근 목록에 나오지 않는다
workspace그 세션도 모든 member가 본다
  • agent_outsider_access와 agent_outsider_session_visibility는 모든 에이전트에 적용된다. 에이전트에는 늘 담당 팀이 있다. 담당 팀이 필수가 되기 전에 팀이 없던 에이전트는 구성원 없는 보관 팀(이름을 바꿔도 서버는 보관 팀으로 알아본다)이 맡았고, 이 에이전트들의 workspace 세션은 에이전트를 맞는 팀으로 옮기기 전까지 이전처럼 모든 멤버에게 보인다.
  • 바깥 member라도 자기가 시작한 세션과, 자기나 자기 팀을 지정해 공유한(people·team) 세션은 계속 본다.
  • 두 설정 모두 다음 요청부터 이미 만든 세션에도 적용된다. 열린 이벤트 스트림은 다음 확인 때 끊긴다. 담당 팀을 바꾸거나 구성원이 팀에서 빠질 때도 같다.
  • 사용량 합계(세션 수·비용·성과 수)는 그대로이고, 최근 성과 목록에서 볼 수 없는 세션의 줄만 빠진다.
  • 공유 범위가 workspace인데 바깥 member가 못 보는 세션이면 GET /v1/sessions/{id}/share의 audience_limited가 true다.
curl -sS -X PATCH "$API/v1/workspaces/$WID/agent-access" "${WEB[@]}" \
  -d '{"agent_outsider_access": "view"}' | jq -c '{agent_outsider_access, agent_outsider_session_visibility, changed: (.receipt_id != null)}'
{"agent_outsider_access":"view","agent_outsider_session_visibility":"managing_team","changed":true}

WID는 GET /v1/auth/me의 workspace.id다.

콘솔에서는 워크스페이스 설정의 에이전트 접근에서 바꾼다. 담당 팀 밖 구성원의 에이전트 접근은 보고 실행(use)·보기만(view)·숨김(none), 워크스페이스 공개 세션은 담당 팀 안에만(managing_team)·모든 구성원(workspace)이다. 바꾼 값만 보내고, 저장하면 화면이 GET /v1/auth/me를 다시 읽는다. owner·admin이 아닌 구성원에게는 고르는 칸 없이 지금 값만 보인다.

이전에는 workspace 공유 세션을 모든 member가 봤다. 지금은 기본값이 managing_team이라, 에이전트의 세션은 바로 안쪽만 본다(보관 팀에 남아 있는 에이전트는 예외다). 예전처럼 보이려면 agent_outsider_session_visibility를 workspace로 바꾼다.

만들기의 AID와 WEB을 이어 쓴다.

curl -sS "$API/v1/agents/$AID/admins" "${WEB[@]}" | jq '{users: [.users[].email], teams}'
{
  "users": [
    "owner@example.test"
  ],
  "teams": []
}
요청하는 일
POST /v1/agents/{id}/admins {"user_id"} 또는 {"team_id"}관리자 지정
DELETE /v1/agents/{id}/admins/users/{user_id}, …/teams/{team_id}해제
PUT /v1/agents/{id}/managing-team {"team_id"}담당 팀 지정·변경
GET /v1/administered-agents내가 관리하는 에이전트

자격 연결

카드는 쓸 자격 증명의 이름만 capabilities.credential_refs에 적는다. 값은 Werft 밖(secret store, remote MCP server 연결)에 있다. 그 이름을 이 에이전트가 써도 된다는 연결은 관리자가 따로 만든다. 카드에 적힌 이름 중 연결되지 않은 것이 있으면 활성화가 409 CREDENTIAL_UNBOUND다.

curl -sS "$API/v1/agents/$AID/credential-bindings" "${WEB[@]}" -d '{"credential_ref": "notion-token"}' \
  | jq '{credential_ref: .binding.credential_ref, created}'
curl -sS "$API/v1/agents/$AID/credential-bindings" "${WEB[@]}" | jq -c '[.items[].credential_ref]'
{
  "credential_ref": "notion-token",
  "created": true
}
["notion-token"]

해제(DELETE /v1/agents/{id}/credential-bindings/{binding_id})는 돌고 있는 세션을 멈추지 않는다. 다음 세션부터 막힌다. 바로 막으려면 외부 서비스에서 그 자격을 폐기한다.

다음 단계

이 페이지