비서가 첫 마디 전부터 9,500자를 들고 있다는 사실
지난 편 끝에서 환각인 줄 알았던 답이 사실 진짜였다는 결론에 도착했습니다. 다만 그 추리 과정에서 다른 신호가 자꾸 보였어요. 새로 시작한 대화 세션이 첫 마디 듣기도 전에 38KB(약 9,500자)였다는 것. 사람이 매번 9,500자짜리 사전을 끼고서 인사를 받으면 좀 이상하잖아요. 그리고 이게 사실 13화 마지막에 떡밥으로 던진 자가 리셋 사건과도 연결되어 있어요. 오늘은 그 연결고리를 풀어봅니다.
13화 떡밥의 정체: 자가 리셋은 왜 일어났나
지난 편 추리 과정에서 로그를 여러 번 봤어요. 같은 도구로 다시 한 번 들어가봅니다.
journalctl --user -u openclaw-gateway --since "1 hour ago" --no-pager | grep -iE "context|overflow|compaction|reset"
journalctl은 13화에서 처음 만난, systemd가 적어두는 일지를 보는 명령이에요. 옵션을 풀어보면 이렇습니다.
| 부분 | 의미 |
|---|---|
--user | 내 사용자 서비스 일지 중에서 |
-u openclaw-gateway | openclaw-gateway 서비스 것만 |
--since "1 hour ago" | 최근 1시간 내 기록만 |
--no-pager | 페이지 넘김 없이 한 번에 출력 |
| grep -iE "..." | 그 출력 중에서 정해진 단어가 든 줄만 거름 |
뒤에 붙은 grep이 이번 편의 단서를 거르는 역할을 해요. context, overflow, compaction, reset — 자가 리셋 사건의 단서가 될 만한 단어들입니다. 14화에선 weather나 web_search 같은 단어로 걸렀고, 오늘은 다른 단어로 같은 도구를 다시 쓰는 거예요. 로그가 길면 grep으로 거르는 게 보는 법.
이런 줄들이 끼어 있었어요.
[context-overflow-precheck] estimatedPromptTokens=16653
promptBudgetBeforeReserve=12768 overflowTokens=3885
error=Context overflow: prompt too large for the model (precheck).
무슨 말이냐면, 모델한테 들어가는 글의 양이 모델이 받을 수 있는 한계를 초과했다는 뜻이에요. 16,653 토큰이 한계인 12,768 토큰을 3,885 토큰 넘었다고 적혀 있어요.
💡 토큰은 모델이 글을 받아들이는 단위예요. 한국어는 대략 한 글자가 1
2 토큰, 영어는 한 단어가 12 토큰. 16,653 토큰이면 한국어로 대략 1만 자 정도입니다.
그 다음 줄들이 사건의 진행을 보여줘요.
context overflow detected (attempt 1/3); attempting auto-compaction
auto-compaction succeeded; retrying prompt
context overflow detected (attempt 2/3)
auto-compaction failed: Already compacted
Auto-compaction failed. Restarting session.
흐름을 풀어보면 이래요.
- 컨텍스트가 한도를 넘음
- 봇이 자동 압축(compaction) 시도 — 그동안의 대화를 요약해서 짧게 줄이려고 함
- 1차 압축 성공, 재시도
- 또 한도 초과
- 두 번째 압축은 실패 — “이미 압축했음”
- 결국 대화 세션 자체를 리셋
이게 13화 마지막에 적은 “다이어트했는데 몸무게가 늘어난 사건”의 정체예요. 비서가 짐을 줄이려고 시도하다 또 시도하다 결국 처음부터 다시 시작해버린 거.
그리고 여기서 11화가 떠오릅니다.
11화의 32K가 부메랑이 되어 돌아왔다
11화에서 본인은 1M의 함정을 겪었어요. Nemotron이 이론적으로는 1M 컨텍스트를 지원하지만, 실제로 1M을 띄우면 INT_MAX 오버플로로 크래시. 그래서 한참 디버깅한 끝에 Modelfile로 새 모델을 만들고 openclaw.json까지 직접 고쳐서 32K로 강제로 줄여놓았어요.
그 결정이 11화 때는 정답이었어요. 1M으로는 모델이 안 떴으니까. 32K 정도면 충분히 크기도 했고요.
근데 지금 보세요.
estimatedPromptTokens=16653
16,653 토큰. 32K 한계의 절반을 한참 넘은 값이에요. 13화에서 텔레그램 연결한 다음, 그 봇한테 몇 마디 던졌더니 어느새 16K가 차버린 거예요.
근데 이상한 게 있어요. 어제 텔레그램으로 비서랑 나눈 말은 고작 몇 마디였습니다. “안녕”, “오늘 날씨”, “고마워” 수준이었어요. 그 정도로 16,000 토큰이 찰 리가 없는데요? 사용자 대화 외에 뭔가가 자리를 차지하고 있는 게 분명합니다.
세션 파일이 얼마나 큰지부터
지난 편에서 본 그 세션 파일들로 다시 돌아옵니다. 이번엔 17번째 줄이 아니라 전체 크기를 봐요.
ls -la ~/.openclaw/agents/main/sessions/
-la는 “자세히(l), 숨김 파일까지(a)” 보여달라는 뜻이에요. 결과에서 .jsonl 파일들을 골라보면 이래요.
| 세션 시작 시점 | .jsonl 크기 | 토큰 환산 (대략) |
|---|---|---|
| 5월 13일 첫 세션 | 80KB | 20,000 |
| 5월 13일 두 번째 | 121KB | 30,000 |
| 5월 15일 (리셋이 발생한 세션) | 64KB | 16,000 |
| 5월 16일 새 세션 (현재 활성) | 38KB | 9,500 |
대략 4글자가 1토큰이에요. 그러면 새로 시작한 세션이 이미 38KB, 약 9,500 토큰입니다. 봇한테 “안녕” 한 마디 던지기도 전에, 비서는 이미 9,500자 분량의 글을 들고 있다는 뜻이에요.
비교를 위해: 13화에서 보낸 “안녕 넌 누구야”라는 메시지와 그 답장은 합쳐서 100자도 안 됩니다. 즉 9,500자 중 사용자 대화 비중은 1% 정도에 불과해요. 나머지 99%가 다른 무엇인가입니다.
그 “다른 무엇”의 정체를 보러 갑니다.
어느 줄이 자리를 차지하나
세션 파일 안에 어떤 종류의 메시지가 몇 개 들었는지는 지난 편에서 이미 봤어요.
1 custom
3 custom_message
14 message
1 session
1 model_change
1 thinking_level_change
같은 명령으로 다시 확인할 수 있어요.
jq -r '.type' ~/.openclaw/agents/main/sessions/현재세션ID.jsonl | sort | uniq -c
이름만 봐도 정체가 분명한 줄들이 있어요. session(메타데이터), model_change(어떤 모델 쓰는지), message(실제 주고받은 대화). 의심 가는 건 custom 1개예요. 이름부터 “OpenClaw가 자기 용도로 끼워 넣는 자리”라는 인상이 강합니다.
custom 줄이 정말 큰지 확인합니다. 지난 편에 본 가장 큰 줄들 목록을 다시 가져오면:
10939 17 ← 17번째 줄: 1만 글자 (지난 편에서 본 toolResult)
4505 7 ← 7번째 줄: 4,500자
2866 10
2370 11
2341 18
17번째 줄은 이미 정체를 알아요 — exec의 결과. 그러면 그 다음으로 큰 **7번째 줄(4,500자)**이 의심 후보입니다. 어떤 종류인지 봅니다.
sed -n '7p' ~/.openclaw/agents/main/sessions/현재세션ID.jsonl | jq -r '.type'
sed -n '7p'로 7번째 줄만 뽑고, jq -r '.type'으로 그 줄의 type 필드만 출력. 결과:
custom
예상이 맞았어요. 7번째 줄이 그 단 하나의 custom 줄이고, 4,500자입니다. 그 안에 뭐가 들었는지 첫 부분만 봅니다.
sed -n '7p' ~/.openclaw/agents/main/sessions/현재세션ID.jsonl | jq '.' | head -50
펼쳐보면 OpenClaw가 봇한테 매번 들려주는 “이런 도구들이 있고, 이런 매뉴얼들이 있다”는 안내문이에요. 18개 Skill 각각의 설명, 사용 가능한 Tool 목록, 응답 형식 규칙 등이 빽빽하게 들어있어요. 우리가 한 번도 직접 쓴 적 없지만 봇은 매번 받아 읽는 자리입니다.
이걸 시스템 프롬프트라고 부릅니다. 직역하면 “시스템이 주는 지시문” 정도인데, 의역하면 **“봇이 일하기 전에 매번 받아 읽는 사용설명서”**예요. 사람으로 치면 출근할 때마다 책상 위에 18페이지짜리 사규를 펴놓고 일을 시작하는 셈입니다.
왜 18개나 들어가지?
OpenClaw는 53개 Skill 중에서 즉시 쓸 수 있는 8개(ready)와, 봇이 항상 가지고 있는 핵심 Tool 약 10개를 합쳐서 매번 봇에게 알려줘요. 합치면 18개. 각 Skill 설명이 짧게는 100자, 길게는 500자 정도 됩니다.
이게 합리적인 설계이긴 해요. 봇이 “내가 무엇을 할 수 있는지”를 알아야 사용자 질문에 맞는 도구를 고를 수 있으니까요. 다만 한 번도 안 쓸 Skill까지 매번 들고 다니는 건 낭비예요. 본인이 안 쓰는 Skill 몇 개만 떠올려봐도:
apple-notes,bear-notes,imsg,peekaboo— Mac 전용notion,trello,discord,xurl— 본인이 안 쓰는 서비스eightctl(스마트 매트리스),openhue(스마트 조명) — 본인 집에 없는 기기
이런 매뉴얼을 봇한테 매번 들려주고 있는 셈인데, 봇은 그걸 다 읽고 있고, 그만큼 토큰을 차지하고 있어요. 이게 11화에서 32K로 줄여놨던 한도를 그렇게 빨리 채워버린 진짜 이유입니다. 사용자 대화로 32K를 채우려면 한참 걸리지만, 9,500자를 깔고 시작하면 몇 마디만 누적되어도 16K, 20K가 금방이에요.
응급처치는 이미 해두었다
자가 리셋 사건이 일어났을 때, 본인은 그 직후 한 가지 결정을 했어요. 컨텍스트를 32K → 128K로 한 단계만 더 올리는 것. 11화에서 1M 함정 때문에 32K로 줄여놨던 그 설정을, 1M까지 다 풀지 않고 128K까지만 푼 거예요. 11화에서 배운 게 있으니까. 모델이 “할 수 있다고 주장하는 최대값”과 “실제로 안정적으로 도는 값”은 다르다는 것.
작업 방식은 11화 때랑 결이 좀 달랐어요. 11화는 “1M에서 죽고 있는 모델을 살리는 응급 상황”이라 sed 한 줄로 1M을 32K로 한방에 바꿔버렸어요. 이번엔 기존 32K 모델은 그대로 두고, 128K 모델을 새로 만들어 추가하는 방식으로 갔어요. 안전망을 두는 거예요. 128K가 갑자기 또 문제 일으키면 잠깐 32K로 되돌릴 수 있게.
1단계 - 새 모델 빌드
11화에서 32K 모델 만들었을 때처럼, Modelfile부터.
cat > ~/Modelfile.nemotron-128k <<EOF
FROM nemotron-3-nano:30b
PARAMETER num_ctx 131072
EOF
ollama create nemotron-128k -f ~/Modelfile.nemotron-128k
베이스는 같고 num_ctx만 131072(=128K)로. 11화에서 본 Modelfile은 베이스 모델을 참조만 하니까, 디스크엔 거의 안 차요.
2단계 - openclaw.json에 새 모델 등록
설정 파일을 손보기 전, 13화에서 한 것처럼 백업부터.
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.backup-before-128k
nano ~/.openclaw/openclaw.json
두 군데를 손봐요.
① agents.defaults.model.primary — 봇이 기본으로 쓸 모델을 128K로 지정.
"primary": "ollama/nemotron-128k"
② models.providers.ollama.models 배열 — 새 모델 항목을 추가. 기존 32K 항목은 그대로 둠 (이게 안전망).
{
"contextWindow": 131072,
"id": "nemotron-128k",
"name": "nemotron-128k",
...
},
💡 11화 때는
sed로 1M을 32K로 한방에 덮어썼는데, 이번엔 nano로 직접 편집했어요. 덮어쓰기가 아니라 추가라서 한방 치환이 안 맞거든요. 도구는 작업의 성격에 따라 골라요.
3단계 - 검증하고 재시작
13화에서 만난 jq로 문법부터 확인.
jq . ~/.openclaw/openclaw.json
jq '.models.providers.ollama.models[].id' ~/.openclaw/openclaw.json
두 번째 명령은 등록된 모델 ID들만 뽑아줘요. "nemotron-128k"랑 "nemotron-32k"가 둘 다 보이면 성공 (32K가 안전망으로 살아 있다는 신호). 그 다음 게이트웨이 재시작.
systemctl --user restart openclaw-gateway
이게 잘 적용됐는지 확인하려면, 현재 활성 세션이 어떤 모델을 쓰고 있는지 보면 돼요.
jq -r 'select(.type=="model_change") | .modelId' ~/.openclaw/agents/main/sessions/현재세션ID.jsonl
이 명령은 “type이 model_change인 줄에서 modelId만 뽑아달라”는 뜻이에요.
nemotron-128k
128k짜리 모델로 잘 떠 있네요. 컨텍스트 한도가 32,000 → 128,000으로 4배 늘었으니, 9,500자 시작 + 대화 누적이 한참 쌓여도 한도 안에서 굴러갑니다.
이게 응급처치예요. 공간을 늘려서 짐 문제를 회피한 것. 차에 짐이 안 들어가서 더 큰 차로 바꾼 셈입니다.
큰 차로 바꾼다고 짐이 없어지진 않는다
지금은 일단 큰 차로 바꿔놨으니 당장 또 자가 리셋이 일어날 일은 잠시 없을 거예요. 다만 18페이지 사규는 그대로 있고, 시간이 갈수록 비서는 그 사규를 들고 다니면서 새로운 짐(대화 누적)도 쌓아갑니다. 언젠가는 128K도 채울 수 있어요.
근본적으로는 두 가지 길이 있어요.
| 접근 | 비유 | 한계 |
|---|---|---|
| 컨텍스트 늘리기 (지금 한 것) | 큰 차로 바꿈 | 결국 또 차게 됨, 메모리 부담 증가 |
| 시스템 프롬프트 줄이기 | 짐 자체를 줄임 | OpenClaw 설정을 손봐야 함 |
두 번째 길은 결국 “본인이 정말 쓸 Skill만 남기고 나머지는 끄기”인데, 그건 시간이 좀 들어가는 작업이에요. 일단은 큰 차로 다닐 만하니까 보류해둡니다.
오늘의 정리
- ✅ 13화 자가 리셋 사건의 정체 = 컨텍스트 오버플로. 모델한테 들어갈 글이 한도를 넘어서, 봇이 스스로 압축하다 또 실패하고 결국 대화 리셋
- ✅ 11화의 32K 설정이 부메랑이 되어 돌아옴. 1M 함정 피하려고 줄여놓은 32K가, 시스템 프롬프트 9,500자 + 대화 누적으로 금방 한계에 도달
- ✅ 사용자 대화는 9,500자 중 1%. 나머지 99%는 OpenClaw가 봇에게 매번 들려주는 사용설명서(시스템 프롬프트)
- ✅ 18개 Skill 매뉴얼이 매번 들어감. 본인이 안 쓰는 Mac 전용/스마트홈 기기 매뉴얼까지 봇이 매번 읽고 있음
- ✅ 응급처치로 32K → 128K로 늘림. 1M까지 안 간 건 11화에서 배운 교훈
여기까지 오면서 알게 된 게 하나 있어요. Skill이 그냥 “능력 추가 기능”이 아니라, 매번 봇이 받아 읽는 사용설명서라는 것. 그러면 거꾸로, 본인이 직접 사용설명서를 써서 봇한테 새 능력을 가르칠 수도 있다는 뜻이기도 해요. 다음 편에서는 그 작업을 해봅니다. 본인만의 Skill을 직접 만들어보는 편.
읽어주셔서 감사합니다.