Synap 사용하기
Synap은 AI 에이전트를 위한 로컬 우선 메모리 엔진입니다. 모든 데이터는 사용자 머신의 ~/.synap/ 아래에 저장됩니다 — 클라우드, 계정, API 키가 필요 없습니다. mcp__synap__* 도구군을 통해 상호작용합니다.
이 스킬은 의사 결정 트리를 가르쳐줍니다: 어떤 도구를, 언제, 어떤 인자로 사용할지. 정확한 매개변수 구문은 도구 스키마를 참조하세요. 이 문서는 올바른 도구를 선택하고 잘 사용하는 방법에 관한 것입니다.
핵심 개념
Synap은 데이터를 앱 단위로 구성합니다. 앱은 네임스페이스(tasks, accounting, recipes, …)와 스키마를 가집니다. 스키마는 내부적으로 EAV(Entity-Attribute-Value) 방식이므로, 마이그레이션 없이 나중에 필드를 추가할 수 있습니다. 각 앱은 엔티티(행)와 속성(필드)을 보유합니다.
Synap 자체가 소유하는 두 개의 내장 앱이 있습니다:
synap.memories— 사용자에 대한 장기 기억. 쓰기는remember/forget을 통해 이루어집니다(write는 절대 사용하지 않음). 읽기는query/search를 통해 합니다.synap.sessions— 대화 기록(턴, 요약). 읽기 전용입니다. 쓰지 마세요.
사용자나 사용자가 만드는 앱은 다른 이름으로 존재합니다(synap. 접두사는 예약되어 있으므로 사용할 수 없습니다).
의사 결정 트리
"나에 대해 X를 기억해"
synap.memories에 대해 remember를 사용하세요. 두 가지 형태가 있습니다:
- 프로필 속성 (
type: "attr") — 사용자에 대한 지속적인 사실(preferred_language,city,allergies). 덮어쓰려면op: "set", 목록에 추가하려면op: "append"를 사용합니다. - 이벤트 (
type: "event") — 타임스탬프가 있는 관찰 기록("사용자가 v0.1.0을 출시함", "사용자가 압도당했다고 언급함"). 추가 전용이며,description,subject, 선택적when을 포함합니다.
지속적인 상태인 사실은 attr을 선택하세요. 나중에 컨텍스트에 중요한 순간은 event를 선택하세요.
"나에 대해 뭘 알고 있어?"
filters=[{field:"subject", op:"eq", value:"user"}] 조건으로 synap.memories에 대해 query를 실행하세요. 사용자가 "내 여행 계획에 대해 아무거나"처럼 모호하게 물었다면 search를 사용하세요. 이 경우 rank: "time_decay"로 최근 관련 이벤트를 찾을 수 있습니다.
"X를 잊어"
forget을 사용하세요. 정확하게 키를 지정합니다. "query 후 null을 쓰는" 방식 대신 forget이 속성 삭제와 이벤트 삭제를 원자적으로 처리합니다.
"작업 / 레시피 / 지출 / … 을 추적해" (새로운 종류의 데이터)
list_apps— 이미 존재하는 앱이 있는지 확인합니다.- 없다면: 신중한 스키마로
init_app을 실행합니다. write로 엔티티를 추가합니다.ops는 배열입니다: op 하나 = 단일 쓰기, N개 op = 원자적 배치(전부 아니면 전무).
스키마 설계 팁:
- 긴 텍스트 필드(설명, 노트)는
vectorized: true로 표시하여 검색이 의미적으로 찾을 수 있게 합니다. - 하나의 앱이 다른 형태를 담는 경우("task" + "note"를 하나의
workspace앱에)entity_type을 사용하세요. - v1을 과도하게 설계하지 마세요. EAV는 나중에 필드를 추가할 수 있습니다.
"내 할 일 / 지난주 노트 / …를 보여줘"
필터와 정렬을 포함한 query를 사용하세요. TSV 출력은 밀집되어 있으므로, 파싱하여 사용자에게 원시 그대로 보여주지 마세요.
"X에 관한 것을 찾아줘"
search를 사용하세요. 세 가지 랭킹 모드가 있습니다:
relevance(기본값) — 순수 의미 + 키워드 일치.recency— 최신순, 유사도 무시. "최신 노트"에 좋습니다.time_decay— 둘의 혼합. 신선도와 관련성이 모두 중요한 진화하는 컨텍스트(이벤트, 저널)에 좋습니다.
범위를 지정하려면 app_id를 전달하고, 앱 간 검색을 원하면 생략합니다.
"이 파일을 읽어줘"
read_file — file://, synap:// URI 및 로컬 경로를 처리합니다. 청크된 페이지를 반환합니다(기본 ~4K 문자). page 매개변수로 페이지를 넘길 수 있습니다.
"아까 말한 그거 뭐였지?" (컨텍스트가 압축된 경우)
대화에서 <compressed_segment turn_ids="..."> 블록을 찾으세요. expand에 해당 turn_ids를 전달하면 원본 그대로의 내용을 복구합니다. <stale_tool_result turn_id="...">는 접힌 도구 출력에 대한 동일한 패턴입니다.
안티패턴
synap.*앱에 쓰지 마세요. Synap이 거부합니다. 메모리에는remember/forget을 사용하세요.- 커스텀 앱으로 메모리를 재발명하지 마세요.
synap.memories에는 프로필+이벤트 의미, 시간 감쇠 랭킹, 매 턴 자동 주입 기능이 내장되어 있습니다. - 수동으로 너무 잘게 나누지 마세요. 긴 텍스트(1024 토큰 초과)는 자동으로 중첩 청킹됩니다. 그냥 전체를 쓰세요.
- subject 필드를 무시하지 마세요. 메모리는
subject(기본값"user")로 범위가 지정됩니다. 사용자가 다른 사람을 추적하는 경우("Alice는 차를 선호해" 기억),subject: "alice"를 전달하세요.
일반적인 패턴
사용자 생활의 새로운 영역 설정
사용자: "독서 기록을 추적하고 싶어."
1. list_apps — "reading" 앱이 없는지 확인
2. init_app name="reading" with fields:
- title (text, required)
- author (text)
- status (enum: to_read | reading | done)
- started_at, finished_at (date)
- notes (text, vectorized: true)
- rating (number, 1-5)
3. "준비됐어요 — 읽고 있는 책을 알려주시면 추가할게요."
대화 시작 시 컨텍스트 노출
[memory: profile] 및 [memory: events] 블록은 매 턴 자동으로 주입됩니다. 이를 보기 위해 query를 호출할 필요가 없습니다. 하지만 사용자가 더 광범위한 회상을 요구하는 경우("요즘 뭐하고 있었지?"), rank: "time_decay"와 함께 synap.memories에 search하는 것이 올바른 방법입니다.
데이터 마이그레이션 / 재구성
Synap에는 공식적인 마이그레이션이 없습니다. 필드를 추가하려면: 그냥 쓰기 시작하면 됩니다. 기존 행에는 해당 속성이 없을 뿐입니다. 이름을 변경하려면: query로 모든 entity_id를 가져온 후, 이전 필드 → 새 필드 매핑, 이전 필드 지우기 작업을 포함한 write 배치 업데이트를 실행하세요. EAV 비용은 최소입니다.
도구 치트 시트
| 목표 | 도구 | 비고 |
|---|---|---|
| 사용자 사실 기억 | remember type=attr | op: set 또는 append |
| 타임스탬프 이벤트 기록 | remember type=event | 추가 전용 |
| 기억 지우기 | forget | 정확한 키, 추측 금지 |
| 데이터 앱 생성 | init_app | 멱등성, 진화 지원 |
| 앱 목록 | list_apps | include_internal: true로 synap.* 확인 |
| 엔티티 추가/업데이트 | write | ops 배열, N개 = 원자적 배치 |
| 엔티티 필터링 | query | TSV 출력, 필드 프로젝션 |
| 의미 검색 | search | rank: relevance | recency | time_decay |
| 파일 읽기 | read_file | 자동 페이지 매김 |
| 압축된 턴 복구 | expand | 압축 블록의 turn_ids 전달 |
| 엔티티 간 연결 감지 | detect_links | 옵트인, 지식 그래프용 |