본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 28. 03:13

AI 에이전트에게 셸(Shell) 권한을 주었습니다. 그리고 저는 그것을 지켜볼 브라우저 탭을 열었습니다.

요약

AI 에이전트에게 셸 권한을 부여할 때 발생하는 블랙박스 문제를 해결하기 위해, 에이전트의 실행 과정을 실시간으로 모니터링할 수 있는 웹 뷰어 시스템을 구축한 경험을 공유합니다.

핵심 포인트

  • 에이전트의 요약 정보 대신 실제 명령어와 출력 스트림(Ground Truth)을 직접 확인해야 함
  • 실시간 웹 뷰어를 통해 명령어 실행 상태를 색상별로 시각화하여 모니터링 가능
  • 세션 그룹화 및 과거 기록 검색 기능을 통해 에이전트의 작업 이력 추적 용이

몇 달 전, 저는 코딩 에이전트(coding agent)가 저를 대신해 셸(shell) 명령어를 실행하도록 허용하기 시작했습니다. 명령어를 제안하는 것이 아니라, 직접 실행하게 한 것이죠. 약 일주일 동안은 마치 마법처럼 느껴졌습니다.

그러던 어느 오후, 에이전트가 실패한 빌드(build)를 수정하려고 시도했습니다. 빌드 과정에서 webpack 노이즈가 약 4,000줄 정도 출력되었습니다. 에이전트는 그 모든 내용을 읽었지만, 맥락을 완전히 놓쳐버렸고, 진짜 문제가 테스트 디렉토리(test directory)에 있다고 판단하여 이를 삭제해 버렸습니다. 저는 한 시간 뒤에야 이 사실을 알게 되었습니다. 왜냐하면 에이전트의 "네, 수정하겠습니다"라는 말과 실제 피해가 발생하기 사이의 과정에서, 무슨 일이 일어나고 있는지 전혀 알 수 없었기 때문입니다. 그것은 제 파일 시스템(filesystem)을 안에 품고 있는 블랙박스(black box)와 같았습니다.

저는 이를 수행할 더 안전한 방법을 찾아보았지만 마음에 드는 것을 찾지 못했고, 결국 직접 만들게 되었습니다. 그날 오후의 경험이 저에게 가르쳐준 네 가지와, 현재 제가 대신 하고 있는 작업들을 소개합니다.

1. 에이전트가 실행하는 동안 무엇을 하고 있는지 직접 봐야 합니다

핵심 문제는 에이전트가 실수를 한다는 것이 아닙니다. 실수가 일어난 '후에야' 알게 된다는 점이 문제입니다. 일반적인 subprocess 호출은 지켜볼 수 있는 아무것도 제공하지 않습니다. 에이전트가 10개의 명령어를 실행하면, 여러분은 에이전트가 스스로에 대해 작성한 요약본을 보게 되는데, 이는 무언가 잘못되었을 때 결코 신뢰할 수 없는 바로 그 정보입니다.

그래서 저는 창(window)을 하나 만들었습니다. 환경 변수(environment variable) 하나를 설정하면 서버가 localhost에서 읽기 전용 웹 뷰어(web viewer)를 시작하고 URL을 제공합니다. 여러분은 일반 브라우저 탭에서 이를 열고, 실행되는 실제 명령어와 실제 출력 스트림(output stream)을 실시간으로 지켜볼 수 있습니다. 색상으로 구분됩니다: 명령어는 청록색(cyan), 정상 종료는 녹색(green), 실패 또는 차단된 명령어는 빨간색(red)입니다. 에이전트가 무엇을 했는지에 대한 설명이 아니라, 실제 사실(ground truth)을 보는 것입니다.

execkit's browser viewer showing an AI agent's live shell transcript: a local session running git status, cargo build, and cargo test, each command in cyan with its output below and a green
실시간 트랜스크립트(live transcript): 에이전트의 요약이 아닌, 실행되는 실제 명령어와 출력.

이 시스템은 세션이 실행된 위치(local, ssh, docker)에 따라 세션을 그룹화하고, 스크롤하여 되돌아볼 수 있는 과거 세션 기록을 유지하며, 긴 실행 과정 중에서 당신이 원하는 특정 명령어를 찾을 수 있는 검색창을 제공합니다. 실시간 보기(live view) 기능을 추가한 날, 저는 에이전트가 커밋되지 않은 작업에 대해 git reset --hard를 실행하려는 것을 포착했습니다. 그렇지 않았다면 결코 알지 못했을 것입니다.

execkit's viewer with the in-transcript search open: the word
검색 기능을 사용하면 긴 실행 과정 중 관심 있는 명령어로 바로 이동할 수 있으며, "next err"를 입력하면 실패 지점으로 즉시 점프합니다.

2. 명령 출력물로 인해 자체 컨텍스트(context)가 침수될 것입니다

그 4,000줄짜리 빌드 로그는 저뿐만 아니라 에이전트도 혼란스럽게 만들었습니다. 모든 줄이 컨텍스트 윈도우(context window)로 들어가 실제 작업 내용을 밀어냈기 때문입니다. 에이전트는 쓰레기 데이터를 많이 제공할수록 더 멍청해지며, 가공되지 않은 셸(raw shell)은 모든 것을 에이전트에게 쏟아붓습니다.

해결책은 지루하지만 효과적입니다. 모델에 도달하기 전에 출력을 정제하는 것입니다. 소음이 심한 빌드 로그의 경우 마지막 200줄만 유지하세요. 방대한 로그에서 몇 줄의 컨텍스트와 함께 에러를 grep 하세요. 전체 글자 수를 제한하세요. 이렇게 하면 에이전트는 정보의 홍수(firehose)가 아닌 신호(signal)를 보게 되며, 당신이 실제로 요청한 작업을 위해 컨텍스트를 깨끗하게 유지할 수 있습니다.

3. 자신이 어디에 있는지 잊어버리는 셸은 쓸모가 없습니다

이 문제는 평범해 보이지만 저를 끊임없이 괴롭혔습니다. 에이전트가 cd packages/api를 실행한 다음 npm test를 실행했는데, 두 번째 호출이 완전히 새로운 셸이었기 때문에 테스트가 레포지토리 루트(repo root)에서 실행되는 식입니다. 홈 디렉토리부터 시작하는 모든 명령어는 셸이 아니라, 서로 관련 없는 낯선 사람들의 연속일 뿐입니다.

상태(State)는 유지되어야 합니다. 실제 터미널이 작동하는 방식처럼 호출 전반에 걸쳐 cd, 환경 변수(environment variables) 등이 유지되어야 합니다. 세션이 실제로 상태를 유지(stateful)하게 되자, 도구와 싸우는 듯한 느낌이 사라졌습니다.

4. 조만간 당신이 되돌리고 싶은 행동을 할 것입니다

아무리 주의를 기울여도 그런 일은 일어날 수 있습니다. 에이전트가 설정을 다시 작성하거나, 마이그레이션 (migration)을 실행하거나, 삭제해서는 안 될 파일을 삭제할 수도 있습니다. 로컬 저장소 (local repo)라면 Git이 있지만, SSH를 통한 원격 서버 (remote box)에서는 작업 도중에 Git이 없는 경우가 많습니다.

따라서 변경 명령을 실행하기 전에 워크스페이스 (workspace)의 스냅샷 (snapshot)을 찍어두세요. 문제가 생기면 복구하면 됩니다. 이는 파일만 되돌릴 뿐 부작용 (side effects)까지 되돌리지는 못하지만 (예: 삭제된 데이터베이스는 그대로 삭제된 상태로 남음), "소스 트리 (source tree)를 망가뜨렸다"는 상황에서는 어깨를 으쓱하며 넘길 수 있느냐 아니면 저녁 내내 고생하느냐의 차이를 만듭니다.

가장 중요해진 지루한 부분들

별로 신경 쓰지 않을 줄 알았지만, 이제는 없어서는 안 될 몇 가지 요소들입니다:

  • 비밀 정보 (Secrets)가 마스킹 (redacted)됩니다: 출력이 저장되거나 표시되기 전에 처리됩니다. 제 API 키들이 평문 (plaintext) 기록에 포함되고 있었는데 미처 알아차리지 못했습니다.
  • 표준 출력 (stdout)과 표준 에러 (stderr)가 분리되어 유지됩니다: 실제 종료 코드 (exit code) 및 실행 시간과 함께 구조화된 데이터 (structured data)로 제공됩니다. 이제 에이전트가 무언가 실패했는지 여부를 추측할 필요가 없습니다.
  • SSH 호스트 키 (host keys)가 기본적으로 검증됩니다: 따라서 키가 바뀌었을 경우 묵인되지 않고 거부됩니다.
  • 뷰어 (viewer)는 구조적으로 읽기 전용 (read-only)이며 로컬 (local)입니다: 루프백 (loopback)에만 바인딩되며, 모든 요청에는 URL 토큰이 필요합니다. 감사 스트림 (audit stream)을 읽을 수는 있지만, 세션 (session), 명령 (command), 또는 사용자의 파일에는 절대 손댈 수 없습니다.
  • 솔직한 부분: 명령 허용/차단 울타리 (command allow/deny fence)는 **권고 사항 (advisory)**입니다. 진정한 안전 경계는 에이전트를 컨테이너 (container) 내의 최소 권한 사용자 (least-privilege user) 또는 제한된 SSH 계정으로 실행하는 것입니다. 문자열 필터 (string filter)는 과속 방지턱일 뿐 감옥이 아니며, 그렇지 않은 척하는 것이 바로 사람들이 피해를 입는 방식입니다.

execkit's viewer showing a Docker session with the per-session menu open (rename, pin, keep, export, screenshot). The transcript has a failing
여기서 권고 울타리가 rm -rf /를 차단했지만, 진짜 가치는 해당 시도가 가시화되고 로그 (log)에 기록된다는 점에 있습니다. 메뉴에서 즉시 모든 세션을 내보내거나(export) 스크린샷을 찍으세요.

결과물

저는 이 모든 것을 execkit으로 패키징했습니다. 작은 Rust 라이브러리와 더불어, MCP를 지원하는 모든 에이전트(Claude Code, Cursor, Gemini CLI 등)가 이를 사용할 수 있도록 MCP 서버를 함께 제공합니다. 라이선스는 Apache-2.0입니다.

pip install execkit-mcp                 # 또는: cargo install execkit-mcp
claude mcp add execkit -- execkit-mcp   # Claude Code에 연결

그다음 작동 과정을 지켜보려면, 서버의 환경 변수에 EXECKIT_MCP_WATCH_WEB=1을 설정하고 출력되는 URL을 여세요. (브라우저가 자동으로 뜨길 원하시나요? 그렇다면 EXECKIT_MCP_WATCH_OPEN=1도 함께 설정하세요.) 또는 기존의 감사 로그(audit log)를 뷰어에 연결할 수도 있습니다: execkit-mcp watch --serve --open path/to/audit.

제가 이것을 만든 이유는 아무것도 모르는 상태(flying blind)로 작업을 진행하고 싶지 않았기 때문입니다. 만약 에이전트가 실제 셸(shell)을 만지도록 허용한다면, 적어도 그 과정을 지켜볼 수 있는 방법은 있어야 한다고 생각했습니다.

저장소 및 문서: https://blinkingbit-oss.github.io/execkit/

직접 사용해 보신다면, 어떤 부분이 제대로 작동하지 않는지 진심으로 듣고 싶습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0