Joyuno's Dev Blog

2026-11-06 (금) 개발일지

dev-blog

오늘은 네 가지를 잡았다. 응답은 하는데 결과가 조용히 버려지던 문제, 맥에서만 명령이 잘려 셸이 멈추던 문제, 참조 문서 하나만 보고 “부족하다”고 단정한 오진, 그리고 윤문이 목록 쉼표 하나를 지워 뜻을 바꿔버린 문제. 공통점은 로그가 조용했다는 것이다. 에러가 터진 게 아니라 아무 일도 안 일어난 것처럼 보였다.

사주 — LLM은 22초마다 응답했는데 풀이는 매번 기본 문장이었다

운영 서버에서 5위·10위 상세 풀이가 엔진 기본 문장으로 나왔다. 그런데 응답 시간이 22초였다. 기본 문장은 즉시 나와야 하는데 22초가 걸린다는 건 모델 호출 자체는 일어났다는 뜻이었다.

첫 가설은 모델이 응답을 안 했거나 타임아웃이라는 것이었다. 하지만 로그를 보니 모델이 두 번 다 응답했고, 결과 검사에서 탈락하고 있었다.

모델은 응답했는데, 검사 코드가 그 응답을 못 읽고 버리고 있었다.

같은 입력으로 모델을 직접 호출해 어느 규칙에 걸리는지 확인했다. 원인은 응답 형식이었다. Workers AI가 결과를 문자열이 아닌 객체로 돌려주는데, 검사 코드가 문자열만 받게 되어 있었다. 임시 프로젝트로 시험할 때와 응답 형식이 달라서 그때는 못 잡은 것이었다.

검사 코드가 객체도 받게 고쳤다. 겸사겸사 상대를 “그/그의”로 부르는 성별 단정, 질문이 아닌 “먼저 꺼낼 대화”, “~습니다” 섞임, 근거 문장 중복도 같이 고쳤다.

검증: 운영 사이트에서 5위·10위가 LLM 풀이로 나오는 것을 확인했다(10~27초). 엔진 테스트 4,000쌍도 통과했다.

남은 한계: 30B 무료 모델이라 누구의 기운인지 가끔 뒤바꾼다. “당신의 흙 기운”이라고 쓰거나 쇠가 나무를 단련하는 관계를 거꾸로 쓰는 식이다. 유료 모델로 올리면 나아지겠지만, 그건 비용 문제라 따로 판단해야 한다.

lepons — 맥에서만 세션 시작 명령이 잘려 셸이 quote>에 멈췄다

맥에서 Lepons로 세션을 켜면 zsh가 quote> 프롬프트를 띄웠다. 명령이 따옴표 중간에서 끊긴 것이다. 같은 명령이 Linux 원격에서는 정상이었다.

첫 가설은 문자열 이스케이프 문제였다. 하지만 명령을 세어보니 1,000자를 넘었다. 그리고 macOS 터미널이 셸이 프롬프트를 띄우기 전 들어온 입력을 한 줄 단위로 모아두는데, 그 버퍼가 macOS는 1,024바이트, Linux는 4,096바이트라는 걸 찾았다. Lepons는 셸이 준비되기 전에 세션 시작 명령을 한 줄로 바로 입력하고 있었다.

맥에서만 1,024바이트를 넘는 뒷부분이 버려졌고, 따옴표가 안 닫혀 quote>가 뜬 것이었다.

명령이 1,000바이트를 넘으면 맥에 작은 스크립트 파일로 먼저 저장하고, 터미널에는 . "$HOME/.lepons-start-<해시>.sh" 한 줄만 입력하게 바꿨다. 파일은 실행되면서 스스로 지워진다. 파일 저장이 안 되는 PC는 예전처럼 그대로 입력한다. 맥에서 Lepons를 직접 쓸 때(로컬 세션)도 같은 문제가 생길 수 있어서 같이 고쳤다.

검증: 테스트 949개 통과. 실제 맥에서는 아직 확인하지 못했다. SSH로 재현 실험을 돌리려다 거절당했고, 개인 맥에 시험 명령을 넣는 것보다 새 빌드로 직접 눌러보는 쪽이 낫다는 판단이었다.

하네스 — SKILL.md 하나만 보고 “부족하다”고 단정한 오진

riff의 되묻기 로직을 grill-me와 대조하는 작업이었다. 처음엔 riff의 SKILL.md만 읽고 “답이 연 새 가지를 이어서 묻기”, “질문끼리 순서 지키기”, “그냥 넘어가는 동의 막기”, “끝내기 전 확인” 네 항목을 “riff 부족”으로 표시했다.

그런데 권한이 돌아와 FRAME 질문 문서들을 직접 읽어보니 판단이 흔들렸다. layers.md에 이미 질문마다 붙는 꼬리질문이 있었고, layers.md:15에 “구체성 없는 ‘네 좋아요’는 승인이 아니다”가 있었고, termination-engine.md에 충분성 스코어카드가 있었다. SKILL.md는 진입점일 뿐이고 실제 규칙은 참조 문서에 있었던 것이다.

SKILL.md 한 파일만 보고 내린 결론이었고, 참조 문서를 읽자 절반이 틀린 판단이었다.

진짜 빈 곳은 만들기 전 가정 공개 단계였다. frame.md:37에 “답을 받으면 반영하고 진행한다”고 되어 있는데, 사용자가 “직원 없음”을 “강사 2명 있어요”로 고치면 강사별 단가·정산 주기·앱 사용 여부 같은 새 질문이 생긴다. riff는 그걸 묻지 않고 바로 만들기로 넘어간다. 그리고 질문 단계에서는 애매한 “네”를 승인으로 안 보는데, 가정 목록은 “틀린 것만 말해주세요”라서 아무 말 없으면 전부 동의로 처리된다.

범위를 “가정 목록에서 사용자가 고친 항목이 새 가지를 열면 그 가지만 1라운드 더 묻는다. 고친 게 없으면 바로 진행”으로 좁혔다. riff의 오버헤드 15% 원칙을 해치지 않으면서, 5~8줄 제한 때문에 항목이 밀리던 문제도 함께 줄어든다.

검증: 아직 가설이다. AGENTS.md 규칙상 고치기 전 실패하고 고친 뒤 통과하는 eval이 있어야 반영할 수 있다. 여러 턴 측정에 “가정을 고치면서 새 사실을 흘리는” 유형을 추가하는 방안을 잡아뒀다.

자동이력서지원 — 윤문이 목록 쉼표를 지워 뜻을 바꿨다

포트폴리오 10개 페이지를 윤문 도구로 돌렸다. 게이트는 10개 페이지 모두 통과했고 변경률은 0.2~3%였다. 그런데 오연 페이지에서 “신고, 연락처 차단”의 쉼표가 빠져 “신고 연락처 차단”이 됐다. 목록 항목 둘이 하나로 붙어 뜻이 달라진 것이다.

추가로 OmniTrace에서 “볼 수 있습니다”가 “봅니다”로 바뀌어 게이트 경고가 났고, “반영되게 했습니다”, “끊기지 않게 했습니다”, “바꿨습니다”가 “반영됩니다”처럼 바뀌면서 직접 만들었다는 뜻이 빠졌다. 메인 페이지에서는 카드 첫 문장이 밋밋해져서 원래 문장으로 되돌렸다.

게이트를 통과한 문장이 뜻은 바뀌어 있었다. 자동 검사가 잡지 못하는 종류의 실패였다.

의미가 바뀐 줄은 원문으로 복원했다. 태그 구조는 10개 파일 모두 원본과 같아서 글자만 바뀐 것을 확인했다.

검증: 게이트 재통과, 금지어 0건, 기여 범위(z-score만, HAProxy 참여, 2시간→15분, AWS 배포는 팀원)도 그대로.

남은 것: 윤문 도구는 의미 보존만 하고 사실 확인은 안 한다. 그래서 윤문 직후 원장 대조를 파이프라인에 남겨뒀다.

오늘의 한줄

조용한 실패가 제일 비싸다 — 모델은 응답했고, 명령은 입력됐고, 윤문은 통과했는데, 셋 다 결과만 봐서는 알 수 없었다.