OpenClaw에서 부모-자식 에이전트 간 컨텍스트를 격리하고 결과를 병합하는 설계
솔직히 처음 OpenClaw 멀티 에이전트 구조를 도입했을 때, 저도 그냥 부모 에이전트 하나에 도구를 잔뜩 붙이는 방식으로 시작했습니다. 동작은 했는데, 컨텍스트 윈도우가 금방 포화 상태가 되더라고요. 코드 리뷰, 스타일 린트, 보안 검사를 한 에이전트가 순서대로 처리하면서 중간 결과물이 다음 추론에 계속 끼어드는 문제였습니다. 그 경험이 결국 spawn_subagent 기반 아키텍처를 제대로 파고들게 만든 계기가 됐습니다.
OpenClaw의 서브에이전트 시스템은 단순히 "도구를 자식에게 넘긴다"는 개념이 아닙니다. 컨텍스트를 격리하고, 병렬로 실행하고, 결과만 선택적으로 부모 컨텍스트에 병합한다는 아키텍처 철학이 핵심입니다. 이걸 제대로 이해하지 못하면 멀티 에이전트를 쓴다고 해도 단일 에이전트보다 느리고 비싸기만 한 시스템이 나옵니다.
beam.ai 분석에서는 멀티 에이전트 파일럿의 상당수가 프로덕션 진입 몇 개월 안에 실패한다고 정리하고 있습니다(원문이 Gartner를 인용한 것인지, 자체 집계인지는 명확하지 않으니 수치를 그대로 인용하기보다는 방향성 지표로만 참고하세요). 실패 원인이 단일하지는 않지만, 컨텍스트 핸드오프 설계가 부실할 때 특히 자주 발생한다는 게 제 경험입니다. 이 글에서는 그 함정을 피하기 위한 부모-자식 컨텍스트 핸드오프 구조를 코드와 함께 살펴봅니다.
왜 컨텍스트 격리가 핵심인가
부모-자식 구조가 해결하는 문제
일반적인 단일 에이전트 방식에서는 태스크가 쌓일수록 컨텍스트 윈도우가 커집니다. 이전 작업의 중간 결과, 오류 메시지, 도구 호출 내역이 누적되면서 나중엔 모델이 앞부분 내용을 사실상 무시하기 시작하죠.
OpenClaw의 서브에이전트 시스템은 이 문제를 컨텍스트 격리(context isolation) 로 해결합니다. 자식 에이전트는 독립된 in-memory 세션에서 실행되고, 자신의 작업이 끝나면 summary 메시지를 부모에게 tool result 형태로 돌려줍니다. 부모는 자식의 중간 과정은 볼 수 없고, 최종 결과만 수신합니다.
토큰 사용량을 비교한 비공식 실험 gist도 있지만, 표본이 작고 재현 조건이 명시적이지 않으니 절대 수치는 인용하지 않겠습니다. 제 프로젝트에서 반복 측정해본 결과의 방향성은 동일했습니다 — 자식 세션이 독립돼 있을수록 부모의 누적 토큰 곡선이 완만해집니다.
세 가지 협업 레이어
OpenClaw가 제공하는 멀티 에이전트 협업 구조는 크게 세 층위로 나뉩니다.
| 레이어 | 구조 | 적합한 상황 |
|---|---|---|
| SubAgent | 부모 1 → 자식 N (수직) | 독립적인 서브태스크 위임 |
| Agent Teams | 역할별 에이전트 수평 분담 | 동일 워크플로의 역할 분리 |
| A2A (OpenClaw가 채택한 Google의 Agent-to-Agent 프로토콜) | 인스턴스·환경 간 통신 | 크로스 환경 에이전트 협업 |
A2A는 OpenClaw 고유 기능이 아니라, Google이 제안한 범용 에이전트 간 통신 프로토콜을 OpenClaw가 어댑터 형태로 채택한 것입니다. 이 글에서 집중하는 건 가장 기본이자 실무에서 가장 많이 쓰이는 SubAgent 레이어입니다.
컨텍스트 전달 모드: fork vs isolated
spawn_subagent를 호출할 때 컨텍스트 전달 방식을 선택할 수 있습니다.
- fork 모드 (기본값): 부모의 현재 트랜스크립트가 자식에게 복제됩니다. 자식이 이전 맥락을 알아야 할 때 유용하지만, 부모 컨텍스트가 클수록 자식 세션의 초기 토큰 비용이 올라갑니다.
- isolated 모드: 자식이 완전히 빈 컨텍스트에서 시작합니다. 서브태스크가 명확하게 정의될 때, 그리고 토큰 효율이 중요할 때 선택합니다.
실무에서는 isolated 모드를 기본으로 두고, 자식에게 필요한 컨텍스트를 프롬프트에 직접 포함시키는 방식이 더 예측 가능한 동작을 만들어줍니다.
코드로 보는 컨텍스트 핸드오프
병렬 위임: PR 리뷰 파이프라인
부모 에이전트가 PR diff를 받으면 보안 검사, 스타일 린트, 로직 검증을 각각 별도 자식에게 병렬로 위임하는 구조입니다.
아래는 개념적 예시입니다. OpenClaw의 실제 메서드 시그니처는 공식 문서에서 확인하시길 권합니다.
# 개념적 예시 - OpenClaw Python SDK 스타일
# 반환 타입 가정: SpawnResult(summary: str, artifacts: dict)
import asyncio
from openclaw import spawn_subagent
async def run_pr_review(pr_diff: str) -> dict:
subtasks = [
{
"role": "security_reviewer",
"prompt": f"다음 PR diff에서 보안 취약점을 검토하세요:\n\n{pr_diff}",
},
{
"role": "style_linter",
"prompt": f"다음 PR diff에서 코드 스타일 위반을 검토하세요:\n\n{pr_diff}",
},
{
"role": "logic_verifier",
"prompt": f"다음 PR diff에서 로직 오류와 엣지 케이스를 검토하세요:\n\n{pr_diff}",
},
]
tasks = [
spawn_subagent(
prompt=task["prompt"],
context_mode="isolated",
timeout_seconds=300,
)
for task in subtasks
]
results = await asyncio.gather(*tasks)
return {
"security": results[0].summary,
"style": results[1].summary,
"logic": results[2].summary,
}직렬 위임: 순서 의존성이 있는 경우
앞 자식의 출력이 다음 자식의 입력이 되는 경우엔 병렬 스폰이 오히려 손해입니다. 이때는 await를 순차적으로 걸어 명시적으로 직렬화합니다.
# 개념적 예시 - 순서 의존 직렬 실행
async def run_incident_triage(alert: str) -> dict:
classify = await spawn_subagent(
prompt=f"다음 알림을 분류하세요:\n\n{alert}",
context_mode="isolated",
timeout_seconds=120,
)
investigate = await spawn_subagent(
prompt=(
f"분류 결과: {classify.summary}\n"
f"원본 알림: {alert}\n"
"이 분류를 근거로 관련 로그와 메트릭을 조사해 요약하세요."
),
context_mode="isolated",
timeout_seconds=600,
)
remediate = await spawn_subagent(
prompt=(
f"조사 요약: {investigate.summary}\n"
"권장 조치를 단계별로 제안하세요."
),
context_mode="isolated",
timeout_seconds=300,
)
return {
"classification": classify.summary,
"investigation": investigate.summary,
"remediation": remediate.summary,
}파일 기반 컨텍스트 핸드오프
자식 에이전트 수가 늘어나면 tool result가 부모 컨텍스트에 쌓이면서 포화 문제가 생깁니다. DEV Community 사례에서 소개된 패턴은 각 단계의 아티팩트를 파일로 공유하는 방식입니다.
# 개념적 예시 - 파일 기반 상태 공유
import json
from pathlib import Path
HANDOFF_DIR = Path("~/.openclaw/coding-agent").expanduser()
HANDOFF_DIR.mkdir(parents=True, exist_ok=True)
def write_handoff(task_name: str, payload: dict) -> None:
path = HANDOFF_DIR / f"{task_name}.json"
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2))
def read_handoff(task_name: str) -> dict:
path = HANDOFF_DIR / f"{task_name}.json"
return json.loads(path.read_text())
# 자식 1: 요구사항 분석 결과를 파일로 저장
async def requirements_agent(spec: str) -> str:
# 자식 에이전트 로직: spec을 분석해 컴포넌트/제약을 도출
result = {
"components": ["auth", "billing"],
"constraints": ["PCI-DSS", "GDPR"],
}
write_handoff("requirements", result)
return "requirements.json 작성 완료"
# 자식 2: 이전 파일을 읽어 구현 아티팩트 목록을 생성
async def implementation_agent() -> str:
req = read_handoff("requirements")
# 자식 에이전트 로직: req 기반 구현 산출물 목록 생성
write_handoff("implementation", {"files": [f"{c}.py" for c in req["components"]]})
return "implementation.json 작성 완료"이 패턴의 핵심은 부모 컨텍스트를 경유하지 않고 자식 간 상태를 전달한다는 점입니다. 부모는 각 단계가 성공했는지만 tool result로 확인하고, 실제 아티팩트는 파일 시스템을 통해 다음 자식에게 전달됩니다.
파일 핸드오프도 한계에 다다르면: DuckDB로 상태 추적
파일 기반 방식은 산출물을 서로 넘기기엔 좋지만, 여러 세션에 걸쳐 "어떤 태스크가 어떤 상태인지" 조회하기 시작하면 디렉터리를 뒤지는 로직이 계속 늘어납니다. 이 시점에서 경량 임베디드 DB로 상태 인덱스만 별도로 두면 관리가 훨씬 편해집니다. DuckDB는 파일 하나로 동작하고 SQL을 그대로 쓸 수 있어 이런 용도에 잘 맞습니다.
# 개념적 예시 - DuckDB로 서브에이전트 상태 추적
import json
from pathlib import Path
import duckdb
db_path = str(Path("~/.openclaw/sessions.db").expanduser())
conn = duckdb.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS subagent_tasks (
session_id VARCHAR,
task_name VARCHAR,
status VARCHAR, -- pending | running | done | failed
summary TEXT,
next_actions JSON,
created_at TIMESTAMP DEFAULT now()
)
""")
def register_task(session_id: str, task_name: str) -> None:
conn.execute(
"INSERT INTO subagent_tasks (session_id, task_name, status) VALUES (?, ?, 'pending')",
[session_id, task_name],
)
def update_task(session_id: str, task_name: str, summary: str, next_actions: list) -> None:
conn.execute(
"""UPDATE subagent_tasks
SET status = 'done', summary = ?, next_actions = ?
WHERE session_id = ? AND task_name = ?""",
[summary, json.dumps(next_actions), session_id, task_name],
)전체 실행 흐름
트레이드오프와 실무 함정
설계 의사결정 분기
장단점 정리
| 항목 | 장점 | 주의할 점 |
|---|---|---|
| 컨텍스트 격리 | 부모 컨텍스트 오염 방지 | isolated 모드면 자식에게 맥락을 프롬프트로 직접 전달해야 함 |
| 병렬 실행 | 독립 태스크 동시 처리 | 출력 의존성 있으면 오히려 지연 발생 |
| 역할 집중 | 단일 태스크 집중으로 정확도 향상 | 태스크가 너무 좁으면 재위임 루프 발생 |
| 역할별 모델 선택 | 정밀도 필요한 태스크와 패턴 매칭 태스크를 분리해 비용 조정 | 백엔드가 늘수록 운영 복잡도 증가 |
| 깊이 제한 | 재귀적 스폰 차단 | 복잡한 중첩 구조 불가, 그룹화 필요 |
역할별 모델 선택 관련해서는 NVIDIA Playbooks의 OpenClaw 로컬 LLM 연결 문서가 참고할 만합니다. Ollama나 vLLM 백엔드를 자식별로 다르게 붙이는 예시가 나오는데, 실제 태그 이름(예: llama3.1:8b, qwen2.5:7b 등)은 배포 시점에 ollama list나 각 백엔드의 모델 카탈로그로 반드시 재확인하고 사용하세요. 문서에 박제된 태그를 그대로 복붙하면 대개 실패합니다.
흔한 실수
1. 오버 위임(over-delegation)
태스크를 너무 잘게 쪼개면 자식이 의미 있는 작업을 하기 어렵습니다. "함수 이름 검토해줘" 수준의 태스크를 자식에게 넘기면, 결과가 너무 빈약해서 부모가 같은 일을 반복하게 됩니다. 자식 하나의 태스크는 "스스로 판단하고 완결된 결과를 낼 수 있는 단위"여야 합니다.
2. 자식 수 증가에 따른 컨텍스트 포화
자식 수가 늘면 tool result가 부모 컨텍스트에 쌓이면서 포화가 오는데, 정확한 임계는 모델의 컨텍스트 창, 각 자식이 반환하는 summary 길이, 부모 프롬프트 크기에 따라 달라집니다. 저희 프로젝트에서는 자식이 대여섯 개를 넘기 시작하면 파일 기반 핸드오프 패턴으로 전환하거나, 자식을 그룹화해서 "중간 부모"를 두는 계층 구조로 재설계했습니다. 절대 수치보다는 실제 세션에서 부모 컨텍스트 사용량을 측정하며 판단하는 편이 안전합니다.
3. 타임아웃 미설정
기본 서브에이전트 타임아웃(runTimeoutSeconds)은 배포 환경에 따라 다르지만 대체로 짧습니다. 장시간 데이터 처리나 복잡한 코드 생성 태스크는 이 값을 명시적으로 조정하지 않으면 중간에 끊깁니다. 태스크 성격에 따라 타임아웃을 다르게 설정하는 습관이 필요합니다.
보안: 자식 스폰 경로의 프롬프트 인젝션
멀티 에이전트 시스템의 보안 위협을 다룬 학술 문헌들(예: 자율 에이전트의 위협 모델을 다룬 arXiv 사례 연구)에서 반복적으로 강조되는 것 중 하나가 자식 에이전트 스폰 경로를 통한 프롬프트 인젝션입니다. 자식이 외부 데이터(웹 페이지, 사용자 입력, API 응답)를 처리하는 경우, 그 데이터가 자식의 프롬프트에 직접 포함되지 않도록 샌드박싱 레이어를 두는 게 좋습니다. isolated 모드 자체가 부모 컨텍스트를 보호하지만, 자식 내부에서의 인젝션은 별개로 방어해야 합니다(입력 필터링, 도구 화이트리스트, 자식이 스폰 가능한 다음 단계 제한 등).
마무리
멀티 에이전트를 도입하면서 가장 크게 바뀐 습관 하나만 꼽자면, 자식의 출력 포맷을 먼저 정하고 거기서 역산해서 태스크를 나누는 것입니다. "이 리뷰를 어떻게 잘 쪼갤까"가 아니라 "부모가 어떤 필드를 어떤 형태로 받아야 다음 판단을 내릴 수 있는가"를 먼저 그리고 나면, 자식의 역할·프롬프트·컨텍스트 모드·타임아웃이 거의 자동으로 결정됩니다. 컨텍스트 격리와 결과 병합이라는 두 요소도 결국 이 출력 계약이 있어야 의미를 갖습니다.
spawn_subagent 자체는 이미 실제 프로젝트에서 쓸 만한 수준으로 자리 잡았지만, 구조화된 컨텍스트 핸드오프 규약은 여전히 커뮤니티에서 다듬어지는 단계입니다. GitHub Discussion #30991처럼 핸드오프 스펙을 표준화하려는 제안이 이어지고 있으니, 실제 구현하다 막히는 지점이 있으면 이런 논의를 먼저 확인해보시는 걸 권합니다.
참고 자료
- Sub-agents — OpenClaw 공식 문서
- openclaw/openclaw — docs/tools/subagents.md (GitHub 원문)
- How I Built a Deterministic Multi-Agent Dev Pipeline Inside OpenClaw — DEV Community (결정론적 파이프라인 사례)
- Multi-Agent Architectures in OpenClaw — 개인 리서치 gist (비공식, 재현 조건 미명시)
- Run OpenClaw with a Local LLM — NVIDIA Playbooks (로컬 LLM 백엔드 연결)
- 6 Multi-Agent Orchestration Patterns for Production — beam.ai (프로덕션 실패율 언급, Gartner 인용 여부 원문 확인 필요)
- Proposal: Structured context handoff for coding-agent skill — GitHub Discussion #30991 (커뮤니티 제안 단계)
- Autonomous Agents의 보안 위협 분석 — arXiv 사례 연구