5주 동안 오픈 소스 멀티 에이전트 오케스트레이터(multi-agent orchestrator)를 구축하며 느낀 점: 어려운 것은 에이전트가
요약
5주간의 개발을 통해 오픈 소스 멀티 에이전트 오케스트레이터인 Praxia를 출시했습니다. 개인의 메모리를 조직의 지식으로 자동 승격시키는 5계층 메모리 스택과 3개 경로 승격 엔진이 핵심 설계 특징입니다.
핵심 포인트
- Apache-2.0 라이선스의 오픈 소스 멀티 에이전트 프레임워크 Praxia 출시
- 개인 메모리에서 조직 메모리로의 자동 승격 메커니즘 구현
- 빈도, 결과 상관관계, LLM 자기 평가를 통한 3개 경로 승격 엔진
- 5계층 메모리 스택을 통한 체계적인 지식 관리
이 글은 Apache-2.0 라이선스의 멀티 에이전트 오케스트레이터(multi-agent orchestrator)인 Praxia를 구축하는 시리즈의 출시 포스트입니다. 이후 포스트에서는 TiDB Vector 메모리 백엔드와 일본어 특화 STT(Speech-to-Text) 통합에 대해 심도 있게 다룰 예정입니다.
요약 (TL;DR)
이번 봄, 저는 약 5주 동안 밤과 주말을 이용해 멀티 에이전트 오케스트레이터 OS인 Praxia(Apache-2.0)를 처음부터 구축하여 출시했습니다.
- 🚀 PyPI:
pip install praxia— https://pypi.org/project/praxia/ - 📦 GitHub: https://github.com/praxia-dev/praxia
- 🎬 60초 데모: https://youtu.be/o_6NbjJU1AA
- 🌐 랜딩 페이지: https://praxia.tools/
차별점은 **개인적 메모리에서 조직적 메모리로의 자동 승격(automatic personal → organizational memory promotion)**입니다. 시니어 엔지니어가 공들여 튜닝한
| 구분 | 발생 상황 |
|---|---|
| 설정 복잡성 (Setup complexity) | 단순히 실행 가능한 상태를 만드는 데만 2~3일이 소요됨. 프로덕션 환경에서 호출할 수 있는 수준이 아님. |
| ... |
두 번째 문제가 결정적이었습니다. 범용 에이전트 프레임워크(General agent frameworks)는 강력한 프리미티브(primitives)를 제공하지만, 지식을 조직으로 축적하는 프로세스는 전적으로 구현자의 몫으로 남겨져 있습니다.
핵심 설계 — 5개 계층 + 3개 경로
5계층 메모리 스택 (5-layer memory stack)
L1 PersonalMemory 사용자별 (6개 백엔드: JSON / Mem0 / Letta / Zep / Hindsight / LangMem)
L2 PromotionEngine 야간 배치(Nightly batch). L1 → L3 승격 결정
L3 SharedMemory 조직 전체. RBAC 게이팅, 시간 경과에 따른 감쇠(time decay)
...
핵심은 L1 → L4 과정이 수동이 아니라는 점입니다. 취침 시간 컨솔리데이터(sleep-time consolidator, 야간 배치)가 개인 메모리를 스캔하고 적절한 부분을 조직 지식으로 자동 승격(auto-promotes)시킵니다.
3개 경로 승격 엔진 (3-path promotion engine)
메모리 승격은 **세 가지 독립적인 신호(signals)**를 통해 병렬로 평가됩니다:
- 빈도 (Frequency) — N명 이상의 사람들에게서 반복되는 사실
- 결과 상관관계 (Outcome correlation) — 승리 / 승인된 PR / 테스트 통과와 함께 발생하는 동시 발생성
- LLM 자기 평가 (LLM self-eval) — 0..1 사이의 "조직 지식 후보 자격(org-knowledge candidacy)" 점수
최종 점수는 가중치가 적용된 혼합 방식이며, 단 하나의 결정적인 경로만으로도 승격이 트리거됩니다. 이는 단일 메커니즘에 대한 의존성(하나의 신호가 고장 나면 전체 시스템이 무너지는 상황)을 의도적으로 피하기 위함입니다.
"나 혼자서도 만들 수 있다"를 가능하게 한 설계 선택
5주 만에 제품을 출시할 수 있었던 것은 네 가지 설계 선택 덕분이었습니다.
1. 7개의 확장 지점 (Seven extension points)
모든 확장 지점은 동일한 praxia.extensions.Registry 프리미티브를 기반으로 구축되었습니다:
| 확장 지점 | 약 코드 라인 수 (LoC) | 진입점 (Entry point) |
|---|---|---|
| 커넥터 (Connector) (Box / Notion / Slack …) | ~50 | praxia.connectors |
| ... |
"pyproject.toml 진입점을 통해 확장하며, 코어 파일은 절대 수정하지 않는다
SSO (Google / Microsoft Entra / Okta / GitHub / Keycloak), RBAC, 감사 로그 (audit logs), 사용자별 OAuth (13개 제공업체), KMS 기반 토큰 암호화 (AWS / Azure / GCP / Vault / 로컬) — 이 모든 것이 OSS 코어에 포함되어 있습니다.
상용 에이전트 플랫폼들이 "엔터프라이즈 티어 (Enterprise tier)"로 유료화하여 제공하는 기능 대부분이 Apache-2.0 라이선스 하에 여기에 포함되어 있습니다. 이는 도입 결정을 직접적으로 가속화합니다.
3. LiteLLM을 통한 100개 이상의 제공업체 지원
제공업체별 특이 사항(Anthropic의 response_format 미지원, GPT-5.x의 temperature 금지, Azure의 배포 이름 형식 등...)은 LiteLLM 레이어에서 흡수됩니다. 특정 제공업체의 API 키가 Praxia 코어로 유출되지 않습니다. 완전한 오프라인 운영도 가능합니다 (Ollama + 로컬 모델 + backend=json).
4. 쉽게 버릴 수 있는 Streamlit UI
UI는 Streamlit을 사용하지만, 백엔드 또한 praxia serve (FastAPI)로서 헤드리스 (headless)로 실행됩니다. 나중에 Next.js나 모바일로 전환할 때, UI 레이어만 버리면 됩니다.
v0.1.0에 포함된 것과 포함되지 않은 것
출시됨 (의도적으로 전체 세트를 구성함):
- 5계층 메모리 + 3경로 승격 엔진 (3-path promotion engine)
- 6가지 비즈니스 스킬 (투자 / 영업 / 디자인 / 조달 / 특허 / 법률)
- 6가지 LTM (장기 메모리) 백엔드 + 복합/라우팅 병렬 융합 (Composite/Routed parallel fusion)
- 13개 제공업체에 대한 사용자별 OAuth
- SSO + RBAC + ACL + 감사 로그 (audit logs)
- 자율 에이전트 (LLM 기반 도구 사용 루프)
- 문서 디자이너 (샌드박스화된
python-pptx/docx→ 설계된 파일 출력) - 8개 언어 국제화 (i18n) (en / ja / zh-CN / ko / es / fr / de / pt-BR)
의도적으로 연기함 (v0.2+):
- 멀티 테넌트 (Multi-tenant) GUI — OSS는 "단일 조직 셀프 호스팅"을 목표로 합니다. SaaS급 테넌트 격리는 향후 오픈 코어 (Open Core) 티어에 포함될 예정입니다.
- PDF 출력 — 현재는 LibreOffice 기반 워크플로우를 권장합니다.
- 네이티브 Pinecone / Weaviate / Qdrant 백엔드 — 복합/라우팅을 통해
mem0로 래핑할 수 있으므로 우선순위가 낮습니다.
"무엇을 만들지 않을 것인가"의 목록은 "무엇을 만들 것인가"의 목록만큼이나 중요했습니다.
실제로 내 시간을 가장 많이 잡아먹은 TOP 3 항목
이번 프로젝트는 "5주 안에 작동하는 무언가를 출시한다"는 목표의 스프린트였으며, 실제로 시간을 가장 많이 잡아먹은 세 가지 항목은 모두 **메모리 레이어 (memory layer) 및 프로모션 엔진 (promotion engine)**에 있었습니다.
1. 완전히 다른 스케일로 존재하는 세 가지 신호의 혼합
PromotionEngine은 **빈도 (Frequency) / 결과 상관관계 (Outcome correlation) / LLM 자기 평가 (LLM self-eval)**를 병렬적으로 처리하여 L1 → L3 프로모션을 평가합니다. 이 과정은 예상보다 까다로웠습니다.
- **빈도 (Frequency)**는 0..∞ 사이의 정수입니다 (동일한 사실에 대한 참조 횟수).
- **결과 상관관계 (Outcome correlation)**는 0..1 사이의 비율입니다 (해당 사실이 함께 나타난 작업들의 성공률).
- **LLM 자기 평가 (LLM self-eval)**는 0..1 사이의 연속적인 값입니다. 하지만 **비결정론적 (non-deterministic)**이며, 제공자(provider)마다 점수 분포가 다릅니다 (GPT-5는 엄격하여 중앙값이 ~0.4, Claude는 관대하여 ~0.7, Gemini는 양봉 분포 (bimodal)를 보임).
저의 첫 번째 구현 방식은 단순한 가중 평균 (weighted average)이었습니다. 하지만 빈도는 상한선이 없기 때문에, "L1에서 우연히 많이 접하게 된 사실들"이 상단으로 떠오르는 문제가 발생했습니다. 결국 저는 다음과 같은 구조에 도달했습니다.
@dataclass(frozen=True)
class PromoteSignal:
frequency: float # raw count
...
핵심 사항:
- **Z-score 정규화 (Z-score normalization)**를 통해 통제 불능인 빈도 신호를 다스립니다.
- **경로별 임계값을 적용한 OR 로직 (OR logic with per-path thresholds)**은 "단 하나의 결정적인 경로만으로도 프로모션이 발생함"을 의미하며, 이는 단일 메커니즘에 의존하는 것을 의도적으로 피하기 위함입니다.
- LLM 자기 평가는 비결정론성을 상쇄하기 위해 **N=3번 호출의 중앙값 (median)**을 사용합니다 (N=5도 시도해 보았으나 이점은 정체되었습니다).
decide_promotion은 **순수 함수 (pure function)**이므로, 과거의 프로모션 로그를 재생하여 파라미터 민감도 분석 (parameter sensitivity analysis)을 수행할 수 있습니다.
"Z-score → sigmoid → 임계값을 적용한 OR" 방식에 도달하기까지 약 5번의 재설계가 필요했습니다. 교훈: 신호 융합 (signal fusion)을 가중 평균으로 시작하지 마세요. 경로별 결정적 임계값 + OR 방식이 가장 견고한 것으로 나타났습니다.
2. Composite 백엔드에서의 "팬아웃 검색(fan-out search) × 단일 목적지 쓰기(single-destination write)" 조정
Composite 백엔드는 여러 백엔드(JSON / Mem0 / TiDB / Letta…)로 검색 쿼리를 병렬로 전송하고, 상호 순위 융합 (Reciprocal Rank Fusion (RRF))을 통해 결과를 융합합니다. 하지만 쓰기(write)는 write_to=로 지정된 단일 백엔드로만 진행됩니다. 이러한 비대칭성은 세 가지 함정을 만들어냈습니다.
(a) 나중에 백엔드를 추가하면, 과거 데이터가 해당 백엔드에는 보이지 않음
Composite(backends=[A, B], write_to="A")를 실행한 후 C를 추가하면, C에는 과거의 쓰기 데이터가 전혀 없게 됩니다. 검색 팬아웃(fan-out)이 불균형해져서, 2개의 백엔드는 조회되지만 1개는 조회되지 않는 상황이 발생합니다. 저는 내부 테스트(dogfooding) 과정에서 이를 "한 백엔드의 검색 품질이 그냥 낮다"라고 오해하며 발견했습니다.
→ 사후 조치로 리밸런싱(rebalancing)을 위해 replay_writes(source=A, target=C, since=...) 관리자 API를 추가했습니다.
(b) write_to가 다운되었을 때의 우아한 성능 저하 (Graceful degradation)
write_to가 다운되었을 때, 쓰기는 중단하되 읽기는 계속하는 방식이 매력적으로 보일 수 있습니다. 하지만 이는 **"검색에서 나타나던 기록이 다음 검색에서는 사라지는 현상"**을 유발할 수 있습니다 (복구 후 쓰기가 다시 실행되지 않기 때문입니다).
결국 저는 우아한 성능 저하를 포기하고 대신 에러를 발생(raising)시키기로 했습니다. 어설픈 가용성은 최종 일관성 (eventual consistency) 문제 위에 데이터 진실성 (data-truthfulness) 문제를 얹게 됩니다. "다운되었을 때는 솔직하게 다운되었다고 말하는 것"이 운영 측면에서 가장 쉬운 방법임이 밝혀졌습니다.
(c) RRF 융합 시의 타이 브레이킹 (Tie-breaking)
여러 백엔드가 동일한 record_id를 랭크 1위로 반환할 때, 점수가 정확히 일치하게 됩니다. 타이 브레이킹 (tie-breaking) 규칙이 없으면 순서가 백엔드 등록 순서에 따라 결정되며, 이로 인해 CI 테스트가 불안정해집니다 (flaky).
→ (rrf_score, timestamp DESC, backend_priority)에 대한 사전식(lexicographic) 타이 브레이킹을 도입했습니다. 이제 CI 환경에서도 순서가 재현 가능합니다.
3. 수면 시간 통합기(sleep-time consolidator)의 멱등성 (Idempotency)
야간 배치 작업이 L1 → L3 승격(promotion)을 재실행하기 때문에, 동일한 사실을 두 번 승격하지 않는 것이 멱등성의 핵심입니다. 엄격한 record_id 기반의 중복 제거만으로는 충분하지 않았습니다:
- 사용자가 "고객 X는 ROI를 우선시함"과 "X씨는 ROI를 중시함"이라는 두 가지 표현을 동일한 사실로 기록할 경우 → 서로 다른
record_id를 갖게 되지만, 의미론적으로는 동일함 - LLM 자기 평가(self-eval)는 **비결정론적(non-deterministic)**임: 동일한 텍스트에 대해 0.78 / 0.82 / 0.76과 같은 점수가 나오므로, 임계값 근처에서는 "어제는 승격되지 않았으나 오늘은 승격됨"과 같은 현상이 발생함
그래서 저는 **퍼지 중복 제거 (fuzzy dedup)**를 추가했습니다:
def is_duplicate(candidate: Record, l3_recent: list[Record]) -> bool:
# 이미 승격된 항목 확인: 최근 윈도우 내의 L3 레코드 중
# 임베딩 코사인 유사도(embedding cosine-sim)가 임계값 이상이면 중복으로 처리
...
어려웠던 점은 두 가지 임계값(유사도 + 윈도우)을 조정(tuning)하는 것이었습니다:
| 설정 | 발생하는 문제 |
|---|---|
| 0.95 / 7일 (엄격함) | 표현이 다른 "동일한 사실"이 L3 전체에 걸쳐 중복되어 나타남 |
| ... |
결정적인 요인은 양방향을 동시에 계측(instrumenting)하는 것이었습니다. 단순히 "중복을 놓치는 비율"뿐만 아니라 **"실제로 구별되는 사실을 중복으로 처리하는 비율"**도 함께 보아야 합니다. 한쪽 측면만 추적하면 필연적으로 불균형하게 조정하게 됩니다. 저는 나중에 이 방식을 PromotionEngine의 전체 교정(calibration) 루프에 적용했습니다.
v0.1.0 수치로 보는 현황
| 지표 | 값 |
|---|---|
| 코드베이스 | 약 25,000 LoC (Python + 테스트 + i18n) |
| ... |
향후 3개월 계획
- v0.2: 일급 시민(First-class) TiDB Vector / pgvector 백엔드 지원 (현재는
mem0래퍼를 통해 지원) - v0.2: localhost 루프백 OAuth (
praxia serve요구 사항 제거) - v0.3: 멀티 테넌트(Multi-tenant) 조직 기능 (Open Core 진입점)
- 문서: 확장된 영어 튜토리얼
- 커뮤니티: Discord, 더 활발한 Discussions
같은 상황에 처한 분들에게
5주 동안 전력 질주하며 느낀 세 가지입니다. 솔로 오픈 소스(OSS) 프로젝트를 시작할지 고민 중인 분들에게 드리는 조언입니다:
- 완성도보다 배포 빈도에 집중하세요 (Ship frequency over polish).
v0.0.4버전을 영원히 다듬는 것보다v0.1.0을 배포하는 것이 훨씬 더 많은 것을 가르쳐 줍니다. - 차별점을 한 줄로 정의하세요. Praxia의 경우: "개인 → 조직 메모리 자동 승격 (personal → org memory auto-promotion)"입니다. 이를 말할 수 없다면, 글을 쓸 수도, 피칭(pitch)을 할 수도 없으며, Pull Request (PR)도 들어오지 않을 것입니다.
- 오픈 소스 (OSS)의 진정한 경쟁자는 동일한 문제를 해결하는 상용 SaaS입니다 — LangChain이나 CrewAI가 아니라, 유료 에이전트 플랫폼의 결제 장벽(paywall)입니다. 이러한 기능들을 Apache-2.0 라이선스 하에 묶는 것 자체가 차별화 요소입니다.
결론 (Closing)
⭐ Stars / 🍴 Forks / Issues / PRs 모두 환영합니다.
github.com/praxia-dev/praxia
이 글이 마음에 드셨다면, 60초 데모를 여기서 확인하세요: https://youtu.be/o_6NbjJU1AA
"개인의 탁월함 × 조직의 연속성"을 AI와 연결하는 것 — 이것이 Praxia가 이번 봄에 시작한 미션입니다.
이 시리즈의 다음 내용: 제가 오픈 소스 (OSS) 코어에 직접 포함시킨 엔터프라이즈 플랫폼 기능들 (SSO, RBAC, 감사 로그 (audit logs), KMS-envelope-encrypted OAuth 토큰)과 왜 이 중 어느 것도 엔터프라이즈 티어(Enterprise tier) 뒤에 숨겨져 있지 않은지에 대하여.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기