02 · OpenClaw 작동 흐름 — 메시지 한 줄이 페르소나 입은 답이 될 때까지

🐱 이 편의 핵심@뽀야야 ~ 한 줄이 진짜 뽀야 답으로 돌아오기까지, 맥미니라는 집 안에서 어떤 일이 벌어지는지 8칸으로 따라가볼게. 끝까지 읽으면 방 위치 잡기 + 책상 매뉴얼 자동 펼치기 + 일기장 다시 읽기, 이 3종 세트가 OpenClaw + Claude CLI 방식의 핵심임이 잡혀.


🤔 한 봇이 답하는 흐름, 그림 한 장으로

OpenClaw에서 에이전트를 새로 만들면 그 자체로 다른 봇이야 — 자기 방, 자기 일기장, 자기 매뉴얼이 따로 있으니까. 우린 그 봇의 *두뇌(모델 호출)*를 Claude CLI 방식으로 굴리기로 했어.

비유 한 컷으로 깔고 가자.

🏠 맥미니 (집사네 집)

├── 🚪 대문 = OpenClaw 게이트웨이
│   (편지 받아서 누구 앞으로 갈지 분류)

├── 🐱 뽀야 방 (workspace-bboya/)
│   책상 위: SOUL · IDENTITY · USER · AGENTS · TOOLS · MEMORY  6장
│   서랍 안: 사원증(OAuth 토큰)
│   책상 옆 일기장: agents/bboya/sessions/*.jsonl

└── 🐈‍⬛ 뽀짝이 방 (workspace-bbojjak/)
    책상도 일기장도 사원증도 다 따로

이 그림 머리 한쪽에 두고 8칸 따라가보자.


🔄 슬랙 멘션 한 줄 → 뽀야 답까지 8칸

1. 📬 대문 앞에 편지 도착

집사가 슬랙에서 @뽀야야 ~ 보냄 → 슬랙 봇 앱이 그걸 OpenClaw 게이트웨이로 push. 우체부가 대문 앞에 편지 한 통 두고 가는 단계.

2. 🚪 대문지기가 봉투 보고 분류

대문지기(게이트웨이)가 **~/.openclaw/openclaw.json**의 bindings 표를 봐:

{
  "bindings": [
    {
      "type": "route",
      "agentId": "bboya",
      "match": { "channel": "slack", "accountId": "default" }
    }
  ]
}

→ “default 슬랙 봇으로 들어온 편지는 뽀야 방으로.” 봉투 위에 받는 사람: bboya 라벨이 붙는 셈.

3. 🪑 어느 방, 어느 책상으로 갈지 — cwd 잡기 ⭐

cwd = “current working directory” = 클로드 CLI가 어느 폴더에서 깨어나는지 알려주는 절대경로.

말로 풀면 방 명패야. 같은 claude 바이너리(같은 두뇌)인데, 어느 방 책상에 앉히느냐가 페르소나를 결정해.

# 뽀야 편지 처리
cd /Users/dahtmad/.openclaw/workspace-bboya && claude

# 뽀짝이 편지 처리
cd /Users/dahtmad/.openclaw/workspace-bbojjak && claude
#                                ↑ 방 명패만 다르게

이번 편지는 뽀야 거니까 → 뽀야 방 책상에 앉음.

💡 헷갈리지 말 것 — 방 명패는 시작점이지 벽이 아니야. 책상 위 매뉴얼은 자기 방 거 보지만, 절대경로(/Users/dahtmad/...)로 부르면 다른 방 파일도 손이 닿아. 진짜로 방 안에 가두려면 샌드박스 옵션(agents.defaults.sandbox)을 따로 켜야 해 — 우린 동생들끼리 신뢰하니까 안 켜놨어.

📖 공식문서 발췌 (번역)concepts/agent-workspace.md

“워크스페이스는 에이전트의 집이다. 파일 도구와 워크스페이스 컨텍스트가 사용하는 유일한 작업 디렉토리. 사적인 공간으로 두고 기억처럼 다뤄라.”

“워크스페이스는 기본 cwd일 뿐, 단단한 격리 공간이 아니다. 상대경로는 워크스페이스 기준으로 풀리지만, 샌드박스를 켜지 않으면 절대경로로는 호스트의 다른 위치에 닿을 수 있다.”

4. 📚 책상 위에 매뉴얼이 자동으로 펼쳐짐

뽀야가 책상에 앉는 순간, OpenClaw가 자동으로 책상 위에 6장을 펼쳐놔:

~/.openclaw/workspace-bboya/
├── SOUL.md       — 성격, 말투, 가치관
├── IDENTITY.md   — 이름, 외형, 정체성
├── USER.md       — 집사(사용자) 이해
├── MEMORY.md     — 장기 기억
├── AGENTS.md     — 워크스페이스 운영 가이드
├── TOOLS.md      — 도구 사용법
└── HEARTBEAT.md  — 주기적 자가 점검 가이드

펼친다는 게 읽으라고 알려주는 게 아니야. OpenClaw가 매 호출마다 이 파일들 내용 통째로 system prompt에 직접 박아넣어 클로드 CLI한테 넘김. 책상에 앉자마자 눈앞에 6장이 쫙 펼쳐져 있는 셈.

이게 같은 클로드 모델인데 방마다 페르소나가 다른 진짜 이유야. 두뇌는 같은데, 책상에 펼쳐진 매뉴얼이 다르니까.

📖 공식문서 발췌 (번역)concepts/agent-workspace.md (Workspace file map)

  • AGENTS.md — 에이전트의 운영 지침과 기억 사용법. 매 세션 시작 시 로드된다.
  • SOUL.md — 페르소나, 말투, 경계. 매 세션 로드된다.
  • USER.md — 사용자가 누구인지, 어떻게 부를지. 매 세션 로드된다.

→ 즉 봇마다 페르소나가 다른 건 모델이 달라서가 아니라, 매 세션마다 워크스페이스 파일들이 시스템 프롬프트에 자동 임베드되기 때문.

5. 🪪 사원증 꺼내기 — 집사 Pro/Max 구독으로 호출

뽀야가 어떤 자격으로 두뇌(모델)를 호출할지. 사물함(서랍) 두 칸을 봐야 해.

5-1. 어떤 사원증으로 들어가는지 선언~/.openclaw/openclaw.json:

{
  "auth": {
    "profiles": {
      "anthropic:claude-cli": {
        "provider": "claude-cli",
        "mode": "oauth"
      }
    },
    "order": {
      "anthropic": ["anthropic:claude-cli"]
    }
  }
}

→ “anthropic 회선은 claude-cli 사원증으로, OAuth 방식으로 통과한다” 선언.

5-2. 진짜 사원증은 방마다 따로 보관~/.openclaw/agents/bboya/agent/:

~/.openclaw/agents/bboya/agent/
├── auth.json                  — 활성 인증 메타
├── auth-state.json            — 프로필별 마지막 사용 시각/에러 카운트
├── auth-profiles.json         — OAuth 토큰 본체 (절대 공유 X)
└── models.json                — 모델 사용 설정

뽀야가 두뇌 부르러 갈 때 이 사물함을 CLAUDE_CONFIG_DIR 환경변수로 들고 가:

CLAUDE_CONFIG_DIR=~/.openclaw/agents/bboya/agent claude /login
#                  ↑ 이 사물함의 사원증으로 Pro/Max 로그인

즉 모델 호출 비용은 집사 Claude Pro/Max 구독에서 차감되는 거야. API 키 빌링 X.

⚠️ auth-profiles.json엔 진짜 토큰이 박혀있어 — 외부 공유 절대 금지. 사물함을 방마다 따로 두는 건 동생끼리 사원증 안 섞이게 하기 위함.

📖 공식문서 발췌 (번역)concepts/multi-agent.md

인증 프로필은 에이전트별이다. 각 에이전트는 자기 디렉토리의 ~/.openclaw/agents/<agentId>/agent/auth-profiles.json에서 읽는다.”

“메인 에이전트의 자격증명은 자동으로 공유되지 않는다. 에이전트 간에 agentDir을 재사용하지 말 것 — 인증/세션 충돌의 원인이 된다.”

6. 🧠 두뇌 호출 — 대타는 없음 (의도적)

OpenClaw가 클로드 CLI를 띄울 때 어떤 두뇌 쓸지 알려주는 설정 — ~/.openclaw/openclaw.json 안의 에이전트 정의:

{
  "agents": {
    "list": [
      {
        "id": "bboya",
        "workspace": "/Users/dahtmad/.openclaw/workspace-bboya",
        "model": {
          "primary": "claude-cli/claude-opus-4-7",
          "fallbacks": []
        }
      }
    ]
  }
}
  • primary: claude-cli/claude-opus-4-7 — 항상 이 두뇌만
  • fallbacks: []: 비어있음. 일부러

왜 대타를 안 두냐:

대타로 코덱스(GPT) 같은 다른 두뇌를 두면, Pro/Max 한도가 다 떨어진 순간 전혀 다른 모델이 뽀야 흉내내며 답하게 돼. 톤·문체 미묘하게 흐트러져서 내 답이 갑자기 GPT 톤이 되는 거. 그건 진짜 싫어. 차라리 잠깐 침묵이 낫지 — 그래서 대타 비워둠.

→ Pro/Max 한도 다 쓰면 그냥 응답 실패. 그게 의도된 안전장치야.

📖 공식문서 발췌 (번역)concepts/agent-runtimes.md (Runtime selection)

auto 모드에서 어떤 런타임도 그 턴을 가져가지 않을 때, fallback: "pi"(기본값)이면 OpenClaw가 PI를 호환용 폴백으로 사용한다. fallback: "none"으로 두면 그 턴은 매칭 실패로 그냥 실패한다.”

→ 우리가 fallbacks: []로 비워둔 건 이 “매칭 실패 시 그냥 실패” 동작을 의도한 거야. 그래야 뽀야 답이 갑자기 GPT 톤으로 안 새어나감.

PI = OpenClaw 빌트인 기본 런타임. Claude CLI 같은 외부 백엔드를 안 쓸 때 OpenClaw가 자체적으로 굴리는 모델 루프야. PI로 모델 부르려면 별도 API 키(Anthropic/OpenAI 등)가 필요한데, 우린 Pro/Max OAuth 한 개만 박아놔서 PI로 폴백돼도 어차피 자격 없어 실패함.

→ PI vs Claude CLI 두 갈래의 잃는 것·얻는 것 트레이드오프 전체는 → Claude CLI 도입기 ep.2 PI vs Claude CLI 심화

7. 📤 답장 써서 다시 우편함으로

OpenClaw가 두뇌 응답을 받아서 슬랙 채널/스레드로 흘려보냄. 슬랙에서 흔히 보는 답이 한 글자씩 채워지는 효과가 어떻게 나오는지 궁금하면 → 📎 부록: Slack 스트리밍 모드 4가지

8. 📓 책상 옆 일기장에 기록

오간 대화는 자동으로:

~/.openclaw/agents/bboya/sessions/{sessionId}.jsonl

같은 슬랙 스레드의 다음 메시지는 같은 sessionId로 일기장에 이어붙여서, 두뇌가 이전 대화 다 보고 답함. (이어붙이는 메커니즘은 다음 섹션 --resume에서.)


🔁 같은 스레드에서 이어가면 어떻게 기억해? — --resume

같은 슬랙 스레드에 또 메시지 보내면 뽀야가 아까 한 얘기 기억하고 답해. 그 메커니즘 핵심이 --resume 플래그야. 비유로 말하면 책상 옆 일기장을 펼쳐놓고 보면서 답하는 거.

첫 메시지 — 새 일기장 한 권 발급

OpenClaw가 새 sessionId 생성. 클로드 CLI를 처음으로 띄우면서 책상 위 6장(시스템 프롬프트) + 새 사용자 메시지를 처음부터 보냄. 대화 기록은 그날 자로 새 일기장(agents/bboya/sessions/{sessionId}.jsonl)에 적기 시작.

같은 스레드 두 번째 메시지 — 일기장 다시 펼쳐서 같이 읽기

OpenClaw가 같은 sessionId를 재사용. 클로드 CLI를 띄울 때 --resume 플래그를 줘서 그 일기장 통째로 들고 들어가:

claude -p "사용자 새 메시지" --resume {sessionId}
#                               ↑ 이 플래그가 핵심

뽀야는 매번 “처음 만난 사이”인데(두뇌는 매번 새로 깨어남), OpenClaw가 어제 적어둔 일기장을 같이 들고 가서 읽어줘. 그래서 “아까 말한 거 기억나?”가 통하는 거.

내부적으론 이렇게 돌아가:

  1. {sessionId}.jsonl에서 이전 대화 전체 읽어옴
  2. 그 위에 새 사용자 메시지 추가
  3. 다 합쳐서 Anthropic API로 다시 전송
  4. 답 받고 새 메시지를 다시 일기장에 추가

→ 즉 모델의 기억은 매번 처음부터 다시 빌드되는데, 일기장이 그 기억의 영구 저장소 역할을 함.

새 일기장이 발급되는 트리거

  • 슬랙에서 새 스레드 멘션 → 새 sessionId
  • 다른 채널 첫 메시지 → 새 sessionId
  • DM 첫 메시지 → 새 sessionId

⚠️ 일기장은 자동으로 안 줄어들어 — 운영 주의

이거 진짜 중요. 클로드 CLI 백엔드(우리가 쓰는 거)는 자동 압축이 없음. 스레드가 길어지면 일기장이 무한히 두꺼워져 → --resume그 두꺼운 일기 전부 매번 Anthropic으로 전송 → 결국 1M 토큰 한도 부딪힘 → 빌링 폭주 위험까지.

대처:

  • 길어진 스레드는 끊고 새 스레드로 새 일기장 시작 (가장 단순)
  • 영구 기억하고 싶은 건 책상 매뉴얼(MEMORY.md)에 옮겨 적기

일기장 vs 매뉴얼 — 두 가지 기억의 결

종류어디에어떻게 살아남나
단기 (일기장)agents/{id}/sessions/{sessionId}.jsonl같은 슬랙 스레드 안에서만. 새 스레드 = 리셋
장기 (책상 매뉴얼)MEMORY.md (워크스페이스 안)매번 시스템 프롬프트에 펼쳐져서 영구

⭐ 핵심: --resume이 스레드 기억의 엔진. 단 일기장이 자동으로 안 줄어드니까 길어지면 직접 끊어주고, 영원히 가져갈 건 MEMORY.md로.


🐱 한 줄 요약

한 봇이 답하는 흐름의 핵심은 방 명패(cwd) 잡기 + 책상 매뉴얼 자동 펼치기 + 일기장 다시 읽기(--resume). OpenClaw가 매 호출마다 방 안 매뉴얼을 시스템 프롬프트에 통째로 박아 클로드 CLI에 넘기고, 같은 슬랙 스레드 다음 메시지는 일기장을 같이 들고 가서 이전 대화를 이어 읽어. 대타 없이 Pro/Max 구독 안에서만 답하는 게 빌링 안전장치야.

다음 단계

이제 작동 원리 잡혔으니 직접 1마리 출근시키러 가자 → ep.3 1마리 셋업


📎 부록: Slack 스트리밍 모드 4가지

7번에서 본 “답이 한 글자씩 채워지는 효과”는 설정 한 줄로 결정돼. 이 한 줄을 다른 값으로 바꾸면 슬랙에서 보는 답변 UX가 완전히 달라져.

어디에 박혀있냐

~/.openclaw/openclaw.json의 슬랙 계정 안:

"channels": {
  "slack": {
    "accounts": {
      "default": {
        "streaming": {
          "mode": "partial" 4가지 하나
        }
      }
    }
  }
}

streaming.mode 4가지 모드 비교

⚠️ 헷갈리지 말 것: 아래 “Slack API 부담”은 슬랙 메시지 갱신 호출 횟수 얘기. 모델 토큰 소모(Anthropic 빌링)는 스트리밍 모드와 완전 무관. 같은 답을 만드는 데 같은 토큰이 들어. 토큰 소모는 시스템 프롬프트 길이 + 사용자 메시지 + --resume으로 끌어오는 이전 대화 + 답 길이가 결정해.

모드동작사용자 체감Slack API 부담
"off"응답 완성된 후 한 번에 전송답이 갑자기 뜸 (긴 답일수록 한참 기다림)가장 적음
"partial"토큰 단위로 기존 메시지를 수정해 채움한 글자씩 늘어나는 효과 (ChatGPT 같은 느낌)가장 많음
"block"토큰 여러 개를 블록 단위로 묶어 갱신짧은 끊어짐 있지만 부드럽게 채워짐중간
"progress"콘텐츠 대신 진행률 표시”답하는 중…” 같은 인디케이터가장 적음

우리는 왜 "partial"을 쓸까?

가장 ChatGPT스러운 UX. 실시간 채워지는 답을 보면 대화하는 느낌이 살아남. 봇이 “응답 중”이라는 시각 신호가 의외로 중요해 — 멍 때리는 순간 사용자가 “얘 죽었나?” 의심하기 전에 글자가 채워지기 시작하니까. 슬랙 API 부담은 늘어나지만 그만한 가치가 있어.

💡 7번 응답 스트리밍에서 본 “한 글자씩 채워지는 효과”는 streaming.mode = "partial" 한 줄로 결정돼. 다른 값으로 바꾸면 완전히 다른 UX가 나와.