본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 17. 09:17

「내 PC에서는 되는데」── 동료의 pip install이 C++ 컴파일러에서 멈춰서, 보이지 않았던 도입 장벽을 깨달은 이야기

요약

멀티 에이전트 프레임워크 C3(Claude Code Conductor) 개발자가 동료의 피드백을 통해 겪게 된 설치 장벽과 UX 개선 과정을 다룹니다. 특정 의존성(chroma-hnswlib)의 C++ 컴파일 요구 문제와 초기 설정 프로세스의 혼선을 해결하는 과정을 담고 있습니다.

핵심 포인트

  • 의존성 라이브러리의 C++ 확장 기능이 환경에 따라 설치 장벽이 될 수 있음
  • 개발자 본인의 환경에서는 발견하기 어려운 '환경 버전'의 문제 인지 필요
  • 사용자 경험(UX) 관점에서 명령어 설계 및 초기 설정 가이드의 중요성

이전 기사: https://zenn.dev/satoh_y_0323/articles/8bcdc387cba5e7

C3 GitHub: https://github.com/satoh-y-0323/claude-code-conductor / PyPI: https://pypi.org/project/claude-code-conductor/ / 공식 문서: https://satoh-y-0323.github.io/claude-code-conductor/

본 기사의 범위: 직접 만든 멀티 에이전트 프레임워크 C3 (Claude Code Conductor)의 v2.37.0 / v2.38.0에 관한 이야기입니다. 이번에는 드물게 두 가지 모두 직장 동료로부터의 피드백이 시작점이었습니다. 첫 번째는 "pip install을 했더니 C++ 컴파일러를 요구해서 설치가 안 된다", 두 번째는 "/start가 있어서 처음에 그걸 입력하게 되는데, 그러면 초기 설정을 건너뛰게 된다"였습니다. 공통점은 익숙한 자신의 환경에서는 절대로 겪지 않을 장벽이었다는 점입니다. 혼자서 개발했다면 아마 평생 깨닫지 못했을 것입니다.

서론 ── 자신에게 보이지 않는 장벽은 대개 타인이 겪는다

C3는 직장 팀에서도 개발에 사용되고 있습니다. 얼마 전 그 동료로부터 연달아 두 가지 피드백이 왔습니다.

  • "새로운 프로젝트에서 pip install claude-code-conductor를 했더니, 라는 에러가 떴다. Visual Studio의 C++ 도구를 설치하니 통과했지만, 이거 보통 사람들은 포기하지 않을까?" Microsoft Visual C++ 14.0 or greater is required

  • "/init-session 다음에 /setup을 하라고 README에 적혀 있지만, /start가 있어서 처음에 /start를 입력하고 싶어진다. 게다가 /init-session을 하면 다음에 무엇을 할지 물어봐서, /setup을 하기 전에 개발이 시작되어 버린다."

둘 다 자신의 머신에서는 한 번도 일어난 적 없는 문제였습니다. 이전 기사에서 "여유가 있으면 비효율은 보이지 않는다"라고 썼는데, 이번에는 그 "환경 버전"입니다. 자신이 익숙하고 잘 갖춰져 있기 때문에 보이지 않았던 장벽을, 새로 들어온 사람이 정확하게 밟아준 이야기입니다. 차례대로 고쳐 나가겠습니다.

pip install이 C++ 컴파일러를 요구한다

첫 번째: v2.37.0 ── ### 증상과 원인

에러는 hnswlib 빌드 과정에서 발생하고 있었습니다. C3에는 의미 검색 기능인 c3 recall (과거의 세션, 리포트, 패턴을 embedding으로 가져오는 기능)이 있으며, 그 근접 검색 인덱스에 chroma-hnswlib라는 의존성을 사용하고 있었습니다. 이것은 내용물이 **C++ 확장 (C++ extension)**입니다.

chroma-hnswlib는 PyPI에 각 Python 버전용 빌드된 wheel을 제공하고 있지만, 매칭되는 wheel이 없는 환경 (예를 들어 너무 최신인 Python)에서는 pip가 어쩔 수 없이 **소스로부터 컴파일 (compile from source)**을 시도합니다. 여기서 C++ 컴파일러 (MSVC)가 요구됩니다. 동료가 "VS의 C++ 도구를 설치하니 통과했다"라고 한 것은 바로 소스 빌드가 실행되었다는 증거였습니다.

그리고 제가 깨닫지 못했던 이유도 이것으로 설명됩니다. 제 환경에는 wheel이 있었기 때문입니다 (혹은 이미 컴파일러가 설치되어 있었습니다). 그래서 한 번도 막히지 않았습니다. chroma-hnswlib는 C3에서 유일하게 컴파일을 요구할 수 있는 의존성이었고, 그것이 신규 사용자의 첫걸음을 조용히 가로막고 있었습니다.

"어쩔 수 없는 것"인가? ── 규모 측면에서 다시 묻다

처음에 스스로에게 던진 질문은 "이것은 C++ 확장이니 어쩔 수 없는 것인가?"였습니다. 하지만 냉정하게 생각하면, HNSW (근사 최근접 이웃 탐색, Approximate Nearest Neighbor search)는 오버스펙이었습니다.

recall이 검색하는 대상은 한 명의 사용자의 .claude/ 디렉토리입니다. 실측치로 수천 개의 청크 (현재 제 환경에서 약 6,000개). HNSW는 수백만~수억 개의 벡터를 고속으로 근사 검색하기 위한 구조이며, 수천 건 정도라면 numpy의 전수 조사 (brute-force) cosine 검색으로도 충분합니다. 행렬과 벡터의 내적 한 번, 쿼리당 수 밀리초. embedding 생성에 사용 중인 fastembed

모든 환경에 wheel이 존재하므로, C++ 컴파일을 요구하는 의존성chroma-hnswlib뿐입니다. 이것을 numpy로 교체하면 장벽 자체가 사라집니다.

교체로 효과를 본 3가지 설계 판단

단순히 "numpy로 바꾼다"라고만 하면 이행 과정이 허술해질 수 있습니다. 이번에 성공적으로 적용한 판단은 세 가지가 있었습니다.

1. 파일명 recall.hnsw는 그대로 유지하고, 내용물만 numpy 형식으로.

인덱스 파일은 .claude/state/recall.hnsw라는 이름을 사용하지만, 이를 변경하지 않았습니다. C3는 "pip 패키지 (CLI 본체)"와 ".claude/ 에 배포되는 hook 군"이라는 두 가지 배포 채널이 있으며, 양쪽 모두 이 파일명을 참조하고 있습니다. 이름을 바꾸면 양쪽 사이에서 버전 불일치 (skew)가 발생할 수 있습니다. 이름은 유지하되 내용물만 numpy로 바꿈으로써, 배포 hook (.npy 페이로드로 변경하는 recall_inject / recall_autorebuild)은 단 한 줄도 건드리지 않고 끝낼 수 있었습니다. .gitignore / 배포 제외 정의 / 빌드 hook의 "3개 파일 동기화"도 필요 없었습니다.

2. 구형 형식은 "읽을 수 없으면 다시 만든다"는 방식으로 자기 수복(Self-healing).

업그레이드 후, 사용자 환경에는 구형 hnswlib 형식의 recall.hnsw가 남아있게 됩니다. 새 코드는 이를 읽을 수 없습니다. 그래서 load()를 "np.load에 실패하면 예외를 던지는 대신 False를 반환한다"는 설계로 만들어, 기존의 "읽을 수 없으면 전체 재구축" 폴백(fallback)으로 연결했습니다. 소스가 업데이트되면 자동 리빌드 hook이 실행되고, 다음 rebuild 시 자연스럽게 numpy 형식으로 다시 만들어집니다. 이행 스크립트를 작성하지 않고도, 단 한 세션 내에서 자기 수복이 이루어집니다.

3. 오히려 코드가 줄었다.

np.save / np.load를 경로 문자열이 아닌 파일 객체를 경유(open()하여 전달)하도록 했더니, 이전에 hnswlib을 위해 작성했던 Windows 비 ASCII 경로 대응을 위한 워크아라운드(workaround) 약 380줄(테스트 포함)이 통째로 불필요해졌습니다. 의존성을 하나 줄이면서 기능을 유지한 채 코드가 가벼워졌습니다. 기분 좋은 리팩터링이었습니다.

보안 측면에서도 np.load(allow_pickle=False)를 명시하여 pickle을 통한 임의 코드 실행 경로를 차단했고, 잘못된 형상의 파일이나 구형 바이너리는 load()False를 반환하도록 통일했습니다. C++ 네이티브 확장이 사라진 만큼 공격 표면(attack surface)은 오히려 줄어들었습니다.

리뷰에서 발견한 실제 버그

이 변경 사항 역시 C3 자체의 워크플로우(설계 $\rightarrow$ 계획 $\rightarrow$ TDD $\rightarrow$ 리뷰)를 통해 진행했는데, code-reviewer가 High 등급의 실제 버그 1건을 찾아냈습니다. "recall.hnsw유효하지만 1차원인 .npy(본래는 2차원 행렬)가 놓일 경우, np.load는 성공하지만 직후의 노름(norm) 계산에서 AxisError가 발생하며, 심지어 이것이 try/except 밖에 있어 CLI가 크래시된다"는 내용이었습니다. 형상 검증(ndim==2 이고 열 수가 일치하지 않으면 False 반환)을 추가하여 해결했습니다. 리뷰를 거칠 가치가 충분했던 지적이었습니다.

두 번째 이야기: v2.38.0 ── "### 증상

"/start가 있으니까, 나도 모르게 제일 먼저 입력하게 돼"

C3의 권장 플로우는 /init-session (매 세션, 이전 상태 복원) $\rightarrow$ /setup (최초 1회, 언어 및 규약 설정) $\rightarrow$ /start (개발 시작)입니다. 하지만 동료의 말대로, /start라는 매력적인 이름의 명령어가 있으면 사람들은 그것을 가장 먼저 입력하고 싶어 합니다. 게다가 /init-session을 실행하면 "다음에 무엇을 하시겠습니까?"라고 묻는데, 이때 /setup (규약 설정)을 건너뛴 채 바로 개발에 돌입할 수 있습니다. 결과적으로 코딩 규약이 설정되지 않은 채 개발이 시작되는 사고가 발생할 수 있습니다.

저 역시 이 실수를 범하지 않았습니다. 제 환경은 이미 설정되어 있었기 때문입니다. 완전히 "숙련자의 맹점"이었습니다.

해결책: 입구에 "자기 보정" 장치를 마련하기

동료의 제안은 탁월했습니다 ── "/start를 실행했을 때 /init-session이 실행되지 않았다면 자동으로 실행하고, /init-session에서 /setup"

조건을 추가하는 것만으로도, 첫 실행이든 지속적인 실행이든 C3가 순서를 제어할 수 있지 않을까요"라고 말했습니다.

정말로 그랬습니다. 입구 명령어 자체가 부족한 전단 단계를 호출하도록 만들었습니다.

/start 실행 시, 해당 세션에서 /init-session이 아직 실행되지 않았다면 → 자동으로 먼저 실행한다

/init-session 실행 시, 규약(Convention)이 설정되지 않았다면 → 자동으로 /setup을 실행한다

이렇게 하면, 처음에 입력하는 것이 /start이든 /init-session이든 상관없이 알아서 올바른 순서로 맞춰집니다.

사용자가 미리 차단한 루프

여기서 동료가 한 가지 더 날카로운 지적을 했습니다 ── "/init-session/start 사이에서 루프가 발생하지 않을까요? /init-session을 실행했는지 여부를 판단할 재료가 있다면, 루프를 피할 수 있을 것 같습니다."

이것은 본질을 꿰뚫는 지적이었습니다. 현재 /init-session은 "워크플로우로 시작하기"를 선택하면 /start를 호출합니다. 여기에 "/start/init-session"을 추가하면 상호 호출이 됩니다. 하지만 "실행했는지를 판단할 재료"가 있다면, 그것이 그대로 루프 가드(Loop Guard)가 됩니다.

조사해 보니, Claude Code는 세션마다 CLAUDE_CODE_SESSION_ID라는 환경 변수를 가지고 있었습니다. 그래서 /init-session은 기동 직후에 이 세션 ID를 .claude/state/init_session.flag에 기록합니다. /start는 "플래그의 내용이 현재 세션 ID와 일치하는가"만을 확인하여, 일치한다면 /init-session을 다시 호출하지 않습니다.

/init-session/start로 다시 호출되어 돌아와도, 플래그가 이미 존재하기 때문에 재귀(Recursion)하지 않습니다. 사용자가 말한 "판단 재료"를 말 그대로 루프의 열쇠로 사용한 것입니다.

제안보다 한 수 적은 구현으로 만들기

동료는 "이 개발 리포지토리(Repository)에 빈 coding-standards.md를 두고, git 대상 외 및 배포 대상 외로 설정하면 오작동하지 않을까요"라고도 제안해 주었습니다 (자신의 리포지토리는 "규약 설정 완료" 상태로 간주하고 싶다는 뜻이었습니다). 이 또한 옳은 방법이지만, coding-standards.md를 두는 rules/ 디렉토리는 전체가 자동으로 로드되는 곳이라 빈 파일은 의미론적으로 혼란을 줄 수 있고, .gitignore / 배포 제외 / 빌드 후크(Build Hook)의 3개 파일 동기화가 발생합니다.

대신, 판정 마커를 .claude/state/ 하위의 플래그로 정했습니다.

state/원래부터 git 대상 외 및 배포 대상 외이므로, 개발 리포지토리에 플래그를 하나 touch 하는 것만으로 충분합니다. 3개 파일 동기화도, 빈 파일도 필요 없이 한 수 적게 처리할 수 있었습니다. "설정 완료" 판정은 coding-standards.md(setup이 실제로 생성하는 결과물이자 이용처이며 git 추적되어 영구적으로 남음)의 존재 또는 이 플래그, 라는 이중 구조로 만들어 견고성도 확보했습니다.

리뷰의 지적: "완전 자동"은 과장이다

이때 리뷰에서 포착한 적절한 지적 ── "'완전 자동 실행'이라고 적혀 있지만, /setup 자체는 언어나 규약을 묻는 질문이 8개 정도 있다. 사람 없이 완료되는 것이 아니므로 문구가 오해를 불러일으킬 수 있다". 확실히 그렇습니다. "자동"이란 "사용자가 명령어를 외우지 않아도 연쇄적으로 기동된다"는 의미이지, "규약 설정을 사람 없이 스킵한다"는 뜻이 아닙니다. README와 skill의 문구를 "연쇄 기동은 되지만 /setup의 응답은 필요함"으로 수정했습니다. 언어의 정확성 또한 품질의 일부입니다.

@가 섞여 들어간 커밋 메시지

덤: 솔직한 에피소드를 하나 공유합니다. 릴리스 커밋에서 이런 메시지가 만들어지고 말았습니다.

@ feat(recall): 索引を chroma-hnswlib(C++) から numpy ...

맨 앞의 @는 무엇이냐 하면, PowerShell의 히어 스트링(Here-string) 구문인 @'...'@를 POSIX 셸(Git Bash)에서 사용해 버렸기 때문입니다. 제 머신은 PowerShell이 주 셸이지만, AI가 실행하는 Bash 도구는 POSIX sh이며, @'...'@는 그저 리터럴일 뿐입니다. @

가 제목 맨 앞에 남았습니다. git commit --amend를 통해 올바른 heredoc으로 수정하여 무사히 넘어갔지만, **"주 셸(Main Shell)과 도구의 셸이 다르다"**라는, 이 또한 환경 특유의 함정이었습니다. 이번 테마인 "나의 전제는 타인(이나 도구)에게 통하지 않는다"를 보여주는 작은 실연이었습니다 (메모리에 새겨두었습니다).

업무·개인 개발에 활용할 수 있는 점

1. "내 환경에서 돌아간다"는 "모두의 환경에서 돌아간다"가 아니다

이번의 가장 큰 교훈입니다. pip install이 통과하느냐, 명령 순서에서 헤매느냐 ── 둘 다 내 정돈된 환경에서는 재현되지 않습니다. 특히 네이티브 확장(Native Extension)의 빌드 요구사항은 wheel의 유무라는 보이지 않는 조건으로 신규 사용자만을 걸러냅니다. 배포물은 완전한 새 환경, 새로 합류한 사람의 눈으로 한 번 검증하면, 자신에게는 보이지 않던 벽이 보입니다.

2. "어쩔 수 없는 의존성"은 규모를 통해 필요성을 재질문하라

C++ 확장은 "빠르니까 당연하다"라고 사고를 정지하기 쉽지만, 내 데이터 규모에서 정말로 필요한가를 묻는다면 답이 달라질 수 있습니다. 수천 건의 검색에는 근사 최근접 이웃(Approximate Nearest Neighbor)이 필요 없었습니다. 전수 조사(Brute-force)로 충분하다면, 의존성을 하나 줄여 배포가 가벼워지고 코드까지 줄어듭니다. 속도를 위한 복잡성은 규모가 작을 때는 부채가 될 뿐입니다.

3. 입구가 여러 개라면, 입구 스스로가 자기 교정을 하게 하라

"올바른 순서로 입력해 주세요"라고 문서에 적는 것은 대개 지켜지지 않습니다. 지키게 만드는 것이 아니라, 어느 입구로 들어오더라도 올바른 순서로 맞춰지도록 입구들끼리 연쇄시키기. 사용자의 기억이 아닌 메커니즘으로 순서를 보장한다는 발상의 전환이었습니다.

4. 초보자·동료의 피드백은 "나의 맹점 그 자체"다

베테랑일수록 빠지지 않는 벽을, 새로운 사람은 정확하게 밟습니다. 이번 두 건은 만약 나 혼자 개발했다면 절대 나오지 않았을 문제입니다. 게다가 동료는 루프(Loop)의 위험성과 그 해결 방법(판단 재료)까지 미리 지적해 주었습니다. 사용자의 "걸림돌"은 개선 아이디어의 일급 소재입니다.

5. "삭제"로 고칠 수 있다면 가장 강력하다

numpy화(numpy-ization)를 통해 의존성을 하나 줄이고, 380줄의 워크아라운드(Workaround)를 삭제하면서도 기능은 유지한 채 견고함을 높였습니다. 더해서 고치는 것보다 빼서 고치는 것이 유지보수 총량을 줄여 더 강력합니다. 삭제할 수 있는 장면을 발견했다면, 대개 정답입니다.

요약

이번(v2.37.0~v2.38.0)은 둘 다 직장 동료의 피드백에서 시작되었습니다.

  • v2.37.0: recall의 인덱스 백엔드를 C++ 확장 (chroma-hnswlib)에서 numpy의 전수 조사(Brute-force) 코사인 검색으로 교체. pip install 시의 C++ 컴파일러 요구라는 도입 장벽을 해소. 파일명을 유지하며 자기 수복형 이행(Self-repairing migration)을 수행하고, Windows용 워크아라운드 380줄을 삭제했으며, allow_pickle=False로 안전성을 확보.
  • v2.38.0: /start/init-session/setup을 **자동 체인(Automatic Chain)**화. 어느 입구로 들어오든 올바른 순서로 맞춰짐. CLAUDE_CODE_SESSION_ID를 사용한 세션 단위 플래그가 그대로 루프 가드(Loop Guard) 역할을 수행함 (사용자의 지적대로). 판단 마커를 state/에 두어 3개 파일의 동기화 문제를 회피.

늘 그렇듯 솔직한 한계도 있습니다. "컴파일러가 없는 클린 환경에서 확실히 설치되는지"에 대한 최종 확인은 제 손에서 이루어지지 못했습니다 (제 환경에는 이미 chroma-hnswlib가 설치되어 있기 때문입니다). 최종적인 증거는 동료의 머신에서의 동작 확인이었습니다 ── 그리고 두 버전 모두 직장에서 실기 확인을 마쳤습니다. 맹점을 알려준 사람에게 고쳐진 모습까지 확인받을 수 있는 것, 이것이 팀으로 만드는 가장 큰 이점일지도 모릅니다.

C3를 써보고 싶다면 ── 시작하는 법

pip install claude-code-conductor # v2.37.0 이후는 C++ 컴파일러 불필요
cd your-project
c3 init # .claude/ 에 에이전트 정의, skill, hook 이 전개됨

그다음 Claude Code에서 /start를 입력하세요. v2.38.0부터는 순서를 기억하지 않아도 괜찮습니다. /start를 입력하면, 미실행 상태라면 /init-session (이전 상태 복원)을, 처음이라면 /setup을 실행합니다.

(규약 설정)을 자동으로 연쇄시킨 후 개발 플로우(히어링(Hearing) → 설계 → 계획 → 구현 → 리뷰)로 진입합니다. 어댑터는 Claude / Codex / Cursor / OpenCode 4종을 지원합니다 (c3 init --platform ...).

"여기서 막혔다", "이 부분이 이상하다" ── 그러한 좌절(つまずき)에 대한 보고가 가장 감사합니다. Issue나 PR을 통해 언제든 편하게 말씀해 주세요.

링크

C3 GitHub: https://github.com/satoh-y-0323/claude-code-conductor -
C3 PyPI: https://pypi.org/project/claude-code-conductor/ -
C3 공식 문서: https://satoh-y-0323.github.io/claude-code-conductor/ -
v2.37.0 릴리스 노트: https://github.com/satoh-y-0323/claude-code-conductor/releases/tag/v2.37.0 -
v2.38.0 릴리스 노트: https://github.com/satoh-y-0323/claude-code-conductor/releases/tag/v2.38.0 -
이전 기사 (세션 영속화의 비대화 루프를 '현재 위치' 1줄로 끊다 / C3 v2.35.0~v2.36.0): https://zenn.dev/satoh_y_0323/articles/8bcdc387cba5e7

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0