Logo ari Space

혼자 오래 돌아가는 AI 에이전트는 어떻게 굴러갈까 — OpenClaw의 agent loop·subagents·task flow 쉽게 읽기

AI
갱신일 May 28, 2026
10 min read
Table of Contents

이 글은 OpenClaw 공식 문서 세 편 — Agent Loop, Sub-agents, Task Flow — 을 제가 이해한 대로 하나의 흐름으로 엮어 쉽게 풀어 정리한 글입니다. 구체적인 이름·수치는 원문을 따랐고, 어려운 용어는 본문 안에서 그때그때 풀어 설명했습니다.


들어가며: “똑똑한 모델”과 “오래 굴러가는 에이전트”는 다른 문제다

요즘 AI 이야기는 대부분 “모델이 얼마나 똑똑한가”에 쏠려 있습니다. 하지만 그 모델로 사람이 지켜보지 않아도 몇 시간씩 알아서 일하는 에이전트를 만들려면, 똑똑함과는 별개의 문제가 줄줄이 따라옵니다. 작업이 중간에 멈추면 어떻게 되나? 한 에이전트가 느린 일에 붙들려 있으면 나머지는 어떻게 하나? 게이트웨이(서버)가 재시작되면 진행 중이던 일은 다 날아가나?

OpenClaw 문서 세 편은 바로 그 “굴러가는 구조”를 아래에서 위로 한 층씩 설명합니다. 이 글도 같은 순서로 따라갑니다.

Note (이 글의 전체 그림)
  • 1층 — Agent Loop: 메시지 하나를 받아 응답까지 끝내는 기본 실행 단위. 모든 것의 토대.
  • 2층 — Sub-agents: 메인 실행이 막히지 않도록 일을 나눠 백그라운드에 맡기는 방법.
  • 3층 — Task Flow: 여러 단계짜리 작업을 재시작에도 끝까지 추적하는 오케스트레이션 층.

한마디로 “한 번 돌리기 → 나눠 돌리기 → 끝까지 추적하기” 의 3단 구조입니다.

Definition (OpenClaw가 뭔가요)

OpenClaw는 AI 에이전트를 돌리는 런타임/게이트웨이 플랫폼입니다. 채팅(Discord 등)이나 CLI로 들어온 요청을 받아 모델에게 일을 시키고, 도구를 실행하고, 그 결과를 세션에 저장합니다. 이 글에서 다루는 세 개념은 모두 그 안에서 “에이전트를 어떻게 실행하고 관리하느냐”에 관한 것입니다. 구체적인 제품을 몰라도 괜찮습니다 — 여기서 다루는 설계 고민은 Claude Code 같은 다른 에이전트 도구에도 거의 그대로 적용되니까요.


1층 — Agent Loop: 메시지 하나가 응답이 되기까지

가장 밑바닥에는 에이전트 루프(agent loop) 가 있습니다. 사용자의 메시지 한 건을 받아서 실제 행동과 답변으로 바꾸는 한 번의 실행 사이클이죠. 문서가 말하는 “정식 경로”는 이렇습니다.

메시지 수신(intake) → 컨텍스트 조립 → 모델 추론 → 도구 실행 → 응답 스트리밍 → 저장(persistence)

좀 더 풀어보면 내부적으로는 ① 파라미터 검증과 세션 확인, ② 모델·기본값 결정 및 스킬 스냅샷 로딩, ③ 내장 에이전트 실행(runEmbeddedPiAgent), ④ 종료/오류 이벤트 발생의 순서를 밟습니다.

들어가는 문(entry point)과 비동기 실행

에이전트는 게이트웨이 RPC(agent, agent.wait)나 CLI(agent 명령)로 호출합니다. 여기서 중요한 점 하나 — agent를 호출하면 작업이 끝날 때까지 기다리지 않고 즉시 { runId, acceptedAt } 를 돌려줍니다.

Definition (왜 곧바로 runId만 돌려줄까)

긴 작업을 동기(synchronous)로 처리하면, 호출한 쪽은 응답이 올 때까지 그냥 멈춰서 기다려야 합니다. 그래서 OpenClaw는 “접수증”에 해당하는 runId를 먼저 반환하고, 실제 작업은 뒤에서 비동기로 돌립니다. 호출자는 이 runId로 나중에 진행 상황을 확인하거나, agent.wait(기본 30초 대기)로 결과를 받아볼 수 있습니다. 음식점에서 주문하면 번호표(=runId)를 먼저 받고, 음식이 되면 그 번호로 찾아가는 것과 같습니다.

진행 상황은 세 갈래로 흘러나온다 (이벤트 스트리밍)

작업이 도는 동안 무슨 일이 일어나는지는 세 종류의 이벤트 채널로 흘러나옵니다.

  • lifecycle — 실행의 단계 표시(start / end / error)
  • assistant — 모델이 만들어내는 텍스트 조각(델타)
  • tool — 도구 실행 이벤트

덕분에 “지금 모델이 글자를 뽑아내는 중”인지 “도구를 실행 중”인지 “끝났는지”를 채널만 보고 구분할 수 있습니다.

같은 세션은 한 줄로 세운다 (직렬화와 락)

루프에서 가장 신경 쓴 부분은 동시성입니다. 문서는 “실행은 세션 키 단위(세션 레인)로 직렬화된다” 고 말합니다. 즉 같은 세션에 요청이 여러 개 들어와도 한 번에 하나씩, 줄을 세워 처리한다는 뜻입니다.

Definition (왜 굳이 한 줄로 세울까 — 레이스 컨디션)

레이스 컨디션(race condition) 은 두 작업이 같은 자원(여기서는 세션 파일)을 동시에 건드리다가 서로 꼬여 결과가 망가지는 현상입니다. 한 에이전트가 세션 기록을 쓰는 도중에 다른 에이전트가 같은 파일을 덮어쓰면, 기록이 섞이거나 사라질 수 있죠.

그래서 OpenClaw는 세션 파일에 쓰기 락(write lock) 을 겁니다. 이 락은 파일 기반이고, 기본적으로 재진입 불가(non-reentrant) 이며, 락을 얻기까지 기다리는 시간(acquireTimeoutMs)은 기본 60,000ms(60초) 입니다. 화장실 문을 안에서 잠그면 다른 사람은 빌 때까지 기다려야 하는 것과 같은 원리입니다.

타임아웃 설정도 상황별로 나뉩니다. agent.wait의 기본 대기는 30초, 에이전트 런타임 기본 한도는 무려 172,800초(48시간), 모델이 아무 말도 안 하고 멈춰 있을 때를 잡아내는 유휴(idle) 타임아웃은 기본 120초 입니다. 48시간이라는 큰 수가 바로 “혼자 오래 돌아가는 에이전트”를 염두에 둔 설정이죠.

중간에 끼어드는 지점 (훅)

루프 곳곳에는 동작을 가로채 바꿀 수 있는 플러그인 훅(hook) 이 있습니다. 모델을 정하기 직전(before_model_resolve), 프롬프트를 만들기 직전(before_prompt_build), 도구를 부르기 직전(before_tool_call), 도구 결과를 저장할 때(tool_result_persist), 에이전트가 끝날 때(agent_end) 등입니다. 핵심 흐름을 건드리지 않고도 동작을 확장할 수 있게 해주는 장치입니다.


2층 — Sub-agents: 일을 나눠 백그라운드에 맡기기

루프 하나는 “한 번에 하나씩”이 원칙입니다. 그런데 리서치나 느린 도구 작업처럼 오래 걸리는 일을 메인 루프가 붙들고 있으면, 그동안 사용자는 아무 응답도 못 받습니다. 그래서 등장하는 게 서브에이전트(sub-agent) 입니다.

Definition (서브에이전트란)

서브에이전트는 기존 에이전트가 띄우는 백그라운드 실행입니다. 자기만의 격리된 세션(agent:<agentId>:subagent:<uuid>)에서 따로 돌고, 일이 끝나면 결과를 요청한 쪽(부모) 채팅으로 보고합니다. 비서에게 “이것 좀 알아봐 줘”라고 시켜두고 나는 다른 일을 계속하다가, 비서가 조사를 마치면 보고를 받는 것과 같습니다.

설계 목표는 세 가지로 요약됩니다. ① 리서치·느린 작업을 메인을 막지 않고 병렬로 돌리기, ② 세션 분리와 선택적 샌드박싱으로 격리 유지, ③ 도구 표면을 좁혀 오용하기 어렵게 만들기 — 그래서 서브에이전트는 기본적으로 세션 조작 도구를 받지 못합니다.

두 가지 맥락 모드: 격리 vs 포크

서브에이전트에게 일을 넘길 때, 부모의 대화 맥락을 얼마나 물려줄지 두 가지 중 고릅니다.

  • Isolated(격리, 기본값): 깨끗한 새 트랜스크립트(대화 기록)에서 시작합니다. 독립적인 리서치나 구현에 적합하고, 토큰 사용도 줄여줍니다.
  • Fork(포크): 부모의 트랜스크립트를 자식 세션으로 가지치기해 넘깁니다. 지금까지의 대화나 이전 도구 결과에 의존하는 작업일 때만 씁니다.
Warning (포크는 아껴 쓸 것)

문서는 “포크는 아껴 쓰라. 이것은 맥락에 민감한 위임을 위한 것이지, 명확한 작업 프롬프트를 잘 쓰는 일을 대신해 주는 게 아니다” 라고 못 박습니다. 맥락을 통째로 넘기면 편할 것 같지만, 토큰을 많이 먹고 자식이 부모의 군더더기까지 떠안게 됩니다. 웬만하면 “할 일을 분명하게 적어주는” 쪽이 낫다는 뜻입니다.

핵심 도구 세 가지

서브에이전트는 주로 세 개의 도구로 다룹니다.

  • sessions_spawn — 서브에이전트를 띄우는 비블로킹 도구. subagent 큐 레인에서 돌고, 즉시 { status: "accepted", runId, childSessionKey } 를 반환합니다. 주요 인자로는 task(필수, 할 일 설명), taskName(안정적 식별자), model/thinking(런타임 오버라이드), runTimeoutSeconds(N초 후 중단), context("isolated" 또는 "fork"), cleanup("delete" 또는 "keep") 등이 있습니다.
  • sessions_yield — 지금 모델 턴을 끝내고, 서브에이전트의 완료 이벤트가 다음 메시지로 도착하기를 기다립니다. 문서는 “이것을 subagents·sessions_list·sessions_history 폴링이나 셸 sleep, 프로세스 폴링 루프로 대체하지 말라” 고 강조합니다. 즉 “계속 들여다보며 확인”하지 말고 “끝나면 알려줘” 방식을 쓰라는 것입니다.
  • subagents — 현재 세션이 띄운 서브에이전트들을 조회합니다. 슬래시 명령으로 /subagents list(목록), /subagents log <id|#>(트랜스크립트 확인), /subagents info <id|#>(메타데이터) 를 쓸 수 있습니다.

보고는 “끝나면 밀어주는” 방식 (push)

서브에이전트의 완료 보고는 푸시(push) 기반 입니다. 자식이 끝나면 안정적인 멱등 키(idempotency key)와 함께 부모 세션으로 agent 턴을 보내 보고합니다. 보고에는 결과(마지막 어시스턴트 응답 텍스트), 상태(completed / failed / timed out / unknown), 통계(실행 시간, 토큰 사용량, 추정 비용, 세션 키, 트랜스크립트 경로), 그리고 부모가 어떻게 검토하면 되는지에 대한 안내가 담깁니다.

Note (이미 답을 보낸 뒤 보고가 도착하면)

부모가 이미 사용자에게 최종 답을 보낸 다음에야 자식의 완료 보고가 뒤늦게 도착할 수 있습니다. 이때 올바른 처리는 아무 말도 덧붙이지 않는 침묵 토큰 NO_REPLY / no_reply 를 내보내는 것 입니다. 뒤늦은 보고로 사용자에게 중복·혼란스러운 메시지를 또 보내지 않기 위한 약속이죠.

서브에이전트가 서브에이전트를 띄울 때 (중첩과 오케스트레이터)

서브에이전트가 또 다른 서브에이전트를 띄우게 하면, 한 에이전트가 여러 일꾼을 지휘하는 오케스트레이터(orchestrator) 패턴 이 됩니다. 다만 무한정 깊어지면 통제 불능이 되므로 깊이를 제한합니다.

  • Depth 0 — 메인 에이전트(agent:<id>:main)
  • Depth 1 — 서브에이전트. 깊이가 2 이상 허용될 때만 자식을 띄울 수 있음
  • Depth 2 — 서브-서브에이전트. 더 이상 띄우지 못함

기본값은 maxSpawnDepth: 1(서브에이전트가 자식을 못 띄움)이고, 오케스트레이터를 쓰려면 maxSpawnDepth: 2로 올립니다. 이 값의 최대 한도는 5 입니다. 한 세션이 동시에 거느릴 수 있는 자식 수는 maxChildrenPerAgent(기본 5, 범위 1~20), 전체 동시 실행 상한은 maxConcurrent(기본 8)로 제한됩니다.

권한도 깊이에 따라 다릅니다. Depth-1 오케스트레이터는 sessions_spawn·subagents·sessions_list·sessions_history를 받지만, Depth-2 일꾼은 세션 도구를 전혀 받지 못합니다.

비용 아끼기

“각 서브에이전트는 기본적으로 자기만의 컨텍스트와 토큰 사용량을 가진다” 고 했습니다. 여기서 절약의 여지가 생깁니다. agents.defaults.subagents.model 로 서브에이전트에는 더 저렴한 모델을 지정하고, 메인 에이전트는 더 똑똑한(비싼) 모델로 유지하는 식입니다. 반복적이거나 단순한 위임 작업이 많을수록 효과가 큽니다.

Tip (알아두면 좋은 운영 디테일)
  • 보고(announce)는 베스트 에포트 라서, 게이트웨이가 재시작되면 아직 못 보낸 보고는 사라질 수 있습니다.
  • 세션은 archiveAfterMinutes(기본 60분) 후 자동 보관되며, 트랜스크립트 이름이 *.deleted.<timestamp> 로 바뀝니다.
  • 서브에이전트 컨텍스트에는 AGENTS.md·TOOLS.md 만 주입되고, 페르소나·정체성 파일은 들어가지 않습니다.
  • runTimeoutSeconds는 실행을 멈출 뿐, 세션을 자동 보관하지는 않습니다.

3층 — Task Flow: 여러 단계를 재시작에도 끝까지 추적하기

서브에이전트로 일을 나눴어도, 여러 단계로 이어지는 긴 작업 전체의 진행 상태를 누가 책임지고 추적하느냐는 또 다른 문제입니다. 그 자리를 채우는 게 태스크 플로우(Task Flow) 입니다.

문서의 표현을 빌리면, Task Flow는 “개별 태스크가 분리된 작업의 단위로 남아 있는 동안, 자체 상태·리비전 추적·동기화 의미를 갖는 내구성 있는 다단계 흐름을 관리하는” 오케스트레이션 층입니다.

Definition (언제 무엇을 쓰나 — task / cron / task flow)

세 가지를 헷갈리기 쉬운데, 쓰임이 다릅니다.

  • 일반 task — 단발성 백그라운드 작업 하나면 충분할 때.
  • cron job — “한 번만, 또는 정해진 시각에” 같은 단발 알림·스케줄.
  • Task Flow여러 단계로 이어지고, 재시작에도 진행 상태가 살아남아야 하는 파이프라인. 또는 외부에서 만든 작업들의 진행을 한곳에서 모아 보고 싶을 때.

요약: 단계가 여러 개이고 “중간에 죽어도 이어서 가야 한다”면 Task Flow입니다.

두 가지 동기화 모드

  • Managed(관리형): Task Flow가 흐름의 전체 생애주기를 직접 소유합니다. 단계마다 태스크를 만들고, 완료될 때까지 상태를 알아서 진행시킵니다.
  • Mirrored(미러형): Task Flow가 외부에서 만들어진 태스크를 관찰만 하면서 흐름 상태를 동기화합니다. 태스크 생성은 제어하지 않고요. cron job이나 CLI 명령에서 나온 진행 상황을 한 화면에 모아 보고 싶을 때 유용합니다.

죽어도 살아남는 상태 (내구성과 리비전 추적)

각 흐름은 자체 상태를 리비전 추적과 함께 영속화 해서 게이트웨이 재시작을 견딥니다. 내부적으로는 “경계 있는 WAL(쓰기 미리 기록) 유지관리를 갖춘 SQLite” 를 쓰고, 주기적·종료 시점 체크포인트를 두어 장시간 도는 게이트웨이가 registry.sqlite-wal 같은 사이드카 파일을 무한정 쌓지 않도록 합니다.

Definition (리비전 추적이 왜 필요할까)

여러 곳에서 같은 흐름의 상태를 동시에 고치려 들면 충돌이 납니다(2층에서 본 레이스 컨디션과 같은 고민이죠). 리비전(revision) 추적 은 상태에 버전 번호를 매겨 두고, 누군가 옛 버전을 기준으로 덮어쓰려 하면 충돌을 감지하게 해줍니다. 문서 편집 협업 도구가 “그새 다른 사람이 수정했어요”라고 알려주는 것과 같은 원리입니다.

취소는 “끈끈하게” 남는다

openclaw tasks flow cancel 명령은 흐름에 끈끈한 취소 의도(sticky cancel intent) 를 박아 넣습니다. 이 의도는 재시작에도 그대로 유지 되어서, 한 번 취소한 흐름이 서버가 되살아났다고 슬그머니 다시 돌아가는 일을 막습니다.

흐름을 다루는 CLI는 이렇습니다.

  • openclaw tasks flow list — 추적 중인 흐름을 상태와 함께 표시
  • openclaw tasks flow show <lookup> — 특정 흐름의 세부 정보 확인
  • openclaw tasks flow cancel <lookup> — 흐름과 활성 태스크를 취소

반복 워크플로우를 위한 권장 조합

마지막으로 문서는, 주기적으로 도는 워크플로우를 만들 때 네 가지를 층층이 쌓아 쓰라 고 권합니다.

  • Scheduled Tasks — “언제 돌릴지”(타이밍)
  • 지속 세션(persistent sessions) — 실행 사이에 맥락 유지
  • Lobster — 결정론적(deterministic) 단계 처리
  • Task Flow — 다단계 진행 추적

여기에 더해, 모델로 요약 같은 LLM 단계를 돌리기 전에 브라우저 가용성·인증·네트워크 도달성 같은 사전 점검(preflight check) 을 넣고, 처리 과정 내내 데이터 출처(provenance) 메타데이터를 보존 하라고 권합니다. 자동화가 엉뚱한 입력으로 헛돌거나, 결과의 근거를 잃지 않도록 하는 안전장치입니다.


마무리: 한 흐름으로 다시 꿰어보기

세 글을 한 줄로 이으면 이렇게 됩니다.

  1. Agent Loop 가 메시지 하나를 받아 응답까지 끝내는 기본 한 사이클을 책임지고 (세션별로 한 줄로 세워 안전하게),
  2. Sub-agents 가 오래 걸리는 일을 백그라운드로 떼어내 병렬로 돌리면서 메인을 막지 않고 (격리·중첩 한도·푸시 보고로 통제하며),
  3. Task Flow 가 그 모든 단계를 재시작에도 끝까지 추적해 흐름이 중간에 증발하지 않게 합니다.

“한 번 돌리기 → 나눠 돌리기 → 끝까지 추적하기” 라는, 자율 에이전트를 오래 안정적으로 굴리기 위한 3단 설계입니다.

Tip (다른 에이전트 도구를 쓸 때 떠올릴 점)

OpenClaw를 직접 쓰지 않더라도 챙길 만한 교훈이 있습니다.

  • 긴 작업은 비동기로. 호출 즉시 “접수증”을 돌려주고 결과는 따로 받는 구조가, 멈춰서 기다리는 구조보다 훨씬 잘 버팁니다.
  • 상태를 끈질기게 저장하라. 재시작·중단을 가정하고 “어디까지 했는지”를 영속화해 두면, 죽어도 이어서 갈 수 있습니다.
  • 위임은 격리해서, 권한은 좁게. 일을 나눠 맡기되 맥락과 도구를 꼭 필요한 만큼만 넘기는 편이, 빠르고 안전하고 저렴합니다.

이 글은 원문을 제가 이해한 대로 풀어 옮긴 정리입니다. 정확한 인자·기본값·동작은 위에 링크한 OpenClaw 공식 문서를 함께 확인하시길 권합니다.