콘텐츠로 이동

OpenClaw 에이전트 루프 구조 이해하기: 실행 흐름 완전 정복

에이전트를 직접 구축하다 보면 상태 관리부터 도구 실행, 그리고 실시간 스트리밍까지 신경 쓸 게 정말 많죠? 특히 세션의 일관성을 유지하면서 복잡한 로직을 처리하는 과정은 꽤나 까다로운 작업이에요. OpenClaw가 이 복잡한 과정을 어떻게 하나의 매끄러운 루프로 연결했는지 그 내부 구조를 자세히 설명해 드릴게요.

에이전틱 루프(Agentic loop)는 에이전트가 실제로 실행되는 전체 과정을 의미해요. 입력(intake)부터 컨텍스트 조립, 모델 추론, 도구 실행, 스트리밍 응답, 그리고 데이터 저장까지 모두 포함되죠. OpenClaw에서 루프는 세션당 하나의 직렬화된 실행 단위로 작동하며, 모델이 생각하고 도구를 호출하고 출력을 내보낼 때마다 라이프사이클과 스트림 이벤트를 발생시킵니다.

  • Gateway RPC: agent 및 agent.wait.
  • CLI: agent 명령어.
  1. agent RPC가 파라미터를 검증하고, 세션(sessionKey/sessionId)을 확인한 뒤 세션 메타데이터를 저장해요. 그 즉시 { runId, acceptedAt }를 반환합니다.
  2. agentCommand가 에이전트를 실행합니다:
    • 모델과 thinking/verbose 기본 설정을 확인해요.
    • skills 스냅샷을 로드합니다.
    • runEmbeddedPiAgent (pi-agent-core 런타임)를 호출해요.
    • 임베디드 루프에서 이벤트가 발생하지 않을 경우를 대비해 lifecycle end/error 이벤트를 보냅니다.
  3. runEmbeddedPiAgent 단계:
    • 세션별 큐와 글로벌 큐를 통해 실행을 직렬화해요.
    • 모델과 auth profile을 확인하고 pi 세션을 생성합니다.
    • pi 이벤트와 assistant/tool 델타 스트림을 구독해요.
    • 타임아웃을 적용해 시간이 초과되면 실행을 중단(abort)합니다.
    • 페이로드와 usage 메타데이터를 반환해요.
  4. subscribeEmbeddedPiSession은 pi-agent-core 이벤트를 OpenClaw agent 스트림으로 연결해 줍니다:
    • 도구 이벤트 => stream: "tool"
    • assistant 델타 => stream: "assistant"
    • 라이프사이클 이벤트 => stream: "lifecycle" (phase: "start" | "end" | "error")
  5. agent.wait는 waitForAgentJob을 사용해요:
    • 특정 runId에 대한 lifecycle end/error를 기다립니다.
    • { status: ok|error|timeout, startedAt, endedAt, error? }를 반환해요.

큐잉 및 동시성 (Queueing + concurrency)

섹션 제목: “큐잉 및 동시성 (Queueing + concurrency)”
  • 실행은 세션 키(세션 레인)별로 직렬화되며, 선택적으로 글로벌 레인을 거치기도 해요.
  • 이렇게 하면 도구나 세션 간의 충돌(race condition)을 방지하고 세션 히스토리를 일관되게 유지할 수 있습니다.
  • 메시징 채널은 이 레인 시스템에 데이터를 공급하는 큐 모드(collect/steer/followup)를 선택할 수 있어요. 자세한 내용은 Command Queue를 참고하세요.
  • 워크스페이스가 확인되고 생성됩니다. 샌드박스 실행의 경우 샌드박스 워크스페이스 루트로 리다이렉트될 수 있어요.
  • Skills를 로드하거나 스냅샷에서 재사용하여 환경 변수와 프롬프트에 주입합니다.
  • Bootstrap/컨텍스트 파일들을 확인하고 시스템 프롬프트 리포트에 주입해요.
  • 세션 쓰기 잠금(write lock)을 획득하며, 스트리밍을 시작하기 전에 SessionManager를 열고 준비합니다.

프롬프트 구성 및 시스템 프롬프트

섹션 제목: “프롬프트 구성 및 시스템 프롬프트”
  • 시스템 프롬프트는 OpenClaw의 기본 프롬프트, skills 프롬프트, bootstrap 컨텍스트, 그리고 실행별 오버라이드 설정을 조합해서 만듭니다.
  • 모델별 제한 사항과 압축(compaction)을 위한 예약 토큰이 적용돼요.
  • 모델이 실제로 어떤 내용을 보는지 궁금하다면 System prompt 문서를 확인해 보세요.

OpenClaw에는 두 가지 훅 시스템이 있어요:

  • Internal hooks (Gateway hooks): 명령어와 라이프사이클 이벤트에 반응하는 이벤트 기반 스크립트예요.
  • Plugin hooks: 에이전트/도구 라이프사이클과 Gateway 파이프라인 내부의 확장 지점입니다.
  • agent:bootstrap: 시스템 프롬프트가 확정되기 전, bootstrap 파일을 빌드하는 동안 실행됩니다. bootstrap 컨텍스트 파일을 추가하거나 제거할 때 사용하세요.
  • Command hooks: /new, /reset, /stop 및 기타 명령어 이벤트가 해당됩니다 (Hooks 문서 참고).

설정 방법과 예시는 Hooks에서 볼 수 있어요.

플러그인 훅 (에이전트 및 Gateway 라이프사이클)

섹션 제목: “플러그인 훅 (에이전트 및 Gateway 라이프사이클)”

이 훅들은 에이전트 루프나 Gateway 파이프라인 내부에서 실행됩니다:

  • before_model_resolve: 세션 로드 전(messages 없음)에 실행되어 모델 결정 전에 provider나 모델을 강제로 오버라이드합니다.
  • before_prompt_build: 세션 로드 후(messages 포함)에 실행되어 프롬프트 제출 전 prependContext, systemPrompt, prependSystemContext, appendSystemContext 등을 주입합니다. 매 턴마다 변하는 동적 텍스트는 prependContext를 사용하고, 시스템 프롬프트 영역에 고정되어야 하는 지침은 system-context 필드를 사용하는 것이 좋아요.
  • before_agent_start: 이전 버전 호환성을 위한 훅으로, 위 두 단계 중 하나에서 실행될 수 있습니다. 가급적 위의 명시적인 훅을 사용하는 것을 추천해요.
  • before_agent_reply: 인라인 액션 이후, LLM 호출 직전에 실행됩니다. 플러그인이 해당 턴을 가로채서 가짜 응답을 반환하거나 아예 응답을 하지 않도록 설정할 수 있어요.
  • agent_end: 완료 후 최종 메시지 리스트와 실행 메타데이터를 검사합니다.
  • before_compaction / after_compaction: 압축 주기를 관찰하거나 주석을 답니다.
  • before_tool_call / after_tool_call: 도구 파라미터나 결과를 가로챕니다.
  • before_install: 내장 스캔 결과를 검사하고 skill이나 플러그인 설치를 차단할 수 있습니다.
  • tool_result_persist: 도구 결과가 세션 기록에 기록되기 전에 동기적으로 변환합니다.
  • message_received / message_sending / message_sent: 인바운드 및 아웃바운드 메시지 훅입니다.
  • session_start / session_end: 세션 라이프사이클의 경계 지점입니다.
  • gateway_start / gateway_stop: Gateway 라이프사이클 이벤트입니다.

아웃바운드/도구 가드(guard)를 위한 훅 결정 규칙:

  • before_tool_call: { block: true }는 즉시 종료되며 우선순위가 낮은 핸들러를 중단시킵니다.
  • before_tool_call: { block: false }는 아무 작업도 하지 않으며 이전의 차단 설정을 해제하지 않습니다.
  • before_install: { block: true }는 즉시 종료되며 우선순위가 낮은 핸들러를 중단시킵니다.
  • before_install: { block: false }는 아무 작업도 하지 않으며 이전의 차단 설정을 해제하지 않습니다.
  • message_sending: { cancel: true }는 즉시 종료되며 우선순위가 낮은 핸들러를 중단시킵니다.
  • message_sending: { cancel: false }는 아무 작업도 하지 않으며 이전의 취소 설정을 해제하지 않습니다.

훅 API와 등록에 대한 자세한 내용은 Plugin hooks를 참고하세요.

  • Assistant 델타는 pi-agent-core에서 스트리밍되어 assistant 이벤트로 발생합니다.
  • 블록 스트리밍은 text_end 또는 message_end 시점에 부분 응답을 내보낼 수 있어요.
  • 추론(Reasoning) 스트리밍은 별도의 스트림으로 나가거나 블록 응답으로 나갈 수 있습니다.
  • 청킹(chunking)과 블록 응답 동작은 Streaming 문서를 확인하세요.
  • 도구의 시작/업데이트/종료 이벤트는 tool 스트림으로 나옵니다.
  • 도구 결과는 로깅되거나 전송되기 전에 크기와 이미지 페이로드가 정리(sanitize)됩니다.
  • 메시징 도구의 전송 상태를 추적해서 assistant의 중복 확인 메시지가 나가지 않도록 억제해요.
  • 최종 페이로드는 다음 항목들을 조합해서 구성됩니다:
    • assistant 텍스트 (및 선택적인 추론 내용)
    • 인라인 도구 요약 (verbose 모드이고 허용된 경우)
    • 모델 에러 발생 시 assistant 에러 텍스트
  • NO_REPLY는 무시 토큰으로 취급되어 출력 페이로드에서 필터링됩니다.
  • 메시징 도구의 중복 항목은 최종 페이로드 리스트에서 제거돼요.
  • 렌더링할 페이로드가 남지 않았는데 도구 에러가 발생했다면, 폴백(fallback) 도구 에러 응답을 내보냅니다 (메시징 도구가 이미 사용자에게 보이는 응답을 보낸 경우는 제외).
  • 자동 압축 기능은 compaction 스트림 이벤트를 발생시키고 재시도를 트리거할 수 있어요.
  • 재시도 시에는 중복 출력을 피하기 위해 인메모리 버퍼와 도구 요약을 초기화합니다.
  • 압축 파이프라인에 대한 내용은 Compaction을 참고하세요.
  • lifecycle: subscribeEmbeddedPiSession에서 발생 (또는 agentCommand에서 폴백으로 발생)
  • assistant: pi-agent-core에서 스트리밍되는 델타 데이터
  • tool: pi-agent-core에서 스트리밍되는 도구 이벤트
  • Assistant 델타는 채팅 delta 메시지로 버퍼링됩니다.
  • lifecycle end/error 시점에 채팅 final 메시지가 발생해요.
  • agent.wait 기본값: 30초 (대기 시간만 해당). timeoutMs 파라미터로 변경 가능합니다.
  • 에이전트 런타임: agents.defaults.timeoutSeconds 기본값은 172800초(48시간)이며, runEmbeddedPiAgent의 중단 타이머에서 강제 적용됩니다.
  • 에이전트 타임아웃 (중단)
  • AbortSignal (취소)
  • Gateway 연결 끊김 또는 RPC 타임아웃
  • agent.wait 타임아웃 (대기만 중단하며, 에이전트 자체를 멈추지는 않음)
  • Tools — 사용 가능한 에이전트 도구
  • Hooks — 에이전트 라이프사이클 이벤트로 트리거되는 스크립트
  • Compaction — 긴 대화를 요약하는 방법
  • Exec Approvals — 쉘 명령어 실행 승인 단계
  • Thinking — 생각/추론 레벨 설정

더 궁금한 점이 있거나 설정에 도움이 필요하면 언제든 물어봐 주세요!

AI Setup Assistant

OpenClaw

OpenClaw Expert

아직 막혀 있나요?

이 문서에서 답을 못 찾았다면 OpenClaw Expert에게 바로 물어보세요.