에이전트를 생성할 수 있는 에이전트는 선의를 가진 포크 폭탄(Fork Bomb)이다
요약
에이전트가 스스로 하위 에이전트를 생성하는 재귀적 구조가 초래할 수 있는 위험성을 경고합니다. 제어 장치 없는 에이전트 생성은 비용 폭발과 시스템 과부하를 일으키는 '선의를 가진 포크 폭탄'이 될 수 있습니다.
핵심 포인트
- 에이전트의 하위 에이전트 생성은 단순 기능이 아닌 강력한 권한임
- 제한 없는 재귀적 생성은 API 비용의 기하급수적 증가를 초래함
- 에이전트 생성의 깊이와 너비에 대한 명확한 상한선 설정이 필수적임
- 에이전트 생성 수는 입력이 아닌 오케스트레이션의 결과물로 다뤄져야 함
미리 밝혀둡니다: 저는 이 시장의 도구 중 하나인 agentproto를 만들었습니다. 뒷부분의 기본 요소(primitives)들은 실제이며 검증 가능하지만, 앞부분의 문제는 모두에게 해당되는 문제입니다. 수정 사항은 언제든 환영합니다 — 이슈(issue)를 남겨주세요.
이것은 멀티 에이전트(multi-agent)의 꿈이며, 아주 멋진 꿈입니다. 유능한 에이전트 하나에게 큰 과업을 맡기면, 혼자서 고군분투하는 대신 작은 팀을 구성합니다. 연구를 위한 서브 에이전트(sub-agent) 하나, 병렬로 구현할 에이전트 둘, 리뷰를 위한 에이전트 하나를 생성하고, 이들 모두의 결과를 기다린 뒤 병합된 결과를 당신에게 전달합니다. 오케스트레이터(orchestrator)를 관리하는 오케스트레이터인 셈입니다. 이는 작동하며, 진정으로 강력합니다.
이제 엔지니어의 편집증을 가지고 그 문장을 다시 읽어보십시오. 오케스트레이터를 관리하는 오케스트레이터. 만약 에이전트가 에이전트를 생성할 수 있다면, 그 에이전트가 생성한 에이전트 또한 에이전트를 생성할 수 있습니다. 그리고 "서브 에이전트를 생성할 수 있다"는 말 어디에도 그것이 어디서 멈춰야 하는지에 대한 언급은 없습니다.
여러분은 이미 이 형태를 알고 있습니다. 이는 Unix의 가장 오래된 장난입니다:
:(){ :|:& };: # 자기 자신의 복사본 두 개를 영원히 호출하는 함수
이것이 바로 포크 폭탄(fork bomb)입니다. 악의적인 행동은 하지 않습니다. 각 복사본은 그저 정중하게 두 개의 복사본을 더 시작할 뿐이며, 프로세스 테이블(process table)이 가득 차고 기계가 무릎을 꿇을 때까지 반복됩니다. 무제한의 생성 도구를 가진 코딩 에이전트는 중간에 언어 모델(language model)이 있고 그 밑에 당신의 API 비용이 깔려 있는 동일한 구조입니다.
다른 것은 다 잊더라도, 이 한 가지만은 기억하십시오:
생성(Spawning)은 당신이 켜는 기능이 아닙니다. 그것은 당신이 부여하는 권한(privilege)입니다. 그리고 깊이도, 너비도, 상한선도 없는 권한은 선의를 가진 포크 폭탄(fork bomb)일 뿐입니다.
당신이 인지하지 못한 채 부여하고 있는 권한
에이전트에게 spawn_sub_agent 도구를 부여한다는 것은 단순히 하나의 기능을 추가하는 것이 아닙니다. 에이전트 스스로에게 동일한 기능(capability)을 재귀적으로, 그리고 추론 과정에서 필요하다고 판단되는 만큼 계속해서 부여할 권리를 주는 것입니다. 대부분의 설정은 이러한 부여를 암묵적이고 전면적으로 만듭니다: 도구가 도구 세트에 포함되어 있다면, 생성(spawning)은 무제한입니다. 즉, 깊이와 자식 에이전트의 수가 제한 없이 늘어나며, 각 자식 에이전트는 부모만큼이나 특권을 가집니다.
이렇게 되면 비용이 빠르게 증가하는 두 가지 요인이 있는데, 둘 다 가설이 아닙니다. 첫째는 비용(cost)입니다. Anthropic 자체에서 제시한 서브 에이전트(sub-agents)에 대한 지침은 바로 이 점 때문에 탐색 과정(exploration)을 그 안에서 격리해야 한다고 말합니다. 왜냐하면 각 에이전트가 단 5줄짜리 요약본 하나를 돌려주기 위해 만 개의 토큰을 소모할 수 있기 때문입니다. 이것이 한 번일 때는 저렴하지만, 혼란스러운 부모 에이전트가 열두 개를 생성하고 그 각각의 자식까지 또 열두 개씩 생성하게 되면 엄청난 비용 폭탄(bonfire)이 됩니다. 둘째는 다중 에이전트적 반사 작용(multi-agent reflex) 자체가 보통 잘못되었다는 점입니다:
속도를 늦춰야 할 영수증. 가장 날카로운 오케스트레이션(orchestration) 작성자들은 하나의 규칙에 수렴합니다: 작업이 생성하는 에이전트의 수는 오케스트레이션 결정의 "*출력"이지, 입력이 아닙니다." 당신은 단순히 다중 에이전트 워크플로우가 가능해서 선택하는 것이 아니라, 그 작업의 형태(shape)가 그것을 요구하기 때문에 선택해야 합니다. 생성 자체가 가능하다는 이유만으로 에이전트를 생성해낸 것은 가장 중요했던 결정 자체를 건너뛴 것입니다.
따라서 문제는 다중 에이전트 오케스트레이션 자체가 아닙니다. 문제는 통제되지 않은(ungoverned) 생성입니다. LLM에게 재귀적이고 자가 복제적인 기능을 넘겨주고, 그것이 절벽 끝으로 달려가지 않으리라 믿는 것입니다. 해결책은
수동으로 동시성 제한 (Concurrency-capped by hand). 대시보드를 모니터링하며, 본인의 하네스 (harness) 코드 내에서 한 번에 실행되는 에이전트의 수를 제한합니다. 더 나은 방법이긴 하지만, 제한을 수행하는 것은 결국 당신의 주의력이며, 이는 순간적인 너비 (width)만 제한할 뿐 깊이 (depth)를 막지는 못합니다. 한계: 당신이 감시하고 있어야 함.
워크트리 격리만 수행 (Worktree-isolated only). 각 병렬 에이전트에게 개별적인 git 워크트리 (worktree)를 부여하여 서로의 파일을 짓밟지 않도록 합니다. 이는 거의 보편적으로 사용되는 격리 기법이며 좋은 방법입니다. 하지만 워크트리는 파일 시스템 (filesystem)을 격리할 뿐, 생성할 권리 (right to spawn)를 격리하지는 않습니다. 깔끔하게 격리된 에이전트라도 여전히 포크 폭탄 (fork-bomb)을 일으킬 수 있습니다. 한계: 파일은 깨끗하지만, 트리는 무제한임.
권한 게이트 적용 (Privilege-gated). 생성 (spawning)은 하나의 역할 (role)이며, 깊이와 너비로 제한됩니다. 또한 자식은 결코 부모가 가졌던 것보다 더 많은 권한을 스스로에게 부여할 수 없습니다. 이것이 바로 통제를 벗어날 수 없는 방법이며, 감시하는 대신 선언할 수 있는 방법입니다.
당신은 어디에 있습니까? 만약 당신의 솔직한 답변이 _"도구를 가진 에이전트라면 무엇이든 원하는 만큼 생성할 수 있다"_라면, 당신에게는 오케스트레이션 (orchestration)이 있는 것이 아니라, 아직 터지지 않은 포크 폭탄 (fork bomb)이 있는 것입니다. 이 글의 나머지 내용은 그것이 절대 터지지 않도록 보장하는 네 가지 경계 (bounds)에 대해 다룹니다.
경계 1: 생성은 하나의 역할이며, 대부분의 에이전트는 이 역할을 가져서는 안 된다
첫 번째 경계는 가장 직설적입니다. 트리 내의 대부분의 에이전트는 물리적으로 위임할 수 없는 _리프 (leaves)_여야 합니다. agentproto에서 이는 생성 시점의 역할 (spawn-time role)입니다.
// 이 자식은 작업을 수행하며 생성할 수 없음 — 생성 도구들이 제거됨
agent_start({
adapter: "claude-code",
...
executor는 구조적으로 리프 (leaf)입니다. agent_start 및 agent_prompt 도구가 그의 도구 세트(toolset)에서 제거되며, 그에게 오케스트레이터 (orchestrator)가 되라고 요청해도 단순히 무시됩니다. 아무리 영리한 프롬프팅 (prompting)을 사용하더라도 생성 능력을 되찾을 수는 없습니다. 왜냐하면 해당 기능(capability) 자체가 존재하지 않기 때문입니다. supervisor는 위임할 수 있습니다. 이 단 하나의 차이가 "모든 에이전트는 잠재적인 부모이다"라는 상황을 "생성은 트리의 대부분이 보유하지 않은 명명된 특권이다"라는 상황으로 바꿉니다.
이는 OWASP Agentic Top 10이 최소 권한 (least agency) 항목 아래에 기록한 것과 동일한 교훈입니다. 에이전트에게는 오직 해당 작업에 필요한 자율성만을 부여하십시오. 함수를 구현하는 작업자에게 군대를 일으킬 권한까지 필요하지는 않습니다. 그 권한을 박탈하면, 통제 불능의 트리(runaway trees)라는 클래스 전체를 재현할 수 없게 됩니다.
제약 2: 깊이 (depth) — 재귀에는 더 이상 파고 내려갈 수 없는 바닥이 있다
리프(Leaves) 노드는 생성할 수 없지만, 관리자(supervisors)는 생성할 수 있으며, 관리자의 체인은 여전히 재귀(recursion)입니다. 따라서 두 번째 제약은 트리가 얼마나 깊게 내려갈 수 있는지를 제한하며, 이 제한을 단방향으로 만듭니다.
agent_start({
role: "supervisor",
orchestrator: { maxDepth: 3, maxChildren: 8 }, // 이 서브트리, 제한됨
...
maxDepth는 기본값이 3이며, 최대 상한선(hard ceiling)은 8입니다. 이를 초과하는 생성 요청은 대기열에 들어가는 것이 아니라 즉시 거부됩니다. 여기서 핵심적인 세부 사항은 방향성입니다. 재귀적 생성은 상속된 제한치를 오직 낮출 수만 있으며, 결코 높일 수 없습니다. 깊이 3에 있는 부모는 자식에게 2라는 예산을 넘겨줄 수 있지만, 그 자식은 자신의 자식에게 5라는 예산을 넘겨줄 수 없습니다. 포크 폭탄(fork bomb)의 핵심 트릭은 무제한 재귀입니다. 단조 감소(monotonically-shrinking)하는 예산으로 깊이를 제한하면 이 트릭을 제거할 수 있습니다. 트리에는 바닥이 있으며, 트리 내부의 그 무엇도 그 바닥을 뚫고 내려갈 수 없습니다.
제약 3: 너비 (breadth) — 단일 에이전트가 군집(swarm)으로 퍼져나가지 못하게 한다
깊이가 트리가 무한히 아래로 내려가는 것을 막는다면, 너비는 단일 노드가 옆으로 폭발하는 것을 막습니다. maxChildren은 부모당 동시에 살아있는 서브 에이전트(sub-agents)의 기본값을 8로 설정하며, 깊이와 마찬가지로 재귀적 생성은 상속된 할당량을 오직 낮출 수만 있고 결코 높일 수 없습니다.
이것은 포크 폭탄의 :|: 부분, 즉 하나의 프로세스가 둘이 되고 다시 넷이 되는 현상에 대한 직접적인 해독제입니다. 분기 계수(branching factor)를 제한하면 기하급수적인 폭발이 시작될 수 없습니다. 깊이 제약과 결합하면, 최악의 경우에도 에이전트의 수는 무한한 것이 아니라 알려진 수(maxChildren ^ maxDepth, 하강할수록 감소함)가 됩니다. 여러분은 생성 설정을 보고 혼란스러운 실행이 얼마나 심각해질지 정확히 말할 수 있습니다. 무제한의 세계에서는 불가능한 문장입니다.
비용은 자체적인 하드 스탑(hard stop)을 가집니다. 트리 형태와 관계없이, 각 세션은 누적 달러 상한선인
maxCostUsd를 소모합니다. 이 상한선을 초과하면 다음 턴이 끝날 때 세션이 중단됩니다. 깊이(Depth)와 너비(Breadth)가 *개수(count)*를 제한한다면, 이것은 *지출(spend)*을 직접적으로 제한합니다. 따라서 비용이 많이 드는 턴들로 구성된 합법적인 크기의 트리라 할지라도, 넘을 수 없는 한계치가 존재합니다.
제약 4: 자식은 부모보다 더 높은 권한을 가질 수 없다
마지막 제약은 미묘하지만, 앞선 세 가지 제약을 신뢰할 수 있게 만드는 핵심 요소입니다. 자식은 자신을 만든 부모보다 결코 더 강력해질 수 없습니다. 데몬(daemon)은 권한 격자(privilege lattice)를 강제합니다. 오케스트레이터(orchestrator)를 통해 생성된 스폰(spawn)은 호출자의 수준과 같거나 그보다 낮은 역할만 생성할 수 있으며, 결코 더 높은 권한을 가진 것을 생성할 수 없습니다. 또한 명시적인 tools 허용 목록(allowlist)은 도구 세트를 좁힐 수만 있을 뿐, 결코 확장할 수 없습니다. 자식은 부모가 가지지 못한 범위를 스스로에게 부여할 수 없습니다.
부모의 관점 또한 제한됩니다. 데몬은 **자식별 스코프 토큰(per-child scope-token)**을 발행하고, 해당 자식의 세션에 스코프가 지정된 서브 게이트웨이(sub-gateway)를 주입하며, **세션이 종료되면 토큰을 취소(revoke)**합니다. 부모의 session_tree에는 자신의 서브트리만 표시됩니다. 부모는 자신의 브랜치 밖에 있는 세션을 볼 수도, 프롬프트를 보낼 수도, 종료시킬 수도 없습니다. 셸(Shell), 파일 시스템(filesystem), 원격(remote), 임포트(import) 도구들은 오케스트레이터 자식에게 절대로 전달되지 않습니다. 따라서 트리는 단순히 크기만 제한되는 것이 아니라, 모든 노드가 점점 좁아지는 상자 안으로 역량(capability)이 제한되며, 작업이 완료되면 그 상자들은 증발합니다.
네 가지 제약, 하나의 속성: 여러분은 에이전트에게 팀을 구축하고 실행할 수 있는 진정으로 강력한 능력을 부여하면서도, 실행 전 — 최대 깊이, 최대 너비, 최대 지출, 그리고 권한의 상한선을 명시할 수 있습니다. 역량은 유지되지만, 포크 폭탄(fork bomb)은 구현 불가능한 상태가 됩니다.
정직한 경계면 (이것을 구축하며 제가 겪은 문제들)
실제 인프라에는 날카로운 모서리(sharp corners)가 존재합니다. 제가 실제 운영 중에 맞닥뜨린 네 가지는 다음과 같습니다:
- 부모는 반드시 Claude Code여야 합니다. hermes 부모는 주입된 오케스트레이션 게이트웨이 (orchestration gateway)를 조용히 무시합니다. 즉, spawn 도구들을 마운트(mount)하지 않으므로 아무 일도 일어나지 않습니다. 자식은 어떤 어댑터(trivial한 작업을 위한 저렴한 Haiku 등)든 될 수 있지만, 오케스트레이션을 수행하는 _부모_는 반드시 Claude Code여야 합니다. 만약 부모가 "오케스트레이션 도구가 마운트되지 않았습니다"라고 보고한다면, 그것이 바로 징후입니다.
- Fan-in은 빠른 자식을 앞지릅니다. 사소한 자식은 부모가 대기(wait)를 연결하기도 전에 자신의 차례를 끝낼 수 있습니다.
turn-end는 일시적인 이벤트이므로, 대기 시간은 이미 성공한 자식에 대해 타임아웃(timeout)이 발생합니다. 부모에게 폴백(fallback) 방법을 가르치세요: 만약 대기가 타임아웃되면, 자식의 출력을 읽어 확인하거나, spawn 하기 전에 이벤트 커서(event cursor)를 가져오도록 해야 합니다. - 부모를 종료해도 자식은 종료되지 않습니다. scope-token은 부모 종료 시 취소되지만, 자식 프로세스들은 그 자체로 완전한 세션입니다. 부모와 각각의 자식을 모두 종료해야 합니다 (자식들의 ID는
session_tree에 있습니다). 그렇지 않으면 실행 중인 에이전트가 누수(leak)됩니다. - 모든 자식에게
cwd는 실제 호스트 경로여야 합니다. 데몬(daemon)은 사용자의 머신에서 실행되며, 해결 가능한 작업 디렉토리(working directory)가 없는 자식은 그냥 에러를 발생시킵니다.
그리고 공정한 비교를 해보겠습니다. 벤더(Vendors)들은 서브 에이전트(sub-agents)를 출시합니다. Claude Code의 Agent Teams는 당신을 위해 팀을 실행해 줄 것이며, 한 벤더의 스택 내부에서는 이 모든 연결 작업이 필요 없이 매끄럽게 작동합니다. 하지만 내장된 팀이 제공하지 않는 두 가지가 있습니다: 첫째, spawn 트리(spawn tree)가 해당 벤더 내부에 머무릅니다 (당신의 저렴한 오픈 모델 실행기들은 초대받지 못합니다). 둘째, 거버넌스(governance)가 당신이 선언하고 소유하는 깊이/너비/역할/비용 제한이 아니라 벤더의 것입니다. 만약 당신이 완전히 한 벤더 내에서만 생활하고 혼합된 플릿(mixed fleet)을 원하지 않는다면, 내장된 기능을 사용하세요. 그것이 일이 더 적습니다. 하지만 여러 벤더에 걸쳐 spawn 하면서도 밤에 잠을 편히 자고 싶다면, 그 제한(bounds)은 당신의 것이어야 합니다.
팀을 부여하세요. 폭발 반경(blast radius)을 유한하게 유지하세요.
에이전트의 생성을 막으려는 본능은 잘못된 교훈입니다. 팀을 생성하는 것이야말로 에이전트를 오케스트레이션(orchestrating)할 가치가 있게 만드는 바로 그 레버리지(leverage)이기 때문입니다. 올바른 교훈은 확률적 추론기(probabilistic reasoner)에게 부여된 자기 복제 능력(self-replicating capability)에는 다른 모든 강력한 능력들이 필요로 하는 것과 동일한 것, 즉 스스로 다시 쓸 수 없는 제한(limits)이 필요하다는 점입니다.
그러므로 권한을 의도적으로 부여하십시오. 대부분의 에이전트를 위임할 수 없는 잎(leaves) 노드로 만드십시오. 재귀(recursion)가 바닥을 가질 수 있도록 깊이(depth)를 제한하십시오. 어떤 노드도 군집(swarm)으로 확산되지 않도록 너비(breadth)를 제한하십시오. 자식은 항상 부모보다 작도록 비용(spend)과 권한(privilege)을 제한하십시오. 그렇게 하면 "에이전트를 생성하는 에이전트"는 포크 폭탄(fork bomb)이기를 멈추고, 당신이 실제로 원했던 것, 즉 십장(foreman)이 있는 팀이자 실수로 도시 전체를 고용할 수는 없는 십장이 있는 팀이 될 것입니다.
에이전트가 자신의 팀을 구축하게 두십시오. 다만 트리의 바닥이 있고, 가지에 제한이 있으며, 작업이 끝나면 전체가 종료되도록 확실히 하십시오.
만약 당신의 오케스트레이터가 생성을 제한하는 더 깔끔한 방법을 알고 있거나, 이 네 가지 제한으로도 잡아내지 못한 폭주하는 트리(runaway tree)를 발견했다면, 어디인지 알려주십시오. 제가 그 부분을 수정하겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기