OpenClaw·Hermes 마이그레이션 (12/13) — upstream 이슈 3개: 환경변수·교집합·환각 식별자

Hermes 마이그레이션 과정에서 upstream 영역에 관찰된 세 이슈를 정리한다. 두 건은 런타임/거버넌스 이슈, 한 건은 의사결정 문서 자체에 박힌 환각 식별자 이슈다. 각 이슈는 독립적으로 보이지만 공통 패턴을 공유한다 — 사실 확인 채널이 끊긴 자리를 그럴듯한 출력이 대체한다는 점.

이 글에서 가져갈 수 있는 기술/정보: - macOS launchd로 에이전트형 프로세스를 상시 구동할 때 $HOME 분리 현상과 plist 대응 키 - Claude Code 계열의 sub-agent 권한 모델에서 메인 toolset ∩ sub-agent toolset 규칙이 만들어내는 빈 도구셋 시나리오 - LLM이 작성한 문서 안의 외부 식별자(PR/이슈 번호)를 검증 가능한 형태로 제한하는 인용 규칙

세 이슈 모두 마이그레이션 시점 기준으로 upstream에 보고/관찰된 상태다. 머지 여부·최종 수정 형태는 본문에서 단정하지 않는다.


이슈 1 — launchd 환경에서 $HOME이 사용자 홈과 분리되는 현상

현상

macOS launchd plist로 Hermes 에이전트를 사용자 데몬으로 등록하면, 에이전트 컨텍스트의 $HOME이 사용자 로그인 셸의 ~와 일치하지 않는다. 결과적으로 ~/.config/... 경로를 기반으로 자격증명·설정 파일을 읽는 라이브러리가 사용자 홈이 아닌 위치를 탐색한다.

작동 원리

launchd가 LaunchAgent를 실행할 때 기본 환경은 셸 세션과 독립적으로 구성된다. plist에 EnvironmentVariables 키로 필요한 변수가 명시되지 않으면, HOME은 launchd 측이 잡아주는 에이전트 sandbox 디렉터리로 설정된다. 라이브러리는 자신의 규약대로 동작하지만, 해당 규약이 전제하는 "홈"의 정의가 어긋나 있다.

대응 키

plist의 <key>EnvironmentVariables</key> 딕셔너리에 HOME을 명시적으로 주입한다. 이 키 한 건 누락이 위 증상을 그대로 재현한다. 다만 plist 전체 양식을 검증된 프로덕션 템플릿으로 단정할 수는 없으므로, 저장소에는 "필요한 환경 변수 키" 수준으로만 기록하고 환경별 검증을 유지한다.

한계

이 현상은 Hermes 고유 문제가 아니라 launchd로 사용자 데몬을 운영하는 일반 케이스다. 사용자 자격증명/설정 파일에 의존하는 도구 체인이라면 설치 문서 수준에서 해당 키를 박아두는 편이 안전하다.


이슈 2 — sub-agent intersection rule과 축소된 toolset의 상호작용

규칙 구조

Hermes 메인 에이전트와 sub-agent의 도구 권한은 교집합으로 결정된다.

effective_tools(sub) = tools(main) ∩ allowed_tools(sub)

의도는 단순하다. 메인 쪽이 도구를 줄이면 하위도 그 범위 이하로 제한되어, 권한 축소가 체계적으로 전파된다.

관찰된 부작용

메인 에이전트의 toolset을 정리하는 작업에서 일부 sub-agent의 교집합이 공집합으로 떨어지는 경우가 발생했다. 도구가 0개가 된 sub-agent는 호출 자체는 실패하지 않는다. 모델은 응답을 반환한다 — 조회 가능한 도구가 없을 뿐이다.

즉 "도구가 없다"는 상태가 명시적 에러가 아닌 그럴듯한 텍스트 응답으로 귀결된다. 호출자 입장에서 반환값은 정상 형태이므로 downstream 로직이 그 출력을 근거로 다음 단계를 진행한다.

연결 관점

권한 축소는 표면적으로 안전 조치지만, 의존 그래프(어떤 sub-agent가 어떤 도구에 기대어 작동하는지)를 같이 점검하지 않으면 그 안전이 환각을 키우는 방향으로 작용한다. 자세한 재현·회피 방식은 별도 글에서 다룬다.


이슈 3 — Plan 문서 내부에 생성된 환각 PR 번호

세 번째는 코드가 아닌 문서 레이어의 이슈다. 의사결정 체인으로 환각이 진입하는 경로를 구체적으로 보여준다.

발생 지점

Hermes 마이그레이션의 의사결정 흐름을 정리한 Plan 문서에 "관련 upstream 이슈/PR" 항목이 있었다. 초기 설계안에 기입된 번호는 PR #110, PR #109 형태였고 연도도 2024년으로 기재됐다. 한 자릿수~세 자릿수 PR 번호는 소규모 프로젝트에서 흔한 범위라 표면 검증을 통과했다.

실제 저장소와의 불일치

Hermes가 의존하는 upstream 저장소의 PR 번호 범위는 훨씬 큰 자릿수다. #110, #109는 해당 저장소에 존재하지 않는 번호였다. LLM이 Plan 문서를 정리하며 그럴듯한 번호·연도를 채웠고, 그 출력이 검증 없이 통과된 구조다.

발견 경로

Plan 재검토 중 해당 PR 링크를 열었을 때 404가 반환되면서 식별됐다. 이후 browser 도구를 가진 별도 sub-agent가 저장소 PR 목록을 직접 조회했고, 같은 주제로 보고된 것으로 보이는 번호 세 건을 회수해 재설계 Plan에 반영했다.

정정된 번호

  • #12497 (추정) — launchd HOME 환경 이슈 관련 보고
  • #12495 (추정) — sub-agent intersection rule 부작용 관련 보고
  • #12494 (추정) — Plan 문서 환각 케이스 자체에 대한 보고

위 세 건은 NousResearch/hermes-agent 저장소로 추정되는 곳에서 조회된 데이터다. sub-agent의 작업 로그에 출처 저장소 URL이 남지 않아 저장소를 단정해 인용하기는 어렵다. 12000번대 자릿수는 인기 OSS의 누적 PR 규모와 일치한다. 본문·Plan 양쪽 모두 해당 번호에는 "확인 필요" 표기를 유지하며, 외부 공식 인용 전 저장소 + 번호 재확인 절차를 둔다.

구조적 함의

코드 환각은 빌드·테스트가 상당 부분 차단한다. 반면 의사결정 문서의 환각은 후속 결정의 앵커로 재사용되는 구조 때문에, 하류에서 그 번호를 신뢰할수록 더 깊이 박힌다. 존재하지 않는 PR #110의 토론을 근거 삼아 누군가 다음 결정을 쌓으면, 환각이 코드가 아닌 의사결정 체인에 누적된다.

이슈 2와 같은 결이다 — 사실 확인 채널이 끊긴 자리를 그럴듯한 출력이 대체한다.


적용 가능 패턴 — 외부 식별자는 verifiable한 형태로만 인용

Plan 문서·회고·마이그레이션 노트에서 외부 저장소의 PR/이슈를 인용할 때 적용할 수 있는 규칙.

  • 식별자 단독 인용 금지. #12497 대신 org/repo#12497 또는 풀 URL을 기본 형식으로 둔다. 가능하면 머지 커밋 SHA처럼 verifiable한 보조 식별자를 병기한다.
  • 조회 로그에 출처 저장소 URL 동반 기록. sub-agent에게 외부 식별자를 회수시킬 때 "어느 저장소에서 조회했는지"를 로그에 남기도록 지시한다. 번호만 돌아오면 이번처럼 저장소 맥락이 사라진다.
  • LLM이 채운 식별자는 링크 검증을 게이트로. 문서 작성 워크플로에서 외부 식별자는 최소 1회 링크 오픈을 거친다. 404 한 건이 환각 한 건을 차단한다.
  • 미확인 상태는 번호 없이 텍스트로. "관련 PR이 있을 것으로 추정" 수준의 서술은 번호 없이 남기고, 번호를 확보 못 한 경우 "확인 필요"를 명시한다. 빈칸이 환각보다 안전하다.

프로세스 오버헤드는 거의 없다. 핵심은 링크 오픈 1회 + 출처 저장소 기록 1회를 문서 워크플로의 게이트로 고정하는 것. 초기 설계안에서 이 게이트가 없어서 #110, #109가 살아남았다.


한계와 열린 질문

  • 세 이슈는 모두 보고/관찰 단계에서 다룬다. 머지 여부·반영 형태는 본 글에서 단정하지 않으며, 추적 결과는 별도 갱신 대상이다.
  • PR #12497, #12495, #12494는 NousResearch/hermes-agent 저장소로 추정되는 상태다. 공식 인용 전 저장소 + 번호 재확인 절차가 남아 있다.
  • launchd HOME 이슈의 plist 키 처리는 macOS에서 일반적으로 유효한 핵심 키에 한정된다. 모든 배포 환경에 복사 가능한 검증 완료 양식은 아니므로 환경별 검증이 필요하다.

이 글에서는 초기 설계안의 #110, #109라는 가짜 번호를 의도적으로 지우지 않고 정정 전후를 병기했다. 동일한 함정을 피하려는 다른 파이프라인에서 환각 → 발견 → 정정의 전체 궤적을 참고 자료로 쓸 수 있다고 보았다.

열린 질문 한 가지 — sub-agent intersection rule이 공집합을 만들 때, 런타임이 이를 명시적 에러로 승격시키는 것이 맞는가, 아니면 "도구 0개" 자체를 합법 상태로 두고 호출자에게 방어 책임을 넘기는 것이 맞는가. 이 선택은 환각 억제 전략 전체에 영향을 준다.

시리즈 전체 안내: 시리즈 목차

댓글

이 블로그의 인기 게시물

"LLM 핵심 학습 (1/6) — 기본: 토큰화·임베딩·어텐션·위치 인코딩"

"LLM 핵심 학습 (2/6) — 파인튜닝: LoRA·QLoRA·증류·Adapter"

"ML 기초 학습 (1/9) — 머신러닝과 sklearn: 학습의 좌표계"