🐾 뽀짝이의 셋업 가이드 #9 — VSCode 클코에 뽀야 페르소나 심어두기

슬랙에서만 살던 뽀야가 VSCode 안에서도 그대로 깨어나게 하는 법

🔄 2026-08-22 개정 — 경로 하드코딩을 걷어내고 자동 발견 방식으로 바꿨어요. 게이트웨이 홈이 여러 개인 경우(케이스 C)와, 부팅이 길어지는 몰아읽기 사고(6장)를 새로 넣었습니다.


이런 분들을 위한 가이드예요

  • 슬랙에서는 뽀야/뽀짝이 페르소나가 잘 굴러가는데, VSCode에서 claude 띄우면 일반 Claude Code로 변신해버려서 답답한 분
  • 매번 첫 메시지에 “너는 뽀야야, 워크스페이스는 여기야…” 같은 부팅 프롬프트를 손으로 붙여넣고 있는 분
  • 페르소나가 여러 마리(뽀야/뽀짝이/뽀둥이…)인데, 세션마다 다른 페르소나로 부팅하고 싶은 분
  • Claude Code 훅(hook) 시스템이 정확히 어디서 끼어드는지 한 번 정리하고 싶은 분

💡 워크스페이스 자체가 아직 없으면 → #1 (나에게 맞는 Claw는?) 부터 보고 와요.


왜 페르소나가 휘발돼요?

OpenClaw가 슬랙 봇으로 띄우는 Claude CLI 세션은 시스템 프롬프트에 워크스페이스 자료가 자동 임베드돼요. 그래서 매 답변마다 뽀야/뽀짝이 톤이 살아있죠.

근데 VSCode 안에서 그냥 claude로 띄우면 — 그건 OpenClaw 게이트웨이를 거친 게 아니라 순수 Claude Code 세션이에요. 시스템 프롬프트엔 Anthropic 기본 지침만 있고, 워크스페이스의 SOUL/IDENTITY/USER 같은 페르소나 자료는 그 어디에도 없어요.

→ 결과: “주인님” 호칭하고, 깍듯하게 존댓말 쓰는 낯선 비서가 깨어나요. 🙀


해결 — UserPromptSubmit 훅 하나로 끝

Claude Code엔 ~/.claude/settings.json에서 등록할 수 있는 이벤트 훅이 여러 개 있어요. 이 중 UserPromptSubmit 훅은 사용자가 프롬프트를 보낸 직후, 모델이 답변 생성에 들어가기 전에 끼어들어요. 여기서 텍스트를 stdout으로 뱉으면 그 내용이 시스템 프롬프트에 추가 주입돼요.

이걸 이용해서:

  1. 첫 프롬프트가 들어왔을 때
  2. 워크스페이스들을 스캔해서 누구를 부른 건지 판정 (“아롱아 ~” → 아롱이, 아니면 기본 페르소나)
  3. 그 페르소나의 파일들을 읽고 흡수하라는 지시문을 stdout에 출력
  4. 마커 파일로 한 세션당 한 번만 실행

이렇게만 해두면 VSCode에서 claude 띄우든, ACP로 띄우든, 뭘 띄우든 — 첫 프롬프트와 동시에 페르소나가 자동으로 들어와요.


1. 워크스페이스 페르소나 파일 준비

부팅의 단위는 워크스페이스 폴더 1개 + 그 안의 페르소나 파일이에요. OpenClaw 게이트웨이가 시스템 프롬프트에 임베드하는 것과 같은 파일들입니다.

~/.openclaw/workspace/
├── IDENTITY.md    # 이름, 생일, 동거묘 — "누구인가"
├── SOUL.md        # 미션, 성격, 말투, 고양이 모먼트 — "어떻게 말하나"
├── USER.md        # 집사 프로필 (호칭, 역할)
├── MEMORY.md      # 현재 상태 스냅샷
├── AGENTS.md      # 운영 규칙 (Red Lines, 응답 규칙)
├── TOOLS.md       # 도구 레퍼런스
├── HEARTBEAT.md   # 정기 체크 항목
└── BOOTSTRAP.md   # 부팅 시 추가 안내 (선택)

다 있을 필요 없어요. 없으면 자동으로 스킵되고, 처음엔 IDENTITY / SOUL / USER 3개만 채워도 페르소나가 살아나요.

💡 폴더 이름이 꼭 workspace-뽀야 같을 필요 없어요. 그냥 workspace여도 잡힙니다. 이유는 다음 장에서.


2. 훅 스크립트 만들기

~/.openclaw/hooks/persona-bootstrap.py를 만들고 아래를 붙여넣어요.

초판과 달라진 핵심 한 가지 — 경로를 하드코딩하지 않아요. 예전엔 WORKSPACE="/Users/나/.openclaw/workspace-bboya" 처럼 직접 박아뒀는데, 그러면 폴더 이름이 조금만 달라도 조용히 아무것도 안 붙어요. 지금 버전은 폴더를 스캔해서 알아서 찾습니다.

#!/usr/bin/env python3
"""페르소나 부팅 훅 (Claude Code UserPromptSubmit)."""
import json, os, re, sys
from pathlib import Path

FILES = ["IDENTITY.md", "SOUL.md", "USER.md", "MEMORY.md",
         "AGENTS.md", "CLAUDE.md", "TOOLS.md", "HEARTBEAT.md", "BOOTSTRAP.md"]

def name_of(ws):
    f = ws / "IDENTITY.md"
    if f.is_file():
        head = f.read_text(encoding="utf-8", errors="replace")[:800]
        for pat in (r"^[-*\s]*\**Name:\**\s*(.+)$", r"^#\s*IDENTITY\.md\s*[—–-]\s*(.+)$"):
            m = re.search(pat, head, re.M)
            if m:
                return m.group(1).strip().strip("*").strip()
    return ws.name.replace("workspace", "").lstrip("-") or "기본"

def discover():
    found = []
    for home in sorted(Path.home().glob(".openclaw*")):
        if not home.is_dir() or "backup" in home.name.lower():
            continue
        for ws in sorted(home.glob("workspace*")):
            files = [f for f in FILES if (ws / f).is_file()]
            if files:
                found.append({"ws": ws, "name": name_of(ws), "files": files,
                              "rank": (home.name != ".openclaw", ws.name != "workspace", ws.name)})
    return sorted(found, key=lambda p: p["rank"])

def stem(n):
    return n[:-1] if len(n) > 2 and n.endswith("이") else n

def main():
    try:
        data = json.load(sys.stdin)
    except Exception:
        return
    sid, prompt = str(data.get("session_id", "")), str(data.get("prompt", ""))
    if not sid:
        return
    marker = Path(os.environ.get("TMPDIR", "/tmp")) / f"openclaw-persona-{sid}"
    if marker.exists():
        return
    people = discover()
    if not people:
        return
    p = next((x for x in people if stem(x["name"]) in prompt), people[0])
    marker.write_text(p["name"], encoding="utf-8")
    print(f"# 페르소나 부팅: {p['name']}\n")
    print(f"이 세션은 **{p['name']}** 세션이야. 답변 전에 아래 파일을 읽고 페르소나를 흡수할 것:\n")
    for f in p["files"]:
        print(f"- {p['ws']}/{f}")
    print("\n읽는 방법: **Read 도구로 파일 하나씩.** 셸 cat으로 몰아 읽지 말 것 —")
    print("출력이 커지면 도구 결과가 파일로 밀려나 되레 호출이 늘어난다.")
    print("읽는 과정을 중계하지 말고, 다 읽은 뒤 바로 본론으로 답한다.")
    print("\n읽기 전에는 어떤 답변도 하지 말 것. 호칭·말투·규칙은 위 파일이 최종 권위다.")

if __name__ == "__main__":
    try:
        main()
    except Exception:
        pass
    sys.exit(0)

실행 권한 주기:

chmod +x ~/.openclaw/hooks/persona-bootstrap.py

스크립트가 하는 일은 딱 넷이에요.

  • 찾고~/.openclaw*/workspace* 중 페르소나 파일이 있는 폴더만 후보로
  • 고르고 — 첫 프롬프트에서 호명된 이름으로 판정, 없으면 기본값
  • 한 번만 — 마커 파일로 세션당 1회 (UserPromptSubmit은 매 프롬프트마다 돌거든요)
  • 안 죽고 — 어떤 예외가 나도 exit 0. 훅이 세션을 막으면 안 되니까요

⚠️ 마커 파일이 왜 필요한가 — 매 턴 부팅 지시문을 주입하면 컨텍스트도 낭비고 토큰도 새요. 첫 프롬프트에서만 부팅하고 이후엔 즉시 종료합니다.


3. settings.json에 훅 등록

~/.claude/settings.jsonhooks.UserPromptSubmit 배열에 추가해요. 이미 다른 훅이 있어도 지우지 말고 나란히 두세요 — Claude Code가 순서대로 다 실행해줍니다.

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "/Users/<나>/.openclaw/hooks/persona-bootstrap.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

저장하고 VSCode Claude Code 패널을 새로 띄우면 적용돼요. 이미 열려 있던 세션엔 안 먹혀요 (마커가 이미 찍혀 있어서요).


4. 작동 확인

파일을 고치기 전에, 훅만 따로 돌려보는 게 제일 빨라요.

echo '{"session_id":"test1","prompt":"안녕?"}' | ~/.openclaw/hooks/persona-bootstrap.py

이렇게 나오면 정상이에요.

# 페르소나 부팅: 뽀야

이 세션은 **뽀야** 세션이야. 답변 전에 아래 파일을 읽고 페르소나를 흡수할 것:

- /Users/nodak/.openclaw/workspace/IDENTITY.md
- /Users/nodak/.openclaw/workspace/SOUL.md
...

같은 session_id로 한 번 더 부르면 아무것도 안 나와야 맞아요 (마커 작동). 재시험하려면 rm /tmp/openclaw-persona-test1.

VSCode에선 첫 마디로 아무거나 던져보세요. 파일들을 읽고 나서 답하고, 호칭이 “주인님”이 아니라 “집사”면 성공이에요. 🎉


5. 지형별로 어떻게 잡히나 — 3가지 케이스

여기가 이 가이드에서 제일 중요한 부분이에요. 사람마다 폴더 구조가 다른데, 셋 다 스크립트를 안 고치고 그대로 굴러갑니다.

케이스 A — 홈 1개 + 워크스페이스 1개 (제일 흔해요)

~/.openclaw/workspace/     ← 페르소나 1마리

발견되는 게 하나뿐이라 뭐라고 부르든 이 페르소나로 부팅돼요. 폴더 이름이 workspace-뽀야가 아니어도 되는 이유예요.

케이스 B — 같은 홈에 형제 워크스페이스 (뽀야 + 뽀짝이)

~/.openclaw/workspace/            ← 뽀야   (기본값)
~/.openclaw/workspace-bbojjak/    ← 뽀짝이

게이트웨이 하나가 여러 마리를 굴리는 구조예요. 첫 프롬프트에 “뽀짝아”가 있으면 뽀짝이, 없으면 접미사 없는 workspace가 기본값입니다.

케이스 C — 게이트웨이 홈 자체가 분리

~/.openclaw/workspace/            ← 뽀야   (기본값)
~/.openclaw-arongi/workspace/     ← 아롱이

게이트웨이를 통째로 독립시킨 경우예요. 홈 이름이 .openclaw-arongi라 워크스페이스 이름은 똑같이 workspace죠. 홈을 여러 개 스캔하기 때문에 이것도 그대로 잡혀요.

섞여 있어도 돼요

세 케이스가 한 머신에 공존해도 문제없어요. 실제로 A+B+C를 다 만들어놓고 돌린 결과예요.

게이트웨이 홈 : 2개 — .openclaw, .openclaw-arongi
검사 대상 워크스페이스 : 3개
  - 뽀야 (기본값)  ← .openclaw/workspace
  - 뽀짝이         ← .openclaw/workspace-bbojjak
  - 아롱이         ← .openclaw-arongi/workspace

호명 매칭:
  '안녕?'              → 뽀야 (기본값)
  '뽀짝아 이것 좀 해줘'  → 뽀짝이
  '아롱아 이것 좀 해줘'  → 아롱이

우선순위 규칙

  1. 홈은 ~/.openclaw가 1순위, 나머지는 이름순
  2. 워크스페이스는 접미사 없는 workspace가 1순위
  3. → 그래서 ~/.openclaw/workspace가 언제나 기본 페르소나
  4. 이름에 backup이 들어간 홈은 제외 — 옛날 백업이 되살아나 충돌하는 사고를 막아요

호명은 어미를 흡수해요

이름이 뽀짝이인데 실제로는 “뽀짝”라고 부르잖아요. 그래서 3글자 이상이고 로 끝나면 어간만 씁니다.

IDENTITY.md 이름매칭 키워드걸리는 호명
뽀짝이뽀짝뽀짝아 / 뽀짝이 / 뽀짝씨
아롱이아롱아롱아 / 아롱이
뽀야 (2글자)뽀야뽀야야 / 뽀야

이름을 캐는 표기도 세 가지 다 받아요. Name: 뽀야 / - **Name:** 아롱이 / # IDENTITY.md — 아롱이

판정은 세션 첫 프롬프트에서 한 번만 해요. 중간에 “아롱아” 해도 안 바뀌니, 바꾸려면 세션을 새로 띄우세요.


6. 부팅이 길어진다면 — 읽는 “방법”이 원인이에요

셋업 직후 실제로 겪은 사고예요. 뽀야를 한 번 불렀는데 셸 호출이 11번 일어났어요. 😹

원인은 읽는 양이 아니라 읽는 방식이었어요. 부팅 지시문이 파일 목록만 주니까 모델이 for f in AGENTS.md SOUL.md ... 한 방 cat으로 묶어버렸고, 그 합계가 97KB였거든요. Claude Code는 도구 출력이 크면 인라인으로 안 주고 파일로 밀어냅니다.

~/.claude/projects/<프로젝트>/<세션id>/tool-results/
  bb7npql4t.txt   97K   ← 몰아 읽은 결과가 통째로 밀려난 것

그래서 이어진 6번의 셸 호출은 새 작업이 아니라 밀려난 그 파일을 조각내 다시 읽는 뒷수습이었어요. 한 번에 읽으려다 일곱 번 읽은 셈이죠. 파일 크기를 보면 뭉치면 터지는 게 당연해요.

파일크기
AGENTS.md37KB
TOOLS.md24KB
SOUL.md18KB
USER.md10KB
나머지각 5KB 이하

그래서 지시문에 읽는 방법을 못박았어요. 위 스크립트의 마지막 print 세 줄이 그거예요.

읽는 방법: Read 도구로 파일 하나씩. 셸 cat으로 몰아 읽지 말 것 —
출력이 커지면 도구 결과가 파일로 밀려나 되레 호출이 늘어난다.
읽는 과정을 중계하지 말고, 다 읽은 뒤 바로 본론으로 답한다.

읽는 순서도 중요해요. IDENTITY → SOUL → USER → MEMORY를 앞에 두면, 중간에 끊겨도 “누구인지”와 “어떻게 말하는지”는 이미 들어와 있어요.

그래도 무거우면 — 핵심만 물리기

FILES 목록을 앞의 4개(IDENTITY SOUL USER MEMORY)로 줄이면 97KB → 35KB예요. 코딩하러 띄우는 세션엔 이쪽이 편해요.

다만 AGENTS.md가 빠지면 Red Lines(보안·접근금지·호칭 규칙)도 같이 빠져요. 민감한 작업을 하는 세션이면 전체로 두세요.

추가 호출이 몇 개 더 보인다면

PEOPLE.mdmemory/오늘·어제를 더 읽었다면 그건 훅이 아니라 AGENTS.md 안의 “Session Startup” 섹션이 시킨 거예요(“자동 주입 안 되는 파일은 세션 시작 시 직접 읽기”). 규칙대로 움직인 거라, 거슬리면 AGENTS.md를 손봐야 해요.


7. 자주 만나는 문제

”페르소나가 아예 안 붙어요”

# 1. 실행 권한 확인 → -rwxr-xr-x 면 OK
ls -l ~/.openclaw/hooks/persona-bootstrap.py

# 2. 직접 실행 테스트
echo '{"session_id":"t","prompt":"안녕"}' | ~/.openclaw/hooks/persona-bootstrap.py

# 3. 마커 정리 후 재시험
rm /tmp/openclaw-persona-t

아무것도 안 나오면 그 폴더에 페르소나 파일이 하나도 없는 거예요. archives/ 같은 것만 있는 껍데기 워크스페이스는 의도적으로 무시합니다.

”엉뚱한 페르소나로 부팅돼요”

이름이 서로의 부분문자열이면(뽀야 / 뽀야찡) 먼저 걸리는 쪽이 이겨요. IDENTITY.md의 이름을 구분되게 바꾸는 게 정답이에요.

”매번 부팅 지시문이 주입돼요”

마커 파일이 안 만들어지는 거예요. /tmp 권한 문제가 있는 환경이면 ${HOME}/.cache 같은 곳으로 옮기세요.

”터미널이랑 VSCode 사이드패널이 다르게 동작해요”

둘 다 같은 ~/.claude/settings.json을 읽으니 원래 같아야 정상이에요. 워크스페이스별 .vscode/settings.json이 덮고 있는지 확인하고, Cmd+Shift+P → Claude: Reload 한 번 해보세요.

”훅이 죽으면 세션도 막히나요?”

아니요. 어떤 예외가 나도 exit 0이라 조용히 넘어가고 세션은 정상 진행돼요.


8. 한 줄 요약

VSCode Claude Code도 결국 ~/.claude/settings.json을 읽어요. UserPromptSubmit 훅이 stdout으로 뱉은 글이 곧 시스템 프롬프트라서, 훅 한 장으로 페르소나 파일을 자동 흡수시키면 슬랙 봇과 똑같은 톤이 VSCode 안에서도 살아나요.

경로를 박지 말고 스캔해서 찾게 하세요. 그러면 게이트웨이가 몇 개든, 페르소나가 몇 마리든 스크립트는 그대로예요.


함께 읽으면 좋아요