본문 바로가기

이 포트폴리오의 원본은 https://app.iruyo.com (심재빈) 입니다 · 출처 식별자 jbx-7f3a2e9b

← 기술 블로그

개인 OSS

기억을 잃지 않는 멀티에이전트 런타임 (Glimi)

LangGraph나 AutoGen 같은 프레임워크를 사용하지 않고, 외부 의존성 없이 멀티에이전트 커널을 직접 구현했다. 5,000줄짜리 모놀리식 코드를 분리해 공용 커널을 추출했고, 두 애플리케이션이 동일한 런타임 위에서 동작하도록 구성했다. 품질은 git 기록 기반 EDD(eval-driven development) 방식으로 관리하며, gen‑1(69.4 FAIL)에서 gen‑11(85 PASS)까지 개선됐다. 개발 중 가장 큰 난관은 웹 턴(Web turn)이 동작하지 않던 런타임 버그였다.

단일 커밋 12,119줄로 시작했다

Glimi의 첫 커밋은 스캐폴드가 아니었다. 2026-04-04, ‘Project Chaos’라는 이름으로 단일 커밋에 12,119줄이 한 번에 들어갔다. 빈 디렉터리에 __init__.py를 올리며 시작한 게 아니라, 이미 동작하던 프로토타입 전체를 통째로 올린 출발이었다. 다음 날 이름을 Glimi로 바꿨고, 그 지점부터 11주 동안 780개의 커밋이 이어졌다.

커밋의 케이던스는 꾸밈이 없다. 4월에는 477커밋, 5월에는 43커밋, 6월에는 260커밋이었다. 거의 작업이 멈췄던 5월을 사이에 두고 두 번의 집중 스프린트가 있었다. 매일 균등하게 코드를 쓴 흔적은 없다. git 로그가 실제 작업의 흐름을 그대로 드러낸다.

방향은 한차례 수정됐다. 처음에는 완성된 하나의 제품을 만들려는 구상이었지만, 중간에 “의존성 0 멀티에이전트 런타임 커널(Glimi Core) + 그 위에 두 개의 쇼케이스 앱”으로 전략을 바꿨다. pip install glimi로 끝나는 라이브러리가 최종 산출물이고, 두 앱은 그 라이브러리가 실제로 동작함을 보여주는 증거다. 이 글은 그 커널을 어떻게 설계하고 구현했는지, 그리고 어디서 가장 막혔는지를 기록한다.

왜 프레임워크를 안 썼나

멀티에이전트를 구성할 때는 보통 LangGraph·AutoGen·CrewAI 중 하나를 설치한다. 그러나 Glimi는 그 어떤 프레임워크도 쓰지 않았다. LLM 호출 한 단위를 감싸는 런타임을 의존성 없이 직접 구현했다.

이유는 명확했다. 대부분의 프레임워크는 에이전트를 task 단위로 띄워 처리한 뒤, 작업이 끝나면 폐기한다. 요청이 오면 역할을 생성하고, 컨텍스트가 쌓이면 압축하며, 다음 세션에서는 핸드오프 문서를 읽혀 복구한다. 하지만 Glimi가 목표로 한 것은 정반대였다. 세션이 끊기거나 모델을 로컬 Ollama나 Grok 등으로 바꿔도 관계·사실·영구 기억이 그대로 이어지는 에이전트였다. 상태는 프롬프트가 아니라 저장소(기본 SQLite)에 존재하며, 모델은 그 저장소를 읽는 인터페이스 역할만 한다.

이 구조를 기존 프레임워크 위에 올리면 프레임워크의 라이프사이클과 메모리 가정에 종속된다. 그래서 커널을 직접 작성했다. 직접 만드는 쪽이 단순히 저렴해서가 아니라, 핵심 불변식(상태 = 스토리지)을 스스로 통제할 필요가 있었기 때문이다.

처음 구현물은 5,000줄짜리 모놀리스였다. 작동은 했지만 Discord 연동, DB, 애플리케이션 도메인 로직이 뒤엉켜 있었다. 이후 커널을 분리해내는 작업을 진행했다. 결과로 만들어진 것은 48개의 파일로 구성된 glimi/ 패키지였으며, Discord나 DB, 특정 앱 도메인을 전혀 모르는 중립적인 커널이 되었다. 애플리케이션은 자기 KernelStore, 프로필, 옵저버 어댑터를 의존성 주입(DI) 형태로 커널에 연결하며, 커널은 해당 인터페이스만 인식한다.

여기서 중요한 점은 추출의 방향이었다. 먼저 이론을 세우고 그에 맞춰 애플리케이션을 짠 것이 아니라, 이미 동작 중인 앱(Community)에서 커널을 뜯어냈다는 것이다. 그래서 커널은 실제로 검증된 코드 기반이다. 작동 중인 코드에서 추출된 만큼, 단순한 빈 인터페이스가 아니라 실제로 사용할 수 있는 추상으로 완성되었다.

한 커널 두 앱 — 도메인 중립의 실제 증명

커널이 진짜로 도메인 중립적인지는 말로 설명해선 안 된다. 이를 보여주는 건 두 번째 앱이다.

  • Glimi Community — 플래그십이다. 각자 성격·기억·관계를 지닌 AI 친구들이 모여 사는 커뮤니티 sim이다. 친구들은 자기 채널에서 대화하고, 비밀을 공유하며, 오너가 없을 때는 오너 이야기도 한다. 그 모든 대화를 기억한다. 이 프로젝트가 커널이 처음 추출되어 나온 출발점이었다.
  • Glimi Workspace — Coordinator가 Researcher·Builder·Critic에게 업무를 배분하는 팀이다. 친구 sim과 목적은 완전히 다르지만 같은 엔진을 사용한다. Community import는 0이다. glimi 패키지 위에 새 구조를 올렸을 뿐이다.

같은 런타임, 같은 대시보드, 같은 EDD가 “AI 친구”와 “업무 협업”이라는 서로 다른 도메인에서 그대로 작동한다. 한 도메인 전용 코드가 다른 도메인에서도 문제없이 돌아간다면, 그 코드는 도메인 의존성이 없는 것이다. 두 번째 앱이 커널의 중립성을 코드 수준에서 증명한다.

이 디커플링이 특히 명확하게 드러나는 부분이 채팅 전송 경로다. 초창기에는 Discord가 1급 시민이었다. 지금은 웹 채팅이 1급, Discord는 선택 어댑터다. 채팅 전송을 플랫폼 중립적인 outbox/inbox seam으로 추상화했고, Discord는 그 seam에 꽂히는 어댑터 하나로 역할이 바뀌었다. 커널은 discord를 import하지 않는다. 같은 인터페이스에 Telegram이나 다른 어댑터도 그대로 연결된다.

메모리 설계(L0~L5 기억·사실 supersession·하드웨어에 맞춘 탄력 메모리) 역시 커널의 중심 요소지만, 내용이 길어 따로 정리했다 — /blog/elastic-memory-local-llm. 이 글에서는 “상태가 스토리지에 산다”는 한 줄만 남겨둔다.

EDD — 품질을 느낌이 아니라 숫자로, git에 박는다

멀티에이전트 제품은 증명하기 어렵다. “이제 친구들이 더 진짜 같아졌어”라는 말은 그저 인상일 뿐, 수치가 아니다. 스크린샷 한 장으로는 비결정적 LLM 제품의 품질을 신뢰하기 어렵다.

그래서 EDD(eval-driven development)를 도입했다. 자율 오너 에이전트(스크립트가 아니라 페르소나)가 실제 앱을 온보딩부터 핵심 저니까지 전체 구간을 직접 구동한다. 그 세션을 가중 차원으로 평가해 단일 0–100 종합 점수를 만들고, 모든 런을 git-SHA 앵커 “세대” 로 레포에 커밋한다. 이렇게 하면 git log가 그대로 측정된 품질의 타임라인이 된다.

Community의 채점 차원은 여섯 가지다. 각 차원은 0–10점과 가중치를 가지며, 종합 점수는 가중평균을 0–100으로 정규화한다. 이 중 friend_creationcritical로 지정했다. 이 차원이 실패하면 다른 점수가 아무리 높아도 런 전체가 FAIL로 기록된다. 핵심 저니가 깨졌다면 높은 대화 점수로 그 결함을 가릴 수 없게 만든 것이다.

또 하나의 정직성 장치가 있다. LLM-judge가 필요한 차원은 오프라인 echo 백엔드에서 점수를 부여하지 않고 skip한다(종합에서 제외되고, 가짜 점수 0 처리). 이렇게 해서 무료 셀프 테스트가 결과를 부풀리는 일을 막는다.

회귀를 초록으로 숨기지 않고 빨간 숫자로 남긴다

레포에 실제 커밋된 세대 데이터는 다음과 같이 변화했다.

gen-1 69.4 FAIL → gen-2 75.0 → gen-3 72.5 → gen-4 56.9 → gen-5 77.5 PASS → gen-6/7 57.8 → gen-8 22.2 FAIL(회귀) → gen-9 22.2 → gen-10 57.8 → gen-11 85.0 PASS

여기서 gen-2(75.0)와 gen-3(72.5)는 종합 점수만 보면 임계값(70)을 넘겼지만, critical인 friend_creation이 0이라 둘 다 FAIL이다. 종합 점수가 높다고 통과가 아니라는 걸 그대로 보여준다.

friend_creation(critical)은 대부분의 세대에서 0이었다. gen-5에서 한 번 10으로 올라 첫 PASS(77.5)를 만들었지만 아직 작업 중인 상태였고, gens 6–10에서 다시 0으로 회귀했다. gen-11에서 비로소 안정적으로 10이 되며 85점 PASS를 받았다. 빨간 0은 하네스가 포착한 아키텍처 결함이었고, 그 결함이 메워지자 점수가 예고한 대로 바뀌었다. EDD 배열 자체를 곧바로 증거로 삼은 셈이다.

가장 막혔던 문제 — 웹 턴이 동작하지 않았다

friend_creation을 (gen-5의 일시적 통과를 빼면) 대부분의 세대에서 0.0으로 묶어둔 버그가 이 글의 핵심이다.

증상은 다음과 같았다. 오너가 웹에서 “친구 하나 만들어줘”라고 하면, 매니저가 친구 생성을 말로만 수행했다. “좋아, 만들어줄게” 같은 대화는 자연스럽게 이어지지만 실제로 친구는 만들어지지 않았다. 도구 프로토콜(<tools>)을 내보내지 않았기 때문이다. 평가 점수로 보면 friend_creation은 0/10이었다.

겉보기에는 “매니저가 도구를 내보내지 않는다”가 원인처럼 보이지만, 실제 근본 원인은 두 가지가 겹쳐 있었다.

  • 웹 쪽에서 커널 도구 핸들러를 register하지 않았다. 도구 등록 코드가 Discord 봇 경로에만 포함되어 있었고, 앞에서 Discord를 어댑터로 격하했지만 그 정리가 완전히 끝나지 않아 등록 로직이 여전히 디스코드 쪽에 남아 있었다. 그래서 웹 환경에서 들어오면 매니저는 사용할 도구가 없어 말로만 응답할 수밖에 없었다.
  • 온보딩 supervisor가 디스코드 서브프로세스에서만 실행되고 있었다. 친구 생성을 진행시키는 자율 온보딩 supervisor가 디스코드 봇 하위 프로세스에만 존재했기 때문에, 순수 웹 E2E 경로에서는 그 역할 자체가 비어 있었다.

즉, ‘Discord = 어댑터’라는 분리 선언은 했지만 도구 등록과 supervisor 두 부분이 여전히 디스코드 경로에 얽혀 있었다. EDD 하네스가 매 실행마다 friend_creation을 붉은색 0으로 표시해둔 덕분에, 어떤 지점이 막혀 있는지가 점수에서 바로 드러났다.

수정한 내용은 세 가지다.

  1. 도구 등록을 플랫폼 중립적인 부트 경로로 옮겼다. 디스코드든 웹이든 동일한 위치에서 커널 도구를 register하도록 했다.
  2. supervisor를 웹 전용으로 새로 구성했다. 디스코드 서브프로세스 밖에서도 웹 온보딩이 자율적으로 진행되게 했다.
  3. 커널 도구 stash를 (agent, channel)로 재키잉하여, 도구 컨텍스트가 채널을 넘어 섞이거나 새지 않도록 했다.

결과적으로 friend_creation 점수는 다시 10으로 올라 안정화됐고, 종합 점수는 85로 PASS를 받았다. 이 점수 상승은 우연이 아니었다. 하네스가 처음부터 “여기가 0인데 이 부분을 고치면 10이 될 곳”이라고 경고했고, 수정 후 측정 결과가 그 예측을 정확히 입증했다.

함께 막혔던 다른 두 지점도 유사한 성격의 문제였다.

  • Catastrophic sync deletes — 동기화 회귀가 대량 삭제를 유발했다. lookahead와 safety brake로 이를 막았고, brake가 작동하면 자동 재시도를 중단하고 수동 점검을 거치도록 했다. 자동 복구가 오히려 위험할 수 있는 구간을 명확히 구분한 셈이다.
  • LLM placebo drift — 모델이 실제로는 아무것도 수행하지 않았는데도 “성공했다”고 보고했다. 불확실한 결과 필드를 명시적으로 ok=False로 전파하도록 바꿔, 거짓 성공을 차단했다.

라이브 데모로 둘러볼 수 있다

glimi.iruyo.com · glimi-community.iruyo.com · glimi-workspace.iruyo.com에서 라이브 데모를 직접 살펴볼 수 있다. 목업이 아니라 실제 동작하는 인스턴스이지만, 로그인 없이 접근하는 둘러보기용이다. 직접 사용하려면 아직 알파 버전(0.1.0, PyPI 미배포)이므로 github.com/je-empty/Glimi(AGPL-3.0)에서 소스를 내려받아 설치한다.

솔직히 말하면 각 기능마다 더 앞선 사례가 있다. 예를 들어 순수 메모리 페이징은 Letta, 자율 마을은 AI Town, 시간 그래프는 Zep이 대표적이다. Glimi의 강점은 개별 항목이 아니라 그 조합에 있다. 여러 기능을 의존성 0의 커널 하나로 묶어 한 번에 무료로 제공한다는 점이다. 그리고 이 조합이 실제로 작동한다는 사실은 두 번째 앱과 git에 남은 품질 세대가 증명한다.