본문으로 건너뛰기

© 2026 Molayo

r/LocalLLaMA분석2026. 06. 21. 03:56

내가 시도한 모든 에이전트 도구들은 모든 에이전트를 하나의 워크스페이스에 쏟아부었습니다. 대신 제가 선택한 구조는 다음과 같습니다.

요약

여러 코딩 에이전트를 동시에 사용할 때 발생하는 워크스페이스 혼선 문제를 해결하기 위한 에이전트 오케스트레이션 아키텍처를 제안합니다. 코드, 결과물(artifacts), 비밀 정보(secrets)를 각각 독립된 디렉토리로 분리하여 보안과 관리 효율성을 높이는 구조를 다룹니다.

핵심 포인트

  • 단일 워크스페이스 사용 시 코드, 결과물, 비밀 정보가 뒤섞이는 문제 지적
  • 보안 강화를 위해 비밀 정보(secrets)를 읽기 전용 디렉토리로 분리
  • 결과물(artifacts)을 워크스페이스와 분리하여 효율적인 버전 관리 가능
  • 에이전트별 독립된 디렉토리 구조를 통한 오케스트레이션 설계

만약 당신이 두 개 이상의 코딩 에이전트 (coding agent)를 동시에 실행해 보려 했다면, 아마 저와 똑같은 벽에 부딪혔을 것입니다. 모든 것이 하나의 워크스페이스 (workspace)에 머물려고 한다는 점 말이죠. 대화할 필요가 없는 단일 에이전트에게는 그것으로 충분합니다. 하지만 여러 에이전트가 병렬로 작동하는 순간 시스템은 무너집니다.

이 글은 제가 구축하고 있는 에이전트 오케스트레이션 (agent-orchestration) 라이브러리의 아키텍처 (architecture)를 다루는 문서 시리즈의 첫 번째 글입니다. 각 문서는 제가 실제로 마주친 실제 문제에서 시작하며, 사용하기 쉬우면서도 실제로 유용할 만큼 깊이 있는 해결책을 찾으려 노력했지만 찾지 못했던 경험을 바탕으로 합니다. 이것은 완성된 정답이 아닙니다. 제가 이 문제를 어떻게 생각하고 있는지에 대한 과정이며, 만약 여러분도 같은 문제에 부딪혔다면 어떻게 처리하고 계신지 듣고 싶습니다.

첫 번째 글은 에이전트가 실행되는 환경에 관한 것입니다.

하나의 워크스페이스를 사용할 때의 문제는 매우 서로 다른 종류의 것들이 같은 장소에 머물도록 강제한다는 점입니다. 즉, 에이전트가 작업하는 코드, 제가 확인하고 싶은 에이전트가 생성한 결과물 (artifacts), 그리고 제가 에이전트에게 전달하는 비밀 정보 (secrets)가 모두 섞입니다. 이들은 단지 같은 디렉토리 (directory)에 위치한다는 점 외에는 공통점이 전혀 없습니다.

이들이 디렉토리를 공유하기 때문에, 저는 이들을 다르게 취급할 수 없습니다. 나머지는 쓰기 가능 (writable) 상태로 두면서 비밀 정보만 읽기 전용 (read only)으로 만들 수 없습니다. 에이전트가 워크스페이스에 가하는 모든 변경 사항을 함께 추적하지 않고서는 결과물 (artifacts)을 git으로 추적할 수 없는데, 대부분의 경우 그 변경 사항들은 제가 신경 쓰지 않는 것들입니다. 게다가 비밀 정보는 결국 추적되는 워크스페이스 안에 놓이게 됩니다. 다른 곳으로 갈 곳이 없기 때문입니다.

이를 해결하기 위해 저는 세 가지 디렉토리를 정의했습니다:

.artifacts/ agents/ <agent-id>/ common/
.secrets/ agents/ <agent-id>/ common/
.workspaces/ <repo>--<agent-id>/

이렇게 분리함으로써 각 종류의 요소는 실제로 필요한 방식대로 취급될 수 있으며, 워크스페이스는 비밀 정보나 결과물을 끌고 다니지 않고도 에이전트가 필요로 하는 무엇이든 될 수 있는 자유를 얻게 됩니다.

비밀 정보 (secrets)는 읽기 전용 (read only)으로 만들 수 있으므로, 에이전트가 이를 사용할 수는 있지만 수정하거나 삭제할 수는 없게 됩니다.

Artifacts (결과물)는 워크스페이스와 분리되어 독자적으로 존재하므로, 에이전트가 결과물에 도달하기 위해 수행한 모든 수정 사항을 일일이 뒤져볼 필요 없이 결과물만 추적하거나, 차이점(diff)을 비교하거나, 에이전트가 생성한 내용만을 확인할 수 있습니다.

Secrets (비밀 정보)의 사례는 더 자세히 살펴볼 가치가 있는데, 이러한 분리가 기본 설정을 바꾸기 때문입니다. 단일 워크스페이스를 사용할 경우, 비밀 정보는 추적되는 디렉토리 외에는 머무를 곳이 없으므로, 있어야 할 자리에 있는 것만으로도 git에 유출됩니다. 일단 비밀 정보가 워크스페이스 외부의 자체적인 공간을 갖게 되면, 이를 유출하는 것이 더 이상 가장 쉬운 경로가 되지 않습니다. 에이전트가 값을 의도적으로 읽어서 추적되는 워크스페이스로 복사해야만 하는데, 이는 자동으로 발생하는 일이 아니라 능동적인 선택이 필요합니다.

이러한 분리가 자격 증명(credential) 처리를 단순하게 만듭니다. 비밀 정보가 정해진 위치를 가지고 있기 때문에, 에이전트에게 자격 증명을 전달하는 것은 단순히 파일을 올바른 위치에 두는 문제일 뿐이며, 런타임(runtime)은 어디서 이를 찾아야 할지 알고 있습니다.

실제로 다음과 같이 작동합니다. init --copy-credentials 서브커맨드(subcommand)는 제 머신에 이미 있는 자격 증명들, 즉 각 도구가 제 홈 디렉토리의 각기 다른 위치(~/.claude/.credentials.json, ~/.codex/auth.json, ~/.config/gh/hosts.yml 등)에 기록하는 것들을 읽어 들여, 에이전트 간에 공유될 수 있도록 공통 비밀 정보 디렉토리에 복사본을 스테이징(staging)합니다. 에이전트가 컨테이너에서 시작될 때, 이미지는 /secrets/agent에서 먼저 자격 증명을 찾고, 그다음 /secrets/common을 확인한 뒤, 발견된 것을 도구가 예상하는 경로로 다시 복사합니다. 따라서 제가 생성하는 모든 컨테이너 내부에서 일일이 로그인할 필요 없이 인증이 자동으로 이루어집니다. 또한 인증 파일이 없는 프로바이더(provider)나 GitHub PAT(Personal Access Token)가 없는 경우처럼 무언가 누락되었을 때 이를 확인하고 알려주는 doctor 서브커맨드도 있습니다.

동일한 메커니즘이 환경 변수(environment variables)도 처리합니다. .env 파일을 비밀 정보 디렉토리에 넣으면 런타임이 이를 읽어 에이전트의 세션으로 값을 내보냅니다(export). 이 역시 별도의 특별한 처리 없이, 해당 디렉토리 자체가 인터페이스 역할을 합니다.

에이전트 식별자 (<agent-id>)

저는 특정 브랜치(branch)나 워크스페이스(workspace)가 에이전트 소유인지, 만약 그렇다면 어떤 에이전트의 것인지 빠르게 확인할 수 있는 방법을 원했습니다. ID에는 세 가지 요소가 포함되며, 각각은 위 질문 중 하나에 답합니다.

첫째, 모든 에이전트 ID는 aiagent-로 시작합니다. 이 접두사(prefix) 덕분에 에이전트가 소유한 작업이 스스로를 식별할 수 있습니다. aiagent-로 시작하는 브랜치나 디렉토리는 예외 없이 에이전트의 것이므로, 한눈에 알아차리거나, 필터링하거나(git branch | grep aiagent-), 혹은 사람이 만든 브랜치와 혼동될 염려 없이 접두사만으로 에이전트 워크스페이스를 정리할 수 있습니다.

그다음은 이름(name)이 옵니다. 이름은 읽기 쉽고 사용자가 직접 선택할 수 있으므로, 목록을 훑어볼 때 실제로 인식하게 되는 부분입니다. 하지만 이름만으로는 에이전트를 식별하기에 충분하지 않습니다. 두 에이전트가 같은 이름을 공유할 수 있고, 이름이 너무 길어질 수도 있기 때문입니다.

따라서 각 에이전트에는 문자(letter)가 하나씩 부여됩니다. 각 문자는 고유하며 a부터 시작하여 자동으로 증가(auto-incremented)되고, 절대 재사용되지 않습니다. z를 넘어가면 aa, ab와 같은 방식으로 넘어갑니다. 이 문자는 이름이 할 수 없는 두 가지 역할을 합니다. 첫째, 고유함이 보장되므로 우연히 이름이 같은 두 에이전트를 구분(disambiguate)해 줍니다. 둘째, 길이가 짧습니다. 이는 브랜치 이름, 디렉토리 이름, 그리고 에이전트가 참조되는 모든 곳에 나타날 때 매우 중요합니다. 또한 문자는 순서대로 부여되므로, 더 높은 문자는 더 최근에 생성된 에이전트를 의미합니다.

이를 종합하면 ID는 다음과 같습니다:
aiagent-<agent-name>-<agent-letter>

동일한 세 가지 구성 요소가 에이전트의 브랜치 이름이 되는데, Git이 브랜치의 네임스페이스(namespace)를 지정하는 방식에 따라 - 대신 /를 사용합니다:
aiagent/<agent-name>/<agent-letter>

따라서 모든 에이전트 브랜치는 aiagent/ 아래에 존재하며, 덕분에 "이 브랜치가 에이전트의 것인가?"라는 확인 작업은 해당 접두사 아래에 있는 모든 브랜치를 찾는 아주 간단한 작업이 됩니다.

에이전트 디렉토리 내부에서는 접두사(prefix)를 제거합니다. 그곳에 있는 모든 것이 이미 에이전트의 것이기 때문입니다:

workspace: .agents/.workspaces/<agent-name>-<agent-letter>/
artifacts: .agents/.artifacts/agents/<agent-name>-<agent-letter>/
secrets: .agents/.secrets/agents/<agent-name>-<agent-letter>/

접두사는 git 브랜치처럼 에이전트의 작업이 인간의 작업과 나란히 놓이는 곳에서만 제 역할을 합니다. 에이전트 외에는 아무것도 들어있지 않은 디렉토리에서 aiagent-는 그저 노이즈(noise)일 뿐입니다.

에이전트 환경 (Agent environment)
에이전트는 어딘가에서 실행되어 무언가를 수행하는 프로그램입니다. 따라서 에이전트의 환경은 두 가지 별개의 관심사(concerns)를 가집니다. 즉, 에이전트가 작업하는 공간인 워크스페이스 (workspace)와 에이전트가 실행되는 곳인 런타임 (runtime)입니다. 각각은 고유한 인터페이스(interface)로 정의되므로, 나중에 새로운 타입으로 확장할 수 있습니다. 이들은 서로 분리된 관심사이지만, 앞으로 살펴보겠지만 완전히 독립적인 것은 아닙니다. 일부 런타임은 유효한 워크스페이스를 제한하기도 합니다.

워크스페이스 (Workspace)
워크스페이스는 에이전트가 작업할 공간이 있는지 확인하는 부분입니다. 실제로 이는 몇 가지 단계를 의미합니다:

  • 디렉토리 (Directories): 워크스페이스를 생성하고 앞서 언급한 아티팩트 (artifacts) 및 시크릿 (secrets) 디렉토리를 연결합니다.
  • Git: 워크트리 (worktree)를 클론하거나 설정하고, 브랜치를 생성하는 등의 작업을 수행합니다.
  • 스킬 연결 (Skills wiring): 스킬은 단순한 파일이므로 에이전트의 환경에 링크됩니다. 이에 대해서는 이후 문서에서 자세히 다루겠습니다.
  • on_provision: 워크스페이스가 설정될 때 사용자가 실행하고자 하는 모든 커스텀 명령어를 위한 훅 (hook)입니다.

1단계는 좀 더 자세히 살펴볼 가치가 있습니다. 디렉토리를 생성하는 것이 단순히 생성만 하면 되는 것처럼 간단하지 않기 때문입니다. 아티팩트와 시크릿은 워크스페이스 외부에 있으며, 앞서 언급한 공유된 .artifacts.secrets 트리 안에 존재합니다. 에이전트는 워크스페이스 내부에서 이들에 접근해야 하며, 접근 방식은 에이전트가 어디에서 실행되는지에 의존해서는 안 됩니다.

컨테이너 (container)에서는 디렉토리를 마운트(mount)할 수 있으므로, 에이전트가 /artifacts/secrets와 같은 경로에서 이를 볼 수 있습니다. 호스트 (host)에서는 마운트가 없습니다.

에이전트는 ../../.artifacts/...와 같은 방식으로 자신의 워크스페이스(workspace) 밖으로 기어 나와야 하는데, 이는 지저분하며 테스트 중에 작동이 중단되었습니다. 더 나쁜 점은 이들이 서로 다른 두 개의 경로라는 것이며, 따라서 에이전트는 자신이 어떤 런타임 (runtime)에 있는지에 따라 서로 다른 지침을 필요로 하게 됩니다. 이것이 바로 제가 피하고자 했던 결합 (coupling)입니다.

해결책은 에이전트에게 어디서나 작동하는 하나의 고정된 경로를 제공하고, 그 아래에 런타임 (runtime)의 차이를 숨기는 것입니다. 프로비저닝 (provisioning) 중에 워크스페이스는 워크스페이스 내부의 고정된 위치에 네 개의 심볼릭 링크 (symlink)를 생성합니다:

<workspace>/.agents/
.artifacts/ agent/ → 이 에이전트의 아티팩트 (artifacts)
common/ → 공유 아티팩트 (shared artifacts)
.secrets/ agent/ → 이 에이전트의 비밀 정보 (secrets)
common/ → 공유 비밀 정보 (shared secrets)

각 심볼릭 링크 (symlink)는 런타임 (runtime)에 따라 다르게 해석됩니다. 호스트 (host)에서는 실제 공유 트리(<repoRoot>/.agents/.artifacts/agents/<agent-name>-<agent-letter>/)를 가리킵니다. 컨테이너 (container)에서는 마운트 지점(/artifacts/agent)을 가리킵니다. 에이전트는 이 중 어느 것도 보지 못합니다. 에이전트는 오직 환경 변수 (environment variables)를 통해 전달받는 고정된 경로만을 보게 됩니다:

AGENTS_ARTIFACTS_DIR = <workspace>/.agents/.artifacts/agent
AGENTS_COMMON_ARTIFACTS_DIR = <workspace>/.agents/.artifacts/common
AGENTS_SECRETS_DIR = <workspace>/.agents/.secrets/agent
AGENTS_COMMON_SECRETS_DIR = <workspace>/.agents/.secrets/common

따라서 에이전트는 모든 환경에서 동일한 방식으로 AGENTS_ARTIFACTS_DIR을 읽고 그곳에 기록합니다. 심볼릭 링크 (symlink)가 호스트 (host)와 컨테이너 (container) 사이의 차이를 흡수하며, 그 상위의 어떤 요소도 자신이 어떤 환경에 있는지 알 필요가 없습니다.

git 단계는 모드 (mode)에 따라 달라집니다. 네 가지 모드가 있습니다:

  • clone: 저장소 (repo)의 전체 클론 (full clone).
  • worktree: 메인 저장소 (repo)를 공유하며 자체 브랜치에서 작동하는 git 워크트리 (worktree).
  • self: 별도의 워크스페이스 (workspace) 없이, 에이전트가 현재 저장소 (repo)에서 그대로 작업함.
  • none: 코드 저장소 (repo)가 필요 없는 에이전트를 위한, git 저장소 (repo)가 전혀 없는 관리형 디렉토리 (managed directory).

그다음에는 모드 (mode)와는 별개인 두 번째 축이 있는데, 바로 워크스페이스 (workspace)가 물리적으로 존재하는 위치입니다. 저는 이것을 환경 (environment)이라고 부르며, 파일 시스템 (filesystem)과 도커 (docker) 두 가지가 있습니다.

파일 시스템 (filesystem)을 사용할 경우 워크스페이스 (workspace)는 호스트 (host) 상의 디렉토리 (directory)가 됩니다. 도커 (docker)를 사용할 경우 코드는 대신 도커 볼륨 (docker volume) 내부에 존재하게 됩니다.

이곳이 두 축 (axes)이 상호작용하는 지점입니다. 모든 모드 (mode)가 모든 환경 (environment)에서 유효한 것은 아닙:

env \ mode worktree clone self none

filesystem ✓ ✓ ✓ ✓
docker ✗ (host-only) ✓ ✗ (컨테이너 내에는 "self"가 없음) ✓

worktree는 메인 리포지토리 (main repo)와 파일을 공유하지만, 볼륨 (volume) 내부에는 메인 리포지토리가 존재하지 않기 때문에 호스트 전용 (host-only)입니다. self는 컨테이너 내에 가리킬 현재 리포지토리가 없으므로 컨테이너에서는 의미가 없습니다. 따라서 도커 (docker)는 clone과 none만 허용합니다. 유효하지 않은 조합은 설정 (config)이 파싱 (parsed)될 때 포착됩니다.

런타임 (Runtime)
런타임 (runtime)은 에이전트 (agent) 프로세스가 실제로 실행되는 환경입니다. 우리가 지원하고자 하는 것은 두 가지입니다:

host: 에이전트가 메인 환경 (main environment) 내의 서브프로세스 (subprocess)로 실행됩니다.
container (docker): 에이전트가 도커 컨테이너 (docker container) 내부에서 실행됩니다.

어느 쪽이든 런타임 (runtime)의 역할은 에이전트가 실제로 그곳에서 실행될 수 있도록 보장하는 것이며, 이는 주로 의존성 (dependencies)이 존재하는지를 의미합니다. 각 하네스 (harness)는 고유한 의존성 세트를 가지고 있으므로, 런타임이 이를 설치합니다: agents --install-dependencies.

이 문서는 환경 (environment)에 대해 다루었습니다: 에이전트의 서로 다른 관심사 (concerns)를 분리하는 디렉토리들, 에이전트 소유의 작업 (work)을 인식할 수 있게 하는 식별자들, 그리고 에이전트를 실행할 준비를 시키는 워크스페이스 (workspace)와 런타임 (runtime)에 대해 설명했습니다.

다음 단계는 실제로 이 모든 것을 어떻게 구동하느냐 하는 것입니다. 즉, 에이전트를 선언하는 설정 (config)과 에이전트를 실행하는 명령들 (agents init, 그 다음 agents provision atlas, agents start atlas)입니다. 그것이 제가 다음에 만들고 있는 것입니다.

하지만 다른 사람들은 워크스페이스 (workspace)와 격리 (isolation) 측면을 어떻게 처리하고 있는지 궁금합니다. 여러분은 각 에이전트에게 이와 같이 고유한 환경을 부여하나요, 아니면 단순히 모든 것을 하나의 워크스페이스에서 실행하나요, 아니면 제가 생각하지 못한 다른 방식으로 해결하나요?

만약 여러분이 동시에 두 개 이상의 코딩 에이전트 (coding agent)를 실행하려고 시도해 보았다면, 아마 저와 똑같은 벽에 부딪혔을 것입니다: 모든 것이 하나의 워크스페이스에 살고 싶어 한다는 사실 말입니다.

실제로 대화할 필요가 없는 단일 에이전트에게는 괜찮습니다. 하지만 여러 에이전트가 병렬로 작동하는 순간 구조가 무너집니다.

이 글은 제가 구축하고 있는 에이전트 오케스트레이션 (agent-orchestration) 라이브러리의 아키텍처를 다루는 일련의 문서 중 첫 번째입니다. 각 문서는 제가 직면했던 실제 문제에서 시작하며, 사용하기 쉬우면서도 실제로 유용할 만큼 충분히 깊이 있는 해결책을 찾으려 했으나 찾지 못했던 경험을 바탕으로 합니다. 이것은 완성된 정답이 아닙니다. 제가 이를 어떻게 생각하고 있는지에 대한 과정이며, 만약 여러분도 같은 문제에 부딪혔다면 어떻게 처리하고 계시는지 듣고 싶습니다.

이 첫 번째 글은 에이전트가 실행되는 환경에 관한 것입니다.

하나의 워크스페이스 (workspace)를 사용할 때의 문제는 매우 서로 다른 종류의 것들이 같은 공간에 머물도록 강제한다는 점입니다: 에이전트가 작업하는 코드, 제가 확인하고 싶은 에이전트가 생성한 결과물 (artifacts), 그리고 제가 에이전트에게 전달하는 비밀 정보 (secrets) 말입니다. 이들은 단지 같은 디렉토리 (directory)에 위치하게 되었다는 점 외에는 공통점이 전혀 없습니다.

이들이 디렉토리를 공유하기 때문에, 저는 이들을 다르게 취급할 수 없습니다. 나머지 부분은 쓰기 가능 (writable) 상태로 두면서 비밀 정보만 읽기 전용 (read only)으로 만들 수 없습니다. 또한 에이전트가 워크스페이스에 가하는 거의 모든 변경 사항(대부분 제가 원하지 않는 것들입니다)을 함께 추적하지 않고서는 결과물들을 git으로 추적할 수도 없습니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0