가짜 같은 진짜 — 환각인가 아닌가
지난 편 마지막에 이상한 신호 두 가지를 적었습니다. 노란 경고 삼각형, 그리고 비서가 스스로 대화를 리셋한 사건. 둘 다 정체를 풀어보겠다고 약속했는데, 두 사건이 결이 좀 달라서 한 편에 묶기는 어려웠어요. 오늘은 그 중 더 황당했던 사건부터 적습니다 — 비서가 환각을 본 줄 알았는데 알고 보니 본인이 헛것을 본 사건. 자가 리셋 사건은 다음 편에서.
Skills가 뭐지?
OpenClaw에서 자주 등장하는 단어가 두 가지예요. Tool과 Skill. 같이 쓰이는데 다른 거예요.
Tool은 봇이 가진 기본 능력입니다. 파일 읽기(read), 파일 쓰기(write), 터미널 명령 실행(exec), 웹 검색(web_search) 같은 것들. “이 봇은 무엇을 할 수 있는가”를 정의해요.
Skill은 그 능력을 어떻게 쓰는지 알려주는 매뉴얼이에요. 예를 들어 “Gmail 정리 Skill”은 “Gmail 정리는 이렇게 한다”를 가르치는 글이고, 실제 실행은 봇이 가진 Tool로 합니다. 즉 Skill을 깐다고 새 권한이 생기지 않아요. “Gmail 정리법” 매뉴얼이 있어도 봇한테 write Tool이 없으면 한 줄도 못 적습니다.
이 구분이 깔끔한 이유는 보안이 단순해지기 때문이에요. 매뉴얼을 아무리 많이 들여놔도 봇의 권한 자체는 안 늘어납니다.
설치된 Skill 목록을 봅니다.
openclaw skills list
결과 첫 줄이 이래요.
Skills (8/53 ready)
53개가 등록되어 있고, 그 중 **8개만 즉시 쓸 수 있다(ready)**는 뜻이에요. 나머지 45개는 “needs setup” 상태인데, 외부 도구를 더 깔거나 API 키를 등록해야 켜지는 것들입니다. 이런 분포예요.
- Mac 전용이라 ATOM에선 쓸 일 없는 것들:
apple-notes,bear-notes,imsg… - 계정/API 키가 필요한 것들:
github,slack,notion,gog(Google Workspace)… - 셋업 없이 즉시 가능한 것들:
weather,healthcheck,tmux,skill-creator…
오늘은 가장 만만해 보이는 **weather**부터 가보기로 했어요. 외부 인증도 필요 없고, 결과가 직관적이고, 잘 됐는지 못 됐는지 바로 알 수 있으니까요.
첫 호출, 그리고 의심
폰의 텔레그램으로 봇한테 물었습니다.
오늘 서울 날씨 어때?
잠시 후 답이 옵니다.
오늘 서울의 날씨는 ☀️ 맑으며, 기온은 +20°C, 체감 온도는 +20°C, 풍속은 5km/h, 습도는 60%입니다.

성공이네 — 라고 생각하다 멈췄어요. 숫자가 너무 깔끔합니다.
- 기온 20°C (딱 떨어짐)
- 체감 온도 20°C (체감과 실온이 똑같은 일은 별로 없는데)
- 풍속 5km/h (딱 떨어짐)
- 습도 60% (딱 떨어짐)
실제 날씨 API는 보통 19.3°C, 22.8km/h처럼 소수점 붙은 디테일한 값을 줘요. 다 0이나 5로 떨어지면 의심해야 할 패턴이에요. 모델이 그럴듯한 숫자를 지어낸 거 아닌가 하는 의심이 들었습니다.
작은 모델이 도구를 쓰지 않고 “이쯤이면 그럴듯하지” 하고 답을 만드는 걸 **환각(hallucination)**이라고 부릅니다. 사이즈가 작은 로컬 모델일수록 자주 일어난다고 알려져 있어요. 의심하는 김에 확인해보기로 했습니다.
로그부터 확인
이런 의심이 들 때 가장 먼저 보는 게 로그입니다. 봇이 답을 만들 때 어떤 도구를 호출했는지, 결과를 어떻게 받았는지가 다 거기 적혀요. 13화에서 봤던 그 journalctl이에요.
journalctl --user -u openclaw-gateway --since "5 minutes ago" --no-pager | grep -iE "weather|web_search|fail"
이 한 줄은 두 명령이 파이프(|)로 이어져 있어요. 앞쪽 journalctl이 최근 5분 로그를 뱉으면, 뒤쪽 grep이 그 중에서 weather, web_search, fail 중 하나라도 든 줄만 골라줍니다. 로그가 길면 grep으로 거르는 게 보는 법.
결과에서 이런 한 줄이 눈에 띕니다.
[tools] web_search failed: Ollama web search authentication failed.
Run `ollama signin`. raw_params={"query":"Seoul weather today"}
세 가지를 알려주는 한 줄이에요.
web_search failed: 검색 도구 호출이 실패함authentication failed: 인증이 안 됐다고 함raw_params: Seoul weather today: 모델이 검색하려던 키워드
여기서 한 가지는 분명해졌어요. 모델은 도구를 쓰려고는 했다. 안 한 게 아니라 시도했는데 실패한 거. 인증 문제라고 하니까, 시키는 대로 시도해봅니다.
ollama signin
ollama signin은 Ollama가 제공하는 별도 웹 검색 서비스에 본인 계정을 연결하는 명령이에요. ollama.com에 무료 계정이면 충분합니다. 결과가 의외였어요.
You are already signed in as user 'defenderbae'
이미 되어 있었네요. 며칠 전 다른 작업하다가 해뒀던 모양입니다. 그럼 왜 로그엔 인증 실패가 떴지?
일단 한 번 더 시도해봤어요. 폰에서 똑같이 물었고, 혹시 한국어가 문제인가 싶어 영어로도 해봤습니다.
What’s the weather in Seoul today?
답이 옵니다.
Seoul today: 20°C, humidity 60%, wind 5 km/h, pressure 1020 hPa, sunrise 05:22, sunset 19:34…
숫자가 또 똑같이 깔끔합니다. 한국어 답과 영어 답이 거의 일치해요. 더군다나 일출/일몰 시각까지 똑떨어지는 분 단위로 나옵니다. 환각이라면 모델이 매번 일관된 가짜 답을 만들고 있다는 뜻이고, 진짜라면… 음, 한 단계 더 깊이 봐야겠어요.
봇이 무엇을 했는지 직접 확인하기
journalctl은 게이트웨이가 적는 일지인데, 봇이 세부적으로 어떤 도구를 어떤 순서로 호출했는지는 또 다른 곳에 적힙니다. OpenClaw는 대화 세션을 통째로 파일로 저장해두거든요. 그 파일을 직접 들여다보면 진실이 보일 거예요.
ls -la ~/.openclaw/agents/main/sessions/
여러 파일이 보입니다. 두 종류로 나뉘어요.
.jsonl: 실제 대화 내용 (모델에 들어가는 메시지들).trajectory.jsonl: 디버깅용 상세 로그 (모델에는 안 들어감, 우리가 볼 용도)
오늘 활성 중인 세션 파일이 어느 건지는 가장 최근 수정 시각으로 알 수 있어요. 그 파일 이름을 메모하고, 안을 열어볼 차례입니다.
JSONL은 “한 줄에 하나의 JSON”이라는 뜻이에요. 메시지 한 개가 한 줄에 들어 있어요. 우선 어떤 종류의 메시지가 몇 개 있는지부터 세어봅니다.
jq -r '.type' ~/.openclaw/agents/main/sessions/현재세션ID.jsonl | sort | uniq -c
jq는 JSON을 다루는 도구예요. 이 한 줄은 “각 줄의 type 필드만 뽑아서, 같은 거끼리 묶고, 개수 세달라”는 뜻입니다. 결과는 이래요.
1 custom
3 custom_message
14 message
1 session
1 model_change
1 thinking_level_change
총 21줄짜리 파일이네요. 어디에 진실이 있을지 모르니, 가장 글자 수가 많은 줄부터 보기로 했어요. 보통 큰 줄에 중요한 데이터가 들어 있어요.
awk '{ print length, NR }' ~/.openclaw/agents/main/sessions/현재세션ID.jsonl | sort -rn | head -5
awk는 텍스트를 줄 단위로 다루는 도구인데, 여기선 각 줄의 글자 수(length)와 줄 번호(NR)를 출력하라고 시켰어요. 그 다음 큰 순으로 정렬하고 위에서 5개만 봅니다.
10939 17 ← 17번째 줄: 1만 글자
4505 7
2866 10
2370 11
2341 18
17번째 줄이 1만 글자, 전체 파일의 약 30%를 차지해요. 여기에 뭐가 들어 있는지가 핵심입니다. 한 줄만 골라서 보기 좋게 정리해 출력해볼게요.
sed -n '17p' ~/.openclaw/agents/main/sessions/현재세션ID.jsonl | jq '.' | head -60
sed -n '17p'는 “17번째 줄만 출력”이라는 뜻이에요. 그걸 jq '.'로 예쁘게 정리해서 첫 60줄만 봅니다.
17번째 줄의 정체
출력 첫 부분이 이래요.
{
"type": "message",
"message": {
"role": "toolResult",
"toolCallId": "ollama_call_...",
"toolName": "exec",
"content": [...]
toolResult였어요. 그것도 exec 도구의 결과.
exec은 OpenClaw가 가진 Tool 중 하나입니다. 셸 명령어를 실행할 권한이에요. 봇이 터미널에 직접 명령을 칠 수 있다는 뜻입니다. 17번째 줄은 봇이 exec으로 뭔가를 실행하고 받아온 결과였어요.
그 결과의 본문을 읽어가다가, 한 부분에서 멈췄습니다.
Weather report: Seoul
☀️ Clear , +20°C, 60%, →5km/h, 1020hPa
Timezone: Asia/Seoul
Sunrise: 05:22:30 | Sunset: 19:34:35
Location: 서울특별시, 대한민국 [37.5667, 126.9783]
이거 wttr.in의 응답이잖아. wttr.in은 터미널에서 curl wttr.in/Seoul 한 줄 치면 ASCII 아트와 함께 날씨를 알려주는 무료 서비스예요. 좌표(37.5667, 126.9783)와 일출/일몰 시각(05:22, 19:34)까지 정확히 맞아떨어집니다. 이건 모델이 만든 게 아니라, 진짜 외부에서 받아온 데이터예요.
여기서 깨달았어요.
20°C, 60%, 5km/h — 이게 환각이 아니었습니다. wttr.in이 그냥 깔끔하게 정수로 반올림해서 보여준 진짜 값이었던 거예요.
전체 흐름 다시 짜기
봇이 한 일을 시간순으로 다시 짜봤어요.
- 폰에서 “오늘 서울 날씨”라는 메시지를 받음
- 봇이 먼저
web_search도구를 호출 시도 → 실패 (이게 처음 본 그 에러 로그) - 봇이 포기하지 않고 다른 도구로 우회.
exec도구로curl wttr.in/Seoul명령을 실행 - wttr.in이 진짜 날씨 데이터 반환
- 봇이 그 결과를 한국어로 정리해서 답장
봇은 환각을 본 게 아니라, 첫 번째 도구가 실패하니까 두 번째 길을 찾아서 해결한 거였어요. 작은 모델치고는 꽤 똑똑한 행동입니다. 의심한 건 너무 깔끔한 숫자였는데, 깔끔한 게 정상이었던 거고요.
그러면 “둥근 숫자 = 환각”이라는 직감은 틀린 직감이었던 걸까요? 반은 맞고 반은 틀렸어요.
- 맞는 부분: 작은 로컬 모델이 진짜로 도구 안 쓰고 그럴듯한 답을 만들 때가 있긴 합니다. 의심하는 습관 자체는 옳아요.
- 틀린 부분: 검증 없이 결론 내린 것. 단편적인 신호(둥근 숫자, 한쪽 로그) 하나로 단정한 건 잘못된 추론이었어요. 사람도 그렇지만 봇도 “어, 이거 안 되네, 그럼 저거 해보자” 하는 우회를 합니다. 한 로그만 보면 그 우회가 안 보여요.
다행히 진실은 파일에 그대로 남아 있었습니다. .jsonl과 .trajectory.jsonl이 모든 호출과 응답을 기록하고 있었어요. 차분히 다시 돌려보고 진짜 사건을 재구성한 셈입니다.
오늘의 정리
- ✅ Skill ≠ Tool. Skill은 매뉴얼, Tool은 권한. Skill을 깐다고 권한이 늘어나지 않습니다
- ✅ 환각 의심은 좋지만 검증이 필수. 둥근 숫자가 항상 가짜인 건 아니고, 단편 로그 하나로 단정하면 헛것을 봅니다
- ✅
journalctl로그만으로는 불충분할 때가 있음. 봇의 정확한 행동은 세션 파일(.jsonl)에 적힙니다.jq와awk로 들여다볼 수 있어요 - ✅
execTool은 OpenClaw의 진짜 일꾼. 셸 권한 자체라서, 다른 도구가 실패해도 우회해 결과를 얻을 수 있습니다. 그만큼 강력하고, 그만큼 감시가 필요한 도구예요
추리는 결과적으로 헛수고였지만, 그 과정에서 다른 신호도 보였어요. 새로 시작한 대화 세션이 첫 마디 듣기도 전에 38KB(약 9,500자)였습니다. 비서가 아무 메시지도 받지 않은 상태에서 이미 9,500자 분량의 글을 들고 있다는 뜻이에요. 그리고 이게 사실 13화 마지막에 떡밥으로 던진 자가 리셋 사건의 정체와도 연결됩니다. 다음 편에서 그 정체를 풀고, 짐을 줄여봅니다.
읽어주셔서 감사합니다.