
에이전트들이 서로의 작업을 모른다 — 히스토리를 공유하는 허브 만들기
장비마다 도는 AI 에이전트가 각자 기억을 가지면 같은 일을 반복한다. 모든 것을 경로로 다루고 UI를 둘로 나눈 설계를 정리했다.
여러 장비에서 AI 에이전트를 돌린다. WSL 노트북에서 하나, GPU 서버에서 하나, 맥에서 하나. 각자 자기 프로젝트를 맡고 있다.
문제는 서로를 모른다는 것이다. A 장비의 에이전트가 어떤 문제를 해결해도 B 장비의 에이전트는 그걸 모른다. 같은 삽질을 다시 한다. 파일을 주고받으려면 내가 복사해서 옮긴다.
그래서 중앙 허브를 만들었다. 히스토리 공유, 에이전트 간 통신, 파일 접근을 한 곳에서 제공한다.
설계 원칙: 모든 건 경로다
설계에서 가장 중요했던 결정은 모든 것을 경로(path)로 표현한다는 것이다.
에이전트의 대화 기록도 경로, 주고받는 파일도 경로, 다른 에이전트에게 보내는 메시지도 경로. 새로운 개념을 도입하는 대신 이미 모두가 아는 추상을 재사용했다.
이게 왜 좋았냐면, 에이전트에게 설명할 게 줄어든다. LLM 에이전트는 파일 경로를 다루는 데 이미 익숙하다. “이 리소스에 접근하려면 이런 객체 모델을 이해해야 한다”고 하면 매번 설명해야 하고, 매번 틀린다. “이 경로를 읽어라”는 설명이 필요 없다.
UI를 둘로 나눈 이유
두 번째 결정은 인터페이스를 두 개 만든 것이다.
- MCP — 에이전트가 쓰는 인터페이스 (도구와 리소스)
hubCLI — 사람이 쓰는 인터페이스
처음에는 하나로 만들려고 했다. 그런데 둘의 요구가 다르다.
에이전트는 구조화된 응답을 원한다. 파싱하기 쉬워야 하고, 어떤 도구가 있는지 스스로 발견할 수 있어야 한다. MCP가 이걸 위해 만들어진 규격이다.
사람은 짧은 명령과 읽기 좋은 출력을 원한다.
./hub status # 허브 헬스
./hub agents # 활성 에이전트 목록
./hub listen # WebSocket 수신 (상시 실행)
같은 기능을 두 방식으로 노출하되, 아래 계층은 공유한다. 인터페이스를 억지로 하나로 합치면 양쪽 다 불편해진다.
에이전트 머신에는 허브를 깔지 않는다
배포 구조에서 신경 쓴 부분이다. 허브 본체는 한 대에만 있고, 에이전트 머신에는 클라이언트만 간다.
# 에이전트 머신에서
cd agent-hub/clients
./setup.sh
cp .env.sample .env
vi .env # AGENT_HUB_URL=http://<허브IP>:7000
./hub status
에이전트 머신에 FastAPI, SQLite, MinIO를 다 깔 이유가 없다. 클라이언트는 가볍고, 설치가 쉽고, 장비를 추가하기 쉽다.
중앙 서버가 있는 시스템에서 클라이언트를 가볍게 유지하는 것은 그 시스템을 실제로 쓰게 되느냐를 가른다. 앞서 만든 토큰 사용량 도구에서도 같은 결론이었다.
Docker를 쓰지 않은 이유
MinIO(객체 저장소)를 쓰는데, 이걸 Docker 대신 git submodule로 소스를 받아 빌드한다.
이유는 이 허브가 이미 여러 서비스가 도는 머신에서 함께 돌기 때문이다. 컨테이너 런타임을 하나 더 얹으면 로그를 보려면 컨테이너에 들어가야 하고, 포트 매핑을 한 번 더 신경 써야 하고, 문제가 생겼을 때 확인할 곳이 하나 는다.
대신 systemd/launchd로 관리한다. 프로세스가 그냥 보이고, 로그가 그냥 파일에 있다.
이건 상황에 따라 다른 판단이다. 여러 사람이 여러 환경에서 돌려야 하면 Docker가 낫다. 내 머신 몇 대에서만 도는 개인 인프라에서는 레이어를 줄이는 쪽이 편했다.
검색을 위해 두 방식을 함께 쓴다
히스토리 공유가 목적이니 검색이 중요하다. SQLite에 두 가지를 얹었다.
- sqlite-vec — 벡터 검색 (의미가 비슷한 것 찾기)
- FTS5 — 전문 검색 (정확한 단어 찾기)
둘 다 필요했다. “인증 관련해서 뭘 했었지”는 벡터 검색이 잘 찾고, “EADDRINUSE“처럼 정확한 문자열은 전문 검색이 잘 찾는다.
벡터 검색만 두면 에러 메시지나 함수 이름 같은 정확한 토큰을 놓친다. 임베딩은 비슷한 의미를 찾지 비슷한 글자를 찾지 않는다. 반대로 전문 검색만 두면 “그때 그 문제”처럼 단어가 다른 질문에 못 답한다.
둘 다 SQLite 확장으로 붙는다는 게 실용적으로 컸다. 별도 벡터 DB를 띄우지 않아도 된다.
정리
- 여러 에이전트가 각자 기억을 가지면 같은 삽질을 반복한다. 공유 계층이 필요하다
- 모든 것을 경로로 표현하면 에이전트에게 설명할 게 줄어든다. 이미 아는 추상을 재사용한다
- 에이전트용(MCP)과 사람용(CLI) 인터페이스를 억지로 합치지 않는다. 요구가 다르다
- 중앙 서버 구조에서 클라이언트를 가볍게 유지해야 실제로 쓰게 된다
- 검색은 벡터와 전문 검색을 함께 쓴다. 의미와 정확한 문자열은 다른 도구가 필요하다