SKILL.md · diff
git:20261004.bf7a9c5 to git:20261004.cdcdfaa
1 added, 1 removed. Audit C to C.
# AnchorMind Skill Reference
AI 에이전트가 AnchorMind 기억 서버를 최대 효율로 활용하기 위한 기술 레퍼런스.
- ## 현재 버전: v6.0.0
+ ## 현재 버전: v6.1.0
AnchorMind 서버는 AI 에이전트의 세션 간 장기 기억을 파편(Fragment) 단위로 영속화하고, 16개 도구를 통해 저장·검색·연결·반성 기능을 제공한다. 마스터 키 세션에서는 관리 도구 4종(`memory_stats`, `memory_consolidate`, `check_update`, `apply_update`)이 추가로 노출되어 총 20개가 된다.
주요 현재 기능:
- `batch_remember`는 동기(기본)와 비동기(`async: true`) 두 모드를 지원한다. 비동기 모드에서는 선검증 후 Redis 큐에 적재하고 `{async, accepted, jobId}`를 즉시 반환하며, 워커가 ack·재시도(최대 3회)·dead-letter·기동 복구(RPOPLPUSH reliable queue)로 at-least-once 처리를 보장한다. `batch_status(jobId)`로 처리 상태(queued/processing/completed/dead)를 조회한다. Redis 비활성 환경에서는 자동으로 동기 모드로 폴백한다. `batch_remember`와 `memory_consolidate`는 표준 단일 JSON-RPC 응답으로 반환되며 `stream` 파라미터는 동작하지 않는다(하위 호환 유지).
- 검색은 3계층(L1 Redis 역색인 → L2 PostgreSQL 키워드·메타데이터 → L3 pgvector 시맨틱)으로 후보를 찾고, 그래프·시간·본문 어휘 후보와 함께 RRF로 병합한다. `computeRecallScore` 함수가 cross-encoder reranker 결과에 topic/keyword 직접 일치 신호를 log 정규화된 가산항으로 반영한다. 시맨틱 임계값 기본값은 0.4이고, `SearchParamAdaptor`가 50회 이상 샘플 축적 후 키별·시간대별로 임계값을 자동 조정한다.
- 형태소 분석은 로컬 CPU 분석기(`MorphemeTokenizer`)가 담당한다. 한글 garu-ko·영어 PorterStemmer·중국어 @node-rs/jieba·일본어 kuromoji로 라우팅하며, 벤치마크 기준 1.06ms/call 수준이다. `MEMENTO_MORPHEME_TOKENIZER=llm` 설정 시에만 LLM 경로가 활성화된다.
- 코어 도구는 MCP `title` + `annotations`(readOnlyHint/idempotentHint/openWorldHint) 메타데이터를 포함한다. Codex Desktop 등 deferred/lazy 로딩 클라이언트를 위한 재검색 가이드가 서버 initialize instructions에 포함된다.
- recall/context/reflect 응답 `_meta`에 `serverTime { iso, epoch_ms, display_kst, timezone }` 필드가 포함되어 LLM 클라이언트가 매 응답마다 서버 현재 시각을 재확인할 수 있다.
- `tool_reflect` 응답에 `_meta.link_suggestions[]`가 포함된다. 이 목록은 schema-fit gate를 통과하지 못해 자동 링크되지 않은 인과 관계 후보다. LLM은 후보를 검토하여 정당한 인과로 판단되는 항목만 `link(fromId, toId, relationType=...)` 도구로 명시 호출한다.
- recall/context 응답에서 `_meta.serverTime.display_kst` 또는 `_meta.serverTime.iso`로 현재 시점을 재확인하고 파편의 `created_at`·`age_days`와 대조하여 stale 여부를 판단한다. 응답 메타에 명시된 서버 시각이 자체 추정 시각과 다르면 서버 시각이 정답이다.
- 긴 파편 자동 분할(`splitLongFragments`)은 자식 파편에 본문 기반 keywords를 부여하므로 분할된 내용도 키워드 검색으로 회수된다. 자식 합집합이 원문의 수치 앵커(날짜·금액·비율)를 모두 담지 못하면 분할을 중단하고 원문을 그대로 유지하며, 자식이 남아 있는 원문은 GC 물리 삭제 대상에서 제외된다.
- 저장소 접근은 `lib/tools/db.js`의 `getPrimaryPool`, `queryWithAgentVector`가 맡는다. `MEMENTO_STORAGE` 환경변수는 저장소 백엔드 이름이며 동작에 영향을 주지 않는다.
- 검색 레이어는 `lib/memory/read/SearchScope.js`를 통해 `(workspace, caseId, resolutionStatus, phase, affect, type, topic, isAnchor, keyId)` scope를 처음부터 정합 적용한다.
- 실제 로직은 `lib/memory/processors/` 4개 클래스(MemoryRememberer·MemoryRecaller·MemoryReflector·MemoryLinker)와 `lib/memory/` 하위 6개 서브디렉토리(`read/`, `write/`, `link/`, `consolidate/`, `embedding/`, `signals/`)로 구성된다.
### LLM 동시성 제어
- Provider별 세마포어: chain key(`provider|baseUrl|model`) 기준 슬롯 한도 관리. 한도 초과 시 대기, 30초 타임아웃 초과 시 다음 fallback provider로 자동 전환.
- 429 쿨다운: HTTP 429 수신 시 해당 provider가 500-2000ms 랜덤 지터 동안 `isAvailable()=false` 반환. 체인 스킵 후 재진입.
- 내장 기본 한도: `ollama=16`, `openai@xiaomi|mimo-v2-pro=8`, `*-cli=1`, 기타 provider=10.
- 환경 변수:
- `LLM_CONCURRENCY_ENABLED=true|false` (기본 true, kill switch)
- `LLM_CONCURRENCY_WAIT_MS=30000` (슬롯 대기 타임아웃 ms)
- `LLM_CONCURRENCY` (JSON, chainKey 또는 provider name 기준 오버라이드)
- `MEMENTO_LLM_CLI_ENV_PASSTHROUGH` (쉼표 구분, 기본 없음): CLI provider 자식 프로세스는 기본 변수(PATH, HOME, 프록시, 인증서 등)와 CLI별 인증 변수만 받는다. 추가로 넘길 변수 이름을 여기에 적는다.
- 메트릭: `memento_llm_provider_concurrency_active{provider}`, `memento_llm_provider_concurrency_wait_ms{provider}`, `memento_llm_provider_429_total{provider}`
### `_meta` 응답 필드 사용 의무
recall / context 응답 메타데이터는 `_meta.searchEventId` / `_meta.hints` / `_meta.suggestion` 경로로만 읽는다. top-level mirror 필드(`_searchEventId` / `_memento_hint` / `_suggestion`)는 지원되지 않는다.
---
## 기억 도구 사용 규칙 (최우선 절대 준수)
이 섹션은 권고가 아니라 강제 규약이다. 위반은 해당 작업·답변의 무효 사유다. 아래 "도구 레퍼런스"의 파라미터 설명보다 이 섹션의 행동 규칙이 우선한다.
### 기본 3원칙
매 대화의 골격은 다음 세 단계로 구성된다.
- 대화 시작 시: `context` 도구를 첫 번째로 호출하여 기억을 로드한다. 미호출 시 규칙 위반.
- 대화 중: 중요한 사실·에러·결정·절차 발생 시 즉시 `remember`로 파편 저장. 작업 전에는 `recall`로 선행 검색.
- 대화 종료 시: `reflect`로 세션 핵심 내용을 영속화한다.
요약: `context 시작 → recall·remember 운용 → reflect 마무리`.
### 세션 시작 시
- 호스트 환경의 SessionStart 훅이 자동으로 `context` 도구를 실행하여 파편을 주입한다.
- 컨텍스트에 `[기억 시스템]` 또는 `[ANCHOR MEMORY]` 섹션이 있으면 숙지하고 적용 (추가 호출 불필요).
- 위 섹션이 없을 때만(훅 실패 시) `context` 도구를 직접 호출한다.
- context 호출만으로 충분하다고 여기지 않는다. context는 핵심 파편만 로드하므로, 사용자의 첫 발화에 등장한 구체적 키워드에 대해서는 추가 recall이 필수다.
선제적 컨텍스트 사냥 예시:
```
사용자: "AnchorMind에 새 기능 하나 추가하려고 해"
↓
1. recall(topic="memento-mcp", contextText="새 기능 추가 계획")
2. recall(type="decision", topic="memento-mcp")
3. recall(type="procedure", keywords=["memento-mcp", "test"])
↓
4. 답변 생성
```
3회의 선행 recall이 1회의 잘못된 조언보다 압도적으로 저렴하다.
### remember 필수 호출 상황 (즉시, 지연 금지)
| 상황 | type | importance |
|-|-|-|
| 에러 원인 파악 완료 | error | 0.8 |
| 에러 해결책 확정 | procedure | 0.8 |
| 사용자 선호·스타일 명시 | preference | 0.9 |
| 아키텍처·기술 스택 선택 | decision | 0.7 |
| 새 서비스 경로·포트·설정값 | fact | 0.6 |
| 배포·빌드 절차 완성 | procedure | 0.7 |
| "기억해", "저장해", "메모해" 언급 | (지정 타입) | 1.0 |
### Recall-First 원칙
답변·코드·조언을 생성하기 전에 recall을 의무적으로 선행 호출한다. 권고가 아니라 규약 위반 여부를 가르는 강제 조항이다.
| 발화 신호 | 예시 표현 | 의무 호출 |
|-|-|-|
| 명시적 과거 참조 | "이전에", "저번에", "지난번", "전에 했던" | `recall(text=관련 내용, includeContext=true)` |
| 프로젝트명 등장 | "memento-mcp", "RealPT", "my-project" 등 고유 식별자 | `recall(topic=프로젝트명, contextText=현재 작업 요약)` |
| 에러·실패·이상 동작 보고 | "에러 떴어", "안 돼", "실패했어", "터졌어" | `recall(type="error", keywords=[에러 키워드])` |
| 설정·환경변수·포트 언급 | "포트 뭐였지", "설정 어떻게 했지", "키 어디 있지" | `recall(type="fact", keywords=[설정명])` |
| 절차·빌드·배포 질문 | "어떻게 배포하지", "빌드 절차", "테스트 돌리는 법" | `recall(type="procedure", keywords=[프로젝트명, "deploy"])` |
| 결정·선택 회상 | "왜 X로 했지", "그때 뭘로 정했더라" | `recall(type="decision", topic=프로젝트명)` |
| 모호한 지시대명사 | "그거", "그 작업", "그 문제" | `recall(text=직전 대화 맥락 요약)` |
위 신호가 없는 경우에도 작업이 사용자의 코드베이스·인프라·운영 환경에 영향을 미친다면 recall을 1회 이상 선행한다. "내 기억에는 없으나 사용자의 기억에는 있을 수 있다"가 기본 가정이다.
### 침묵 호출 원칙
사용자가 "이전에 어떻게 했지?"라고 묻는 순간, recall 없이 "어떤 방식이었나요?"로 되묻는 것은 명백한 규약 위반이다. 비서가 자신의 노트를 들춰보지도 않고 상관에게 "지난번 어떻게 하셨죠?"라고 되묻는 것과 같다.
올바른 순서:
1. recall을 먼저 호출한다 (사용자에게 알릴 필요 없음)
2. 결과가 있으면 그 내용을 근거로 답변한다
3. 결과가 없거나 부족할 때에만 사용자에게 추가 정보를 요청한다
### Recall 결과 활용 의무
recall로 회수한 파편을 답변에 반영하지 않고 무시하는 것은 토큰 낭비이자 신뢰 훼손이다.
- similarity ≥ 0.7 파편: 답변에 명시적으로 반영하고, "이전에 ~하셨던 것에 따르면" 형태로 사용자에게 인지시킨다.
- similarity 0.4~0.7 파편: 참고 자료로 활용하되 사용자 확인을 받는다.
- stale_warning=true 파편: "기록상 X였으나 오래된 정보입니다. 현재도 동일한지 확인 부탁드립니다" 형태로 검증 요청.
- 결과 0건: `_meta.suggestion`에 `recommendedTool`이 있으면 즉시 후속 호출 (graph_explore, reconstruct_history 등).
### tool_feedback 의무화
recall은 호출로 끝나지 않는다. 응답의 `_meta.searchEventId`를 휘발시키는 행위는 학습 기회 폐기다.
```
recall 호출
↓
응답에서 _meta.searchEventId 추출 및 보관
↓
답변 생성에 사용된 파편 id 추적
↓
응답 직후 tool_feedback 호출:
- 활용한 파편 → relevant=true, fragment_ids=[활용된_id들]
- 무관한 파편 → relevant=false, fragment_ids=[무관한_id들]
- search_event_id=보관한_id
```
이 누적이 fragment_links의 weight를 갱신하여 다음 recall이 더 정확해진다. Reconsolidation 사이클을 끊는 것은 기억 시스템을 정체시키는 행위다.
### Recall 회피 안티패턴
| 금지 행위 | 이유 |
|-|-|
| "내가 기억하기로는 ~" 형태로 추측 답변 | 환각 위험. recall로 검증 가능한 사실을 검증하지 않은 것 |
| 사용자에게 "이전 설정 알려주세요"라고 되묻기 | 본인의 기억 도구를 사용하지 않고 사용자에게 인지 부담 전가 |
| recall 1회 결과 0건으로 즉시 포기 | keywords 재구성, text 전환, contextText 추가, type 필터 제거 등 재시도 의무 |
| `_meta.suggestion` 무시 | 서버가 명시적으로 권고한 후속 도구를 묵살하는 행위 |
| context만 호출하고 recall 생략 | context는 전반적 맥락, recall은 작업별 정밀 검색. 둘은 대체재가 아니라 보완재 |
### Recall 자가 점검 체크리스트
매 응답 직전에 내부적으로 점검한다.
```
[ ] 사용자 발화에 과거 참조 신호가 있었는가? → recall 했는가?
[ ] 사용자 발화에 프로젝트·서비스명이 있었는가? → 해당 topic으로 recall 했는가?
[ ] 사용자가 에러·실패를 언급했는가? → type="error"로 recall 했는가?
[ ] 내 답변이 사용자의 구체적 환경 가정에 의존하는가? → 그 환경을 recall로 확인했는가?
[ ] 직전 recall의 tool_feedback을 전송했는가?
```
하나라도 미수행이면 답변 송출 전에 즉시 보정한다.
### Recall 최소 빈도 가이드라인
| 세션 유형 | 권장 recall 최소 횟수 |
|-|-|
| 단순 질의응답 (1~2 턴) | 1회 (사용자 발화 분석 직후) |
| 코드 작성·수정 작업 | 3회 이상 (시작 시 컨텍스트, 작업 중 검증, 완료 전 절차 확인) |
| 에러 디버깅 | 5회 이상 (에러 검색, 유사 사례, 절차, 결정 이력, 케이스 재구성) |
| 아키텍처·설계 논의 | 5회 이상 (decision/episode 중심, depth="high-level") |
빈도가 미달하면 능동성이 부족한 것이다. 과잉은 비용일 뿐이지만 결핍은 신뢰 훼손이다.
### forget 의무 호출 상황
- 에러를 완전히 해결한 직후: 해당 error 파편 삭제.
- 사용자가 "잊어", "지워" 언급 시: 즉시 삭제.
### 세션 종료 시
- 중요한 작업 결과는 `reflect`로 요약하여 저장.
- 잘못된 파편은 `forget`으로 정리.
- 미저장 상태로 세션을 종료하지 않는다.
### 세션이 만료되어 기억 도구를 사용할 수 없을 때
- curl로 직접 호출하여 기억을 보존한다. 상세 절차는 아래 "MCP 도구 사용 불가 시 curl 직접 호출" 섹션 참조.
- "MCP가 안 되니 저장을 포기한다"는 선택지는 존재하지 않는다.
- 발동 조건: `Session not found`, `Session expired`, `connection closed`, `ECONNREFUSED`, `401`, `403` 등.
- 의무 순서: 내부 요약 확보 → initialize로 세션 재발급 → reflect 우선 호출 → remember 분리 저장 → 필요 시 recall·context → 사용자 한 줄 보고 → 원래 작업 재개.
---
### 원격 CLI 활용
#### 원격 CLI
로컬 AnchorMind 서버 없이 원격 서버에 직접 연결한다.
```bash
# 환경변수 방식 (영구 설정에 적합)
export MEMENTO_CLI_REMOTE=https://memento.anchormind.net/mcp
export MEMENTO_CLI_KEY=mmcp_xxx
memento-mcp recall "query"
memento-mcp stats
# 플래그 방식 (일회성 호출)
memento-mcp recall "query" --remote https://memento.anchormind.net/mcp --key mmcp_xxx
```
원격 모드는 recall, remember, stats, inspect, session만 지원한다. local-only 명령(serve, migrate, cleanup, backfill, health, update, export, import, benchmark, anchor-scope)을 원격 모드에서 호출하면 에러가 반환된다.
내부 동작: CLI가 MCP initialize → tools/call 2단계 세션을 생성하고 같은 세션을 재사용한다(`lib/cli/_mcpClient.js`).
#### dryRun 파라미터
remember / link / forget / amend 4개 도구에서 실제 저장 없이 예상 결과를 확인한다.
```json
{ "tool": "remember", "arguments": { "content": "테스트", "type": "fact", "dryRun": true } }
```
응답에 `"simulated": true` 필드가 포함되며 DB에 어떤 변경도 발생하지 않는다.
#### X-RateLimit 헤더
API 응답 헤더로 잔여 쿼터를 확인한다.
```
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4982
X-RateLimit-Resource: fragments
```
master key 또는 limit=null 설정인 키는 헤더가 생략된다.
---
### _meta 래퍼 및 sparse recall
#### _meta 래퍼
recall / context 응답에서 `_meta` 필드를 통해 검색 메타데이터를 읽는다.
```javascript
const res = await recall({ text: "nginx 설정" });
const eventId = res._meta.searchEventId; // tool_feedback에 사용
const hint = res._meta.hints; // signal + trigger
const suggest = res._meta.suggestion; // recommendedTool + recommendedArgs
```
top-level `_searchEventId` / `_memento_hint` / `_suggestion` mirror 필드는 지원되지 않는다. `_meta.*` 경로만 사용할 것.
#### fields 파라미터 (sparse fieldsets)
대역폭과 토큰 예산을 절약하기 위해 반환 필드를 제한한다.
```json
{
"text": "postgresql",
"fields": ["id", "content", "type", "importance"]
}
```
화이트리스트 19개: id / content / type / topic / keywords / importance / created_at / access_count / confidence / linked / explanations / workspace / context_summary / case_id / valid_to / affect / ema_activation / key_id / key_name.
#### idempotencyKey
동일 내용의 중복 저장을 방지한다.
```json
{ "content": "PostgreSQL 15 사용", "type": "fact", "idempotencyKey": "proj-db-version-2026" }
```
같은 key_id 범위에서 동일 `idempotencyKey`로 다시 호출하면 기존 파편 id를 반환하고 저장을 건너뛴다.
`idempotencyKey`가 없어도 같은 본문은 키와 workspace 단위(`MEMENTO_DEDUP_SCOPE=workspace`, 기본)로 한 번만 저장된다. 같은 workspace나 전역 파편에 같은 본문이 있으면 응답의 `id`가 기존 파편 id이고 `duplicate_of`에 같은 id가 실린다. 다른 workspace의 같은 본문은 별도 파편이다.
---
### 내부 구조
사용자 MCP API는 안정적이다. 실제 로직은 `lib/memory/processors/` 4개 클래스로 분리됐으며, 핵심 모듈은 `lib/memory/` 하위 6개 서브디렉토리(`read/`, `write/`, `link/`, `consolidate/`, `embedding/`, `signals/`)로 분류돼 있다. 기존 위치에는 stub re-export가 유지되어 외부 import가 무변경이다.
- MemoryRememberer: remember / batchRemember
- MemoryRecaller: recall / context
- MemoryReflector: reflect
- MemoryLinker: link / graph_explore
테스트 코드에서 메서드 본문을 검증할 때는 `MemoryManager.prototype.remember.toString()` 대신 `MemoryRememberer.prototype.remember.toString()`을 사용한다.
---
### Mode Preset / Affective Tagging / Tool 메타 레지스트리
#### Mode Preset 시스템
서버가 지원하는 4가지 동작 모드를 연결 시점 또는 API 키 설정으로 선택할 수 있다.
활성화 우선순위 (높음 → 낮음):
1. HTTP 헤더: `X-Memento-Mode: recall-only`
2. initialize params: `{ "mode": "recall-only" }`
3. API 키 기본값: `api_keys.default_mode` 컬럼
4개 preset:
- `recall-only`: 조회 전용. remember/amend/forget/link/reflect/memory_consolidate 차단.
- `write-only`: 저장 전용. recall/context 차단. CI/크론 잡용.
- `onboarding`: 도구 전체를 노출하고 초심자용 skill_guide를 반환. 앞의 세 경로로 지정할 때만 적용된다.
- `audit`: master key 전용. memory_stats/search_traces/reconstruct_history 중심.
tools/list 응답이 mode의 excluded_tools 필터링 후 반환된다. get_skill_guide는 mode별 skill_guide_override를 우선 반환한다.
#### Affective Tagging
파편에 감정적 맥락을 태깅하여 검색 필터와 CaseRewardBackprop과 연계한다.
- `affect` 파라미터 (remember/recall): `neutral` / `frustration` / `confidence` / `surprise` / `doubt` / `satisfaction` 6 enum
- recall에서 배열 또는 단일 문자열 필터링 지원
- 사용 예:
- "과거 좌절했던 에러 파편만" → `recall({type: "error", affect: "frustration"})`
- "성공적으로 해결한 사례만" → `recall({type: "procedure", affect: "satisfaction"})`
- CaseRewardBackprop 연동 (옵션): 검증 통과 시 `confidence`, 반복 실패 시 `frustration` 자동 태깅
#### recall 응답의 `_suggestion` 메타 필드
서버가 감지한 사용 패턴 개선 힌트. 강제 차단 없음.
필드: `{code, message, recommendedTool, recommendedArgs}`
4개 감지 규칙:
- `repeat_query`: 최근 5분 내 동일 keyId에서 같은 keywords로 3회 이상 recall → `reconstruct_history` 또는 `graph_explore` 권유
- `empty_result_no_context`: 결과 0건 + contextText 미전달 → SpreadingActivation 활용 권유 (`contextText` 추가)
- `large_limit_no_budget`: limit ≥ 50 + tokenBudget 미지정 → `tokenBudget` 명시 권고
- `no_type_filter_noisy`: type 미지정 + 소유 파편 100개 이상 → type 필터 권고
`_suggestion`이 있으면 다음 호출에서 `recommendedTool` + `recommendedArgs`를 검토하여 적용한다.
#### Tool 메타 레지스트리
서버의 도구 레지스트리(`lib/tool-registry.js`)는 각 도구에 다음 `meta`를 두며 권한 판정에 쓴다. tools/list 응답 항목은 `name`, `title`, `annotations`, `description`, `inputSchema`로 구성되고 `meta`는 포함하지 않는다.
- `capabilities`: `["memory:read" | "memory:write" | "memory:destructive" | "analytics:read" | "admin"]`
- `riskLevel`: `"safe"` | `"caution"` | `"destructive"`
- `requiresMaster`: boolean (master key 전용 여부)
- `beta`: boolean
- `idempotent`: boolean
클라이언트는 `annotations`(`readOnlyHint`, `destructiveHint` 등)를 확인 프롬프트와 권한 UI 구성에 참조한다. `riskLevel=destructive` 도구는 사용자 확인을 권장한다.
#### LLM Provider 체인 확장
`LLM_PRIMARY` 및 `LLM_FALLBACKS`에 `agy-cli`, `codex-cli`, `copilot-cli`, `qwen-cli`, `opencode-cli` 추가 지원.
예시 `.env`:
```
LLM_PRIMARY=gemini-cli
LLM_FALLBACKS='[{"provider":"codex-cli"},{"provider":"copilot-cli"},{"provider":"ollama","baseUrl":"https://ollama.com","apiKey":"...","model":"glm-5.1:cloud"}]'
```
CLI provider는 API 키 불필요. 로컬 바이너리(`gemini`/`agy`/`codex`/`copilot`/`opencode`) 설치 + 로그인만 필요.
#### 로컬 임베딩 Provider
`EMBEDDING_PROVIDER=transformers`로 외부 API 없이 로컬 모델 사용.
- 기본 모델: `Xenova/multilingual-e5-small` (384차원, ~150MB)
- 고품질 옵션: `Xenova/bge-m3` (1024차원, ~600MB)
- API와 상호 배타: `EMBEDDING_API_KEY`와 동시 설정 시 config 로딩에서 throw
- 상세: `docs/embedding-local.md` 참조
#### 세션 안정성 개선
- **토큰 기반 세션 재사용**: 같은 Bearer/API 키로 initialize 재호출 시 기존 세션 재활용. claude.ai 커넥터의 Mcp-Session-Id 유실 문제 대응
- **null crash 방어**: 빈 POST body를 400 Invalid Request로 거부
- **MorphemeIndex LLM timeout**: 60s. `MEMENTO_MORPHEME_TOKENIZER=llm` 경로 사용 시에만 적용. 기본 경로(`local`)는 로컬 CPU 분석기(MorphemeTokenizer)를 사용하므로 이 값을 참조하지 않는다.
- **키 상태 재확인**: API 키로 연 세션은 `MEMENTO_SESSION_KEY_RECHECK_MS`(기본 30000ms, `0`이면 끔) 주기로 키 상태를 다시 읽는다. 비활성 또는 삭제된 키의 세션은 닫히고 404 `Session not found`를 받으며, 권한 변경은 열린 세션에 반영된다. 관리 API로 키를 비활성화하거나 삭제하면 그 키의 세션이 즉시 닫힌다.
- **세션 ID 수신**: `MEMENTO_SESSION_ID_POLICY`(`warn` 기본, `enforce`)가 쿼리스트링 세션 ID와 서버 발급 형식(UUID)이 아닌 ID의 자동 복구를 다룬다. `warn`은 경고 로그를 남기고 처리하며 `enforce`는 쿼리 ID에 400, UUID가 아닌 ID의 복구에 404를 돌려준다. 세션 ID는 `MCP-Session-Id` 헤더로 보낸다.
- **인증 저장소 조회 실패**: API 키 저장소 조회가 실패해 인증을 판정하지 못한 initialize와 세션 자동 복구는 기본 401로 응답한다. `MEMENTO_AUTH_STORE_UNAVAILABLE_STATUS=503`이면 `Retry-After: 10`과 함께 503으로 응답한다.
---
## Symbolic Memory
Symbolic Verification Layer는 확률론적 검색 파이프라인 위에 추가된 opt-in 레이어다. 모든 `MEMENTO_SYMBOLIC_*` 플래그는 기본 `false`이므로 활성화하지 않으면 동작에 변화가 없다.
신규 응답 필드 요약:
- `remember` → `validation_warnings: string[]` (`MEMENTO_SYMBOLIC_POLICY_RULES=true` 시, rule 이름 배열)
- `recall` → 각 파편의 `explanations: [{code, detail, ruleVersion}]` (`MEMENTO_SYMBOLIC_EXPLAIN=true` 시)
- 에러 `-32003 SYMBOLIC_POLICY_VIOLATION` (해당 키의 `symbolic_hard_gate=true` 상태에서 PolicyRules 위반 또는 고신뢰 민감 정보 탐지 시, `MEMENTO_SENSITIVE_SCAN=reject`이면 모든 키에서 고신뢰 민감 정보 탐지 시)
## 보안 기본값 및 설정
다음 항목은 서버 운영 시 반드시 확인해야 하는 보안 설정이다.
### 인증 (Auth)
- `MEMENTO_ACCESS_KEY` 필수화: 미설정 시 서버 기동이 거부된다. 개발/테스트 환경에서 인증을 비활성화하려면 `MEMENTO_AUTH_DISABLED=true`를 명시적으로 설정한다.
- `ALLOWED_ORIGINS` 미설정 시 모든 Origin에 응답하며 응답 방식은 `MEMENTO_CORS_MODE`(`reflect`, `observe`, `allowlist`)로 정한다. 접근을 제한하려면 허용할 오리진을 `ALLOWED_ORIGINS`에 명시적으로 열거한다.
### OAuth
- 동의 화면: `GET /authorize`는 `redirect_uri`가 localhost이거나 `OAUTH_ALLOWED_REDIRECT_URIS` 또는 `OAUTH_TRUSTED_ORIGINS`에 맞으면 동의 화면 없이 바로 인가 코드를 발급하고, 그 밖의 클라이언트에는 동의 화면을 보여 준다. `POST /register`에 API 키를 Bearer 헤더로 보내 등록한 클라이언트(키에 묶인 클라이언트)는 `redirect_uri`가 허용 목록에 있어도 항상 동의 화면을 거친다. 오류 응답의 리다이렉트 대상 확인은 `MEMENTO_OAUTH_REDIRECT_CHECK`(기본 `warn`, `enforce`면 미등록 대상에 400)로 정한다.
### RBAC
- Default-deny: 등록되지 않은 도구 이름은 `Unknown tool: <name>`(-32601)으로 거부된다. `lib/rbac.js`의 `TOOL_PERMISSIONS`에 없는 도구는 권한과 무관하게 `Permission denied`(-32001)로 거부되므로, 도구를 추가할 때 `TOOL_PERMISSIONS`에 등록한다.
### 환경변수 주요 목록
| 변수명 | 타입 | 기본값 | 설명 |
|--------|------|--------|------|
| `MEMENTO_ACCESS_KEY` | string | (없음, 필수) | 마스터 API 키. 미설정 시 기동 거부. |
| `MEMENTO_AUTH_DISABLED` | boolean | `false` | `true` 설정 시 인증을 비활성화한다. 개발 전용. |
| `ALLOWED_ORIGINS` | string | (없음) | CORS 허용 오리진 목록 (쉼표 구분). 미설정 시 모든 Origin에 응답하며 방식은 `MEMENTO_CORS_MODE`로 정한다. |
| `ENABLE_OPENAPI` | boolean | `false` | `true` 설정 시 `/openapi.json` 엔드포인트 활성화. |
| `SESSION_TTL_MINUTES` | number | `43200` | 세션 TTL(분). OAuth access token 유효 시간도 이 값 × 60초로 산출된다(기본 30일). refresh token 유효 시간은 그 두 배(기본 60일)다. |
| `MEMENTO_REMEMBER_ATOMIC` | boolean | `false` | `true` 시 remember()의 quota check + INSERT를 단일 트랜잭션으로 원자화(TOCTOU 완전 차단). 동시 요청이 드문 환경에서는 기본값 유지. |
| `MEMENTO_CASE_BACKPROP_ENABLED` | boolean | `false` | `true` 시 case verification 이벤트마다 증거 파편 importance를 자동 역전파(CaseRewardBackprop). 비활성 시 no-op. |
| `MEMENTO_STORAGE` | string | `pgvector` | 저장소 백엔드 이름. 현재 `pgvector` 하나이며 이 값은 동작에 영향을 주지 않는다. |
| `MIGRATION_LINT_FROM` | string | (없음) | `npm run lint:migrations` cutoff override. 지정 마이그레이션 번호 이후만 검사. |
| `TRUST_PROXY_HOPS` | number | (없음) | 신뢰하는 리버스 프록시 hop 수. 미설정 시 `X-Forwarded-For` 첫 항목, `0`이면 소켓 주소, N이면 체인 오른쪽에서 N번째 항목을 클라이언트 주소로 쓴다. |
| `MEMENTO_CORS_MODE` | string | `observe` | `ALLOWED_ORIGINS` 미설정 시 교차 출처 응답 방식(`observe`, `reflect`, `allowlist`). |
| `MEMENTO_FRAME_OPTIONS` | string | (없음) | `deny`일 때만 `X-Frame-Options: DENY`를 붙인다. |
| `MEMENTO_OAUTH_REDIRECT_CHECK` | string | `warn` | `/authorize` 오류 응답의 리다이렉트 대상 확인(`warn`, `enforce`). |
| `MEMENTO_SSE_QUERY_KEY` | string | `allow` | Legacy SSE `?accessKey=` 처리(`allow`: master 키만, `deny`: 401). |
| `MEMENTO_TOOL_ARGS_VALIDATION` | string | `warn` | tools/call 인자 inputSchema 점검 모드(`off`, `warn`, `enforce`: 위반 시 -32602). 스키마의 `maxLength`, `maxItems`, `pattern`, `oneOf`, `enum`, 범위를 해석한다. |
| `MEMENTO_TOOL_ARGS_ALLOW_UNKNOWN` | boolean | `false` | `true`이면 스키마에 없는 인자 이름을 위반으로 보지 않는다. |
| `MEMENTO_SESSION_KEY_RECHECK_MS` | number | `30000` | API 키 세션의 키 상태 재확인 주기(ms). `0`이면 끈다. |
| `MEMENTO_SESSION_ID_POLICY` | string | `warn` | 쿼리스트링 세션 ID와 UUID가 아닌 ID의 복구 처리(`warn`, `enforce`). |
| `MEMENTO_RESERVED_AGENT_IDS` | string | `warn` | 예약 agentId(`system`, `admin`) 처리(`warn`, `enforce`: API 키 요청은 -32001로 거부, master 키 허용). |
| `MEMENTO_DCR_MAX_PER_HOUR` | number | `100` | `/register`의 시간당 등록 상한(프로세스 단위, `0`은 상한 없음). 키에 묶인 등록은 따로 센다. 초과 시 429와 `Retry-After`(현재 창의 남은 초). |
| `MEMENTO_ADMIN_AUTH_BACKOFF` | string | `off` | `on`이면 관리 인증 연속 5회 실패 뒤 최대 60초까지 다음 시도를 늦춘다. |
| `MEMENTO_LLM_CLI_TOOL_APPROVAL` | string | `none` | gemini-cli, copilot-cli, opencode-cli의 도구 실행 승인 방식. `none`은 제한된 승인과 빈 임시 디렉터리에서 실행하고, `all`은 gemini `-y`, copilot `--allow-all-tools`를 쓰고 서버 작업 디렉터리에서 실행한다. |
---
## v6.0.0에서 달라진 점
`get_skill_guide(section="release")`로 이 요약만 조회할 수 있다.
- 본문 어휘 검색이 3계층 검색을 보강한다. 임베딩을 사용할 수 없어도 코드명, 오류 문구, 한글 단어를 본문에서 직접 찾는다.
- `recall(format="pack")`은 저장일, 확인 상태, 출처가 붙은 답 꾸러미를 반환한다. `context`도 날짜와 assertion 주석을 붙여 오래된 정보와 추정을 구분한다.
- `remember(origin=...)`이 기억의 출처와 신뢰 등급을 기록한다. 신뢰도가 낮거나 지시문 덮어쓰기가 의심되는 기억은 핵심 맥락에서 제외하고 검토 대기열로 보낸다.
- `forget`은 대상 기억이 남긴 사례 요약과 모순 해소 기록의 본문 사본까지 같은 트랜잭션에서 정리한다.
- workspace 읽기 허가, 앵커 권한과 상한, API 키 만료·회전·폐기, 관리자 역할과 TOTP를 지원한다. 폐기된 키의 활성 세션도 다시 검사해 종료한다.
- 외부 LLM 전송은 키와 workspace별 정책으로 제한·마스킹·감사할 수 있다. 관리 변경과 기억 쓰기 기록은 해시 체인으로 위변조 여부를 검증한다.
- Claude Code와 Codex용 세션 시작·종료 훅이 맥락 주입과 회고를 자동화한다. `anchormind init --target claude|codex`로 필요한 파일을 만든다.
- 지식 그래프는 항성계 형태의 배치, 거리별 세부 표현, 대규모 링크 예산을 사용해 수천 개 기억을 탐색할 수 있게 바뀌었다.
---
## 서버 개요
AnchorMind는 MCP(Model Context Protocol) 기반의 장기 기억 서버다. AI 에이전트의 세션 간 지식을 파편(Fragment) 단위로 영속화하고, L1 Redis 역색인·L2 PostgreSQL 키워드/메타데이터·L3 pgvector 시맨틱 검색의 후보를 RRF로 병합해 맥락에 맞는 기억을 회상한다.
### 핵심 개념
- 파편(Fragment): 1~3문장의 자기완결적 지식 단위. id, content, topic, type, keywords, importance로 구성.
- 타입: fact, decision, error, preference, procedure, relation, episode
- 에피소드(Episode): 전후관계를 포함하는 서사 기억. 복수의 원자적 파편을 시간순/인과순으로 연결하는 내러티브. contextSummary로 맥락 보존. 최대 1000자.
- 앵커(Anchor): isAnchor=true인 파편. 통합(consolidation)에서 중요도 감쇠 및 만료 삭제 대상에서 제외되는 영구 지침.
- 유효 기간: valid_from/valid_to로 시간 범위를 가진 임시 지식 표현.
- 대체(Supersession): supersedes 파라미터로 구 파편의 valid_to를 설정하고 importance를 반감하여 버전 관리.
- 키 격리: API 키별로 파편이 분리되어 다른 키의 기억에 접근 불가. 키 그룹으로 공유 가능.
- 스코프: permanent(기본, 장기 기억)와 session(세션 워킹 메모리, 세션 종료 시 소멸) 2종.
## 세션 생명주기 프로토콜
### 1. 세션 시작 (필수)
```
context() 호출
-> core_memory: 앵커 + 고중요도 파편 (preference, error, procedure, decision)
(앵커는 기본 최대 20개가 항상 포함된다. effective workspace가 있으면 기본 10개를 workspace 상위 anchor에 먼저 예약하고, 나머지는 잔여 workspace/global 통합 중요도순으로 채운다. total만 바꾸면 reserve는 total/2 내림, 최대 10으로 유도된다. `MEMENTO_CONTEXT_ANCHOR_LIMIT`, `MEMENTO_CONTEXT_WORKSPACE_ANCHOR_RESERVE`로 설정)
-> working_memory: 현재 세션의 워킹 메모리
-> system_hints: 미반영 세션 경고, 시스템 알림
```
system_hints에 미반영 세션 경고가 있으면 사용자에게 알린다.
context 로드 후 행동:
- preference 파편을 확인하여 사용자의 코딩 스타일, 언어 선호, 작업 방식을 즉시 적용
- error 파편을 확인하여 현재 작업과 관련된 과거 에러/해결책을 인지
- procedure 파편을 확인하여 프로젝트별 빌드/배포/테스트 절차를 파악
- 사용자가 언급하는 주제에 대해 recall로 추가 컨텍스트 검색
- 오늘 할 작업 맥락을 contextText로 전달하면 관련 파편을 선제적으로 활성화 가능:
`recall(topic="프로젝트명", contextText="오늘 작업 주제 한 줄 요약")`
### 2. 작업 중 (능동적 기억 관리)
#### remember 즉시 호출 시점
| 상황 | type | importance | 예시 |
|------|------|------------|------|
| 사용자 선호/스타일 명시 | preference | 0.9 | "한국어로 답변해" |
| 에러 원인 파악 | error | 0.8 | "CORS 에러: nginx proxy_pass에 Host 헤더 누락" |
| 에러 해결책 확정 | procedure | 0.8 | "nginx에 proxy_set_header Host $host 추가" |
| 아키텍처/기술 결정 | decision | 0.7 | "인증은 OAuth 2.0 + PKCE로 결정" |
| 배포/빌드 절차 완성 | procedure | 0.7 | "배포: git push -> CI -> Docker build -> kubectl apply" |
| 새 설정값/경로 확인 | fact | 0.5 | "memento-mcp 포트: 57332, admin: /v1/internal/model/nothing" |
| 멀티 프로젝트 공유 키로 remember/reflect 호출 | (해당 타입) | (유지) | workspace 파라미터 필수 기입: `workspace="memento-mcp"` |
#### recall 선행 호출 시점 (작업 전 의무)
| 상황 | 호출 예시 |
|------|-----------|
| 에러 해결 시작 전 | `recall(keywords=["에러키워드"], type="error")` |
| 설정/환경변수 변경 전 | `recall(keywords=["설정명", "프로젝트명"])` |
| 동일 토픽 코드 작성 전 | `recall(topic="프로젝트명")` |
| "이전에", "저번에" 언급 시 | `recall(text="관련 내용")` |
| 복잡한 맥락의 작업 시작 | `recall(keywords=[...], contextText="작업 배경 요약")` |
recall 후 결과 피드백 (누적 효과):
```
# recall 응답의 _searchEventId 보관 후 피드백 전송
tool_feedback(
tool_name="recall", relevant=true/false, sufficient=true/false,
fragment_ids=["반환된_파편_id들"],
search_event_id=_searchEventId
)
```
→ relevant=true: 링크 weight +0.2 (reinforce)
→ relevant=false: 링크 weight -0.15 (decay), 반복 시 quarantine 가능
#### forget 시점
- 에러를 완전히 해결한 직후 해당 error 파편 삭제
- 사용자가 명시적으로 요청 시
#### link 활용
- 에러 -> 해결책: `link(fromId=에러, toId=해결책, relationType="resolved_by")`
- 원인 -> 결과: `link(fromId=원인, toId=결과, relationType="caused_by")`
- 관련 지식: `link(fromId=A, toId=B, relationType="related")`
- 모순 발견: `link(fromId=A, toId=B, relationType="contradicts")`
### 3. 세션 종료
```
reflect(
summary=["사실1", "사실2"],
decisions=["결정1"],
errors_resolved=["원인: X -> 해결: Y"],
new_procedures=["절차1"],
open_questions=["미해결1"],
workspace="프로젝트명"
)
```
reflect 규칙:
- 배열의 각 항목은 독립적으로 이해 가능한 원자적 사실 1건 (1~2문장)
- 여러 사실을 한 항목에 뭉치지 않는다
- 관련 파편들이 맥락상 연결되어 있다면 episode 유형 파편을 추가 생성
- contextSummary로 전후관계 요약을 첨부
- reflect가 새 episode나 파편을 저장하면 같은 workspace(없으면 topic)의 직전 milestone과 preceded_by 엣지가 생성됨 (경험 흐름 그래프 보존). 새로 저장할 내용이 없으면 milestone도 생기지 않음
- reflect 한 번에 파편 수십 건을 쏟아붓지 않는다. 세션 진행 중 중요한 사실·결정·에러·절차가 확정되는 시점마다 remember로 즉시 개별 저장하고, 세션 종료 시 reflect는 그 세션의 narrative_summary와 open_questions 정리 및 누락분 최종 집계 용도로만 사용한다. 이 분할 저장 패턴이 중요도 가중, 키워드 정밀도, 링크 품질 모두에서 한 번에 몰아 넣는 reflect보다 유의미하게 우수하다.
## 키워드 작성 규칙 (가장 중요)
### 필수 포함 키워드
1. 프로젝트 작업인 경우: 프로젝트명을 keywords에 반드시 포함
- 예: `keywords: ["memento-mcp", "oauth", "DCR"]`
- topic도 프로젝트명으로 설정: `topic: "memento-mcp"`
2. 디바이스/호스트 구분이 가능한 경우: hostname 포함
- 작업 디렉토리 경로에서 추출 (예: /srv/apps/paysvc -> "paysvc")
- 환경변수, 시스템 정보에서 추출 (예: os.hostname())
- 예: `keywords: ["memento-mcp", "my-host", "oauth"]`
3. reflect의 summary/decisions/errors_resolved에도 동일 규칙 적용
### workspace 파라미터 기입 규칙 (필수)
- 멀티 프로젝트·멀티 클라이언트를 하나의 API 키로 공유하는 환경에서는 remember/reflect/batch_remember 호출마다 workspace 파라미터를 필수 기입한다.
예: `workspace: "memento-mcp"`, `workspace: "client-acme"`, `workspace: "personal"`
- workspace 미기입은 다른 프로젝트의 기억과 섞여 recall/reflect 결과에 혼입되는 원인이 된다. "이 정도는 괜찮겠지"로 생략하지 않는다.
- 프로젝트 전용 키(1:1 매핑)라 default_workspace가 이미 지정된 경우에만 미지정 호출이 허용된다. 그 외에는 매 호출 명시가 원칙이다.
- reflect도 workspace를 받는다. 멀티 프로젝트 환경에서는 reflect에 workspace를 반드시 지정해 세션 요약이 다른 프로젝트 context에 주입되는 것을 방지한다.
- 전역 기억(모든 workspace에서 조회)으로 저장하려면 workspace를 의도적으로 비우고, 키에도 default_workspace가 없어야 한다. 의도치 않은 미기입과 의도된 전역 저장은 구분해서 판단한다.
- 검색 시 workspace를 지정하면 해당 workspace 파편과 workspace=NULL(전역) 파편이 함께 반환된다.
- `default_workspace`가 없는 공유 키에서 쓰기마다 workspace를 명시했다면 recall/context에도 같은 workspace를 반드시 명시한다. 생략하면 전역(NULL) 범위만 조회되며, 빈 결과의 `_meta.hints[0]`가 workspace를 지정한 재검색을 안내한다.
- 업그레이드 전 Redis Working Memory 항목처럼 workspace 필드가 없는 데이터는 scoped/global-only context에서 제외된다. master의 `allWorkspaces=true` 조회에서만 범위를 안전하게 넓혀 포함할 수 있다.
#### workspace 활용 예시
프로젝트별 기억 분리:
```
remember(content="...", topic="error", type="error", workspace="memento-mcp")
recall(keywords=["auth"], workspace="memento-mcp")
```
전역 기억 (모든 workspace에서 공유):
```
remember(content="선호하는 코딩 스타일: ...", topic="preference", type="preference")
// workspace 미지정 + 키에 default_workspace 없음 → workspace=NULL(전역)
```
### 키워드 품질 기준
- 3~5개 권장. 너무 적으면 검색 누락, 너무 많으면 노이즈
- 구체적이고 검색 가능한 단어 (X: "문제", "해결" / O: "nginx", "CORS", "proxy_pass")
- 약어와 전체명 혼용 가능 (예: "DCR", "dynamic-client-registration")
## 검색 전략 의사결정 트리
```
질문: "정확한 용어/키워드를 알고 있는가?"
|
+-- YES --> recall(keywords=["정확한용어"])
| * L1 Redis 역색인 -> L2 PostgreSQL 키워드 검색
| * 필요하면 합성 질의 L3 시맨틱 보조(L3kw)를 병렬 실행
| * 설정값, 포트번호, 파일 경로 등 검색에 최적
|
+-- NO --> "자연어로 설명할 수 있는가?"
|
+-- YES --> recall(text="자연어 설명")
| * L2 키워드/메타데이터 + L3 시맨틱을 병렬 검색하고 RRF 병합
| * 본문 어휘 채널도 병렬로 후보를 보강
| * 개념적 유사성 기반 검색
|
+-- 둘 다 --> recall(keywords=["키워드"], text="보충 설명")
* L1+L2+L3와 보조 후보를 RRF 병합. 최고 품질.
* 토큰 비용 가장 높음
추가 필터:
- topic="프로젝트명" --> 프로젝트별 검색 범위 제한
- type="error" --> 에러만 검색
- timeRange={from, to} --> 시간 범위 제한
- includeLinks=true --> 연결된 파편 1-hop 포함 (기본값)
- includeContext=true --> episode의 context_summary + 인접 파편 포함
맥락 사전 활성화 (ENABLE_SPREADING_ACTIVATION=true 환경에서 권장):
- contextText="현재 대화 요약" 추가 → 검색 전 관련 파편 activation_score 선제 부스트
- 효과: 키워드에 직접 등장하지 않지만 맥락상 관련된 파편이 상위 랭크됨
- 예: recall(keywords=["nginx"], contextText="SSL 인증서 갱신 중 오류 발생")
```
## 토큰 예산 관리
| 상황 | tokenBudget | 근거 |
|------|-------------|------|
| 세션 시작 context | 2000 (기본) | 핵심 기억만 로드 |
| 일반 recall | 1000 (기본) | 대부분의 질문에 충분 |
| 깊은 조사 | 3000~5000 | 복잡한 주제, 다수 파편 필요 시 |
| 에러 디버깅 | 2000 | 에러+해결책+관련 컨텍스트 |
recall은 연결 파편을 포함한 후보에 최종 점수를 매긴 뒤 tokenBudget 안에서 점수가 높은 파편을 고른다(`MEMENTO_RANK_BEFORE_BUDGET=on`, 기본). 연결 파편도 같은 예산을 쓴다. 중요한 정보가 누락되면 tokenBudget을 올려서 재검색.
## recall 결과 해석
```json
{
"fragments": [{
"id": "frag-abc123",
"content": "...",
"similarity": 0.85,
"stale_warning": true
}],
"searchPath": "L1:2 → L2:5 → L3:4 → RRF",
"_meta": {
"searchEventId": 1234,
"hints": [
{ "signal": "consider_context", "suggestion": "...", "trigger": "recall" }
],
"suggestion": { "code": "empty_result_no_context", "message": "..." }
}
}
```
- `_meta.searchEventId`: tool_feedback에 전달하여 검색 품질 개선. search_events 테이블의 정수 id
- `_meta.hints[0].signal`: 시스템 권고 신호. hints는 최대 1건이 담긴 배열이며 신호가 없으면 빈 배열
- `_meta.suggestion.recommendedTool`: 후속 호출 권장 도구
- similarity 0.7 이상: 높은 관련성
- similarity 0.4~0.7: 참고 수준
- stale_warning: 파편이 오래되었거나 접근 빈도가 낮음. 내용을 재확인하고 필요시 amend나 supersedes로 갱신.
- searchPath: 어떤 검색 경로가 사용되었는지 확인. L1만 사용됐으면 키워드가 정확히 매칭된 것.
## 에피소드 기억 활용
에피소드(episode)는 개별 사실(fact)과 함께 사용하여 "안다"와 "이해한다"를 모두 커버한다.
### 사실 vs 에피소드
| 사실 (fact) | 에피소드 (episode) |
|-------------|-------------------|
| "nginx 포트는 8443" | "nginx SSL 설정 과정: 처음에 443을 시도했으나 well-known 포트 금지 규칙에 따라 8443로 변경. certbot으로 인증서 발급 후 ssl-params에 경로 설정." |
| 검색이 정확하고 빠름 | 전후관계와 이유를 보존 |
| recall(keywords=["nginx","포트"]) | recall(text="nginx 설정 과정", includeContext=true) |
### 에피소드 저장 시점
- 복잡한 문제 해결 후: 시도 -> 실패 -> 원인분석 -> 해결의 전체 과정
- 아키텍처 결정 후: 대안 비교 -> 트레이드오프 분석 -> 최종 선택의 과정
- 여러 세션에 걸친 작업 완료 시: 전체 진행 경과 요약
```
remember(
content="OAuth 구현 과정: DCR 엔드포인트 추가 -> Claude.ai가 client_id=Authorization을 보내는 버그 발견 -> auto-register로 우회 -> redirect_uri를 origin 기반으로 변경하여 ChatGPT connector 동적 경로 대응",
type="episode",
topic="memento-mcp",
keywords=["memento-mcp", "oauth", "DCR", "my-host"],
contextSummary="2026-04-02 세션에서 OAuth MCP 준수 구현. Claude.ai/ChatGPT 연동 완료."
)
```
## MCP 도구 사용 불가 시 curl 직접 호출 (기억 유실 방지 프로토콜)
절대 원칙: MCP 세션 연동이 끊기거나 도구 호출이 차단된 상황에서도 기억은 절대 유실되어선 안 된다. MCP 채널이 막히면 즉시 curl 직접 호출로 전환하여 reflect/remember를 우선 수행한다. "MCP가 안 되니 저장을 포기한다"는 선택지는 존재하지 않는다.
### 발동 조건 (다음 중 하나라도 해당하면 즉시 curl 폴백)
- `mcp__*__remember` / `mcp__*__reflect` / `mcp__*__recall` / `mcp__*__context` 호출이 `Session not found`, `Session expired`, `Invalid session`, `connection closed`, `ECONNREFUSED`, `ETIMEDOUT`, `401`, `403` 중 하나로 실패
- MCP 서버 재시작 직후 첫 도구 호출이 실패
- 플랫폼(Claude.ai 웹, ChatGPT Connector 등)에서 MCP 도구 목록에 기억 도구가 노출되지 않음
- OAuth 토큰 만료·갱신 실패로 MCP 툴이 회색 처리됨
- 사용자가 "MCP 안 되네", "저장 안 돼", "recall 실패" 등을 언급
- 동일 세션에서 기억 도구 호출이 2회 연속 실패
### 의무 행동 순서 (위 조건 충족 즉시)
1. 현재까지 중요 의사결정·에러 해결·새 사실을 내부 요약(수 문장)으로 휘발 전에 문자열로 확보
2. curl `initialize` 호출로 `SESSION_ID` 재발급
3. curl `reflect` 호출로 세션 핵심 내용 즉시 저장 (손실 최소화를 위해 remember보다 먼저 수행)
4. 개별 사실/결정/에러가 있으면 curl `remember`로 타입별 분리 저장
5. 작업 이어가기 위한 과거 맥락이 필요하면 curl `recall` 또는 `context`로 확보
6. curl 성공·실패 결과를 사용자에게 한 줄 보고 후 원래 작업 재개
7. MCP 도구가 복구되면 이후부터 다시 MCP 경로 사용. curl은 폴백 전용.
### 금지 사항
- MCP 실패를 이유로 저장을 연기·누락하는 행위
- "다음 세션에서 다시 시도" 같은 지연 처리
- MCP 실패 메시지를 사용자에게만 보고하고 curl 폴백을 시도조차 하지 않는 행위
- curl 응답을 확인하지 않고 성공으로 간주하는 행위 (항상 JSON 응답의 `result` 또는 `error` 필드 확인)
서버 주소(`SERVER_URL`)와 `ACCESS_KEY`는 MCP 연결 설정(claude_desktop_config.json, .claude/settings.json 등)에서 확인한다. 찾을 수 없으면 사용자에게 1회 질의하여 즉시 확보한다.
```bash
# Step 1: 세션 초기화 (SESSION_ID 획득 — 이후 모든 요청에 필요)
SESSION_ID=$(curl -s -X POST $SERVER_URL \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_KEY" \
-D - \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
2>/dev/null | grep -i "^mcp-session-id" | tr -d '\r' | awk '{print $2}')
# reflect — 세션 핵심 내용 요약 저장
curl -s -X POST $SERVER_URL \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_KEY" \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"reflect","arguments":{
"summary":["요약 내용1","요약 내용2"],
"decisions":["기술/아키텍처 결정사항"],
"errors_resolved":["원인: X → 해결: Y"]
}}}'
# remember — 단일 파편 저장
curl -s -X POST $SERVER_URL \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_KEY" \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"remember","arguments":{
"content":"저장할 내용",
"topic":"주제",
"type":"fact",
"importance":0.7,
"keywords":["키워드"]
}}}'
# recall — 기억 검색
curl -s -X POST $SERVER_URL \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_KEY" \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"recall","arguments":{
"text":"검색어",
"keywords":["키워드"]
}}}'
# context — 핵심 기억 로드
curl -s -X POST $SERVER_URL \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ACCESS_KEY" \
-H "MCP-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"context","arguments":{
"structured":true
}}}'
```
응답에서 결과 추출:
```bash
# 위 명령 끝에 파이프로 추가
| python3 -c "import sys,json; r=json.load(sys.stdin); print(r['result']['content'][0]['text'])"
```
curl 응답 검증 체크:
- HTTP 200 + `result` 필드 존재 → 성공
- `error.code` 존재 → 메시지 파싱 후 재시도 또는 사용자 보고
- HTTP 404 `Session not found`(-32000) → `initialize`부터 다시 수행
- `error.code === -32001` → 권한 오류. 도구 권한과 키 상태를 확인
- 응답 본문이 비어 있거나 파싱 실패 → 네트워크·방화벽 문제, `SERVER_URL`과 포트 노출 상태 확인
## 다중 플랫폼/디바이스 기억 관리
기억은 API 키 단위로 격리된다. 같은 그룹의 키는 기억을 공유한다.
### 구성 예시
```
그룹: team-a
+-- team-a-claude (Claude Code용)
+-- team-a-cursor (Cursor용)
+-- team-a-gpt (ChatGPT용)
+-- team-a-GC (기존 기억 보관용)
```
이 구성에서 Claude Code에서 저장한 기억을 Cursor에서도 recall 가능.
### 키워드로 출처 구분
같은 그룹 내에서도 어떤 플랫폼/디바이스에서 생긴 기억인지 구분하려면:
- keywords에 플랫폼명 포함: `["memento-mcp", "claude-code", "my-host"]`
- recall 시 플랫폼 필터: `recall(keywords=["claude-code"])`
## 멀티에이전트 협업
여러 에이전트(또는 여러 플랫폼의 세션)가 하나의 팀 기억을 공유할 때의 운영 패턴. 별도 기능 없이 현행 도구만으로 동작한다.
1. 같은 API 키 또는 같은 키 그룹 + 동일 workspace를 사용한다. 파편 공유 범위는 키 그룹 단위다.
2. 공동 작업은 동일 caseId를 공유하고, 진행 파편은 즉시 remember한다(기본 scope=permanent — 저장 즉시 상대 에이전트가 recall로 조회 가능). scope=session 파편은 세션 전용 스크래치라 공유되지 않는다.
3. 공동 기억은 `agentId`를 생략하거나 `default`로 통일한다. 일반 API key의 팀 고정 ID는 신뢰 가능한 agent 인증이 아니다. 전환 릴리즈의 legacy 호환 기본값은 true지만 이관 후 `MEMENTO_ALLOW_LEGACY_UNBOUND_AGENT_SCOPE=false`로 차단하며, specific agent 관리와 peer 조회는 master 권한을 사용한다.
4. 중간 가설은 assertionStatus="inferred"로 저장하고, 검증한 에이전트가 amend로 verified/rejected 전환한다. 미검증 가설과 확정 사실을 섞지 않는 것이 협업 오염 방지의 핵심이다.
5. 상대 에이전트의 파편이 유용했으면 tool_feedback(relevant=true)을 보낸다 — 링크 가중치 강화가 팀 검색 품질을 누적 개선한다.
6. 전체 흐름 복기는 reconstruct_history(caseId) 또는 recall(caseMode=true). 모순 발견 시 link(relationType="contradicts") 명시 후 대표 파편을 amend로 정리한다.
## Codex Desktop / Deferred Tool Discovery (클라이언트 호환)
Codex Desktop 등 일부 MCP 클라이언트는 도구를 deferred/lazy 로딩한다. tool_search가 검색어와 limit에 따라 그 턴에 일부 도구만 노출하므로, 서버 tools/list에 분명히 존재하는 recall이 저장 편향 쿼리+낮은 limit에서 빠질 수 있다.
핵심 원칙: 한 번의 좁은 검색 결과를 서버의 실제 도구 목록으로 오해하지 마라. remember/batch_remember/reflect는 보이는데 recall이 안 보이면, 즉시 더 넓은 쿼리와 큰 limit으로 재검색한다.
재검색 패턴:
- query: `memento context recall remember reflect batch_remember search_traces reconstruct_history`
- limit: 20 이상
검증: 서버 raw tools/list에는 context/recall/remember/reflect/batch_remember가 항상 포함된다. healthy 서버에서 특정 턴에 도구가 안 보이면 클라이언트 deferred 검색 한계이지 서버 누락이 아니다.
## 도구 레퍼런스 (20개)
RBAC default-deny: 등록되지 않은 도구 이름은 `Unknown tool: <name>`(-32601)으로, `TOOL_PERMISSIONS`에 없는 도구는 `Permission denied`(-32001)로 거부된다.
도구 처리 중 내부 예외(DB 드라이버, 런타임 오류)는 응답에 `Internal error`로 나가고 원문은 서버 로그와 감사 기록에만 남는다. `remember`, `amend`, `link`, `tool_feedback`의 열거형 인자 값이 저장소 제약에 맞지 않으면 `Invalid arguments for <tool>: <param>: must be one of a|b|c` 메시지와 `code: "INVALID_ARGUMENT"`를 돌려준다.
### remember
새 파편을 생성한다. 반드시 1~2문장 단위의 원자적 사실 하나만 저장한다.
파라미터:
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| content | string | O | 기억할 내용. 1~3문장, 300자 이내 작성 권장(저장 시 300자로 절삭, episode는 1000자). 4000자 초과 시 접수 단계에서 즉시 거부된다(-32602). |
| topic | string | O | 주제 라벨. 프로젝트명 권장. |
| type | string | O | fact, decision, error, preference, procedure, relation, episode |
| keywords | string[] | - | 검색용 키워드. 3~5개. 프로젝트명+호스트네임 포함. |
| importance | number | - | 0.0~1.0. 미입력 시 type별 기본값. |
| source | string | - | 출처 (세션 ID, 도구명 등) |
| linkedTo | string[] | - | 연결할 기존 파편 ID 목록 |
| scope | string | - | permanent(기본) 또는 session. session 응답의 `working_memory`가 저장 경로(`redis`, Redis가 준비되지 않아 PostgreSQL 작업 기억 행에 저장한 `postgres-fallback`, 저장하지 못한 `none`)를 알린다. `none`이면 permanent로 다시 저장한다 |
| isAnchor | boolean | - | true면 영구 보존. 핵심 규칙/정책용. master 키와 권한 목록에 `anchor`가 있는 키만 지정할 수 있고, 권한이 없으면 `MEMENTO_ANCHOR_PERMISSION`(기본 `warn`)에 따라 일반 파편으로 저장하고 경고하거나 거부한다. |
| supersedes | string[] | - | 대체할 기존 파편 ID. 지정 파편은 만료 처리. |
| contextSummary | string | - | 맥락/배경 요약 (1-2문장) |
| sessionId | string | - | 현재 세션 ID |
| agentId | string | - | 에이전트 ID (에이전트 구분용) |
| workspace | string | - | 워크스페이스 이름. 미지정 시 키의 default_workspace 자동 적용. |
| caseId | string | - | 이 파편이 속한 케이스 ID. 미지정 시 session_id 사용 |
| goal | string | - | 에피소드 목표 (episode 타입 권장) |
| outcome | string | - | 에피소드 결과 |
| phase | string | - | 작업 단계 (예: planning, debugging, verification) |
| resolutionStatus | string | - | open / resolved / abandoned |
| assertionStatus | string | - | observed(기본) / inferred / verified / rejected |
| affect | string | - | 감정 태그. neutral(기본) / frustration / confidence / surprise / doubt / satisfaction |
| idempotencyKey | string | - | 재시도 안전 식별자. 같은 key_id 범위에서 같은 값으로 반복 호출하면 새 파편을 만들지 않고 기존 id를 반환. 권장 형식 {작업명}-{날짜}-{순번} |
| origin | string | - | 기억의 출처 주장: user_stated, agent_inferred, tool_output, external_content, consolidation, import. 서버는 이 값과 키 상한(권한 `trusted_origin` 또는 master 키 3, 그 밖 2) 중 작은 값으로 신뢰 등급(0~3)을 정한다. 등급 1 이하(external_content 포함)는 context의 ANCHOR와 CORE 주입에서 빠진다. 허용 밖의 값은 -32602. 미지정이면 출처 없음(등급 2). |
| dryRun | boolean | - | true 시 저장 없이 할당량·충돌 검사 결과와 실행 계획만 반환 |
품질 게이트: content < 10자, URL만, type+topic null인 경우 거부. content > 4000자면 "content length N exceeds max 4000" 메시지와 함께 -32602로 거부(300자 절삭보다 앞단의 수신 게이트). importance < 0.3이면 경고 + TTL short 자동 설정.
에러: fragment_limit_exceeded 시 forget/memory_consolidate로 정리 안내.
검토 대기열(`MEMENTO_REVIEW_QUEUE=on`, 기본): 에이전트 지시 덮어쓰기 문구, 신뢰 등급 1 이하의 앵커·preference·procedure, 권한 없는 앵커 요청에 걸린 쓰기는 거부하지 않고 검토 대기(`review_state=pending`)로 저장한다. 검토 대기 파편은 쓴 키의 `recall`에만 `pending_review` 표지와 함께 보이고 context 주입과 앵커 승격에서 빠진다. 관리자가 승인하면 일반 파편이 되고 거절하면 만료된다. 같은 키 범위에 같은 본문이 있으면 응답의 `duplicate_of`가 기존 id를 알린다(`MEMENTO_DEDUP_SCOPE`).
### batch_remember
여러 파편을 한번에 저장. 단일 트랜잭션, 최대 200건. episode/contextSummary/isAnchor/supersedes/linkedTo/scope 미지원. 항상 표준 단일 JSON-RPC 응답을 반환한다(`stream` 파라미터는 deprecated, 동작 없음).
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| fragments | array | O | [{content, topic, type, importance?, keywords?, origin?}] 최대 200건(`origin`은 remember와 같은 값과 규칙). 항목별 content가 4000자를 초과하면 해당 항목만 -32602로 실패 처리되고 나머지 항목은 정상 저장된다. 배열 전체의 content 총 문자수가 상한(기본 200,000자, `BATCH_REMEMBER_MAX_TOTAL_CHARS`)을 초과하면 sync/async 분기 이전에 요청 전체가 거부된다(항목별 4000자 게이트와 별개). |
| async | boolean | - | true 시 비동기 모드. 선검증 후 Redis 큐 적재, `{async, accepted, jobId}` 즉시 반환. 워커가 ack·재시도(최대 3회)·dead-letter·기동 복구로 at-least-once 처리. 기본 false(동기). Redis 비활성 시 동기 폴백. |
| stream | boolean | - | deprecated. 더 이상 SSE progress 이벤트를 보내지 않는다. 무시됨. |
| workspace | string | - | 배치 기본 워크스페이스. 개별 파편에 workspace 미지정 시 이 값으로 대체. 미지정 시 키의 default_workspace 적용. |
| agentId | string | - | 에이전트 ID |
async 사용 지침: 대량(수십~200건) 일괄 저장에서 호출자 대기를 피하려면 `async: true`. 즉시 반환되는 것은 `accepted` 수와 `jobId`이며, per-fragment id는 반환되지 않고 파편은 워커 처리 후에 recall 가능(eventual)하다. `batch_status(jobId)`로 처리 상태를 확인할 수 있다. 재시도 안전이 필요하면 각 항목에 `idempotencyKey`를 넣는다. 소수 저장이나 직후 해당 파편을 곧바로 참조해야 하는 경우는 기본 동기 모드(async 생략)를 쓴다.
### recall
파편 검색. 키워드/시맨틱/하이브리드 자동 선택.
반환 파편에 `key_id` 필드가 포함된다. 멀티테넌트 환경에서 파편 소유 키를 확인하는 데 활용할 수 있다.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| keywords | string[] | - | 정확 키워드 검색(L1 Redis, L2 PostgreSQL). text가 없으면 합성 질의 L3 보조가 실행될 수 있음. |
| text | string | - | 자연어 쿼리. L2와 L3를 병렬 실행하고 본문 어휘 후보를 보강한 뒤 RRF로 병합. |
| topic | string | - | 주제 필터 |
| type | string | - | 타입 필터 (fact, decision, error, preference, procedure, relation, episode) |
| tokenBudget | number | - | 최대 반환 토큰. 기본 1000. |
| includeLinks | boolean | - | 연결 파편 포함. 기본 true. |
| linkRelationType | string | - | 연결 관계 필터 (related, caused_by, resolved_by, part_of, contradicts) |
| threshold | number | - | similarity 임계값 0~1 |
| includeSuperseded | boolean | - | 만료 파편 포함. 기본 false. |
| includePeerAgents | boolean | - | master 전용. 같은 키/workspace 범위의 다른 agent 파편 포함. 일반 API 키는 권한 오류. 기본 false. |
| includeKeyName | boolean | X | true 시 각 파편에 key_id·key_name(액세스 키 라벨) 포함. 같은 키 그룹 스코프의 정보만 노출. 팀 공유 workspace에서 파편 생성 주체 식별용. 기본 false |
| asOf | string | - | ISO 8601. 해당 시점에 가까운 파편을 상위로 올리는 시간 근접 랭킹 기준(anchorTime)으로만 작동. 주의: 그 시점에 유효했던 버전을 복원하는 bitemporal as-of 필터가 아니며, 과거 시점 스냅샷 조회는 미구현. 특정 기간의 파편을 실제로 한정하려면 timeRange를 쓴다. |
| timeRange | object | - | {from, to} 생성시각(created_at) 기준 시간창 필터. ISO 8601과 한국어 자연어("3일 전","지난 주","오늘") 모두 지원. 지정 시 시간 검색 경로가 동작하고 RRF에서 시간 근접 가중이 부스트된다. |
| cursor | string | - | 페이지네이션 커서 |
| pageSize | number | - | 기본 20, 최대 50 |
| excludeSeen | boolean | - | context()에서 주입된 파편 제외. 기본 true. |
| includeContext | boolean | - | context_summary + 인접 파편 포함 |
| includeKeywords | boolean | - | 응답에 keywords 배열 포함 |
| agentId | string | - | 에이전트 ID |
| workspace | string | - | 지정 workspace + 전역(NULL). 미지정 시 key default를 적용하고, 둘 다 없으면 전역(NULL)만 반환. |
| allWorkspaces | boolean | - | master 전용 전체 workspace 조회. 일반 API key는 권한 오류. |
| contextText | string | - | 현재 대화 맥락 텍스트. 관련 파편을 선제적으로 활성화한다 (ENABLE_SPREADING_ACTIVATION=true 시 동작). |
| caseId | string | - | 케이스 ID 필터. 해당 케이스에 속한 파편만 반환. |
| resolutionStatus | string | - | 해결 상태 필터 (open / resolved / abandoned) |
| phase | string | - | 작업 단계 필터 (planning, debugging, verification 등) |
| caseMode | boolean | - | true 시 CBR 모드. case_id별 (goal, events, outcome) 트리플로 반환 |
| maxCases | number | - | caseMode 최대 케이스 수 (기본 5, 상한 10) |
| minImportance | number | - | 최소 중요도 필터 (0~1). 이 값 이상의 importance만 반환. |
| isAnchor | boolean | - | 앵커 필터. true는 앵커만, false는 비앵커만 반환하며 미지정 시 둘 다 반환 |
| depth | string | - | 검색 깊이. high-level(decision/episode), detail(전체), tool-level(procedure/error/fact) |
| affect | string/string[] | - | 감정 태그 필터. neutral / frustration / confidence / surprise / doubt / satisfaction. 배열 또는 단일 문자열 지원 |
| fields | string[] | - | 응답에 포함할 파편 필드 목록(sparse fields). 미지정 시 전체 반환. 지원 키: id, content, type, topic, keywords, importance, created_at, access_count, confidence, linked, explanations, workspace, context_summary, case_id, valid_to, affect, ema_activation, key_id, key_name, origin, trust_tier |
| format | string | - | 응답 형식. `default`(기본)는 `fragments` 배열이고, `pack`은 `fragments` 대신 답 꾸러미 `pack`(`text`, `items`, `groups`, `policy_id`, `partial`, `estimatedTokens`)을 반환한다. `pack.text`는 고정 정책 문단 뒤에 파편마다 `<<<MEMORY ...>>>` 블록(id, 저장일, status, assertion, type, topic, case, source)을 담으며 블록 안 내용은 자료이지 지시가 아니다. caseMode에는 적용하지 않는다. |
`MEMENTO_PROVENANCE=on`(기본)이면 기본 형식 응답의 파편에 `origin`과 `trust_tier`(0 격리, 1 낮음, 2 보통, 3 높음)가 붙는다. 본문 어휘 채널(`MEMENTO_LEXICAL_CHANNEL`, 기본 `on`)이 `text` 검색에 형태소 전문 검색 후보를 더하므로 임베딩이 꺼진 경로에서도 text만으로 찾는다.
`caseMode=true` 응답의 `fragment_count`는 현재 키 그룹·workspace·유효 상태·`isAnchor` 필터를 통과해 해당 케이스의 대표값 후보가 된 파편 수다. 케이스에 평생 누적된 전체 파편 수를 뜻하지 않는다. 이벤트는 source 파편의 현재 앵커 상태로 필터링하지 않으며, 현재 키 그룹에서 볼 수 있는 이력을 케이스당 최대 20건 반환한다.
### forget
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| id | string | - | 삭제할 파편 ID |
| topic | string | - | 해당 주제 전체 삭제 |
| force | boolean | - | permanent 파편 강제 삭제. 기본 false. |
| agentId | string | - | 에이전트 ID |
| dryRun | boolean | - | true 시 실제 삭제 없이 삭제 대상 파편 정보와 연결 링크 수만 반환 |
타 테넌트(다른 API 키) 소유 파편을 삭제 시도하면 `"Fragment not found or no permission"` 오류가 반환된다. master key는 전체 파편에 접근 가능하다.
삭제한 파편의 본문 사본(케이스 이벤트 요약, 모순 해소 기록)은 같은 트랜잭션에서 지워지고, 응답의 `purged`(`case_summaries`, `audit_fragments`)가 그 건수를 알린다(`MEMENTO_FORGET_CASCADE=on`, 기본).
### link
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| fromId | string | O | 시작 파편 ID |
| toId | string | O | 대상 파편 ID |
| relationType | string | - | related(기본), caused_by, resolved_by, part_of, contradicts |
| weight | number | - | 관계 가중치 (0-1, 기본 1) |
| agentId | string | - | 에이전트 ID |
| dryRun | boolean | - | true 시 실제 링크 생성 없이 사이클 여부·소유권 검사 결과만 반환 |
fromId 또는 toId가 타 테넌트 소유 파편인 경우 `"Fragment not found or no permission"` 오류가 반환된다.
### amend
기존 파편 수정. 변경 필드만 전달.
타 테넌트 소유 파편 수정 시도 시 `"Fragment not found or no permission"` 오류가 반환된다.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| id | string | O | 수정할 파편 ID |
| content | string | - | 새 내용. 4000자 초과 시 -32602로 거부. |
| topic | string | - | 새 주제 |
| keywords | string[] | - | 새 키워드 |
| type | string | - | 새 유형 |
| importance | number | - | 새 중요도 |
| isAnchor | boolean | - | 고정 여부 |
| supersedes | boolean | - | 기존 파편 대체 |
| assertionStatus | string | - | 확인 상태 변경 (observed, inferred, verified, rejected) |
| resolutionStatus | string | - | 케이스 해결 상태 변경 (open / resolved / abandoned). case_id 보유 파편은 resolved 전환 시 case_closed 이벤트가 자동 기록된다 |
| outcome | string | - | 케이스 종결 결과 요약. resolutionStatus='resolved'와 함께 기록 |
| phase | string | - | 작업 단계 변경 (planning, debugging, implementation, verification 등) |
| agentId | string | - | 에이전트 ID |
| dryRun | boolean | - | true 시 실제 변경 없이 패치 적용 후 예상 파편 상태만 반환 |
### reflect
세션 학습 내용을 원자 파편으로 영속화.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| summary | string/string[] | - | 세션 개요. 배열 권장 (1항목=1사실). |
| sessionId | string | - | 세션 ID. 이 세션의 Working Memory 중 저장되지 않은 항목만 종합한다. 이미 저장된 파편은 다시 저장하지 않는다. |
| decisions | string[] | - | 결정 목록 (1항목=1결정) |
| errors_resolved | string[] | - | 해결 에러 ('원인: X -> 해결: Y') |
| new_procedures | string[] | - | 확립된 절차 |
| open_questions | string[] | - | 미해결 질문 |
| narrative_summary | string | - | 3~5문장 서사 요약. episode 파편으로 저장되어 세션 연속성에 기여. |
| task_effectiveness | object | - | {outcome, evaluator, evidence, unmet_requirements[], overall_success, tool_highlights[], tool_pain_points[]} |
| workspace | string | - | 호출자가 넘긴 항목에 적용할 워크스페이스. 세션 종합 그룹은 원래 workspace를 유지한다. 미지정 시 키의 default_workspace, 그것도 없으면 전역(NULL). |
| agentId | string | - | 에이전트 ID |
summary 또는 sessionId 중 하나 이상 필수.
task_effectiveness 세부 필드:
| 필드 | 타입 | 설명 |
|------|------|------|
| outcome | string | 작업 종료 상태. completed(요구사항 전부 충족) / partial(일부 충족) / blocked(외부 요인으로 진행 불가) / abandoned(중단) / unknown(판정 불가). 확신이 없으면 추정하지 말고 unknown. 목록 밖 값은 미보고(NULL)로 기록된다. |
| evaluator | string | outcome 판정 주체. agent(에이전트 자기보고) / automatic(테스트·빌드 자동 판정) / human(사용자 확인). outcome이 있을 때만 기록되며 생략 시 agent. |
| evidence | string | outcome 판정 근거 (1000자 이내). 통과한 테스트 수, 확인한 로그, 사용자 승인 발화 등. |
| unmet_requirements | string[] | 충족하지 못한 요구사항 (최대 20건·각 200자). partial/blocked/abandoned일 때 남은 항목을 명시. |
| overall_success | boolean | 호환 유지 필드. 생략 시 outcome이 completed면 true, 그 외에는 false로 파생된다. |
| tool_highlights | string[] | 유용했던 도구 |
| tool_pain_points | string[] | 불편했던 도구 |
### context
세션 시작 시 핵심 기억 로드.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| tokenBudget | number | - | 기본 2000 |
| types | string[] | - | 기본: preference, error, procedure, decision |
| sessionId | string | - | 워킹 메모리 로드용 |
| structured | boolean | - | 계층 구조 반환. 기본 false. |
| includeKeyName | boolean | X | true 시 fragments 각 항목에 key_id·key_name(액세스 키 라벨) 포함. structured=true 트리 응답에는 적용되지 않음. 기본 false |
| agentId | string | - | 에이전트 ID |
| workspace | string | - | 지정 workspace + 전역(NULL). 미지정 시 key default를 적용하고, 둘 다 없으면 전역(NULL)만 포함. |
| allWorkspaces | boolean | - | master 전용 전체 workspace context 조회. |
`MEMENTO_CONTEXT_ANNOTATE=on`(기본)이면 주입 줄 끝에 저장일(UTC)과 assertion 주석 ` (YYYY-MM-DD, assertion)`이 붙는다. `MEMENTO_PROVENANCE=on`(기본)이면 신뢰 등급 1 이하 파편은 ANCHOR와 CORE 주입에서 빠진다. `MEMENTO_ANCHOR_PERMISSION`이 `off`가 아니면 앵커 주입 줄에 비식별 주체 표지(`[k:xxxx]`, master 앵커는 `[master]`)가 붙는다.
### tool_feedback
도구 결과 유용성 피드백.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| tool_name | string | O | 도구명 |
| relevant | boolean | O | 결과 관련성 |
| sufficient | boolean | O | 결과 충분성 |
| suggestion | string | - | 개선 제안 (100자) |
| context | string | - | 사용 맥락 (50자) |
| session_id | string | - | 세션 ID |
| trigger_type | string | - | sampled 또는 voluntary |
| irrelevance_reason | string | - | not_stored / search_miss / scope_leak / topic_mismatch / other. relevant=false일 때만 기록되며, 그 외에는 값을 보내도 폐기된다 |
| fragment_ids | string[] | - | 피드백 대상 파편 ID (EMA 조정) |
| search_event_id | integer | - | recall의 _searchEventId |
fragment_ids를 지정하고 ENABLE_RECONSOLIDATION=true인 경우: relevant=false이면 해당 파편들의 fragment_links에 decay action, relevant=true이면 reinforce action이 적용된다. 이를 통해 검색 피드백이 링크 강도에 반영된다.
### memory_stats
기억 시스템 전역 통계. master key 전용, 파라미터 없음.
### memory_consolidate
수동 GC 트리거. TTL 전환, 감쇠, 만료 삭제, 중복 병합. master key 전용. 항상 표준 단일 JSON-RPC 응답을 반환한다.
| 파라미터 | 타입 | 설명 |
|---|---|---|
| stream | boolean | deprecated. 더 이상 SSE progress 이벤트를 보내지 않는다. 하위 호환성을 위해 파라미터는 유지되지만 동작에 영향 없음. |
### graph_explore
에러 인과 관계 추적 (RCA). caused_by/resolved_by 1-hop.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| startId | string | O | 시작 파편 ID (error 권장) |
| agentId | string | - | 에이전트 ID |
| includePeerAgents | boolean | - | master 전용. 같은 key/workspace 범위의 다른 agent 노드 포함. 기본 false. |
| workspace | string | - | 생략 시 key 기본값, 둘 다 없으면 전역(NULL) 범위. 시작·이웃 파편에 함께 적용. |
| allWorkspaces | boolean | - | master 전용. workspace 필터를 제거하고 agent/key 범위는 유지. 기본 false. |
startId가 타 테넌트 소유 파편인 경우 `"Fragment not found or no permission"` 오류가 반환된다.
### fragment_history
파편 변경 이력. amend 이전 버전 + superseded_by 체인.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| id | string | O | 조회할 파편 ID |
| agentId | string | - | 에이전트 ID |
| includePeerAgents | boolean | - | master 전용. 같은 key/workspace 범위의 다른 agent 이력 포함. 일반 API 키는 권한 오류. 기본 false. |
| workspace | string | - | 생략 시 key 기본값, 둘 다 없으면 전역(NULL) 범위. 현재 파편·버전·superseded chain에 함께 적용. |
| allWorkspaces | boolean | - | master 전용. workspace 필터를 제거하고 agent/key 범위는 유지. 기본 false. |
id가 타 테넌트 소유 파편인 경우 `"Fragment not found or no permission"` 오류가 반환된다.
### get_skill_guide
이 문서(SKILL.md)의 내용을 반환. 전체 또는 섹션별 조회 가능. 플랫폼에 기억 도구 설정이 없는 경우 이 도구를 호출하여 최적 활용법을 안내한다.
| 이름 | 타입 | 필수 | 설명 |
|------|------|------|------|
| section | string | - | release, overview, lifecycle, keywords, search, episode, multiplatform, collaboration, codex, tools, importance, experiential, cbr, triggers, workspace, antipatterns |
미지정 시 전체 가이드를 반환한다. 활성 mode에 `skill_guide_override`가 있고 `section`이 없으면 그 텍스트가 우선 반환된다.
### reconstruct_history
**목적**: case_id 또는 entity 기반으로 작업 히스토리를 시간순 재구성한다. 인과 체인, 미해결 브랜치, case_events DAG를 함께 반환하여 복잡한 디버깅 세션의 전체 맥락을 파악할 수 있다.
**언제 사용**: 특정 케이스/이슈의 전체 흐름 파악, 인과 관계 분석, 미해결 문제 확인.
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| caseId | string | - | 재구성할 케이스 ID (caseId 또는 entity 중 하나 필수) |
| entity | string | - | topic/keywords ILIKE 필터 (caseId 없을 때 사용) |
| timeRange | object | - | { from: ISO8601, to: ISO8601 } 시간 범위 |
| query | string | - | content 키워드 추가 필터 |
| limit | number | 100 | 최대 반환 파편 수 (최대 500) |
| workspace | string | - | 워크스페이스 필터 |
| allWorkspaces | boolean | false | master 전용. true이면 timeline·event·evidence·인과 링크의 workspace 필터를 제거 |
| agentId | string | default | 특정 agent 지정은 master 전용 |
| includePeerAgents | boolean | false | master 전용. 같은 key/workspace 범위의 모든 agent 포함 |
반환값:
- `ordered_timeline`: 시간순 파편 배열 (각 항목에 agent_id 포함 — 멀티에이전트 케이스에서 기여 에이전트 식별용)
- `causal_chains`: BFS 인과 체인 배열 `{ root_id, chain[], length, is_resolved }`
- `unresolved_branches`: 미해결 파편 + error_observed 이벤트 배열
- `supporting_fragments`: 체인에 포함되지 않은 나머지 파편
- `case_events`: case_events 테이블 이벤트 배열 (caseId 지정 시)
- `event_dag`: case_event_edges 배열
- `summary`: 요약 문자열
**예시**:
```json
{ "caseId": "debug-auth-2026-04-01" }
```
### search_traces
**목적**: fragments 테이블을 grep하듯 선택적으로 탐색한다. reconstruct_history보다 경량하며, 특정 조건에 맞는 파편을 빠르게 조회할 때 사용한다.
**언제 사용**: 키워드 검색, 특정 세션/케이스의 파편 확인, 이벤트 타입별 필터링.
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| event_type | string | - | fragment type 필터 (fact, decision, error, procedure 등) |
| entity_key | string | - | topic ILIKE 필터 |
| keyword | string | - | content ILIKE 필터 |
| case_id | string | - | 특정 케이스 필터 |
| session_id | string | - | 특정 세션 필터 |
| time_range | object | - | { from: ISO8601, to: ISO8601 } |
| workspace | string | - | 워크스페이스 필터. 지정 시 해당 workspace + 전역(NULL) 파편만 대상 |
| allWorkspaces | boolean | false | master 전용. true이면 trace의 workspace 필터를 제거 |
| agentId | string | default | 특정 agent 지정은 master 전용 |
| includePeerAgents | boolean | false | master 전용. 같은 key/workspace 범위의 모든 agent 포함 |
| limit | number | 20 | 최대 반환 수 (최대 100) |
snake_case 파라미터에는 camelCase alias가 있다: `eventType`, `entityKey`, `caseId`, `sessionId`. 두 표기 중 어느 쪽을 보내도 동일하게 처리된다.
반환값: `{ success, traces[], count }`
**예시**:
```json
{ "keyword": "authentication", "event_type": "error", "limit": 10 }
```
### session_rotate
**목적**: 현재 세션을 종료하고 새 `sessionId`를 발급한다. 토큰 탈취 의심 시 또는 주기적 로테이션에 사용한다.
**언제 사용**: 키 노출이 의심되거나 스케줄된 회전 시점에서 credential의 `bound_key_id` / key group / `permissions`를 재검증하고, 기존 세션의 `defaultWorkspace` / `mode`를 유지한 새 세션을 발급받을 때.
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| reason | string | - | 회전 사유 (감사 로그 기록). 예: `scheduled_rotation`, `suspected_leak`, `user_request` |
반환값: 새 `sessionId`와 회전 결과.
**예시**:
```json
{ "reason": "scheduled_rotation" }
```
### batch_status
**목적**: `batch_remember(async: true)`가 반환한 `jobId`의 처리 상태를 조회한다. 읽기 전용.
**반환**: `{ success, jobId, status }`. `status.state`는 `queued` | `processing` | `completed` | `dead`이고 `accepted`, `inserted`, `skipped`, `error`, `ts`가 함께 실린다(값은 문자열). Redis 비활성이거나 알 수 없는 jobId면 `status: null`.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| jobId | string | O | `batch_remember(async:true)` 응답의 `jobId` |
**예시**:
```json
{ "jobId": "brw-1750000000000-a1b2c" }
```
반환값 예시: `{ "success": true, "jobId": "...", "status": { "state": "completed", "accepted": "42", "inserted": "42", "skipped": "0", "ts": "1750000012345" } }`
### check_update
현재 버전과 최신 GitHub 태그를 비교하여 업데이트 가용 여부를 확인한다. master key 전용.
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| force | boolean | false | true면 캐시 무시하고 GitHub 재조회 |
### apply_update
지정된 단계의 업데이트를 실행한다. `check_update` 선행 필수. master key 전용.
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| step | string | O | 실행할 단계: `fetch` / `install` / `migrate` |
| dryRun | boolean | - | true(기본)면 명령어 미리보기만. false면 실제 실행. |
git 설치본에서 `UPDATE_REQUIRE_SIGNED_TAG=true`(기본 `false`)이면 `install` 단계가 checkout 앞에 `git verify-tag <대상 태그>`를 실행하고, 확인에 실패하면 업데이트를 중단한다.
## 자동 백그라운드 동작
다음 3개 기능은 별도 도구 호출 없이 동작한다. CaseRewardBackprop은 설정으로 켜야 한다.
### ProactiveRecall
- **트리거**: 모든 `remember()` 호출 직후
- **동작**: 저장된 파편의 키워드와 50% 이상 겹치는 기존 파편을 검색하여 `related_to` 링크를 자동 생성
- **영향**: recall 시 관련 파편이 그래프 이웃으로 함께 조회됨 (L2.5 경로)
- **제어**: search 의존성이 없으면 비활성 (기본값: 활성)
### CaseRewardBackprop
- **트리거**: case_events에 `verification_passed` 또는 `verification_failed` 이벤트가 추가될 때
- **동작**: 해당 케이스의 증거(fragment_evidence) 파편 importance를 역전파
- verification_passed: importance +0.15, quality_verified = true
- verification_failed: importance -0.10
- **영향**: 검증된 파편의 recall 우선순위가 자동 조정됨
- **범위**: importance [0.0, 1.0] clamp
- **제어**: `MEMENTO_CASE_BACKPROP_ENABLED=true`일 때만 동작한다(기본 `false`, 비활성 시 no-op).
### SearchParamAdaptor
- **트리거**: 모든 `recall()` / 검색 호출 시
- **동작**: key_id x query_type x hour 조합별 검색 결과 수를 추적하여 minSimilarity를 자동 조정
- 결과 부족(avg < 1): 임계값 하향 (더 관대한 검색)
- 결과 과다(avg > 8): 임계값 상향 (더 엄격한 검색)
- **학습**: 50회 이상 샘플 축적 후 적용, 범위 [0.10, 0.60]
- **영향**: 사용 패턴에 따라 검색 정밀도가 자동 최적화됨
## 중요도 기본값
| 타입 | 권장 | 코드 상한(isAnchor=false) | 근거 |
|------|------|--------------------------|------|
| preference | 0.9 | 0.9 | 사용자 의도 정확 반영 |
| error | 0.8 | 0.6 | 재발 시 즉시 해결. 0.6 초과 지정 시 0.6으로 clamp됨. |
| procedure | 0.8 | 0.6 | 안정적 회상 필요. 0.6 초과 지정 시 0.6으로 clamp됨. |
| decision | 0.7 | 0.7 | 모순 방지 |
| fact | 0.6 | 0.7 | 일반 사실. 0.7 이하까지 허용됨. |
| episode | 0.6 | 0.7 | 맥락 보존용 |
| relation | 0.5 | 0.7 | 관계 기록 |
코드 기준(`lib/memory/write/FragmentWriter.js` `MAX_INITIAL_IMPORTANCE`): `isAnchor=true`이면 상한 제거. content 20자 미만이면 최대 0.2로 추가 제한.
## 기억 저장 규칙
1. 간결성: 파편 하나에 하나의 개념. 300자 이내 (episode 1000자). 4000자 초과는 -32602로 즉시 거부되므로 사전에 300자 내로 요약해 작성한다.
2. 범주화: topic에 프로젝트명. 검색 효율에 직결.
3. 키워드: 3~5개. 프로젝트명 + 호스트네임 + 구체적 용어.
4. 보안: API 키, 비밀번호, 토큰을 파편에 저장하지 않는다.
5. 앵커: 절대 변경되지 않는 핵심 규칙만 isAnchor=true.
6. 대체: 정보 업데이트 시 supersedes로 구 파편 연결. 새 파편이 구 파편을 대체.
7. 연결: 인과 관계가 있는 파편은 link로 즉시 연결. 나중에 graph_explore로 추적 가능.
8. 워크스페이스: 멀티 프로젝트 공유 키에서는 workspace 파라미터를 필수 기입한다. 프로젝트 전용 키(default_workspace 지정)만 예외로 미지정을 허용한다.
## 검색 계층 구조
| 계층 | 방식 | 용도 | 속도 |
|------|------|------|------|
| L1 | Redis 역색인·Hot Cache | 저장 키워드·topic·type의 빠른 후보 조회 | 가장 빠름 |
| L2 | PostgreSQL 키워드·메타데이터 | 정확 키워드와 필터 조건 검색, L1 후보 hydration | 빠름 |
| L2.5 | 그래프 이웃 | 연결된 파편 확장 (deleted_at IS NULL 활성 링크만) | 빠름 |
| L3 | pgvector cosine | 자연어 의미 유사도 검색 | 보통 |
| 본문 어휘 | PostgreSQL GIN 전문 검색 | 코드명·오류 문구·한글 단어 후보 보강 | 빠름 |
| 병합 | RRF + 최종 재정렬 | L1·L2·L2.5·L3·시간·본문 어휘 후보 통합 | 보통 |
`keywords`는 L1과 L2를 거치며 설정에 따라 합성 질의 L3 보조를 병렬 실행한다. `text`를 전달하면 L2와 L3, 본문 어휘 검색을 병렬 실행한 뒤 RRF로 합친다.
## 경험적 기억 활용 (Experiential Memory)
"단순 기억 저장소"를 넘어 경험에서 학습하고 성장하는 기억 시스템을 위한 고급 패턴.
### 1. 확산 활성화 — 맥락 연관 파편 선제 부스트
`contextText`를 recall에 전달하면 검색 전 ACT-R 모델로 관련 파편의 activation_score를 미리 높인다.
키워드 매칭에 등장하지 않아도 맥락상 관련된 파편이 상위로 올라온다.
```
# 일반 recall (키워드만)
recall(keywords=["OAuth", "client_id"])
# Spreading Activation recall (맥락 포함)
recall(
keywords=["OAuth", "client_id"],
contextText="Claude.ai 연동 중 authentication 오류 발생. redirect_uri 관련 이슈 의심"
)
```
contextText 작성 팁:
- 현재 대화의 핵심 주제 1~2문장
- 에러 메시지, 사용 중인 도구명, 의심 원인 포함
- 100~200자 내외가 최적 (너무 길면 키워드 집중도 하락)
### 2. 링크 재통합 — 피드백이 기억 강도를 바꾼다
`tool_feedback`에 `fragment_ids`를 포함하면 해당 파편들의 연결 링크 weight/confidence가 실시간 갱신된다.
(ENABLE_RECONSOLIDATION=true 환경 필요)
```
# 검색 결과가 유용했을 때 → fragment_links reinforce (+0.2)
tool_feedback(
tool_name="recall",
relevant=true,
sufficient=true,
fragment_ids=["frag-abc", "frag-def"],
search_event_id=12345
)
# 검색 결과가 무관했을 때 → fragment_links decay (-0.15)
tool_feedback(
tool_name="recall",
relevant=false,
fragment_ids=["frag-xyz"],
search_event_id=12345
)
```
누적 효과:
- 자주 함께 검색되고 유용했던 파편 쌍은 link weight가 높아져 L2.5 검색에서 더 많이 같이 반환됨
- 무관한 파편 쌍은 weight가 낮아지고 quarantine_state='soft'로 격리될 수 있음
- 모순(contradicts) 링크가 감지되면 인접 related/temporal 링크가 자동 격리됨
### 3. 에피소드 연속성 — 경험 흐름을 그래프로 보존
`reflect` 호출 시 생성된 episode 파편은 이전 세션의 episode와 자동으로 `preceded_by` 엣지로 연결된다.
(EpisodeContinuityService, idempotency_key 기반 중복 방지)
```
# 세션 1 종료 시
reflect(
summary=["OAuth 구현 1단계: DCR 엔드포인트 추가 완료"],
sessionId="sess-001"
)
# → episode-A 생성
# 세션 2 종료 시
reflect(
summary=["OAuth 구현 2단계: Claude.ai redirect_uri 동적 처리 완료"],
sessionId="sess-002"
)
# → episode-B 생성 + episode-A --preceded_by--> episode-B 엣지 자동 생성
```
이 그래프를 통해:
- `reconstruct_history(entity="OAuth")` 호출 시 세션 간 경험 흐름을 시간순으로 재구성
- 인과 체인(`caused_by`/`resolved_by`)과 에피소드 연속(`preceded_by`)을 함께 분석
### 4. 히스토리 재구성 — 언제 어떤 도구를 쓸까
| 상황 | 도구 | 이유 |
|------|------|------|
| 특정 케이스의 전체 흐름 파악 | `reconstruct_history(caseId=...)` | 인과 체인 + case_events DAG 포함 |
| 특정 세션의 기록 확인 | `search_traces(session_id=...)` | 경량, 빠름 |
| 에러 이벤트만 필터링 | `search_traces(event_type="error")` | 타입별 grep |
| 특정 키워드 포함 파편 탐색 | `search_traces(keyword="nginx")` | 전문 키워드 매칭 |
| 복잡한 버그의 근본 원인 추적 | `graph_explore(startId=error_frag_id)` | caused_by/resolved_by 1-hop RCA |
### 5. 최적 활용 워크플로우
**세션 시작:**
```
context() → 핵심 맥락 복원
recall(keywords=[프로젝트명], contextText="오늘 할 작업 한 줄 요약")
→ Spreading Activation으로 관련 기억 사전 로드
```
**작업 중:**
```
# 검색 후 반드시 피드백 (reconsolidation 누적)
recall(...) → _searchEventId 보관
tool_feedback(fragment_ids=[...], search_event_id=..., relevant=true/false)
# 인과 관계 발생 즉시 link
link(fromId=에러파편, toId=해결파편, relationType="resolved_by")
```
**세션 종료:**
```
reflect(
summary=["사실1", "사실2"],
errors_resolved=["원인: X → 해결: Y"],
sessionId=현재세션ID
)
# → 새로 저장한 내용이 있으면 episode 파편 생성 + preceded_by 엣지 연결
```
### 6. Case-based 작업 추적 — 복잡한 작업을 케이스 단위로 관리
`caseId`를 중심으로 remember/recall/amend/reconstruct_history를 연계하면 복잡한 디버깅, 기능 구현, 장애 대응 등의 전체 흐름을 하나의 케이스로 추적할 수 있다.
#### 케이스 생명주기
```
작업 시작 → caseId 부여 + goal + phase="planning" + resolutionStatus="open"
↓
진행 중 → 동일 caseId로 에러/발견/결정 기록 + phase 갱신
↓
완료 → amend로 resolutionStatus="resolved" + outcome 기록
↓
재구성 → reconstruct_history(caseId=...) 로 전체 흐름 + 인과 체인 조회
```
#### 1단계: 작업 시작 — 케이스 열기
caseId는 `{작업유형}-{주제}-{날짜}` 형식을 권장한다.
```
remember(
content="nginx SSL 인증서 갱신 실패 조사 시작. certbot renew 실행 시 403 에러 발생.",
type="episode",
topic="nginx",
keywords=["nginx", "ssl", "certbot", "my-host"],
caseId="debug-nginx-ssl-2026-04-05",
goal="certbot SSL 인증서 갱신 403 에러 해결",
phase="planning",
resolutionStatus="open",
importance=0.8
)
```
#### 2단계: 진행 중 — 발견/에러/결정 누적
동일 `caseId`로 파편을 계속 추가한다. `phase`를 작업 단계에 맞게 갱신한다.
```
# 에러 원인 발견
remember(
content="certbot 403 원인: nginx가 .well-known/acme-challenge를 proxy_pass로 넘기고 있었음. location 블록 우선순위 문제.",
type="error",
topic="nginx",
keywords=["nginx", "certbot", "acme-challenge", "location"],
caseId="debug-nginx-ssl-2026-04-05",
phase="debugging",
importance=0.8
)
# 해결 시도
remember(
content="nginx에 location ^~ /.well-known/acme-challenge/ 블록을 proxy_pass 위에 추가하여 certbot 검증 경로를 직접 서빙하도록 수정.",
type="procedure",
topic="nginx",
keywords=["nginx", "certbot", "location", "acme-challenge"],
caseId="debug-nginx-ssl-2026-04-05",
phase="verification",
importance=0.8
)
```
#### 3단계: 완료 — 케이스 닫기
케이스의 첫 파편(또는 대표 파편)을 `amend`로 갱신한다.
```
amend(
id="첫_파편_id",
resolutionStatus="resolved",
outcome="nginx location 블록 우선순위 수정으로 certbot 갱신 성공. cron 재설정 완료."
)
```
#### 4단계: 사후 재구성 — 전체 흐름 파악
```
reconstruct_history(caseId="debug-nginx-ssl-2026-04-05")
```
반환값:
- `ordered_timeline`: 시간순 전체 파편
- `causal_chains`: caused_by/resolved_by 인과 체인
- `unresolved_branches`: 미해결 브랜치 (있다면)
- `case_events`: 시맨틱 마일스톤 이벤트
- `event_dag`: 이벤트 간 DAG 관계
#### phase 권장 값
| phase | 의미 | 전환 시점 |
|-------|------|----------|
| planning | 작업 계획/분석 | 케이스 시작 |
| debugging | 원인 조사 | 에러 분석 시작 |
| implementation | 구현/수정 | 코드 작성 시작 |
| verification | 검증/테스트 | 수정 완료 후 |
| resolved | 완료 | 검증 통과 |
#### resolutionStatus 값
| 상태 | 의미 |
|------|------|
| open | 진행 중 |
| resolved | 해결 완료 |
| abandoned | 포기/보류 |
### 7. Assertion 신뢰도 관리 — 가설과 사실을 구분
`assertionStatus`는 파편의 신뢰 수준을 4단계로 표현한다. 가설을 저장하고, 검증 후 상태를 갱신하여 기억의 신뢰도를 체계적으로 관리한다.
#### 4단계 신뢰 모델
| assertionStatus | 의미 | 사용 시점 |
|-----------------|------|----------|
| observed | 직접 확인한 사실 (기본값) | 로그/출력/테스트 결과로 확인한 것 |
| inferred | 추론/가설 (검증 전) | "아마 이것이 원인일 것" — 아직 증명 안 됨 |
| verified | 테스트/실행으로 확인 완료 | 수정 후 테스트 통과, 재현 확인 |
| rejected | 틀린 것으로 판명 | 가설이 틀렸음을 확인 |
#### 워크플로우: 가설 → 검증 → 확정
```
# 1. 가설 저장 (inferred)
remember(
content="메모리 누수 원인은 EventListener 미해제로 추정. useEffect cleanup 누락 의심.",
type="error",
topic="frontend",
keywords=["memory-leak", "useEffect", "EventListener"],
assertionStatus="inferred",
caseId="debug-memleak-2026-04-05",
phase="debugging",
importance=0.7
)
# → frag-hypothesis-001
# 2. 검증 성공 → verified로 갱신
amend(
id="frag-hypothesis-001",
assertionStatus="verified",
content="메모리 누수 원인 확정: EventListener 미해제. useEffect cleanup에 removeEventListener 추가로 해결."
)
# 3. 만약 가설이 틀렸다면 → rejected
amend(
id="frag-hypothesis-001",
assertionStatus="rejected",
content="EventListener 미해제는 원인이 아니었음. 프로파일러 확인 결과 클로저의 대형 객체 참조가 실제 원인."
)
```
#### 활용 패턴
검증 전 가설을 `inferred`로 저장하면:
- 다음 세션에서 recall 시 해당 파편이 "아직 검증되지 않은 가설"임을 인지할 수 있다
- 검증 후 `amend`로 `verified`/`rejected`를 명시하면 기억의 정확성이 보장된다
- `rejected` 파편은 "이미 시도했지만 실패한 경로"로서 같은 실수를 반복하지 않게 한다
recall 시 신뢰도 기반 판단:
- `observed`/`verified` 파편: 신뢰하고 적용
- `inferred` 파편: 참고하되 재검증 고려
- `rejected` 파편: 이 경로는 이미 실패했으므로 다른 접근 필요
## CBR (Case-Based Reasoning) 활용
### 유사 사례 검색
과거 유사 작업의 해결 사례를 참조할 때 `recall(caseMode=true)` 사용:
- 검색 결과 파편에서 case_id를 추출하여 케이스별 그루핑
- 각 케이스를 (goal, events, outcome, resolution_status) 트리플로 반환
- resolved 케이스가 우선 정렬
### depth 필터 전략
| depth | 대상 type | 용도 |
|-------|----------|------|
| high-level | decision, episode | Planner — 고수준 의사결정 참조 |
| detail | 전체 (기본값) | 일반 검색 |
| tool-level | procedure, error, fact | Executor — 구체적 실행 절차 참조 |
## 능동 활용 트리거
사용자 요청 없이도 아래 신호를 감지하면 즉시 해당 도구를 선제 실행한다.
### 상황별 의사결정 트리
```
세션 시작
└─ context(structured=true) 즉시 호출
└─ _meta.hints.signal = "empty_context"?
└─ remember 또는 reflect 제안
└─ _meta.hints.signal = "active_errors"?
└─ 각 error 파편을 사용자에게 알리고 해결 여부 확인
에러/오류/실패 발화 감지
└─ recall(type="error", keywords=[관련 키워드]) 먼저 실행
└─ 과거 해결 기록 있으면 → 사용자에게 제시
└─ _meta.hints.signal = "no_results"? → 에러 해결 후 새로 저장 예정임을 인지
에러 원인 확정
└─ remember(type="error", importance=0.8) 즉시 저장
에러 해결 완료
└─ forget(id=해당 error 파편 ID)
└─ remember(type="procedure", importance=0.8) — 해결책 저장
└─ link(fromId=에러파편, toId=해결파편, relationType="resolved_by") — 이미 forget 전이라면
"설정/포트/경로/버전" 변경 시작 전
└─ recall(keywords=[설정명]) — 이전 결정 확인
아키텍처/기술 선택 확정
└─ remember(type="decision", importance=0.7) 즉시 저장
"이전에/저번에/전에" 언급
└─ recall(text=관련 내용) 즉시 호출
세션 종료 의도 감지 ("잠깐", "나중에", "오늘은 여기까지" 등)
└─ reflect(summary=[...], decisions=[...], errors_resolved=[...])
```
### _meta.hints 처리 규칙
recall·context 응답과 쓰기 도구(remember/amend/forget) 성공 응답의 `_meta.hints` 필드를 읽는다:
- `signal` 값을 읽어 상황 파악
- `suggestion` 텍스트를 사용자에게 알리거나 즉시 실행 고려
- `trigger` 필드에 지정된 도구를 다음 행동으로 우선 고려
| signal | 의미 | 권장 행동 |
|--------|------|----------|
| topic_mismatch | 지정한 topic 파편 없음, 유사 topic 존재 | 제안된 유사 topic으로 재검색 |
| no_results | 관련 기억 없음 | 작업 완료 후 remember |
| contradiction_pending | 반환 파편에 미해결 contradicts 링크 | 상충 파편 확인 후 amend 정리 또는 잘못된 쪽 forget |
| stale_results | 30일+ 경과 파편 | amend로 갱신 또는 forget |
| consider_context | 파편 5개 이상 | includeContext=true 재검색 |
| active_errors | 미해결 error 파편 존재 | 각 파편 확인 후 forget |
| empty_context | 저장된 기억 없음 | 세션 후 remember/reflect |
| feedback_sampled | 쓰기 도구 응답에 피드백 요청이 표집됨 | 직전 결과를 `tool_feedback(tool_name=대상 도구, trigger_type="sampled")`로 평가. relevant=false이면 `irrelevance_reason`도 함께 전달 |
recall/context 힌트는 위 표의 순서대로 우선순위가 판정되어 최상위 1건만 실린다(topic_mismatch가 no_results보다 먼저 판정된다). `active_errors`·`empty_context`는 context 전용이다. `feedback_sampled`는 recall/context가 아니라 remember·amend·forget 성공 응답에만 실리며, `hints[0].args`에 `tool_name`·`trigger_type`이 채워져 있다.
### 능동 활용 트리거 테이블
| 감지 신호 | 권장 도구 |
|----------|----------|
| "이전에 비슷한 문제를", "과거 사례" 언급 | recall(caseMode=true, text=관련 내용) |
| Planner 역할로 고수준 계획 수립 | recall(depth="high-level") |
| Executor 역할로 구체적 절차 참조 | recall(depth="tool-level") |
| recall 응답에 `_suggestion` 포함 | `_suggestion.recommendedTool` + `recommendedArgs` 검토 후 적용 |
| Mode 활성화 상태에서 차단된 도구 호출 시도 | 현재 mode 확인 (`memory_stats`), 필요 시 모드 전환 요청 |
## LLM Provider Fallback
Gemini CLI 외 `agy-cli`, `codex-cli`, `copilot-cli`, `qwen-cli`를 포함한 18개 provider로 자동 fallback 가능. 설정: `LLM_PRIMARY=gemini-cli` (기본) + `LLM_FALLBACKS` JSON 배열. env 미설정 시 기존 Gemini CLI 단독 동작 유지. 형태소 분석은 기본적으로 로컬 CPU 분석기(MorphemeTokenizer)가 담당하며 LLM provider 체인을 사용하지 않는다(`MEMENTO_MORPHEME_TOKENIZER=llm` 설정 시에만 LLM 경로 활성화). 자세한 운영은 `docs/operations/llm-providers.md` 참조.
## Symbolic Memory 활용 (opt-in)
모든 `MEMENTO_SYMBOLIC_*` 플래그는 기본 `false`다. 아래 기능은 해당 플래그를 명시적으로 활성화한 환경에서만 동작한다.
### validation_warnings 해석
`remember` 응답에 `validation_warnings: string[]` 필드가 포함된다 (violations 있을 때만). 각 요소는 rule 이름 문자열이다. 필드 자체가 없으면 위반 없음. 정책 규칙 이름은 `MEMENTO_SYMBOLIC_POLICY_RULES=true`일 때, `sensitive.<규칙>` 이름은 `MEMENTO_SENSITIVE_SCAN`이 off가 아닐 때 담긴다.
예시: `{"success": true, "id": "frag-...", "validation_warnings": ["decisionHasRationale"]}`
배열이 비어있지 않으면 다음 중 하나에 해당한다:
- `decisionHasRationale` — decision 타입인데 근거가 약함 → `linkedTo`에 2건 이상 연결하거나 "왜냐하면", "because" 같은 근거 키워드 포함
- `errorHasResolutionPath` — error 타입인데 해결 경로 부재 → cause/fix 키워드 또는 `resolutionStatus` 명시
- `procedureHasStepMarkers` — procedure 타입인데 단계 부재 → "1.", "2.", "먼저", "다음" 등 마커 포함
- `caseIdHasResolutionStatus` — case_id 보유 파편인데 resolution_status 미설정 → `resolutionStatus: "resolved"` 등 명시
- `assertionNotContradictory` - assertion이 verified이면서 rejected로 표시됨 → `amend` 또는 `forget`으로 정리
- `fragmentHasWorkspace` - workspace가 명시값과 키 기본값 어느 쪽으로도 해석되지 않음 → `workspace` 명시. 저장 차단 대상은 `MEMENTO_WORKSPACE_GATE=true`일 때뿐이다
`sensitive.<규칙>` 항목은 알림이다. 서버가 비밀이나 개인 식별 번호를 이미 가려 저장했으므로 재호출하지 않는다. 이메일과 전화번호는 가리기만 하고 경고에 나타나지 않는다. 저장된 파편의 `validation_warnings` 열에도 규칙 이름만 남고 일치한 문자열은 남지 않는다. 규칙 이름: `api_key_legacy`, `password_field`, `anthropic_key`, `openai_project_key`, `api_key_generic`, `github_token`, `aws_access_key`, `slack_token`, `jwt`, `private_key`, `mmcp_key`, `bearer_token`, `rrn_kr`, `card_number`.
정책 경고는 soft gate이므로 기본적으로 저장을 차단하지 않는다. `api_keys.symbolic_hard_gate=true`로 전환하면 해당 키는 정책 경고와 고신뢰 `sensitive.*` 경고(레거시 규칙인 `password_field`와 `api_key_legacy` 포함) 발생 시 저장이 거부된다. `MEMENTO_SENSITIVE_SCAN=reject`이면 키와 관계없이(마스터 키 포함) 고신뢰 `sensitive.*` 탐지에서 거부된다. 이 경우 MCP 도구 에러가 아닌 JSON-RPC **프로토콜 레벨** 에러 코드 `-32003` (SYMBOLIC_POLICY_VIOLATION)이 반환된다:
```json
{"jsonrpc": "2.0", "id": 5, "error": {"code": -32003, "message": "policy_violation: decisionHasRationale", "data": {"violations": ["decisionHasRationale"], "fragmentType": "decision"}}}
```
마스터 키(keyId=NULL)는 대상에서 제외된다. 자세한 운영 절차는 `docs/operations/symbolic-hard-gate.md` 참조.
### explanations 필드 활용
`recall` 응답 파편에 `explanations: [{code, detail, ruleVersion}]` 배열이 포함된다 (`MEMENTO_SYMBOLIC_EXPLAIN=true` 시, 설명이 있을 때만). 해당 파편이 왜 검색 결과에 포함됐는지 최대 3개 이유를 제공한다. 클라이언트가 파편의 관련성을 UI에 표시하거나, LLM 컨텍스트에 설명을 주입할 때 활용한다.
예시: `{"explanations": [{"code": "semantic_similarity", "detail": "cosine 0.87", "ruleVersion": "v1"}]}`
reason code 6종 (`code` 필드값):
- `direct_keyword_match` — L2 형태소 매칭
- `semantic_similarity` — L3 pgvector 임베딩
- `graph_neighbor_1hop` — L2.5 그래프 이웃
- `temporal_proximity` — 시간적 근접
- `case_cohort_member` — case_mode 코호트
- `recent_activity_ema` — ema_activation 가점
단계적 활성화 권장 순서: `MEMENTO_SYMBOLIC_ENABLED=true` → `MEMENTO_SYMBOLIC_SHADOW=true` + `MEMENTO_SYMBOLIC_CLAIM_EXTRACTION=true` → `scripts/backfill-claims.js --dry-run` 선행 → `MEMENTO_SYMBOLIC_EXPLAIN=true` → `MEMENTO_SYMBOLIC_POLICY_RULES=true`.
---
## 안티패턴
다음 행동은 AnchorMind를 무력화한다. 반드시 피할 것.
| 안티패턴 | 왜 나쁜가 | 올바른 행동 |
|---------|----------|------------|
| 사용자가 "기억해"라고 해야만 remember 호출 | 중요 정보가 세션 경계에서 유실됨 | 중요 발생 시점에 자동 저장 |
| recall 없이 에러 수정 시작 | 과거 동일 에러 해결책을 중복 재발견하는 낭비 | recall(type="error") 선행 필수 |
| 에러 해결 후 forget 생략 | 다음 세션에도 동일 error 파편이 context에 잡혀 혼란 | 해결 즉시 forget |
| context 호출 없이 작업 시작 | 이전 세션에서 축적된 전체 맥락 유실 | 세션 시작 즉시 context |
| reflect 없이 세션 종료 | 이번 세션 작업이 전부 휘발됨 | 중요 작업 완료 후 reflect |
| remember 후 link 생략 | 고립된 파편만 생성, 그래프 연결 없음 | 인과관계 있는 파편은 link로 연결 |
| 모든 내용을 하나의 파편에 저장 | 검색 정밀도 저하, 중요도 희석 | 원자적 분해 (1 사실 = 1 파편) |
| 불필요한 remember 남발 | fragment_limit 쿼터 소진, 노이즈 증가로 검색 품질 저하 | 저장 전 "다음 세션에서 필요한가?" 자문, 일시적 정보는 저장하지 않음 |
| importance 미지정 (모든 파편 0.5) | recall 시 중요/비중요 파편 구분 불가, 핵심 정보가 노이즈에 묻힘 | 상황별 중요도 기본값 표 참조, 최소 0.6 이상 명시 |
| keywords 미지정 | 자동 추출에 의존하면 프로젝트명/호스트명 등 핵심 키워드 누락 | 프로젝트명 + 토픽 + 고유 식별자를 keywords에 명시적으로 포함. 지정해도 본문 추출 결과가 병합되므로 본문의 코드 식별자는 별도로 적지 않아도 색인된다 |
| 정책 validation_warnings 무시 후 반복 remember | 동일 경고 파편이 누적되면 symbolic_hard_gate 활성화 시 전면 차단됨 | 경고 내용에 따라 content/linkedTo/resolutionStatus를 보강 후 재저장. `sensitive.*` 경고는 서버가 이미 가렸으므로 재저장 대상이 아니다 |
| recall 결과의 explanations reasonCodes를 fragment content에 복사 저장 | 검색 품질 메타데이터는 저장하면 안 됨. 노이즈로 검색 정밀도 저하 | reasonCodes는 UI 표시나 컨텍스트 힌트용으로만 사용, 저장 금지 |
| Shadow mode 없이 Phase 2+ 직행 | 기존 데이터의 claim 백필 없이 explain/policy 활성화 시 경고 오탐 증가 | `MEMENTO_SYMBOLIC_SHADOW=true` + `scripts/backfill-claims.js --dry-run` 선행 후 단계적 활성화 |