헤드리스 및 CI에서 에이전트 실행하기
헤드리스 및 CI에서 에이전트 실행하기 (Running Agents Headless & in CI)
TUI 없이 Docker Agent를 실행해요 — 구조화된 JSON 출력, 이벤트 훅, 샌드박스 CI 격리, 그리고 GitHub Actions 예제를 다뤄요.
출처: 문서
본문
TUI 없이 Docker Agent를 실행하는 방법을 알려드려요: 구조화된 JSON 출력, 이벤트 훅, 샌드박스 CI 격리, 그리고 GitHub Actions 예제를 다뤄요.
--exec 모드 기초 (--exec Mode Basics)
--exec는 대화형 TUI 없이 에이전트를 실행해요. 출력은 stdout으로 가고 대화가 끝나면 프로세스가 종료돼요. 스크립트, CI, 그리고 터미널이 없는 모든 환경에서 사용하는 모드예요.
# One-shot task, message as an argument
$ docker agent run --exec agent.yaml "Summarize the open issues in this repo"
# Pipe the message via stdin instead
$ echo "Summarize the open issues in this repo" | docker agent run --exec agent.yaml -
# Multiple messages are processed as a multi-turn conversation, in order
$ docker agent run --exec agent.yaml "question 1" "question 2" "question 3"
전체 플래그 참조는 docker agent run --exec를 보세요.
대용량 입력 전략 고르기 (Choosing a Large-Input Strategy)
Docker Agent에는 큰 문서·파일·데이터셋을 에이전트 앞에 가져다주는 서로 다른 메커니즘이 몇 가지 있어요. 어떤 걸 쓸지는 콘텐츠가 Docker Agent를 실행하는 머신에 로컬로 있는지, 얼마나 자주 다시 봐야 하는지, 에이전트를 CLI/TUI로 구동하는지 HTTP 서버 중 하나로 구동하는지에 따라 달라져요:
| 상황 | 사용 | 비고 |
|---|---|---|
| 대화 한 턴을 위한 디스크의 파일 | TUI의 @path 또는 /attach(File Attachments 참조), 시작 시 --attach(대화형 TUI 실행을 --exec만큼 쉽게 시드해요), --exec 아래의 /attach |
로컬 파일시스템에서 읽고 로컬 승인 검사(admission check)를 통과하면 메시지에 인라인돼요. 로컬(비---remote) 실행이거나 --remote 실행의 첫 메시지라면 Docker Agent 자신의 인바운드 HTTP 경계를 넘지 않아요 — 하지만 원격 실행의 이후 메시지에 추가된 로컬 해석 첨부는 그 경계를 넘어요(아래 remote-runtime 행 참조). 승인 검사 자체와 거부 보고 방식은 진입점별로 달라요(아래 read-time 제한 참조). |
| 매 에이전트 턴이 보아야 하는 지침/컨텍스트 | 에이전트 구성의 add_prompt_files, 또는 CLI의 --prompt-file(Prompt Files 참조) |
매 턴마다 디스크에서 전체를 다시 읽어 instruction 컨텍스트로 주입해요. 로컬이고 HTTP 업로드가 아니며 위의 첨부 인라인 검사 대상도 아니에요 — 메모리와 모델의 컨텍스트 윈도우가 실질적인 상한이에요. |
| 로컬 헤드리스 실행을 위한 일회성 텍스트 | --remote 없이 stdin(--exec Mode Basics 참조) |
파이프된 텍스트가 그대로 메시지가 돼요. Docker Agent 자신의 인바운드 HTTP 경계를 넘지 않고 자체 크기 상한도 없어요. 다만 Docker Agent가 HTTP로 모델/provider에 계속 보낼 수는 있어요. |
| 원격 런타임을 구동하는 일회성 텍스트 | docker agent run --remote ... -와 함께 stdin |
CLI가 그 stdin 텍스트를 네이티브 API 실행 요청으로 직렬화해 --remote 주소가 가리키는 Docker Agent 서버(serve api 프로세스 또는 다른 run의 --listen 제어판, 절대 serve chat은 아님)로 보내요. 그래서 그 서버 자체의 한도로 측정돼요: serve api의 구성 가능한 --max-request-size, 또는 --listen 제어판의 고정·비구성 가능한 1 MiB 상한. (아래 HTTP 본문 제한 참조) 그 초기 요청은 메시지 텍스트만 담아요: 변환 과정에서 첫 메시지에 대해 로컬로 해석된 첨부(@path / /attach, --attach)를 보내기 전에 현재 버리며, 같은 원격 run에 대한 이후 메시지(에이전트가 바쁠 때 기본 steer 동작이나 명시적 후속(Alt+Enter)으로 보냄)는 로컬로 해석된 첨부를 그 네이티브 API 요청의 일부로 전달하므로 같은 한도에 포함되고 다른 과대 요청처럼 413이 될 수 있어요. 클라이언트 측 --prompt-file 값은 어느 쪽이든 원격 런타임으로 전달되지 않아요. 에이전트 자체에 구성된 프롬프트 파일(add_prompt_files)은 여전히 해석되지만 서버의 파일시스템 기준이에요. |
| 반복 조회할 문서 모음, 또는 인라인이 불가능할 만큼 큰 문서 | rag 툴셋 |
백그라운드에서 한 번 인덱싱되고, 각 조회는 전체 모음 대신 관련 청크만 컨텍스트로 가져와요. |
| HTTP로 Docker Agent를 구동하는 OpenAI 호환 클라이언트 | Chat Server | OpenAI 스타일 text와 image_url 콘텐츠 부분을 받아들여요. 전체 요청은 그 서버의 --max-request-size로 제한된 단일 HTTP 본문으로 이동해요. data URL의 바이트는 그 한도에 포함되지만, 원격 http(s):// 이미지 URL은 chat server가 가져오는 대신 provider에 전달되며 해당 provider/모델이 지원할 때만 동작해요. |
| Docker Agent의 자체 세션/제어 프로토콜을 구동하는 네이티브 통합 | API Server | 문서화된 session, run, event-streaming 흐름을 제한된 단일 HTTP 본문 위에서 지원해요(제공하지 않는 업로드 계약은 아래 주석 참조). |
| 이미 실행 중인 대화형 세션을 구동하는 감독 프로세스 | 첨부된 run의 --listen 제어판 |
API 서버와 같은 session/follow-up/event-streaming API를 노출하지만, 고정·비구성 가능한 1 MiB 요청 본문 상한이 있고 --auth-token이 없어요 — 이 표면에는 --max-request-size나 --auth-token 플래그가 없어요. 루프백, 유닉스 소켓, 또는 인증 리버스 프록시 뒤에 두세요(다른 곳에서 도달해야 한다면). |
세 가지 독립적인 상한이 적용되며, 하나에 걸렸다고 다른 것에 대해 말해주지는 않아요:
- HTTP 본문 제한 — Docker Agent의 세 인바운드 HTTP 표면(serve api, serve chat, 대화형 run의
--listen제어판) 중 하나에 도달하는 모든 요청에 적용돼요.docker agent run --remote ... -는 stdin으로 네이티브 run 요청을 만들어--remote주소가 가리키는 serve api 또는--listen제어판 중 하나로 보내요 — 다른 프로토콜을 말하는 serve chat에는 절대 보내지 않아요. serve api와 serve chat은 각자--max-request-size(기본 1 MiB, 구성 가능)를 적용하고 이를 초과하면 413 Request Entity Too Large를 반환해요.--listen제어판은 같은 1 MiB 기본값을 적용하지만 변경할--max-request-size플래그(또는--auth-token)를 노출하지 않아요 — 고정돼 있고, 요청을 줄이는 것만이 해결책이에요. 진단·해결법은 Troubleshooting: HTTP 413을 보세요. 로컬(비---remote) stdin과 프롬프트 파일은 이 경계를 절대 넘지 않아요. 로컬로 해석된@path//attach/--attach첨부도 원격 run의 첫 메시지에서는 넘지 않지만, 같은 원격 run의 이후 steer 또는 후속 메시지에 추가되면 넘어요(위 remote-runtime 행 참조). - 로컬 읽기 시점 제한 — 경로별로 달라요. TUI와 비-TUI(CLI/
--exec) 메시지 조립은 text/binary 규칙을 공유하지 않아요. TUI(/attach와 타이핑한@path참조 모두)는 파일 유형을 보기 전에 모든 파일 참조를 하나의 평평한 크기 상한에 태워요. 비-TUI 조립(CLI의--attach플래그와--exec의 자체/attach파싱)은 자체 상한까지 텍스트를 인라인하고, 지원되는 바이너리 파일(이미지, PDF)은 더 높은 상한을 가진 별도 경로로 넘겨요. 그래서--attach/--exec에 괜찮은 바이너리 파일이 TUI의 더 평평하고 낮은 상한에는 너무 클 수 있어요. 어느 쪽이든 과대 파일은 조용히 잘리거나 메모리를 다 쓰게 두는 대신 거부돼요. 하지만 실제로 알려주는지는 표면에 따라 달라요: 대화형 TUI의/attach명령만 보이는 오류 알림(파일 선택기 폴백 포함)을 보여주고, 순수@path참조는 타이핑할 때만 추측적으로 검사돼요 — 거기서의 거부는 debug 수준으로만 로그되고 참조는 표시 알림 없이 메시지에 평문으로 남아요. 대화형 TUI 밖에서는 CLI의--attach플래그도--exec의/attach(TUI의 알림 흐름이 아닌--attach와 같은 비-TUI 조립·거부 경로를 재사용)도 일반 run 출력에서 거부된 첨부에 대해 아무것도 출력하지 않아요 — 메시지는 그냥 text-only로 폴백하고 이유는--debug가 켜진 동안만 debug 로그에서 볼 수 있어요. 프롬프트 파일(add_prompt_files/--prompt-file)은 상당한 가드가 없어요 — 크기와 무관하게 매 턴 전체를 읽으므로 메모리와 모델 컨텍스트 윈도우가 실제로 그것을 묶어요. - 모델/provider 컨텍스트 및 미디어 제한 — 처음 두 층을 통과한 콘텐츠도 모델의 토큰/컨텍스트 윈도우에 맞아야 해요. 바이너리 첨부(이미지, PDF, 오디오, 비디오)는 모델이 해당 미디어 유형 지원을 선언할 때만 동작해요(Attachment Capability Overrides 참조). 컨텍스트 윈도우 초과는 위 두 제한과는 별개의 실패예요 — Context Window Exceeded 참조.
참고 — 파일 업로드 엔드포인트 없음: Docker Agent의 세 인바운드 HTTP 표면(API 서버, chat 서버, 첨부된 run의 --listen 제어판) 중 어느 것도 multipart 파일 업로드 엔드포인트를 노출하지 않고, 어느 것도 대신 원격 URL을 가져오지 않아요 — chat server의 image_url 지원은 원격 URL을 provider에 통과시킬 뿐이에요(위 표 참조). 콘텐츠는 로컬(@path / /attach, 프롬프트 파일, rag)로든 그 서버들이 이미 문서화한 HTTP 메시지 본문에 직접 임베드로든 에이전트에 도달해요. 그 어느 것에도 별도 첨부 채널은 없어요.
머신을 위한 구조화 출력 (Structured Output for Machines)
--exec 실행 출력을 파싱하기 쉽게 만드는 두 가지 독립된 것이 있어요: 트랜스크립트가 어떻게 방출되는지, 그리고 모델 자신의 답이 어떤 모양을 갖는지.
--json은 트랜스크립트 자체를 사람이 읽는 텍스트에서 newline-delimited JSON으로 전환해요: 각 런타임 이벤트(메시지, 도구 호출, 도구 결과, 오류, …)당 JSON 객체 하나씩이에요. 형식화된 텍스트와 도구 호출 상자가 섞이는 대신이에요. jq나 NDJSON 인식 로그 프로세서에 파이프하세요:
$ docker agent run --exec agent.yaml --json "List the 5 largest files in this repo" | jq -c 'select(.type == "agent_choice")'
structured_output는 --json과 무관하게 에이전트에 정의한 JSON 스키마로 모델 자신의 응답을 제약해요. 다운스트림 코드가 모델의 답을 예측 가능한 모양(발견 목록, 분류, …)으로 필요로 할 때 자유 형식 산문 대신 사용해요. 전체 필드 참조는 Structured Output을 보세요 — --exec에서 --json과 결합하면 파싱 가능한 트랜스크립트와 스키마 검증된 최종 답을 둘 다 얻을 수 있어요.
이벤트에 반응하기 (Reacting to Events)
--on-event <type>=<cmd>는 해당 유형의 이벤트가 발생할 때마다 셸 명령을 실행하고, 이벤트의 JSON 페이로드를 명령의 stdin으로 파이프해요. *= <cmd>로 모든 이벤트 유형을 매치해요. 이 플래그는 반복 가능해요.
경고 — --on-event는 --exec 아래에서는 아무것도 하지 않아요: 이벤트 훅은 대화형 App의 이벤트 버스에 설치돼요. docker agent run --exec 실행은 그 배선이 일어나기 전에 반환되므로 --on-event는 그곳에서 조용히 no-op예요 — 오류도 없고, 훅도 실행되지 않아요. --on-event는 일반 대화형 run이나 --lean(여전히 훅을 설치하지만 대체 화면을 건너뛰는 것)과 함께 쓰세요. 헤드리스 --exec 실행은 --json NDJSON 스트림을 직접 파싱하고 원하는 이벤트에서 셸로 넘겨 같은 효과를 얻어요 — 예를 들어 턴이 정상 종료될 때 발생하는 stream_stopped.
# Post a Slack notification when the agent finishes a turn (interactive or --lean only)
$ docker agent run agent.yaml --lean --on-event stream_stopped = "./notify-slack.sh" "Fix the failing test"
# Log every event to a file for later inspection
$ docker agent run agent.yaml --lean --on-event "*=cat >> events.ndjson" "Fix the failing test"
# Headless equivalent: capture the --json NDJSON stream, then react to it yourself
$ docker agent run --exec agent.yaml --json "Fix the failing test" | tee events.ndjson
$ jq -e 'select(.type == "stream_stopped")' events.ndjson >/dev/null && ./notify-slack.sh
훅은 비동기로 실행되고 결코 기다리지 않아요: 각각 run 자체의 컨텍스트에서 분리된 채 스폰되고, 프로세스는 run이 끝나자마자(os.Exit) 실행 중인 훅 하위 프로세스를 기다리거나 신호하지 않고 종료돼요. 훅 자신의 실패는 로그되지만 run을 실패시키지는 않아요 — 그리고 그것과 무관하게 프로세스 종료 시 그 운명은 명시되지 않아요: 고아 프로세스로 계속 실행될 수도 있고, 작업을 감독하는 어떤 것(컨테이너를 내리는 CI 러너, 프로세스 그룹을 죽이는 셸, …)이 내려버릴 수도 있어요. 환경에 달려 있지 docker-agent가 보장하는 것에 달려 있지 않아요. 프로세스가 종료되기 전에 반드시 끝나야 하는 것에 --on-event를 의존하지 마세요. 실행됐다는 증거가 필요하면 훅 스크립트가 스스로 분리(예: nohup / disown)하거나 자체 완료 마커를 쓰게 하세요.
CI에서 무인 실행 (Running Unattended in CI)
대화형으로는 도구 호출이 allow 권한 패턴에 포함되지 않으면 TUI가 실행 전에 확인 프롬프트를 띄워요. CI에는 그 프롬프트에 답할 사람이 없으므로, 무인 --exec 실행은 무엇이 물어보지 않고 실행될 수 있는지에 대한 명시적 정책이 필요해요. 그렇지 않으면 모델이 시도하는 모든 도구 호출이 그냥 거부돼요(물을 stdin이 없으므로 --exec가 없으면 당신을 대신해 "no"라고 답해요. 위 --json의 자동 거부 동작 참조).
여기서 두 가지 다른 질문이 나오고, 별도로 유지할 가치가 있어요:
- 물어보지 않고 실행해도 되는 것은 무엇인가? — 안전 모드(
--safety strict|balanced|restricted|autonomous,--yolo는 autonomous의 레거시 표기)와 권한 allow-list가 이에 답해요. - 모델이 하지 말아야 할 것을 실행하면 어떻게 되는가? —
--sandbox만이 이에 답해요. 이 섹션의 나머지는 왜 그런지 설명하고, 그 구분 자체가 핵심이에요.
--sandbox: 격리 경계
신뢰할 수 없거나 자율적인 에이전트 — 승인을 지켜보는 사람 없이 행동하는 어떤 것 — 에게 --sandbox는 더 영리한 allow-list가 아니라 도달해야 할 격리 경계예요. 에이전트 전체(셸 호출 포함)를 sbx가 관리하는 VM 안에서 실행해요: 오작동하거나 프롬프트 주입에 성공한 에이전트가 어떤 명령을 실행하든 마운트된 작업 디렉토리 바깥의 어떤 것도 건드리거나 다른 호스트/CI 상태에 도달할 수 없어요. 그 VM은 일회용·임시가 아니에요 — 현재 워크스페이스와 마운트 세트와 일치하는 샌드박스는 세션이 끝날 때 내려지는 대신 이후 실행에 보존·재사용돼요(How It Works 참조). 전체 플래그 참조, sbx 요구사항, 네트워크 allowlist, kit 스테이징 동작은 Sandbox Mode를 보세요.
$ docker agent run --sandbox --exec agent.yaml --json "Fix the failing test"
폭발 반경이 VM 경계 안에 담기므로 --sandbox는 CI에서 무인 운영을 합리적으로 만들어요 — 그리고 정확히 그렇게 기본 설정돼요: 이미 자체 --yolo나 --safety 플래그를 전달하지 않았다면 --sandbox는 VM 안에서 실행하는 에이전트 프로세스에 --yolo를 주입하므로 위 명령은 확인 프롬프트 없이 이미 무인으로 실행돼요. --yolo를 명시적으로 전달하는 것(--sandbox --yolo --exec ...)은 동일하며 스크립트에서 의도를 더 명확하게 할 수 있지만 선택적이에요. 샌드박스 안에서도 확인 프롬프트를 유지하려면 더 엄격한 모드를 선택하거나(--sandbox --safety strict) --yolo=false로 레거시 기본값을 거부하세요 — --sandbox는 두 안전 플래그가 모두 설정되지 않았을 때만 --yolo를 채워요.
CI 제공자가 이미 각 작업을 자체 일회용 VM이나 컨테이너에서 실행한다면(많은 호스티드 러너가 그럼), 작업이 끝나면 러너에서 아무것도 중요하지 않다면 그 자체로 이미 격리 경계를 줄 수 있어요. --sandbox는 여전히 CI 제공자와 무관하게 같은 보장을 주고, 에이전트가 영구 셀프 호스티드 러너·장수 컨테이너·자신의 워크스테이션에서 실행되는 순간 중요해지기 시작해요.
심층 방어, 경계가 아니라: 권한과 셸 명령 매칭
권한 allow-list(에이전트의 permissions.allow, 또는 전역으로 settings.permissions.allow — Permissions 참조)와 balanced 안전 모드의 셸 분류기(Safety modes 참조)는 물어보지 않고 실행되는 것을 좁혀요. 잘 쓰면 프롬프트 빈도를 줄이고 명백히 파괴적인 호출이 실행되기 전에 잡아요. 그것들은 보안 경계가 아니에요:
- 둘 다 셸 명령 문자열(또는 권한의 경우 도구 인자)을 매칭해 동작해요. 분류기의 safe-list는 셸 메타문자(
;,&,|,<,>, 백틱,$(, 개행 — 공백이 있든 없든)를 담은 어떤 명령도 보증하지 않아요. 그래서ls && rm -rf ~,grep foo|rm -rf /,grep x > /etc/passwd모두 안전 판정을 이어받는 대신 확인으로 넘어가요. 하지만 문자열 매칭은 명령이 실제로 무엇을 하는지 추론할 수 없어요 — 다음 요점 참조. - 명령 문자열·인자 매칭은 일반적으로 명령이 실제로 무엇을 하는지 추론할 수 없어요. 동적으로 만들어진 문자열, 특이한 인용 형식, 래퍼 스크립트는 어떤 고정 패턴 세트도 빠져나갈 수 있어요.
무인 실행에서는 restricted 안전 모드가 이 입장을 fail-closed 기본값으로 묶어요: 분류기가 안전하다고 한 호출은 실행되고, 다른 모든 매치 안 된 호출은 아무도 답하지 않을 확인 프롬프트로 넘어가는 대신 outright 거부돼요. 작업이 실제로 필요로 하는 것으로 범위가 좁혀진 allow-list와 짝지으세요 — 명시적 allow 규칙은 여전히 모드를 이기므로 작업의 알려진 정상 명령은 분류기가 보증할 수 없어도 실행돼요:
# agent.yaml — the allow-list overrides restricted's deny for these calls
permissions:
allow:
- "shell:cmd=go test*"
- "shell:cmd=go build*"
# Safe and allow-listed calls run; every other call is denied without prompting
$ docker agent run --exec --safety restricted agent.yaml --json "Fix the failing test"
allow-list처럼 restricted도 심층 방어이지 보안 경계가 아니에요: 무인으로 실행되는 것을 좁히지만, 허용된 호출로 오작동하는 에이전트가 무엇을 할 수 있는지 담는 것은 오직 --sandbox뿐이에요.
권한과 balanced/restricted 모드를 프롬프트 피로를 줄이고 명백한 경우를 잡는 방법으로 취급하고, 최소 권한 CI 자격 증명과 짝지으세요 — 결코 CI 작업이 무인으로 안전하게 실행되는 이유로 삼지 마세요. 그런 용도에는 --sandbox를 쓰세요.
경고 — --sandbox 없는 --yolo는 경계 없이 신뢰할 수 없는 무인 코드를 실행해요: CI 작업은 정확히 달아나거나 오도된 에이전트가 아무도 눈치채기 전에 가장 큰 피해를 주는 환경이에요 — 나쁜 셸 호출이 실행되기 전에 잡을 키보드의 사람이 없고, 위처럼 권한 allow-list나 셸 분류기도 모든 것을 잡도록 신뢰할 수 없어요. --sandbox를 추가할 수 없다면, 막연한 --yolo보다 작업이 실제로 필요로 하는 것에 범위를 맞춘 권한 allow-list와 함께 --safety restricted를 선호하고, 에이전트 toolset의 자격 증명과 폭발 반경을 작업 자체가 손상된 것처럼 예산을 잡으세요 — 작업된 allow/deny 목록은 examples/permissions.yaml을 보세요.
참고 — worktree도 보안 경계가 아니에요: --worktree는 에이전트가 수정하는 브랜치와 체크아웃을 격리해요 — 에이전트에게 자체 작업 디렉토리와 브랜치를 줘서 기본 체크아웃이 손대지 않게 하지만, 셸 toolset은 여전히 호스트에서 네이티브 프로세스로 실행되고 worktree는 저장소의 기본 객체 저장소를 다른 체크아웃들과 공유해요. 체크아웃 격리이지 보안 경계가 아니에요. 오직 --sandbox만이 그것을 제공해요.
CI에서 시크릿 제공하기 (Providing Secrets in CI)
provider API 키나 MCP 토큰을 절대 에이전트 구성 파일에 넣지 마세요. CI 제공자의 시크릿 저장소에서 환경 변수로 주입하거나, 작업 시작 시 실체화된 파일로 --env-from-file을 쓰세요. 모든 지원 방법(Docker Compose secrets와 1Password 참조 포함)은 Managing Secrets를 보세요 — 둘 다 CI 시크릿 저장소에 깔끔히 매핑돼요.
텔레메트리 비활성화 (Disabling Telemetry)
Docker Agent의 익명 사용 텔레메트리는 기본적으로 켜져 있어요. CI에서는 끄고 싶을 수 있어요:
$ TELEMETRY_ENABLED = false docker agent run --exec agent.yaml "..."
정확히 무엇이(그리고 무엇이 아닌지) 수집되는지는 Telemetry를 보세요.
예제: GitHub Actions (Example: GitHub Actions)
베어 OCI 레지스트리 참조(myorg/coder)는 제어할 로컬 구성이 없으므로, 보안에 민감한 CI 작업은 대신 작은 에이전트 구성을 체크인해야 해요. 이 예제는 체크인된 리뷰 에이전트를 빌드 중인 저장소에 대해 비대화형으로 실행해요:
# .github/agents/review-agent.yaml
agents:
root:
model: anthropic/claude-sonnet-4-5
description: Reviews the changes in a pull request for bugs and security issues
instruction: Review the changes in this PR for bugs and security issues.
toolsets:
- type: shell
# .github/workflows/agent-review.yml
name: Agent code review
on:
pull_request:
permissions:
contents: read
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install docker-agent
run: |
curl -L "https://github.com/docker/docker-agent/releases/latest/download/docker-agent-linux-amd64" -o docker-agent
chmod +x docker-agent
sudo mv docker-agent /usr/local/bin/
- name: Run the review agent
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
TELEMETRY_ENABLED: "false"
run: |
docker-agent run --exec --yolo .github/agents/review-agent.yaml --json \
"Review the changes in this PR for bugs and security issues" \
| tee agent-events.ndjson
- name: Upload transcript
if: always()
uses: actions/upload-artifact@v4
with:
name: agent-events
path: agent-events.ndjson
이 작업은 리뷰 에이전트가 하는 모든 셸 호출을 --yolo로 자동 승인해요. 코드 리뷰가 필요로 할 수 있는 모든 git / grep / cat 호출을 allow-list하려 하지 않아요 — "이 diff 리뷰"의 읽기 표면은 개방형이고, 고정 패턴 목록은 정확히 이전 섹션이 의존하지 말라고 한 종류의 셸 매칭 경계예요. CI 환경에 sbx가 설치·구성되어 있다면(GitHub 호스티드 ubuntu-latest는 상자에서 제공하지 않아요), --sandbox를 추가하고 그 --yolo 주위에 실제 격리 경계를 얻으세요:
$ docker-agent run --sandbox --exec --yolo .github/agents/review-agent.yaml --json "..."
--sandbox 없이 이 워크플로우의 안전은 대신 최소 권한 시크릿(ANTHROPIC_API_KEY만 주입 — 저장소 쓰기 토큰 없음), 최상위 permissions: contents: read 블록과 체크아웃 단계의 persist-credentials: false(둘 다 작업이 쓰기 가능한 GITHUB_TOKEN을 절대 쥐지 않고 git이 집게 디스크에 영속하지 않음을 뜻해요), 그리고 작업 후 폐기되는 GitHub 호스티드 임시 러너에서 실행되는 것에 의존해요.
이 예제는 이 가이드의 이전 개정에서 보여준 GitHub MCP toolset(docker:github-official)을 생략해요: 그 서버는 이 워크플로우가 제공하지 않는 GITHUB_PERSONAL_ACCESS_TOKEN을 요구하고 — 위 toolset에 name: 필드가 없으므로 그 도구는 github_* 스타일 한정 이름이 아닌 원시 MCP 이름(get_file_contents, search_code, …)으로 노출돼, 그 접두사에 맞춰 쓴 권한 패턴은 어차피 아무것도 매치하지 않아요. 리뷰 에이전트가 GitHub API 접근을 필요로 하면 명시적 name: github로 toolset을 다시 추가하고, 저장소 시크릿에서 env:로 GITHUB_PERSONAL_ACCESS_TOKEN을 연결하고, 권한 패턴을 그것이 실제로 노출하는 도구 이름에 맞춰 쓰세요(github_get_*는 toolset이 그 name:을 가진 뒤에만 동작해요).
모델, toolset, provider 시크릿을 여러분 것으로 바꾸세요 — 형태(체크아웃, 바이너리 설치, 체크인된 구성으로 --json 포함 --exec 실행, 트랜스크립트 업로드)는 셸 단계를 실행할 수 있는 어떤 CI 제공자에도 일반화돼요.