OpenAI Codex · 멀티 에이전트 그래프

멀티 Agent는 지속해야 하는 그래프

내보내는 것은 노드이고, 변은 태어날 때부터 Open입니다. 편지는 먼저 큐에 들어가고, followup이 깨웁니다. 끄는 것은 변이고, 역사는 남아요.

강의 목표읽고 나면 세 가지를 말할 수 있어요. 자식 agent는 그래프 위의 노드이고 변은 Open과 Closed뿐이며, send_message는 큐에만 넣고 followup_task가 깨우며, 종료는 런타임을 내리고 변을 닫아야 그래프에서 이름이 지워집니다. 내일도 말을 이어갈 수 있는지는 변을 먼저 보세요.
먼저 해보기 · 탐색자를 파생하기
/root에서 Hypatia를 보내고, 편지가 어떻게 가고 결과가 어떻게 돌아오는지 보세요
마무리
종료는 런타임만 내립니다. 변은 여전히 Open이에요. 변을 닫아야 Closed가 됩니다. 바꿔 다시 재생하면 재시작 뒤 명단이 달라집니다.
루트 세션 /root 한가함, 아이를 보낼 수 있음
아직 안 보냄 spawn 대기 그래프에 이 노드가 아직 없음
SQLite 변 카드
아직 thread_spawn_edges 기록이 없습니다.
우편함과 레지스트리
MESSAGE FOLLOWUP RESULT
레지스트리는 비어 있습니다. 복구는 Open 변만 따라 신원을 다시 붙입니다.
논리 궤적 · 애니메이션 각 단계가 소스의 어느 구간에 대응하는지
  1. 다음 층 깊이를 계산하고 ThreadSpawn을 씀registry.rs L87
  2. task_name으로 절대 경로 /root/explore_auth를 만듦multi_agents_common.rs L117
  3. 과학자 명단에서 별명 Hypatia를 뽑음control/spawn.rs L32
  4. 임시가 아닌 세션은 바로 Open 변을 upsertcontrol.rs L776
  5. send_message는 QueueOnly로 싸고 큐에만 넣음message_tool.rs L103
  6. 우편함은 세션급 큐, 넣은 뒤 Mailbox 활동을 냄input_queue.rs L127
  7. followup_task가 trigger_turn을 켜야 깨움handlers.rs L98
  8. 자식 turn이 끝나면 부모에게 Result를 보내고 깨우지 않음session/mod.rs L1977
  9. 종료는 변을 안 바꿈. 변 닫기는 대상 자신의 입변만 Closedlegacy.rs L6
  10. 재시작은 Open 후손의 신원만 레지스트리에 다시 넣음control/spawn.rs L158
재생을 눌러, 탐색자가 그래프에 어떻게 자라고, 편지가 어떻게 가고, 결과가 어떻게 돌아오는지 보세요.
그래프가 런타임보다 먼저노드는 태어날 때부터 경로, 별명, Open 변을 갖습니다. 런타임은 내릴 수 있고, 변은 남으며, 역사는 rollout에 남아요.
편지와 깨움은 다른 일MESSAGE는 큐에만 들어갑니다. FOLLOWUP이 일을 시작해요. RESULT는 부모 우편함으로 날아가고, 기본으로 현재 턴을 빼앗지 않습니다.
마무리가 내일이 남을지를 정함위의 마무리를 바꿔 다시 재생해 보세요. 재시작 뒤 레지스트리가 이 아이를 아직 아는지.
수업용 스케치: 별명은 Hypatia, 경로는 /root/explore_auth로 고정해 그래프, 우편함, 변 상태를 보여 줍니다. 논리 궤적 오른쪽 행 번호는 openai/codex 저장소 commit 4f39251a01에 대응해요.
아이디어 1 · 부모–자식 관계를 상태가 있는 변으로
어떤 문제를 푸는가

루트 세션이 탐색자를 보내 auth 모듈을 보게 합니다. 모델이 spawn_agent를 호출하면 도구가 작업 이름 /root/explore_auth와 별명 Hypatia를 돌려줘요. 몇 분 뒤 wait_agent를 누르면 우편함에 FINAL_ANSWER가 누워 있습니다.

다음 날 같은 thread를 엽니다. 자식 세션의 런타임은 이미 내려가 있어요. 시스템이 어제 호출이 한 번 있었다는 것만 기억하면 이 경로를 찾지 못합니다. send_message를 다시 내면 제어면이 live agent path not found를 신고해요.

아이디어는 무엇인가

Codex는 부모–자식을 방향 변으로 만듭니다. 변은 값이 둘뿐이에요. Open은 열린 spawned agent로 복구할 수 있다는 뜻입니다. Closed는 그래프 시선에서 이미 닫혔다는 뜻입니다. 직렬화는 openclosed예요.

출처:codex-rs/agent-graph-store/src/types.rs 4–12행

그래프는 SQLite의 thread_spawn_edges 표에 있습니다. child_thread_id가 기본 키라 아이 하나는 부모 둘에 걸릴 수 없어요. 같은 아이를 다시 spawn하면 부모와 상태가 새 값으로 덮입니다. 세션 본문은 이 표에 안 들어갑니다. 자식 agent의 모델 컨텍스트는 자기 rollout을 걸어요. 그래프는 누가 누구를 낳았는지, 이 변이 지금 열려 있는지 닫혀 있는지만 답합니다.

출처:codex-rs/state/migrations/0021_thread_spawn_edges.sql 1–8행

임시가 아닌 세션은 스레드가 만들어진 뒤 바로 Open 변을 upsert합니다. 쓰기 실패는 warn만 쳐요. 자식 thread는 이미 돌고 있고, 그래프는 나중에 메울 수 있습니다. 메움은 ON CONFLICT DO NOTHING이라 이미 Closed인 변을 되돌리지 않습니다.

출처:codex-rs/core/src/agent/control.rs 767–780행

후손을 나열할 때 필터는 걷는 모든 변에 작용합니다. Some(Open)은 Open만 따라가요. 부모 변이 이미 Closed인 서브트리는 손자 변이 아직 Open이어도 나열되지 않습니다.

출처:codex-rs/agent-graph-store/src/store.rs 49–54행

경로가 주소 키이고, 별명은 사람이 알아보는 이름입니다. 루트는 /root예요. 상대 이름은 현재 경로 뒤에 붙습니다. ...는 거절되므로 상대 경로로 형제에게 기어갈 수 없어요. 역할은 능력을 줄일 수 있지만 부모 세션의 권위를 대신할 수는 없습니다. 내장 살아 있는 역할은 default, explorer, worker입니다. explorer.toml은 빈 파일이에요.

출처:codex-rs/core/src/agent/role.rs 1–4행

spawn_agent 새 child thread 경로 + 별명 Open 변 upsert SQLite child rollout JSONL 누가 누구를 낳았나, 열림인가 닫힘인가 세션 본문은 변 표에 안 들어감
spawn 한 뒤: 신원은 레지스트리, 변은 SQLite, 본문은 rollout.
왜 오래가는가

재시작 뒤에는 물을 수 있어야 합니다. 이 아이가 아직 살아 있나. spawn 사건 한 번만 기억해서는 답할 수 없어요. Open만 복구 목록에 들어갑니다. Closed는 서브트리 순회에서 사라집니다. child_id를 기본 키로 두면 그래프는 나무로 남고, 순회는 깊이 BFS를 할 수 있어요. 다른 언어로 다시 써도 최소 형태는 여전히 세 열짜리 표입니다. parent, child, status.

아이디어 2 · 통신과 깨움을 나누기
어떤 문제를 푸는가

자식 agent가 끝나면 FINAL_ANSWER를 돌려보내야 합니다. 이 편지가 깨움을 스스로 달고 있으면, 부모가 사용자가 보는 최종 답을 쓰는 중에 자식 결과가 억지로 한 턴을 엽니다. 사용자는 말의 반쪽을 보고, 갑자기 끼어든 완료 알림을 한 장 더 봐요.

아이디어는 무엇인가

통신 종류에는 태그 네 개입니다. Spawn, Message, Followup, Result. OTEL이 쓰는 이름이에요. 프로토콜 쪽에는 InterAgentCommunication 하나뿐이고, trigger_turn으로 깨울지를 가릅니다.

send_message는 QueueOnly라 큐에만 넣습니다. followup_task는 TriggerTurn이라 그때 깨워요. 빈 메시지는 바로 거절합니다. followup_task는 루트 노드를 칠 수 없습니다.

출처:codex-rs/core/src/tools/handlers/multi_agents_v2/message_tool.rs 11–24행

처리 함수는 먼저 큐에 넣고, 일을 시작할지 나중에 정합니다. trigger_turn이 거짓이면 편지는 계속 누워 있어요. 참이거나 세션에 끝나지 않은 durable sleep이 있을 때만 일을 시작합니다. V2의 완료 알림은 Result이고 trigger_turn은 거짓입니다. 부모가 말하는 중이면 이 편지는 우편함 위상으로 줄을 서요.

출처:codex-rs/core/src/session/handlers.rs 89–99행

우편함은 세션급 큐입니다. 사용자 끼어들기는 pending_input으로, 자식 메일은 mailbox_pending_mails로 — 칸이 둘이에요. 형제는 절대 경로, 예를 들어 /root/worker_b만 쓰면 서로 보낼 수 있습니다. 상대 이름 worker_b는 자기 뒤에 붙어 자기 아이가 됩니다.

출처:codex-rs/core/src/session/input_queue.rs 76–80행

send_message QueueOnly followup_task TriggerTurn 자식 turn이 끝남 Result 부모 또는 자식 우편함 먼저 큐에 넣고 trigger_turn을 봄 누워 다음 턴을 기다림 MESSAGE / RESULT 일 시작, maybe_start_turn FOLLOWUP만 여기를 감
세 편지 모두 우편함에 들어갑니다. followup만 trigger_turn을 열고, 완료 알림은 현재 턴을 빼앗지 않아요.
왜 오래가는가

깨움권은 드뭅니다. 한 턴을 열 수 있는 자는 함부로 열면 안 돼요. 배달과 일 시작을 나누면, 완료 알림은 기본으로 깨우지 않고 깨움권은 followup_task와 사용자에게 남습니다. 다른 메시지 버스에도 이 불리언이 씁니다. wake인가, 큐에만 넣는가.

아이디어 3 · 변 닫기와 종료는 다른 일
어떤 문제를 푸는가

V2 상주 자리가 가득 차면 LRU로 아이 하나를 내립니다. 런타임을 내리며 변까지 Closed로 찍으면 그 아이는 Open 서브트리에서 사라져요. 다음 복구가 찾지 못합니다. 사용자가 닫은 적이 없는데 시스템이 이름을 지운 것입니다.

아이디어는 무엇인가

shutdown_live_agent는 살아 있는 agent를 끄고, rollout을 쓸며, Shutdown을 보내고, 관리자에서 thread를 떼어냅니다. 변은 여전히 Open이에요. 다음 복구도 살아 있는 서브트리 구성원으로 봅니다.

출처:codex-rs/core/src/agent/control/legacy.rs 6–8행

close_agent는 먼저 대상 자신의 입변을 Closed로 찍고 종료합니다. 후손의 변은 여기서 Closed로 찍히지 않아요. 부모 turn이 정상 끝나면 TurnComplete를 걷고 close_agent를 호출하지 않습니다. 아이는 계속 돌고, 변은 Open을 유지해요.

출처:codex-rs/core/src/agent/control/legacy.rs 48–58행

V2 복구는 두 걸음입니다. 먼저 Open 후손의 신원을 레지스트리에 넣고 런타임은 다시 열지 않아요. 누군가 정말 send_messagefollowup_task를 할 때서야 rollout으로 thread를 다시 겁니다.

출처:codex-rs/core/src/agent/control/spawn.rs 144–162행

변은 Open 종료 변 닫기 변은 Open, 런타임은 내림 변이 Closed가 됨 복구 때 신원을 레지스트리에 복구 때 이 변이 걸러짐
런타임을 내리는 것은 그래프에서 이름을 지우는 것이 아닙니다. close만 status를 바꿉니다.
끄는 것은 변이고, 역사는 남습니다.
왜 오래가는가

런타임이 살아 있는 것과 그래프에서 이름이 지워지는 것은 독립된 일입니다. 상주 LRU, 프로세스 재시작, 사용자가 창을 닫는 것 — 모두 런타임을 내릴 수 있어요. 오케스트레이터가 분명히 close해야 Closed로 씁니다. 복구는 Open 변을 따라갑니다. 이 분개는 Rust에 의존하지 않아요.

가로 비교 · 자식 agent는 무엇으로 추상화해야 하나

DSH: 이음새가 먼저, 그래프는 나열 결과

DSH는 자식 agent를 갈아 끼울 수 있는 provider 이음새로 만듭니다. SubagentProvidername, capabilities, inheritsParentContext, start를 달고 있어요. 프로세스 안 fork, Claude Code, Codex, ACP — 같은 인터페이스 위의 다른 구현입니다.

아이와 후손도 나열할 수 있고, 살아 있는 session store와 선택적 persistence에서 읽기 전용으로 열거합니다. thread_spawn_edges 같은 Open / Closed 변 표는 없어요. 토폴로지는 session header의 origin: subagent에 사후에 접어 낸 것입니다. 구현을 바꾸는 것은 싸고, 변으로 복구하는 일은 따로 해야 합니다.

소스 대조 완료 · 2026-08-22 · packages/subagent/subagent/src/types.ts 285–295행 · DSH · Subagent는 seam

Claude Code: 도구 호출에 transcript 곁사슬

모델이 보는 도구의 지금 이름은 Agent입니다. 옛 선 이름은 여전히 Task라 권한 규칙, hook, 복구 중인 세션과 호환돼요. Explore / Plan은 한 번이고, 부모는 이어 달리지 않습니다.

런타임은 아이마다 agentId를 줍니다. 별도의 spawn-edge 표는 없어요. 복구는 그 id의 transcript를 읽습니다. 부모가 살아 있는 아이를 나열하려면 곁사슬을 훑어야 하고, 변으로 거르는 일은 없습니다. Codex는 표 하나와 상태 두 벌을 더 써서, 재시작 뒤에도 그래프로 말할 수 있게 합니다.

소스 대조 완료 · 2026-08-22 · restored-src/src/tools/AgentTool/constants.ts 1–4행
수업 실습
01

Closed 부모 변 아래 Open 손자 변은 아직 있나

세 층 나무를 그리세요. 루트에서 A는 Closed, A에서 B는 Open. Some(Open)None으로 후손을 한 번씩 나열합니다. B가 나타날까요?

필터는 걷는 모든 변에 작용합니다. 그다음 codex-rs/agent-graph-store/src/store.rs 49–54행 주석을 대조하고 두 결과를 적으세요.

Takeaway:자식 agent는 그래프 위의 노드입니다. 변은 Open과 Closed뿐이고, 세션 본문은 rollout을 걸어요. send는 큐에만 넣고 followup이 깨우며, 완료 알림은 현재 턴을 빼앗지 않습니다. 종료는 런타임을 내리고, 변을 닫아야 그래프에서 이름이 지워집니다. 내일은 변을 먼저 보고, 런타임을 다시 걸지 정하세요.