TRENDSHIFT DAILY #4 · 2026-07-25 · PAXM · 코딩 에이전트용 영속 메모리

pax-beehive/paxm 딥다이브
— 새 세션마다 프로젝트를 다시 설명하지 않게 해주는, 코딩 에이전트용 메모리 어댑터

PAXM은 코딩 에이전트(Codex · Claude Code · OpenCode · Pi · 그리고 아무 MCP 클라이언트)에 영속 메모리(persistent memory)를 붙여주는 Go 단일 바이너리 어댑터다. 한 문장으로 말하면 "한 세션에서 내린 결정·컨벤션·작업 맥락을 저장해 두었다가, 나중에 열리는 다른 세션·다른 에이전트가 그대로 꺼내 쓰게 해주는 메모리 계층"이다.

이 프로젝트의 급소는 이것이다: PAXM은 또 하나의 클라우드 메모리 '서비스'가 아니라, 여러 메모리 백엔드를 갈아끼우는 '어댑터'다. 로컬 SQLite로 계정·API 키·임베딩·별도 LLM 호출 없이 바로 시작하고, 나중에 Zep·Mem0·MemOS·OpenViking 같은 전용 메모리 시스템이나 내가 만든 프로바이더로 설정만 바꿔 옮겨탈 수 있다. 에이전트를 다시 배선할 필요가 없다.

(저장소 pax-beehive/paxm · 언어 Go 97% · 기본 저장소 SQLite(FTS5/BM25) · 인터페이스 CLI · MCP · 스킬 · 라이프사이클 훅 · 라이선스 Apache-2.0 · 분석 시점 v0.2.4 · shallow clone 소스 직접 분석)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 주목받는가 — '서비스'가 아니라 '어댑터'
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

"매번 다시 설명하는" 코딩 에이전트에게 기억을 달아준다

코딩 에이전트를 써 본 사람은 다 안다. 어제 "우리는 배포를 GitHub Actions로만 한다, 노트북에서 직접 배포 금지"라고 알려줬는데, 오늘 새 세션을 열면 에이전트는 그걸 까맣게 잊는다. 아키텍처 결정, 팀 컨벤션, "이 함수는 건드리지 마라" 같은 운영 제약을 매 세션 처음부터 다시 설명해야 한다. PAXM은 이 반복을 끊는다.

한 세션에서 paxm remember로 결정을 남겨두면, 나중 세션에서 paxm recall로 되살린다. 게다가 패시브(passive) 통합을 켜면, 에이전트가 답하기 전에 관련 맥락을 알아서 불러와 넣어주고, 대화가 끝난 후에 완료된 턴을 조용히 저장한다. 프로바이더가 느리거나 죽어도 코딩 세션 자체는 멈추지 않는다.

핵심 비유

"매일 아침 기억이 리셋되는 신입" vs "인수인계 노트를 든 팀원"

메모리 없는 에이전트는 영화 속 매일 기억이 초기화되는 주인공 같다. 실력은 좋은데, 어제 합의한 걸 오늘 또 처음부터 설명해야 한다.

PAXM은 그 사이에 공용 인수인계 노트를 끼워 넣는다. Codex가 적어 둔 결정을 Claude Code가 펼쳐 읽고, OpenCode가 이어 쓴다. 노트는 내 서랍(로컬 SQLite)에 있고, 원하면 팀 공용 캐비닛(Zep·Mem0 등)으로 옮길 수 있다.

중요한 설계 철학이 하나 더 있다: 사용자가 통제권을 쥔다. 자격증명, 훅 신뢰(hook trust), 라우팅, 데이터 위치, 비활성화·삭제·롤백이 전부 사용자 소유다. 에이전트 툴(recall/remember)은 훅을 설치하거나 자격증명을 바꾸거나 라우팅을 조작할 수 없다 — 에이전트가 몰래 내 메모리 설정을 장악하지 못하도록 권한을 최소화했다.

용어 풀이
메모리 어댑터 (memory adaptor)
에이전트와 실제 저장소 사이에 끼는 얇은 중간층. 에이전트에게는 항상 똑같은 인터페이스(recall/remember)를 보여주고, 뒤에서는 SQLite·Zep·Mem0 등 어떤 백엔드로도 연결한다. 저장 기술을 바꿔도 에이전트 쪽 코드는 그대로다. "메모리 서비스"가 아니라 "메모리 배선(plumbing)"이라고 이해하면 정확하다.

2왜 주목받는가 — '서비스'가 아니라 '어댑터'

TrendShift Daily 상위권(#4)에 오른 이유 4가지

AI 메모리 도구는 이미 여럿 있다(Mem0, Zep, MemGPT 계열 등). 그런데 PAXM이 트렌딩에 오른 건 접근각이 다르기 때문이다. 네 가지 결이 겹친다.

① 제로 셋업 — 계정도, API 키도, 임베딩도 없다

대부분의 메모리 시스템은 시작부터 임베딩 모델 + 벡터 DB + API 키를 요구한다. PAXM의 기본값은 로컬 SQLite 하나다. 임베딩도, 외부 LLM 호출도, 계정도 없이 바로 "기억하고 되살리는" 완결된 루프가 돈다. 검색은 SQLite에 내장된 FTS5 전문검색 + BM25 랭킹으로 처리한다. 벡터가 아니라 단어 기반이라 결정론적이고 빠르며, 오프라인에서도 완전히 동작한다.

② provider 중립 — 나중에 갈아탈 자유

로컬에서 시작했다가 팀 규모가 커지면 전용 메모리 시스템으로 옮기고 싶어진다. PAXM은 이때 에이전트를 다시 배선하지 않는다. 프로파일(profile) 설정만 바꾸면 된다. 기본 내장 프로바이더만 해도 SQLite · Zep · Mem0 · Mem0 Cloud · MemOS · MemOS Cloud · OpenViking, 여기에 커스텀 JSON-RPC로 사내 비공개 메모리 시스템까지 붙일 수 있다. 여러 프로바이더를 동시에 켜고 라우팅할 수도 있다.

③ 에이전트를 가로지르는 하나의 기억

Codex에서 붙잡은 결정을 Claude Code·OpenCode·Pi·아무 MCP 클라이언트에서 되살린다. 도구가 달라도 기억은 하나다. "어제 Codex랑 정한 걸 오늘 Claude Code가 모른다"는 단절을 없앤다. 이건 특정 벤더 툴에 락인되지 않으려는 개발자에게 특히 매력적이다.

④ "막히지 않는" 신뢰성 설계

메모리 기능을 켜면 흔히 생기는 걱정이 "메모리 서버가 느리면 코딩도 같이 느려지지 않나?"이다. PAXM은 패시브 쓰기를 로컬 durable 큐에 먼저 커밋하고, 프로바이더 전달은 백그라운드에서 재시도한다. 패시브 recall도 전체 800ms·프로바이더당 250ms 예산을 걸어 느린 프로바이더가 있어도 건강한 부분 결과만 받고 넘어간다. 지연·실패가 세션을 막지 않는다.

한 걸음 더 — 벤치마크를 '정직하게' 다룬다

README는 초기 LoCoMo 평가에서 SQLite 턴 메모리가 30문제 중 13개, Mem0 기본값이 11개를 맞혔다고 밝히면서도, "이건 공식 점수가 아니라 탐색적 결과이며, 한 번의 대화만 다뤘다"고 스스로 못박는다. 성능을 과장하지 않고 한계를 먼저 적는 태도가, 이 프로젝트의 엔지니어링 신뢰도를 오히려 높인다.

구분전형적 메모리 서비스PAXM
시작 조건계정 + API 키 + 임베딩 모델없음 — 로컬 SQLite 바로 시작
검색 방식벡터 임베딩(외부 모델 호출)FTS5 + BM25 (단어 기반, LLM 불필요)
백엔드그 회사 저장소 고정7종 내장 + 커스텀 JSON-RPC 교체
에이전트 범위대개 특정 툴Codex·Claude·OpenCode·Pi·MCP 공통
느린 백엔드세션이 같이 느려짐durable 큐 + 타임아웃 예산으로 격리
데이터 소유클라우드로컬 우선, 자격증명·위치 사용자 소유

3기술 스택 전체 지도

"의존성을 극도로 줄인" Go 단일 바이너리 — 직접 의존성 7개

PAXM의 코드를 열면 가장 먼저 놀라는 건 의존성이 이상하리만치 적다는 점이다. go.mod의 직접 의존성은 단 7개. CLI 프레임워크도, MCP SDK도 외부 라이브러리를 쓰지 않고 손으로 짰다. 이건 "코딩 에이전트에 설치되는 도구"라서 가볍고, 크로스 컴파일이 쉽고, 공급망 위험이 작아야 한다는 판단으로 읽힌다.

go.mod — 실제 직접 의존성 (Go 1.25.0)

// module github.com/pax-beehive/paxm — go 1.25.0
charm.land/huh/v2        v2.0.0   // 대화형 setup TUI 폼
charm.land/lipgloss/v2   v2.0.1   // 터미널 스타일링
github.com/charmbracelet/x/term    v0.2.2
github.com/getzep/zep-go/v3        v3.23.0  // Zep provider 공식 SDK
golang.org/x/sys         v0.44.0
gopkg.in/yaml.v3         v3.0.1   // config.yaml 파싱
modernc.org/sqlite       v1.53.0  // 순수 Go SQLite (cgo 불필요)
영역선택왜 이렇게 했나
언어Go 1.25.0단일 정적 바이너리 → macOS·Linux·Windows(amd64·arm64) 한 번에 배포
SQLite 드라이버modernc.org/sqlite순수 Go 구현. cgo·C 컴파일러 없이 크로스빌드(가장 흔한 mattn/go-sqlite3가 아님)
CLI표준 flag + 자작 디스패처cobra 안 씀. 의존성·바이너리 크기 최소화
MCP 서버자작 JSON-RPC 2.0MCP SDK 안 씀. stdio로 프로토콜 직접 구현(internal/mcp/server.go)
대화형 setupCharm 스택(huh·lipgloss·bubbletea)사용자 ID·프로바이더·통합을 고르는 TUI 폼
설정yaml.v3config.yaml — 프로바이더·프로파일·에이전트·훅·텔레메트리
외부 SDKzep-go/v3 (Zep만)나머지 프로바이더는 표준 net/http로 직접 호출
용어 풀이
cgo 없는 순수 Go SQLite
modernc.org/sqlite는 C로 된 SQLite를 Go로 트랜스파일한 구현이다. 보통 SQLite를 쓰면 C 컴파일러가 필요해 크로스 컴파일이 까다로운데, 이건 순수 Go라 GOOS=windows go build 한 줄로 다른 OS 바이너리를 뽑을 수 있다. "설치가 쉬운 CLI 도구"에는 결정적 이점이다.
함정 주의
SQLite는 부모 디렉토리에 '쓰기'가 돼야 한다

SQLite 헬스체크는 설정된 DB 옆에 WAL/SHM 파일을 만들 수 있어야 한다. DB는 읽히는데 부모 폴더에 쓰기가 막힌 샌드박스에서는 SQLite error 14가 뜬다. 샌드박스 평가 때는 쓰기 가능한 격리 경로를 SQLite 경로로 지정해야 한다(같은 설정이 실제 에이전트 프로세스에선 정상일 수 있음).

4아키텍처 심화 분석

어댑터 · 라우터 · 액티브/패시브 · durable 큐 네 축

PAXM은 스스로를 "메모리 서비스가 아니라 메모리 어댑터"라고 규정한다. 전체 데이터 흐름은 한 줄로 요약된다:

AI 에이전트 (Codex · Claude Code · OpenCode · Pi · MCP 클라이언트) │ ┌────┴──────┬───────────┬──────────┐ │ CLI │ MCP │ 스킬 │ 훅(passive) └────┬──────┴─────┬─────┴─────┬────┘ └────────────┼───────────┘ ┌───────▼────────┐ │ paxm │ 런타임 + provider 라우터 │ (adaptor) │ 프로파일·랭킹·타임아웃·벌크헤드 └───────┬────────┘ ┌──────┬───────────┼──────────┬───────────┬──────────┐ SQLite Zep Mem0 MemOS OpenViking JSON-RPC (기본, (선택) (선택) (선택) (선택) (커스텀 어댑터) FTS5/BM25) ↑ 액티브(CLI·MCP·스킬) + 패시브(라이프사이클 훅)가 같은 라우터를 공유

축 ① 최소 인터페이스 + 옵셔널 capability (internal/memory/types.go)

어떤 백엔드든 PAXM에 붙으려면 단 4개 메서드만 구현하면 된다. 나머지 고급 기능(배치 쓰기·삭제·만료 정리 등)은 옵셔널 인터페이스(capability)로 분리해, 지원하는 프로바이더만 골라 구현한다. Go의 "인터페이스 타입 어서션" 관용구를 교과서처럼 쓴 예다.

// 모든 프로바이더가 반드시 구현하는 최소 계약
type Provider interface {
    Name() string
    Search(ctx context.Context, query SearchQuery) ([]MemoryHit, error)
    Put(ctx context.Context, item MemoryItem) (MemoryRef, error)
    Health(ctx context.Context) error
}
// 지원하는 프로바이더만 추가로 구현하는 옵셔널 capability
type BatchProvider        interface { PutBatch(...) ([]MemoryRef, error) }
type TurnBoundaryProvider interface { PreserveTurnBoundaries() bool }
type CleanupExpiredProvider interface { CleanupExpired(...) (int, error) }
// 그 외 CloseProvider / DeleteProvider / EvalScopeCleaner ...

저장 단위인 MemoryItem·MemoryHitOrigin(누가 만들었나 — user·agent·session·turn)과 Scope(가시성 경계)를 분리한다. 메모리 계층은 stm(단기, 작업 메모리)과 ltm(장기, 영속 사실) 2단계다.

축 ② Router — 벌크헤드로 느린 프로바이더 격리 (internal/memory/router.go)

여러 프로바이더를 동시에 켰을 때의 오케스트레이터다. 핵심은 1-슬롯 벌크헤드(bulkhead). 프로바이더마다 크기 1짜리 채널(searchSlot·putSlot chan struct{})을 두어, 어떤 프로바이더가 슬롯을 잡고 늦으면 그 프로바이더로 가는 후속 요청은 빠르게 실패(fail-fast)한다. 한 곳이 막혀도 전체가 멈추지 않는다.

용어 풀이
벌크헤드 패턴 (bulkhead)
배의 격벽처럼, 한 구역이 침수돼도 배 전체가 가라앉지 않게 칸을 나누는 설계. 여기서는 "느린 프로바이더 하나가 스레드/슬롯을 다 잡아먹어 전체 검색을 멈추는" 사태를 막는다. 1-슬롯 채널이 그 격벽이다.

축 ③ 액티브 vs 패시브 — 두 갈래 진입로

에이전트가 PAXM에 닿는 길은 두 가지다. 둘 다 같은 런타임·같은 프로바이더 라우터를 쓰므로 필터링·프로파일·랭킹·타임아웃이 일관된다.

경로진입점쓰임새
액티브(Active)CLI · MCP · 스킬의도적 recall, 명시적 쓰기, 검사(inspection)
패시브(Passive)에이전트 라이프사이클 훅프롬프트 직전 자동 recall + 턴 종료 시 자동 캡처

패시브 훅은 internal/capture/handler.go가 처리한다. 훅 종류는 user_input(recall+write), tool_use·tool_failure, turn_end. 첫 user_input이면 느슨한 passive_initial 프로파일로 초기 recall을 하고, 이후는 passive 프로파일(결과 2개, 최소 관련도 0.75)로 좁혀 잡는다.

축 ④ durable 큐 — 크래시가 나도 안 잃는다 (internal/capturequeue/queue.go)

패시브 쓰기는 프로바이더로 바로 보내지 않는다. 먼저 로컬 SQLite durable 큐에 커밋하고, 전달은 백그라운드에서 lease·재시도로 처리한다. 훅은 큐에 durable append만 하고 즉시 반환하므로, 프로바이더 지연이 세션을 막지 않는다. 큐는 3개 테이블로 상태를 관리한다.

// capturequeue — 3개 테이블 상태 머신 (modernc sqlite 백엔드)
capture_events      // 원시 훅 이벤트 (session_key, sequence …)
capture_episodes    // 세션별 묶음 (checksum, 누락 sequence 추적, complete)
capture_deliveries  // 프로바이더별 전달 (state, lease_until, next_attempt_at)

// 크래시 복구: 시작 시 state='delivering' → 'retry'로 리셋
RecoverDelivering()   // 중단된 전송을 재시도 가능 상태로 되돌림
// RetryMin 기본 1s · MaxAttempts 기본 10 · 초과 시 'dead'(dead-letter)
잘 만든 지점
"메모리 메아리(echo)"를 끊는 provenance 스트리핑

패시브 recall로 불러온 맥락을 그대로 다시 저장하면, 같은 기억이 무한히 자기 복제되며 부풀어 오른다. PAXM은 패시브 쓰기 전에 recall provenance를 벗겨내(strip) 이 메아리를 차단한다. 또 정확 일치 LTM 통합으로 중복 누적을 제한한다. 메모리 시스템을 오래 굴려 본 사람만 아는 함정을 미리 막아 둔 설계다.

5디렉토리 구조 해부

14줄짜리 main.go, 그리고 로직이 전부 모인 internal/ 22개 패키지

진입점 cmd/paxm/main.go14줄뿐이다 — cli.MainWithDependencies(...)만 호출한다. 실제 로직은 전부 internal/ 아래 22개 서브패키지에 모여 있다. Go의 internal/ 규칙(외부 임포트 차단)으로 공개 API 표면을 좁게 유지하는 전형적 구성이다.

paxm/ ├─ cmd/paxm/main.go 진입점 (14줄 — 디스패처만 호출) ├─ internal/ ★ 로직 전부 (22개 서브패키지) │ ├─ memory/ ★ Provider 인터페이스 · Router · 랭킹 · 타입 │ ├─ adapters/ ★ provider 레지스트리 + 백엔드별 어댑터 │ │ ├─ sqlite/ 기본 SQLite 프로바이더(FTS5 스키마·마이그레이션) │ │ │ └─ retrieval/ BM25 검색 · 3단계 플랜 · analyzer · excerpt │ │ ├─ jsonrpc/ 커스텀 프로바이더 프로토콜(process-per-request) │ │ │ └─ conformance/ 외부 프로바이더 블랙박스 적합성 키트 │ │ ├─ zep/ mem0/ mem0cloud/ memos/ openviking/ 내장 백엔드들 │ │ ├─ team/ 팀 스코프 durable 쓰기 프로파일 │ │ └─ contracttest/ 모든 어댑터가 통과하는 공유 계약 하네스 │ ├─ cli/ audience별 명령 디스패처(operator/tool/internal) │ ├─ mcp/ ★ 자작 stdio MCP 서버(602줄, SDK 없음) │ ├─ capture/ 패시브 훅 핸들러(user_input·tool_use·turn_end) │ ├─ capturequeue/ ★ durable 큐(1133줄, 크래시 복구 상태 머신) │ ├─ config/ YAML 모델 + 정규화(타임아웃 예산 상수) │ ├─ runtime/ facade/ config+router+tools 로딩 · 구버전 호환 │ ├─ telemetry/ bounded 로컬 JSONL 로그·메트릭(해시·길이만 저장) │ ├─ backfill/ eval/ dashboard/ operator/ sessions/ ... ├─ plugins/ │ ├─ paxm-claude/ Claude Code 플러그인(.mcp.json · hooks · 스킬 5훅) │ └─ paxm-memory/ Codex 플러그인(.codex-plugin · hooks) ├─ skills/paxm/ SKILL.md + agents/openai.yaml ├─ examples/jsonrpc-provider/ 커스텀 프로바이더 참조 구현(Go) ├─ docs/ architecture · provider-adapter-contract · adr/ ├─ evals/ baseline(100케이스) · locomo · cross-agent └─ go.mod Go 1.25.0 · 직접 의존성 7개
읽는 순서 팁

이 레포를 처음 읽는다면 memory/types.go(계약) → memory/router.go(오케스트레이션) → adapters/sqlite(기본 구현) → adapters/jsonrpc(확장 방식) 순서를 추천한다. "인터페이스를 먼저, 그다음 그 인터페이스를 구현한 가장 단순한 백엔드, 마지막에 외부 확장 규격"으로 내려가면 설계 의도가 자연스럽게 보인다. mcp/server.gocapturequeue/queue.go는 각각 "MCP를 손으로 짜면 이렇게 된다"·"durable 큐를 SQLite로 짜면 이렇게 된다"의 독립 교재로 읽어도 좋다.

6학습 포인트

이 저장소에서 실제로 배울 수 있는 것들

① 최소 인터페이스 + 옵셔널 capability (Go 관용구)

"모든 백엔드가 구현할 4개 메서드"와 "지원하는 백엔드만 구현할 옵셔널 인터페이스"를 나눈 설계는, 플러그형 시스템을 확장 가능하게 만드는 정석이다. 새 capability가 생겨도 기존 프로바이더 코드를 건드리지 않는다. Go의 인터페이스 타입 어서션(if p, ok := prov.(BatchProvider); ok)을 실무 규모로 어떻게 쓰는지 배울 수 있다.

② 벌크헤드 + 타임아웃으로 장애를 격리하는 법

분산 시스템 신뢰성의 핵심 패턴이 router.go 한 파일에 응축돼 있다. 1-슬롯 채널로 느린 의존성을 fail-fast시키고, context.WithTimeout으로 각 호출에 예산을 매기고, required/best-effort로 실패 전파를 제어한다. "외부 서비스에 의존하는데 그게 느려질 때 내 시스템은 어떻게 버티는가"의 실전 답안이다.

③ 임베딩 없이 FTS5 + BM25로 검색하는 법

벡터 DB가 유행이지만, PAXM은 SQLite FTS5 + BM25만으로 쓸 만한 메모리 검색을 만든다. 배울 점: FTS5 가상 테이블과 동기화 트리거, BM25는 "낮을수록 좋음"이라 rawScore = -rank로 부호를 뒤집는 정규화, 그리고 3단계 폴백 플랜(정확 구절 → 모든 단어 → 부분 일치)으로 "빈손 결과"를 줄이는 기법. camelCase·snake_case·CJK 분해와 8KiB 초과 히트의 질의-관련 발췌(excerpt)까지 결정론적으로 처리한다.

// adapters/sqlite/provider.go — FTS5 가상 테이블 + 동기화 트리거
CREATE VIRTUAL TABLE memory_fts USING fts5(
    text, source,
    content='memories', content_rowid='rowid',
    tokenize='unicode61'
);
// AFTER INSERT/DELETE/UPDATE 트리거로 memories ↔ memory_fts 동기화
// PRAGMA: journal_mode=WAL · busy_timeout=5000 · SetMaxOpenConns(1)

// retrieval/search.go — BM25는 작을수록 좋아 부호를 뒤집는다
ORDER BY bm25(memory_fts) ASC, m.created_at DESC LIMIT ?
const RawScoreKind = "sqlite_fts_bm25_negated"  // rawScore = -rank

④ durable 작업 큐 + 크래시 복구 상태 머신

"쓰기를 잃지 않으면서 백엔드 지연을 흡수"하려면 durable 큐가 필요하다. capturequeue/queue.go(1133줄)는 이벤트를 세션별 episode로 묶고, lease·재시도·dead-letter 상태 머신으로 전달하며, 시작 시 RecoverDelivering()으로 중단된 전송을 되살린다. SQLite로 신뢰성 있는 작업 큐를 짜는 완결된 예제다.

⑤ 프로세스-per-요청 JSON-RPC 플러그인 경계

언어에 상관없이 메모리 백엔드를 붙이는 방법이 adapters/jsonrpc에 있다. PAXM은 요청 1건마다 프로바이더 프로세스를 새로 띄우고, stdin으로 요청 하나를 쓰고, stdin을 닫고, stdout에서 응답 하나를 읽은 뒤 프로세스를 종료한다. 상태는 프로세스 밖에 영속화한다. "표준 입출력만으로 만드는 언어 중립 플러그인 규격"의 교과서다.

⑥ SDK 없이 손으로 짠 MCP stdio 서버

internal/mcp/server.go(602줄)는 MCP SDK 없이 JSON-RPC 2.0을 직접 구현한다. protocolVersion "2025-11-25", 개행 구분 JSON 읽기/쓰기, 툴 4개(paxm_recall·paxm_remember·paxm_history·paxm_config_doctor)의 inputSchema를 손으로 구성한다. MCP 프로토콜 내부가 어떻게 생겼는지 뜯어보기에 이상적이다.

설계 사상 — 최소 권한
에이전트 툴은 설정을 못 바꾼다

CLI 명령을 operator / tool / internal 3분류로 나눠, 에이전트가 부르는 recall·remember·mcp(tool 계열)는 훅 설치·자격증명 변경·라우팅 조작을 할 수 없다. setup·config·uninstall 같은 위험한 조작은 operator 계열에만 있고 사람이 실행한다. MCP 서버도 setup·자격증명·훅 설치를 툴로 노출하지 않는다 — 에이전트가 몰래 자기 메모리 환경을 장악하지 못하게 한 경계다.

7하드웨어 / 시스템 요구사항

단일 바이너리 설치 vs 소스 빌드 — 그리고 실측 성능

바이너리 설치(권장)

릴리즈는 macOS · Linux · Windowsamd64·arm64를 모두 커버한다. 아카이브에 SHA256SUMS가 포함되고, 설치 스크립트가 교체 전 아카이브를 검증한다. 임베딩·GPU·별도 서버가 필요 없다 — 기본 SQLite는 로컬 파일 하나면 된다.

# 설치 (최신 릴리즈 바이너리 검증 후 설치)
curl -fsSL https://github.com/pax-beehive/paxm/releases/latest/download/install.sh | bash
paxm setup            # 사용자 ID·프로바이더·통합 대화형 선택
paxm config doctor    # 설정·프로바이더 헬스체크

# 첫 루프 검증
paxm remember --profile stm --text "PAXM_FIRST_RECALL_OK"
paxm recall   --query  "PAXM_FIRST_RECALL_OK"

소스에서 빌드(개발자)

필요버전 / 비고
Go1.25.0 (go.mod 기준)
C 컴파일러불필요 — 순수 Go SQLite라 cgo 없음
디스크SQLite DB + WAL/SHM 쓰기 가능한 경로
go test ./...
go vet  ./...
go build -o /tmp/paxm ./cmd/paxm

실측 성능 (Apple M4 기준, 어댑터 지연)

작업어댑터 지연
128 KiB SQLite 쓰기1.84 ms
2 MiB SQLite 쓰기14.31 ms
10개 / 1.25 MiB 배치12.36 ms
10만 개 짧은 메모리에서 recall0.54 ms
1만 개 × 32 KiB 메모리에서 recall0.61 ms
해석 주의
이 수치는 '어댑터'만 잰 것이다

위 벤치마크는 PAXM 어댑터 자체의 지연이지, 에이전트의 종단 응답 시간이 아니다. 실제 체감 속도는 여기에 모델 추론·네트워크가 더해진다. 또 벤치마크는 런타임 생성 임시 데이터셋으로 측정하며, 벤치마크 코퍼스를 레포에 커밋하지 않는다(재현 명령은 docs/benchmarks.md).

8직접 해볼 수 있는 실습 과제

난이도별 5개 — 설치 체감부터 커스텀 프로바이더까지
과제 1 · 난이도 ★☆☆☆☆

로컬 SQLite로 기억→되살리기 루프 체감

바이너리를 설치하고 paxm setup 후, paxm remember --profile ltm --text "우리는 배포를 GitHub Actions로만 한다"로 결정을 남긴다. 그다음 paxm recall --query "배포는 어떻게 하지?"로 되살아나는지 확인하라. 계정·키·임베딩 없이 완결되는 루프를 눈으로 보는 게 목표다.

과제 2 · 난이도 ★★☆☆☆

Claude Code / Codex 플러그인으로 패시브 메모리 붙이기

claude plugin marketplace add pax-beehive/paxm → 플러그인 설치 → paxm setup --integration claude-plugin. 새 세션을 열어, 이전 세션의 결정이 프롬프트 직전에 자동으로 recall되는지 관찰하라. paxm history --days 1로 캡처된 턴도 확인.

과제 3 · 난이도 ★★★☆☆

SQLite 검색 3단계 플랜 코드 추적

internal/adapters/sqlite/retrieval/search.go를 열어 exact_phrase → strict_all_terms → relaxed_partial 3단계가 어떻게 "가장 먼저 결과가 차는 단계"만 반환하는지 읽어라. FTS5로 top-K가 안 차면 LIKE 스캔을 보강해 RRF로 융합하는 지점, 그리고 analyzer.go의 camelCase/CJK 분해를 함께 본다.

과제 4 · 난이도 ★★★★☆

커스텀 JSON-RPC 프로바이더 만들고 적합성 통과시키기

examples/jsonrpc-provider/의 참조 구현을 베껴, paxm.health/search/put/capabilities에 응답하는 나만의 프로바이더(아무 언어)를 만든다. paxm eval provider jsonrpc --command ./provider로 블랙박스 적합성을 돌려 통과시켜라. "프로세스-per-요청" 계약(stdin 한 요청 → close → stdout 한 응답 → 종료)을 지키는 게 핵심.

과제 5 · 난이도 ★★★★★

durable 큐 크래시 복구 재현

패시브 쓰기가 capture_deliveries에 쌓이는 중에 프로세스를 강제 종료한 뒤 재시작해, RecoverDelivering()state='delivering''retry'로 되돌려 전달이 재개되는지 확인하라. MaxAttempts(기본 10)를 넘겨 'dead'로 떨어지는 경로, 그리고 recall provenance 스트리핑으로 "메모리 메아리"가 안 생기는지도 함께 검증한다.

9관련 기술 심화 학습 로드맵

4주 커리큘럼 — 이 저장소를 발판으로
주차주제할 것
1주차인터페이스 설계memory/types.go 정독 → "최소 계약 + 옵셔널 capability" 패턴 이해 → 직접 더미 프로바이더를 Provider만 구현해 등록해보기
2주차신뢰성(벌크헤드·타임아웃)router.go의 1-슬롯 채널·context.WithTimeout·required/best-effort 재현 → config의 800ms/250ms 타임아웃 예산이 어디서 적용되는지 추적
3주차검색·저장(SQLite/FTS5)FTS5 가상 테이블·트리거·BM25 학습 → retrieval/의 3단계 플랜·RRF·excerpt 코드 정독 → 임베딩 없는 검색의 한계·강점 정리
4주차확장(MCP·JSON-RPC·큐)mcp/server.go로 MCP 프로토콜 뜯기 → adapters/jsonrpc로 언어 중립 플러그인 규격 → capturequeue로 durable 큐 상태 머신 재현
학습 순서 팁

이 저장소는 "외부 서비스에 의존하는 신뢰성 있는 어댑터 계층을 어떻게 짜는가"의 종합 교재다. AI 메모리라는 주제를 걷어내고 봐도, 인터페이스 분리 · 장애 격리 · durable 큐 · 언어 중립 플러그인 · 손으로 짠 프로토콜 서버라는 백엔드 엔지니어링 정석이 한 레포에 다 들어 있다. Go로 견고한 CLI/서버를 짜고 싶은 사람에게 특히 값지다.

10핵심 키워드 사전

이 문서에 나온 용어 빠른 정리
용어
메모리 어댑터에이전트와 실제 저장소 사이의 얇은 중간층. 에이전트엔 고정 인터페이스, 뒤엔 교체 가능한 백엔드
provider(프로바이더)실제 메모리 백엔드. SQLite·Zep·Mem0·MemOS·OpenViking·커스텀 JSON-RPC 등
액티브 / 패시브액티브=CLI·MCP·스킬로 의도적 recall/write. 패시브=라이프사이클 훅으로 자동 recall/capture
stm / ltm메모리 계층. stm=단기(작업 메모리), ltm=장기(영속 사실)
Origin / Scope메모리의 출처(user·agent·session·turn)와 가시성 경계를 분리해 저장
FTS5 / BM25SQLite 내장 전문검색 엔진과 랭킹 함수. 임베딩 없이 단어 기반 검색
RRFReciprocal Rank Fusion(k=60) — 여러 후보 목록의 순위를 합쳐 하나로 융합
벌크헤드(bulkhead)1-슬롯 채널로 느린 프로바이더를 fail-fast 격리해 전체 멈춤을 방지하는 패턴
required / best-effortrequired 실패만 전체 실패로 전파, best-effort는 실패해도 건강한 결과 유지
durable 큐쓰기를 로컬에 먼저 커밋하고 백그라운드 재시도로 전달하는 큐(크래시에도 유실 없음)
dead-letterMaxAttempts(기본 10) 초과로 전달 실패한 항목이 떨어지는 최종 상태('dead')
process-per-requestJSON-RPC 프로바이더를 요청 1건마다 새 프로세스로 띄우고 응답 후 종료하는 계약
capability 협상paxm.capabilities로 배치·삭제 지원 여부를 물어, 미지원(-32601)이면 개별 put으로 폴백
메모리 메아리(echo)recall한 맥락을 그대로 재저장해 같은 기억이 무한 복제되는 현상. provenance 스트리핑으로 차단
MCPModel Context Protocol — 외부 도구·서버를 표준 방식으로 에이전트에 꽂는 개방 규격

11참고 링크

원문으로 더 파고들기