Codex · 확장점: Subagents (병렬 멀티에이전트 spawn)
Codex · 확장점: Subagents (병렬 멀티에이전트 spawn)
한 줄 요약
Codex가 큰 작업을 직접 다 하지 않고 임시 비서(서브에이전트) 를 여러 명 동시에 불러 일을 나눠주는 기능이다. 왜 배우나 → 메인 대화창을 잡음으로 더럽히지 않으면서 큰 작업을 병렬로 빠르게 처리하는 “지휘자형” 작업 방식을 이해하기 위해서다.
그림
flowchart TD
U["사용자: 병렬로 리뷰해줘"] --> M["루트 에이전트<br/>지휘자 orchestrator"]
M -->|spawn_agent 3번| D{"관문 검사<br/>깊이 한도 OK?<br/>동시 슬롯 남았나?"}
D -->|초과| R["거부 메시지 반환<br/>직접 처리해라"]
D -->|통과| S1["Euclid · explorer<br/>별도 대화 스레드"]
D -->|통과| S2["Newton · worker<br/>별도 대화 스레드"]
D -->|통과| S3["Gauss · default<br/>별도 대화 스레드"]
S1 -.로그·잡음은 여기 가둠.-> S1
S1 -->|요약 한 장 반환| M
S2 -->|요약 한 장 반환| M
S3 -->|요약 한 장 반환| M
M --> O["요약만 취합해<br/>최종 답 작성"]
쉽게 풀기
비유: 사무실의 팀장과 임시 직원들.
당신이 팀장(루트 에이전트)이라고 하자. “이 코드를 보안·테스트·유지보수 관점에서 한꺼번에 봐줘” 같은 큰 일이 들어왔다. 혼자 다 하면 오래 걸리고, 책상(대화창) 위에 자료가 산더미처럼 쌓여 정작 중요한 결론이 묻힌다. 그래서 팀장은 임시 직원 3명을 부른다.
-
호출(spawn) — 팀장이 “너는 보안만 봐, 너는 테스트만 봐”라고 각자에게 임무 한 줄을 주며 직원을 부른다. Codex에서는 이게
spawn_agent도구 호출이다. -
별도 책상(격리) — 각 직원은 자기 책상(별도 대화 스레드) 에서 일한다. 코드를 뒤지며 쏟아지는 로그, 에러 메시지, 명령 출력 같은 잡음이 전부 그 직원 책상에만 쌓인다. 팀장 책상은 깨끗하게 유지된다. 이 잡음이 메인 대화에 쌓여 성능이 떨어지는 현상을 영어로 context rot(컨텍스트 부패) 라고 부르는데, 격리가 바로 이걸 막는다.
-
이름표(닉네임) — 직원이 누가 누군지 헷갈리지 않게 Euclid, Newton, Gauss 같은 과학자 이름이 자동으로 붙는다.
-
요약 보고(회수) — 직원은 일을 마치면 자료 더미가 아니라 요약 한 장만 들고 팀장에게 돌아온다. 팀장은 그 요약들만 모아 최종 답을 만든다.
중요한 두 가지 안전장치.
- 직원이 또 직원을 부르고, 그 직원이 또 부르고… 무한정 늘어나면 회사가 마비된다. 그래서 깊이 제한(몇 단계까지 부를 수 있나)과 동시 인원 제한(한 번에 몇 명까지)이 걸려 있다.
- 비서는 자동으로 생기지 않는다. 사용자가 “병렬로 돌려”, “한 관점당 한 명씩 띄워” 라고 명시적으로 말할 때만 모델이 비서를 부른다.
핵심 정리
| 도구 | 한 줄 역할 |
|---|---|
spawn_agent | 비서 1명 소환, agent id 반환 |
send_input / send_message | 기존 비서에게 메시지 전달 |
followup_task | 비루트 비서에 후속 작업 전달 |
wait_agent | 비서가 끝날 때까지 대기 |
list_agents | 살아있는 비서 목록 |
interrupt_agent | 현재 작업 중단(비서는 살아있음) |
close_agent | 비서+자식 종료, 동시성 슬롯 반납 |
resume_agent | 닫힌 비서를 id로 재개 |
[!note]
spawn_agent입력 파라미터 (V2) 근거:core/src/tools/handlers/multi_agents_spec.rs의spawn_agent_common_properties_v2().
message(string, 사실상 필수): 새 비서에게 줄 최초 작업 지시 한 줄.agent_type(string, 선택): 역할 이름. 생략 시default. 빌트인default/explorer/worker+ 사용자 정의 role.fork_turns(string, 선택): 부모 대화 이력을 몇 턴 물려줄지. 기본all, 그 외none/"3"같은 정수 문자열.model(string, 선택): 비서 모델 오버라이드. 생략 시 부모 모델 상속.reasoning_effort(string, 선택): 추론 강도 오버라이드. 생략 시 부모 상속.service_tier(string, 선택): 서비스 티어 오버라이드.V1 도구(
spawn_agent_common_properties_v1)는message/items/agent_type/model/reasoning_effort를 노출하고,hide_spawn_agent_metadata_options()가 켜지면agent_type/model/reasoning_effort/service_tier가 스키마에서 빠진다(모델이 못 건드리게).
[!note] 안전장치 4종
- 재귀 방지:
agent_max_depth로 자식 깊이 제한. 깊이 = 부모 깊이+1, 루트=0.- 동시성 제한:
AgentRegistry.total_count(AtomicUsize) +try_increment_spawned(max_threads)CAS 루프. 루트는 카운트 제외(AgentPath::is_root).- 고아 정리:
SpawnReservation의Drop이 commit 안 된 예약을 자동 롤백.- 컨텍스트 격리: 각 비서 = 독립 ThreadId + 독립 대화 이력(
fork_turns로 일부만 상속).
서브에이전트의 “정체성”은 SessionSource::SubAgent(ThreadSpawn{...})에 담긴다. 주요 필드: parent_thread_id(나를 부른 부모), depth(중첩 깊이, 루트=0), agent_nickname(Euclid 등), agent_role(explorer/worker/default 등).
실제 예시
1) 사용자가 만드는 역할 정의 (config 레이어 .toml)
# ~/.codex/agents/reviewer.toml (사용자 정의 role 예시)
model = "gpt-5.5"
model_reasoning_effort = "high"
developer_instructions = """You are a code reviewer.
Focus on bugs, behavioral regressions, and missing tests.
Return findings ordered by severity with file:line references. Return ONLY a summary."""
# config에서 role 등록 — 이 description이 spawn_agent 도구 설명에 들어감
[agent_roles.reviewer]
description = "Use for severity-ordered code review. Returns summary only."
config_file = "agents/reviewer.toml"
2) 모델이 실제로 호출하는 spawn 형태 (도구 인자 JSON)
{
"name": "spawn_agent",
"arguments": {
"message": "Review this branch for security risks. Return a summary with file refs.",
"agent_type": "reviewer",
"fork_turns": "none",
"reasoning_effort": "high"
}
}
3) 빌트인 role 등록 방식 (Rust 정적 맵)
// core/src/agent/role.rs (mod built_in::configs)
"explorer".to_string(),
AgentRoleConfig {
description: Some(r#"Use `explorer` for specific codebase questions.
Explorers are fast and authoritative.
They must be used to ask specific, well-scoped questions on the codebase.
Rules:
- ... You are encouraged to spawn up multiple explorers in parallel when you have
multiple distinct questions ... This parallelism is a key advantage of delegation ..."#.to_string()),
config_file: Some("explorer.toml".to_string().parse().unwrap_or_default()),
nickname_candidates: None,
}
explorer.toml은 빈 파일(0바이트)이다. explorer는 등록만 되고 별도 오버라이드 없이 기본 설정으로 돈다. 즉 빌트인 role의 “성격”은role.rs의description텍스트(도구 설명문으로 모델에 주입)가 담당하고,.toml은 model/timeout/developer_instructions 같은 잠긴 설정 레이어를 담당한다.
4) 비서가 끝나면 부모에게 돌아오는 요약 (notification)
// core/src/context/subagent_notification.rs
fn type_markers() -> (&'static str, &'static str) {
("<subagent_notification>", "</subagent_notification>")
}
fn body(&self) -> String {
format!("\n{}\n", serde_json::json!({
"agent_path": &self.agent_reference,
"status": &self.status, // Completed(last_agent_message) 등
}))
}
status는 status.rs::agent_status_from_event()가 이벤트에서 파생한다: TurnComplete → Completed(last_agent_message), TurnAborted(Interrupted/BudgetLimited) → Interrupted, Error → Errored. 완료 메시지(요약)만 메인으로 돌아온다(원시 로그 금지).
5) 닉네임 풀
과학자/철학자 이름 100개(Euclid, Archimedes, … Newton, Einstein, Turing, … Erdos, Jason)가 agent_names.txt에 있다. registry.rs의 reserve_agent_nickname()이 안 쓰는 이름을 랜덤으로 뽑아 주고, 풀이 소진되면 전체를 비우고 nickname_reset_count를 올려 "Newton the 2nd", "Euclid the 3rd"처럼 서수 접미사를 붙인다.
6) spawn 실행 흐름 (핸들러 내부)
- 모델이
spawn_agent(message, agent_type=...)호출. - 핸들러가 인자 파싱 →
child_depth = next_thread_spawn_depth(...)(부모 depth+1). - 깊이 제한 검사(V1): 초과 시 모델에
"Agent depth limit reached. Solve the task yourself."반환 → 무한 재귀 차단. - 동시성 슬롯 예약:
reserve_spawn_slot(max_threads)가 원자적 카운터 증가, 초과면AgentLimitReached. - role 적용:
apply_role_to_config()가 role.toml을 우선순위 레이어로 끼워 넣되 부모의model_provider/service_tier는 sticky 유지. - 닉네임 예약 +
SessionSource::SubAgent(ThreadSpawn{...})구성 → 새 스레드 spawn. - 비서는 자기 별도 스레드에서 일하고 잡음은 거기 머문다.
- 부모가
wait_agent로 기다리거나, 종결 시<subagent_notification>이 부모에 user 메시지로 주입됨.
직접 만들 때 체크리스트
- 도구 입력에
message(필수), 선택agent_type/fork_turns/model/reasoning_effort/service_tier제공. - role description에 “언제 쓰는지 + 병렬 권장 + 요약만 반환” 명시.
- role
.toml에 고정할 model/effort/developer_instructions 잠금(설명문에 “cannot be changed” 자동 표기됨). - 깊이 제한(
agent_max_depth) + 동시성 한도(max_threads) 설정. - spawn 시 부모 depth+1 계산, 한도 초과면 모델에 거부 메시지.
- 닉네임 풀 준비, 소진 시 reset+서수 접미사.
- 종결 시 부모에 요약만 주입(원시 로그 금지).
- 루트 프롬프트(orchestrator)에 “spawn 후 결과 대기, 직접 일하지 말고 coordinate” 지침 삽입.
요약 & 셀프체크
3줄 요약:
- 서브에이전트는 큰 작업을 임시 비서 여러 명에게 병렬로 나눠주는 기능이고, 핵심 가치는 잡음 격리(context rot 방지) 다.
- 비서는 자동 생성되지 않고 사용자가 명시 요청할 때만 모델이
spawn_agent로 부르며, 각자 별도 스레드에서 일하고 요약만 돌려준다. - 무한 증식을 막기 위해 깊이 제한·동시성 한도·고아 자동 정리·컨텍스트 격리라는 네 가지 안전장치가 있다.
셀프체크:
- 서브에이전트의 로그·에러가 메인 대화창을 더럽히지 않는 이유는? (힌트: 별도 ThreadId)
- 비서가 또 비서를 무한히 부르는 것을 막는 장치 두 개는 무엇인가?
- 부모 에이전트가 비서로부터 받는 것은 “원시 로그”인가 “요약”인가, 그리고 어느 코드가 그걸 정하는가?
연결
근거 파일(소스):
/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/mod.rs/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/registry.rs/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/agent_names.txt/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/agent_resolver.rs/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/role.rs/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/status.rs/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/control.rs(prepare_thread_spawn, thread_spawn_depth)/home/seunghyeong/harness-work/codex/codex-rs/core/src/agent/builtins/awaiter.toml,explorer.toml(0바이트)/home/seunghyeong/harness-work/codex/codex-rs/core/src/session/multi_agents.rs(MultiAgentVersion::V2 usage hint)/home/seunghyeong/harness-work/codex/codex-rs/core/src/context/subagent_notification.rs/home/seunghyeong/harness-work/codex/codex-rs/core/src/tools/handlers/multi_agents_spec.rs(spawn/wait/send/list 도구 스펙)/home/seunghyeong/harness-work/codex/codex-rs/core/src/tools/handlers/multi_agents/spawn.rs(depth 제한 enforce),multi_agents_v2/spawn.rs/home/seunghyeong/harness-work/codex/codex-rs/core/templates/agents/orchestrator.md/mnt/d/6study/10_프레임워크분석/_원문아카이브/codex/32_subagents.md
[!tip] Codex 교차검증 (원본 분석 보존)
- 빌트인 awaiter role (
core/src/agent/builtins/awaiter.toml): 명령/작업 완료를 기다렸다가 끝났을 때만 상태를 보고하는 전용 비서. 폴링 시 타임아웃을 지수적으로 늘리도록 지시됨(model_reasoning_effort = "low",background_terminal_max_timeout = 3600000).- 도구 스펙 생성: spawn 도구의 설명문은
role.rs::spawn_tool_spec::build()가 빌트인+사용자 role 설명을 합쳐 만든다. 모델은 프롬프트에서 “explorer는 코드베이스 질문에 빠르고 권위있다, 병렬로 여러 개 띄워라” 가이드를 읽고 소비한다.- 트리거: 공식 문서(
32_subagents.md)대로 사용자가 “spawn two agents”, “delegate in parallel”, “use one agent per point”처럼 명시 요청해야 모델이spawn_agent를 호출한다(자동 생성 아님).- 루트 강제 프롬프트:
orchestrator.md가 행동을 강제한다 — “Prefer multiple sub-agents to parallelize your work”, “If sub-agents are running, wait for them before yielding”, “your only role becomes to coordinate them. Do not perform the actual work while they are working”, “process them in parallel by spawning one agent per step”.