Skip to content

Releases: etinpres/mindvault-v3

v3.8.2 — 전체 시스템 적대적 점검 (28개 결함 수정)

01 Jun 06:55

Choose a tag to compare

v3.8.2 — 전체 시스템 적대적 점검 (28개 결함 수정)

Summary

/goal "전체 시스템 점검 → 버그 수정 → 추가 발견 없을 때까지 반복" 으로 v3.8.1 코드베이스
전체(src 28모듈 + hooks + scripts)를 5라운드 적대적 워크플로(find → adversarial verify
→ runtime 재현)와 프로덕션 debug.log(30k줄) 텔레메트리 분석으로 점검했다. 747 테스트가
이미 그린이던 성숙한 코드에서 28개 실재 결함(데이터 유실 HIGH 1 포함)을 찾아 수정하고
각 수정에 회귀테스트를 붙였다(+29). 라운드별 발견 추세 11 → 13 → 2 → 3(HIGH 1) → 0 으로
수렴 확인. false positive 3건은 런타임으로 기각(예: raw sqlite3.connect 의 busy_timeout 은
Python 기본 5000ms 라 무해), 5건은 근거와 함께 defer.

테스트: 747 passed + 25 subtests → 776 passed + 41 subtests (회귀 0).

Fixed — HIGH (데이터 유실)

  • _collect_md_files 중복 경로 미dedup → dedup-merge 자기삭제 영구 유실 (memory_indexer.py):
    sources.json 에 DEFAULT 자동발견 슬롯을 add 하면 union 에 같은 dir 가 두 번 들어가 같은
    .md 가 두 번 방출 → dedup_cli._scan 이 단일 파일을 자기 자신과 'name-dup' 으로 보고 →
    cmd_merge 가 canonical 을 overwrite 후 삭제(ok:True 보고). resolved-path dedup 으로 단일
    방출 보장(end-to-end 재현 확인).

Fixed — MED

  • FTS5 예약어 누수 (search.py/memory_search.py): AND/OR/NOT/NEAR 포함 쿼리가
    fts5: syntax error 로 FTS 0건이 되던 회귀(운영 15건+). 토큰을 따옴표 phrase 로 감싸 리터럴
    prefix 검색 강제.
  • 비-문자열 frontmatter 가 인덱서 run 전체 중단 (memory_indexer.py): description: 2026
    같은 YAML 비-str 값에 .strip() → AttributeError 가 per-file 가드 없는 루프를 뚫고 인덱싱
    전체를 중단. str() 강제.
  • 세션 인덱서: 임베드 장애 중 재인덱싱 시 sessions_vec 영구 stale (indexer.py): 기존
    세션 본문 변경 + 임베드 실패가 겹치면 FTS 는 새 본문, vec 는 옛 임베딩으로 고착(mtime skip +
    backfill IS NULL 둘 다 못 잡음). 실패 시 vec 행 삭제 → backfill 재충전.
  • extractor finish_reason=length 절단 → 빈 결과 영구 negative 캐시 (memory_extractor.py):
    긴 응답이 max_tokens 에서 잘리면 non-empty 절단 문자열이 parse 0건 → 호출 성공 오인 → 고가치
    세션 영구 추출 스킵. 절단을 호출 실패로 취급.
  • query_intent: 일시 장애를 7일 negative 센티넬로 캐시 (query_intent.py): timeout/서버다운
    (None)을 'Gemma 가 other 라 응답' 과 합쳐 캐시 → transient 장애가 해당 prompt 분류를 7일 배제.
    호출 실패는 캐시하지 않고 재시도.
  • recall_cli "항상 exit 0" 계약 위반 (recall_cli.py): numpy 없는 인터프리터(배포 /recall
    컨텍스트)에서 lazy import ImportError 전파 → 비-0 종료. source 별 try/except 로 빈 결과 흡수.
  • contradiction_review 블록리스트 정규식이 공백 항목 미탐 (contradiction_review_cli.py):
    - some old memory 같은 공백 포함 YAML 블록리스트를 못 잡아 mutation 거부 가드 우회 → 중복
    inline 키로 YAML 손상. \S[^\r\n]* 로 항목 전체 매칭.
  • memory_review 비원자 promote → 고아가 재시도 영구 차단 (memory_review_cli.py): target
    기록 후 INDEX append/src.unlink 실패 시 target 이 남아 'target exists' 로 재시도 차단. 후속
    실패 시 target+INDEX 대칭 롤백.
  • dedup_cli frontmatter raw 보간 (dedup_cli.py): 멀티라인/비-str name·description·type 이
    raw 보간되면 frontmatter 손상. _serialize_fm_value 단일라인 정규화.

Fixed — LOW (견고성·일관성)

  • 모든 Gemma 호출자 5곳(search/memory_extractor/contradiction_detector/alias_generator/
    query_intent) 비-문자열 content 가드 + UnicodeDecodeError 흡수
    — content-block 리스트
    응답에 .strip()/.splitlines() AttributeError, max_tokens 경계 멀티바이트 절단 시
    UnicodeDecodeError 가 회수/추출 루프를 떨구던 동일 안티패턴을 전 호출자에 통일.
  • self_eval turns_cache import 3곳 — src 가 sys.path[0] 가 아닌 컨텍스트에서 'No module
    named turns_cache' 로 캐시 silent 사망(운영 9건/5-31). late-import 패턴 통일.
  • reverify 비-dict sidecar → .get AttributeError 로 scan 영구 skip. dict 가드(자가복구).
  • memory_indexer._acquire_lock 가 BlockingIOError 외 OSError 시 fd 누수. 대칭 처리.
  • session_memory.cache_set 결정적 tmp 이름 → 병렬 SessionStart interleave 손상. PID-tmp.
  • memory_extractor._iter_balanced_arrays 선행 불균형 [ 에서 조기 종료로 유효 배열 유실.
  • gemma_rerank LLM 중복 인덱스 미dedup → 동일 세션 중복 회수.
  • backfill_cli --limit 0/음수 인자, provenance_backfill 미존재 dir 등 CLI 입력 검증.
  • alias_generator meta-fail 진단 로그(원인 파일 추적 가능), memory_review procedural 인덱스
    링크 경로, deploy_drift_check 경고 방향 중립화.

Method — 적대적 수렴 점검

  • 5라운드 워크플로: finder 후보 → 독립 verifier 가 적대적 반증(코드 재확인 + 호출 도달성 +
    테스트 커버 + runtime 재현). 라운드 간 수정 코드를 재투입해 fix-the-fix 도 검출(round4 가
    round3 promote-rollback 비대칭 회귀 + dedup type 누락을 잡음).
  • 프로덕션 debug.log 텔레메트리로 이론이 아닌 실재 실패 우선 추적(FTS5 syntax error, import
    실패, json parse fail, alias_sync failed).
  • 수렴 기준: 신규 confirmed 0 라운드. R5 = 0 → dry.

Deferred (근거와 함께 미수정 — 유지보수자 판단 영역)

  • query_intent '전에' 시간접속사 오탐 → cosine 게이트 완화 오적용 (튜닝 — live hit rate
    영향, query 분포 데이터 필요).
  • alias_generator 동시 generate lost-update (CLAUDE.md 가 유사 race 를 수용된 한계로 명시).
  • self_eval recall_utilization 시간 윈도우가 세션 경계 무시 (측정 정확도 — 침습적 + NEXT-37
    baseline 영향, 재baseline 은 유지보수자 결정).
  • drift 백스톱이 '신규 미배포 모듈' 미탐 (구조적 — install.sh 하드코딩 배열).
  • install.sh drift-hook settings 폐기 분기 (노출 ≈ 0).

v3.8.1 — 배포 후크화: git hook 자동 배포 + drift 백스톱

01 Jun 01:07

Choose a tag to compare

v3.8.1 — 배포 후크화: git hook 자동 배포 + drift 백스톱

배경 (근본 원인)

contradiction 분류 false-positive 를 93% 줄이는 프롬프트 보정 커밋(4a4fdac)이 repo 에는
2026-05-28 존재했으나, 배포 경로(~/.claude/scripts/mindvault)에는 06-01 까지 미반영이었다.
install.sh 가 모델 다운로드·launchctl·헬스체크·인덱싱까지 도는 무거운 설치라 코드 한 줄을
고쳐도 수동 재실행을 미루게 되고, 그 사이 라이브 hook 은 옛 코드로 계속 돈다. 실제로 이
3일 지연 때문에, 이미 수정 완료된 'haruko_novel 서사 타임라인' 항목이 fact_correction
오탐(conf=1.00)으로 review 큐에 올랐다. (구프롬프트 → fact_correction, 현 프롬프트 →
no_conflict, temp=0.0 결정론적 5/5 재현으로 배포 지연이 원인임을 확정.)

변경

  • install.sh — MV3_SYNC_ONLY 경량 경로: 파일 복사 + settings.json 등록만 수행하는
    sub-second sync (0.594s 실측). 모델 다운로드·pip·plist(launchctl)·헬스체크 대기·
    인덱싱·pre-warm 은 전부 skip. src→target 매핑(파일 rename 포함)은 기존 deploy 호출을
    그대로 재사용해 단일 진실원본 유지. 비arm64 인터랙티브 프롬프트도 우회.
  • .githooks/post-commit · post-merge: deploy 소스(src/·hooks/·skill/·scripts/·
    install.sh) 변경 시 자동 경량 sync. 문서/테스트-only 커밋은 skip. install.sh 가
    core.hooksPath=.githooks 를 self-wire 한다.
  • scripts/deploy_drift_check.py — SessionStart 백스톱: repo src/*.py ↔ 배포본
    sha256 비교, drift 시 한 줄 경고 + 해결 명령(MV3_SYNC_ONLY=1 ./install.sh)을
    additionalContext 로 주입. 훅 우회(--no-verify)·외부 동기화 누락까지 포착. 항상
    exit 0(세션 비차단), drift 없으면 무출력, repo 경로 없으면(엔드유저 설치) 조용히 skip.
  • tests/test_sync_only.py: sync 배포·슬로우 단계 skip·drift 검출/복구 6 케이스.

효과

repo 커밋이 ~/.claude 배포 경로에 즉시 반영 → "repo 는 고쳤는데 배포는 stale" 상황이
구조적으로 불가능해진다. 훅을 우회해도 다음 세션 시작에 drift 경고로 포착되는 이중 안전망.

검증

747 passed + 25 subtests (신규 6 포함, 회귀 0). post-commit·post-merge 라이브 발동,
drift 양성/복구 스모크, 라이브 sync 0.594s 실측.

v3.8.0 — Phase 1 ③ 신뢰성 검증 (Phase 1 토대 완성)

31 May 23:54

Choose a tag to compare

v3.8.0 — Phase 1 ③ 신뢰성 검증 (over-trust 해소 / stale 자동 감지)

Phase 1(토대: 신뢰 가능한 효과적 회수)의 마지막 축. 회수된 stale 메모리를 검증 없이 믿는 over-trust를 막는다. 이로써 Phase 1 토대 3축이 모두 완성된다 — ①출처(v3.6.0) / ②효과적 회수(v3.7.0) / ③신뢰성(이번 릴리즈).

핵심 기능 (③)

  • Canonical Facts Registry + 라이브 verifier: 메모리의 코드/사실 참조(임베딩 모델명·포트 등)를 현행 코드와 결정론적으로 대조해 stale 의심 판정. Gemma 미사용 — 운영비 0, CI에서 정확히 핀 가능.
  • 판별 신호: 메모리가 옛 토큰(예: bge-m3)을 담으면서 현행 토큰(arctic)을 미언급하면 stale 의심. 둘 다 언급하는 정당한 이력은 면제.
  • 회수 시 경고 라벨: stale 의심 메모리에 "재검증 필요: … (현행 코드/사실 대조 후 신뢰)" 1줄 동반 주입 (양 포맷터 byte-parity, v1 토큰낭비 금지 준수 — stale에만).
  • SessionEnd 증분 트리거 + sidecar(주 1회 self-throttle) + 수동 CLI reverify_cli(scan / list / verify-registry, 레지스트리 self-check).
  • 완료 게이트: 2026-05-30 "BGE-M3 → Arctic-ko" over-trust 사고(어시스턴트가 stale 임베딩 표기를 믿고 시스템 상태를 틀리게 답함)를 scan→yaml→formatter end-to-end 회귀 케이스로 고정.

신뢰성 (4-round 적대적 sweep, loop-until-dry)

구현 후 다차원 find→adversarial verify 워크플로로 Phase 1 전체를 새 결함 0건까지 반복 점검 (R1 8 → R2 2 → R3 4 → R4 0 수렴). 누적 14건 처리(13 수정 + 1 문서화):

  • CRITICAL: 테스트 스위트가 사용자 실제 메모리를 변경하던 격리 누락(conftestMV3_MEMORY_DIR 미격리) — 수정 + 회귀 가드.
  • HIGH: install.sh가 reverify 모듈 미배포 → 배포 환경에서 ③ silent no-op — 수정 + 가드.
  • 그 외: YAML 미인용 frontmatter 소실, CRLF 본문 보존, 클록 역행 영구 SKIP, _is_deprecated 2KB cap의 Layer 5 감쇠 누락, parser BOM 불일치, _atomic_write 동시 race 등.

검증

  • 회귀: 678 → 741 passed, 1 skipped, 25 subtests (회귀 0)
  • Layer 5(모순감지) 무손상, 두 회수 포맷터 byte-parity 유지
  • 자동 게이트 조정·메모리 본문 auto-edit은 미구현 유지(설계 §4.4 — 잘못 학습된 loop 위험 회피)

설계·plan: docs/specs/2026-05-31-phase1-reliability-design.md, docs/plans/2026-05-31-phase1-reliability.md.

v3.7.0 — Phase 1 ②: 효과적 회수 강화 (self-check 계약 + checkable 게이트)

31 May 09:13

Choose a tag to compare

v3.7.0 — Phase 1 ②: 효과적 회수 강화 (self-check 계약 + checkable 게이트)

Summary

직전 v3.6.0(Phase 1 ① Provenance, 출처 추적)에 이은 Phase-1 두 번째 축 — 효과적 회수(effective recall). 회수된 메모리가 답변 reasoning 에 거의 통합되지 않는 under-integration(strict cited 7.62% baseline — self_eval.recall_utilization 측정) 문제를 두 갈래로 손본다: ① 회수 출력 CONTRACT 에 self-check 조항을 추가해 옵션·권장·다음 단계를 제시할 때 회수 메모리의 룰·제약과 충돌하는 항목을 스스로 제거/표기하도록 의무화하고, ② self_evalcheckable pass/fail 게이트(recall_utilization_gate() + --target CLI)를 얹어 spec §4.3② 완료 기준("strict cited 상승")을 prose 가 아니라 운영 가능한 임계값으로 고정한다.

12 커밋. 기능을 먼저 깐 뒤 4라운드 adversarial audit(19렌즈 × 2인 검증) 으로 계약·게이트의 갭을 교차 발견·수정했다. 양 포맷터(Layer 4 hook / compact 재주입) byte-parity 와 self_eval ingestion·sanitize 회귀를 모두 가드. 전체 회귀 678 passed · 1 skipped(pre-existing) · 25 subtests passed.

Added

  • 회수 CONTRACT 에 self-check 조항 (hooks/memory-recall.py _format_output / src/recall_core.py CONTRACT): 기존 "회수 노트:" 의무 출력 + "모순되면 즉시 표기" 뒤에 한 문장을 덧붙인다 — "옵션·권장·다음 단계 제시 시 위 회수 메모리에 명시된 룰·제약과 충돌하는 항목은 제거하거나 \"회수 메모리 <이름> 위반 가능성\"으로 표기." Layer 4 hook 과 compact 재주입(SessionStart)은 별도 구현이지만 동일 CONTRACT 상수/문구를 싣고, parity 테스트가 두 경로 출력의 byte-동일을 강제한다.
  • strict cited 목표 게이트 (src/self_eval.py recall_utilization_gate()): recall_utilization() 결과를 받아 strict cited 목표 대비 pass/fail 을 판정한다. judged = cited + marker_only + unused(no_response 제외, 측정 정의와 일치)가 min_judged(기본 30) 미만이면 소표본 noise 차단을 위해 insufficient_sample(pass=False). 반환 dict 에 pass / strict / target / judged / min_judged / scope / reason 노출. 측정 로직은 불변 — 게이트는 판정만 얹고 임계값을 자동 튜닝하지 않는다(설계 D6).
  • --target CLI 플래그 (self_eval argparse): --recall-utilization 의 strict cited 목표치를 [0.0~1.0] 유한 실수로 받는다(기본 RECALL_UTILIZATION_TARGET = 0.15 ≈ 2× baseline). --json 출력 시 결과에 gate 키를 동봉.
  • 상수: RECALL_UTILIZATION_TARGET = 0.15, RECALL_UTILIZATION_MIN_JUDGED = 30, RECALL_UTILIZATION_SCOPE(측정면 = Layer-4 hook 회수만 — compact 재주입 효과 미측정 명시).

Changed / Fixed (4라운드 adversarial audit)

  • R2E-1 — 계약을 비가시 type 의존 → content+이름 기반으로 재서술: 초기 self-check 문구가 회수 출력에 가시화되지 않는 정보(type-scoping)에 의존하던 약점을 교정. 회수 결과에 실제로 보이는 "위 회수 메모리에 명시된 룰·제약" 문구 + 구체 placeholder <이름> 기반으로 재서술해 모델이 실제로 따를 수 있게 했다(parity 테스트가 양 포맷터에서 해당 문구 존재를 명시 단언).
  • R2C-1 — 게이트 측정 scope 명시: 게이트가 신뢰하는 strict 는 recall_utilization 의 측정면 = Layer-4 hook 회수(UserPromptSubmit)만 포함한다. compact 재주입(SessionStart)은 동일 CONTRACT 를 싣지만 measurement 면에서 제외(_compact_metric 은 recalled_ids 미기록 + RECALL_INJECTION_HEADERS 가 compact intro 의 em-dash 변형 미인식). 게이트가 '효과적 회수' 를 과대 인증하지 않도록 scope 키로 이를 명시(pass/insufficient 양 경로 모두 노출, 테스트로 가드).
  • R2A-1 — --target nan/inf 검증: _target_arg[0.0, 1.0] 유한 실수만 통과시킨다. nan/inf 가 통과하면 json.dump 가 비-스펙 NaN/Infinity bare token 을 출력해 엄격 JSON 파서를 깨뜨리던 경로를 argparse 단계에서 차단(nan·inf·-inf·범위 밖·비숫자 모두 ArgumentTypeError).
  • R2-D-1 — compact 전파 테스트 갭: "compact 재주입은 format_memory_context 경유라 self-check 계약이 자동 전파된다"는 설계 주장(D7)을 prose 로만 두지 않고, 실제 COMPACT_INTRO 로 렌더해 조항이 들어가는지 직접 고정하는 테스트를 추가.
  • docs: 설계 doc(D2 content-scope, D3 확정 문구) 및 plan prose(Goal·회귀 예상치 675) 의 stale 기술을 audit round2/3 정정 반영(R3D-1/R3D-2).

Tests

  • self_eval 게이트 (tests/test_self_eval.py TestRecallUtilizationGate, +7): target 이상 pass / 미만 fail / insufficient_sample / strict == target 경계 통과(>=) / 기본 상수 / scope 노출(R2C-1) / --target nan·inf·범위밖 거부(R2A-1).
  • 계약 parity·회귀 (tests/test_recall_core_parity.py, +4): self-check 조항이 양 포맷터에 존재하고 기존 "회수 노트:" 계약과 byte-parity 유지(D7), 새 계약 footer 가 RECALLED_NAME_RE 추출 noise 를 만들지 않음(ingestion 회귀), snippet 의 </system-reminder> 누출 차단(sanitize 회귀), compact intro(COMPACT_INTRO) 경로 전파(R2-D-1).
  • 전체 회귀: 678 passed · 1 skipped(pre-existing) · 25 subtests passed.

Notes

  • 솔직한 한계 ①: self-check 계약은 회수 출력에 실린 지시일 뿐, hook 이 모델의 준수를 100% 강제하지는 못한다(계약 텍스트가 답변에 들어가도록 보장할 뿐, 옵션·권장 단계의 실제 cross-reference 수행은 모델 재량).
  • 솔직한 한계 ②: strict cited 게이트는 Layer-4 hook 회수면(UserPromptSubmit)만 측정한다. compact 재주입(SessionStart) 효과는 미측정이며, 회수면 합산은 Phase 2 후속 과제(측정 로직 동결 — D6). 또한 strict cited 는 substring-match 기반 lower bound 라 목표(0.15) 미달이 곧 실패는 아니다(의역·rephrase 통합은 marker_only/unused 로 과소집계).
  • 코드 변경은 src·hooks·tests 5개 파일에 국한(+240 -2). 회수 측정 임계값(raw cosine/score)은 게이트 결과로 자동 튜닝하지 않는다.
  • Phase-1 마지막 축(③ stale 자동 감지)은 별도 릴리즈로 분리.

v3.6.0 — Phase 1: Provenance (영구기억 출처 추적)

31 May 04:40

Choose a tag to compare

v3.6.0 — Phase 1: Provenance (영구기억 출처 추적)

Summary

"신뢰 가능한 효과적 회수(trustworthy effective recall)"를 향한 Phase-1 기반 로드맵의 첫 축 — 출처 추적(Provenance). 영구기억이 어디서 왔는지(어떤 세션·소스에서, 언제 포착됐는지)를 staging 시점에 frontmatter에 기록하고, 회수될 때 그 출처를 결과에 부착·표시한다. 사용자가 회수된 메모리를 볼 때 "이게 언제·어디서 온 기억인지"를 한 줄로 확인할 수 있어, AI의 "딴소리"를 검증·신뢰하는 근거가 된다.

12 커밋. 기능 골격(write → recall → format)을 먼저 깔고, 다단계 적대적 버그헌팅(find → adversarial verify → fix-the-fix, 2 라운드 + completeness sweep) 으로 프로덕션 갭과 직렬화 취약점을 교차 발견·수정했다. provenance 테스트 +28 (전체 668개 수집 · 667 passed · 1 skipped(pre-existing) · 25 subtests passed).

Added

  • staging 시 출처 기록 (write_staged): 영구기억 후보를 staged할 때 source_type / source_ref를 frontmatter에 남긴다. 기본값 파라미터(source_type="session", source_ref=session_id)라 기존 호출부는 무변경. Phase-2(URL ingest 등) 다양한 소스 유형을 받을 수 있도록 시그니처를 미리 열어둠.
  • 회수 시 provenance 부착 (recall_memory): 회수 결과에 메모리 파일의 frontmatter를 재파싱해 provenance(source_type / source_ref / captured_at)를 동반한다. (DB 스키마 불변 — 결과 path frontmatter 재파싱 방식, TOP_K=1이라 비용 무시 가능.)
  • 회수 출력 "출처:" 라벨 (_format_output): - [name] 라인 직후 출처: {source_type} {ref8} {date10} 짧은 한 줄을 삽입(ref·날짜는 앞 8·10자만, 토큰 낭비 방지). 노이즈 방지를 위해 unknown·빈 source_type은 미표시. Layer 4 hook(hooks/memory-recall.py _format_output)과 compact 재주입(src/recall_core.py format_memory_context)은 별도 구현이지만, parity 테스트(test_formatter_byte_equivalence)가 두 경로의 출력이 byte-동일함을 강제한다(provenance 4 shape 포함 — datetime captured_at / unknown 억제 / None ref / 8자 초과 ref).
  • 기존 메모리 backfill CLI (provenance_backfill_cli): 출처가 없는 기존 메모리에 소급 부여. 억측 금지 원칙 — 기록된 session id가 있으면 session, 없으면 unknown fallback(git blame류 추론 안 함). 실제 기록 형식을 모두 인식하도록 session id 우선순위를 staged_from_sessionoriginSessionId(top-level) → metadata.originSessionId(nested — memory-production 파이프라인의 실제 형식)로 확장. _procedural/ 디렉토리 포함(recall 인덱싱 범위와 일치), _staged/·MEMORY.md 제외, atomic write(tmp + os.replace), trailing-whitespace fence 파일도 crash 없이 처리. dry-run 기본 / --apply 시에만 쓰기, 멱등, skip·실패 파일명 리포트.

Fixed (adversarial sweep)

  • cmd_approve 승격 시 출처 소실 — 게이트1 프로덕션 갭 (핵심): write_staged는 출처를 staged frontmatter에 박았지만, cmd_approve가 승격(promote) 시 frontmatter를 처음부터 재구성하며 source_type/source_ref/captured_at를 통째로 떨어뜨려 — 완료 게이트 "신규 영구기억 출처 추적률 100%"를 정작 영구 진입 경로(/memory_review 승인)에서 깨고 있었다. _provenance_passthrough 헬퍼(_supersede_passthrough 패턴 미러)를 NEW-PROMOTE / UPDATE 두 final_fm 블록에 모두 wire-in. UPDATE 경로는 기존 영구메모리의 원본 출처를 우선하고, absent(또는 unknown)일 때만 staged 메타로 폴백 — donor를 원자적으로 선택해 incoherent한 source_type/source_ref 짝 섞임을 차단.
  • recall 핫패스 UnicodeDecodeError 견고화: provenance frontmatter 재파싱 루프가 (OSError, UnicodeDecodeError, KeyError)를 모두 catch — 비-UTF8/손상 메모리 파일 한 개가 recall 결과 전체를 [] 로 날리던 경로 차단(backfill CLI와 대칭).
  • source_ref str 정규화 (JSON-safe): recall_memory provenance 루프에서 source_refcaptured_at과 동일 방식으로 정규화(isoformat 우선, 그 외 비-None/비-빈은 str(), None/"" 유지). YAML이 bare 숫자·날짜를 int/date로 파싱해도 /recalljson.dumps가 깨지지 않도록 보장(+ recall_cli json.dumps(..., default=str) 방어).
  • contradiction-aware writer kwargs forward (Phase-2 forward-proof): make_contradiction_aware_writer의 wrapper가 **kwargs를 base_writer로 전달하지 않아, source_type/source_ref를 넘기는 미래 호출자(Phase-2 URL ingest 등)가 writer를 통과하며 provenance를 silently drop할 latent 버그를 사전 차단. 기존 caller(kwargs 없음) 무영향, contradiction detection 로직 변경 없음.
  • backfill 견고화: ref 확정 시 re.sub(r"\s+", " ", str(ref))로 multiline 값을 단일 라인으로 collapse(frontmatter 구조 보호), trailing-ws 닫는 fence를 tolerant 탐지(exact lines.index("---") ValueError → 전체 run abort + --apply half-migration 차단), per-file try/except로 한 파일 실패가 배치를 중단시키지 않음.

Tests

  • provenance end-to-end 통합(write_staged → cmd_approve 승격 → index → recall → format 출처 라벨), cmd_approve NEW/UPDATE/fallback·unknown-adopts-staged 커버리지, recall_core parity(provenance shape), JSON-safe 직렬화, trailing-ws fence·multiline-ref collapse 단언 등 +28 테스트.
  • vacuous(실제로 fix를 검증하지 못하던) fixture 교정 — multiline collapse 경로를 강제 트리거하도록 fixture에 실제 \n embed, docstring 부정확 기술 정정.
  • 전체 회귀: 668 collected · 667 passed · 1 skipped(pre-existing) · 25 subtests passed.

Notes

  • 기존 메모리 backfill은 별도 CLI(python -m src.provenance_backfill_cli <memory_dir> [--apply])로 수행 — 운영 메모리 172건(session 83 / unknown 89) 소급 적용 완료.
  • Phase-1의 나머지 두 축(②효과적 회수 self-check, ③stale 자동 감지)은 별도 릴리즈로 분리.

v3.5.0 — compact 재주입 (압축 후 회수 맥락 복원)

30 May 10:32

Choose a tag to compare

v3.5.0 — compact 재주입 (압축 후 회수 맥락 복원)

Summary

긴 세션에서 컨텍스트 **압축(compaction)**이 일어나면 그동안 회수돼 있던 메모리 맥락이
함께 사라진다. v3.5.0 은 압축 직후 SessionStart 가 source="compact" 로 재발화하는
시점에, 무거운 5-세션 요약 대신 현재 세션의 최근 사용자 발화로 hybrid recall 을 돌려
관련 메모리만 경량 재주입
해 그 맥락을 되살린다 (Layer 1 보강, agentmemory 차용 후보
A — docs/specs/2026-05-30-agentmemory-adoption-candidates.md).

기능 자체는 hook 1개 분량이지만, 5라운드 적대적 코드감사(find → adversarial verify
→ mutation 입증)로 진짜 버그 3종을 잡고 fix-the-fix 까지 수렴시켰다. 전 과정 mutation
15/15 통과(각 fix 를 망가뜨리면 해당 테스트가 정확히 실패), 두 compact 테스트 파일
인접 실행 flakiness 0/5, 617 → 639 passed.

Added

  • compact 재주입 (session_memory.handle_compact_reinjection): PreCompact hook 은
    압축 이후 컨텍스트에 살아남는 additionalContext 를 주입할 수 없으므로(공식: decision
    필드만), 압축 직후 다시 fire 하는 SessionStart + source=compact 경로를 사용한다.
    SessionStart 가 matcher="*" 로 등록돼 있어 별도 등록 변경 없이 compact source 가
    들어온다. 현재 세션 transcript 의 최근 genuine user 발화(최대 4개)로 recall_memory
    를 호출해 COMPACT_TOP_K=3 메모리를 hookSpecificOutput.additionalContext 로 재주입.
  • recall_core 공용 게이트 (src/recall_core.py): Layer 4(memory-recall.py)와
    compact 경로가 같은 임계값(SCORE_THRESHOLD / RAW_COSINE_MIN_* / RECALL_HINTS)과
    포맷터(format_memory_context)를 공유하는 single source of truth. parity 테스트가
    두 경로의 상수 동등성과 포맷 byte-동일성을 강제해 silent skew 를 차단.
  • compact 관측성 (_compact_metric): 종결점마다 metrics.jsonl 에 1줄 기록
    (injected / no_results / intent_skip / short_query / budget_timeout /
    no_transcript / no_turns / import_skip) — Layer 4 와 대칭으로 발동률·스킵사유
    분포·latency 를 집계 가능.

Fixed (5-round audit)

Round 1 — query 오염 + 시간예산 (MEDIUM)

  • 압축 시점 query 오염: 압축 직후 transcript 에는 isCompactSummary=True 요약
    메시지(~13KB)·스킬 본문·<local-command-*> 스캐폴딩이 type=user 로 섞인다. 초기
    구현이 이들을 회수 query 로 포착해 "사용자 의도" 대신 boilerplate 로 회수가 빗나갔다
    (recall-on-recalled). _recent_genuine_turns 가 isCompactSummary·노이즈 프리픽스를
    스킵하고 deque(maxlen=N) 로 마지막 N개 genuine 발화만 보존(redact N→k), per-turn
    캡(MAX_MSG_CHARS) 후 tail-keep(query[-MAX:])으로 가장 최근 발화를 보존.
  • 시간예산 부재: compact recall 에 Layer 4(400ms SIGALRM)에 해당하는 가드가 없어
    cold/hung Arctic-ko 서버(embed 5s) 가 compaction 재개를 통째로 블로킹할 수 있었다.
    COMPACT_BUDGET_S=2.0 SIGALRM + _CompactTimeout(BaseException) sentinel 로 상한.

Round 2 — vacuous 테스트 + intent 오탐 (HIGH)

  • vacuous 회귀 테스트 2건: (a) intent-skip 테스트가 sentinel 을 raise
    broad except 에 삼켜져 fix 제거에도 green, (b) tail-keep 테스트 fixture 가 너무
    작아 truncation 미발생. mutation 으로 폭로 후 spy-flag / 강제-truncation fixture 로
    교정.
  • intent join-blob 오분류: query_intent.classify 는 단일 prompt 설계인데 join 된
    multi-turn blob 을 분류해, oldest 턴의 인사말("안녕")이나 어딘가의 meta 어구("토큰")
    하나가 전체를 chat/meta 로 오분류해 회수를 스킵시켰다.
  • source 스칼라 가드 + parity: "+".join(source) 가 스칼라 "vec" 를 글자분해
    ('v+e+c')하던 잠재 버그. recall_corememory-recall._format_output 양쪽에
    isinstance 가드 mirror + scalar parity 테스트.

Round 4 — SIGALRM 핸들러 누수 (MEDIUM)

  • 핸들러 미복원: 시간예산 SIGALRM 핸들러를 설치만 하고 이전 핸들러를 복원하지
    않아, 프로세스에 _compact_alarm 이 남고 이후 stray SIGALRM 이 무관한 코드에서
    _CompactTimeout(BaseException) 을 던졌다. 단발 hook 프로세스는 무해하나 장수
    인터프리터(pytest)에서 flaky. _prev 저장→finally 복원으로 차단(compact·Layer 4
    양쪽, defense-in-depth).
  • compact intro sanitize, short-query 테스트 vacuity, ImportError 별도 outcome 등
    관측성·테스트 보강.

Round 5 — intent all-chat + 구조 정리 (MEDIUM)

  • Layer 4 핸들러 복원 무테스트: round-4 가 복원을 양쪽에 적용했으나 compact 쪽만
    테스트했다. mutation 이 전체 스위트를 통과(silent skew). Layer 4 복원 회귀 테스트 추가.
  • intent 게이트: 최신 턴만 분류하면 final ack("ok") 가 substantive 맥락을 억제. 최근
    genuine 턴이 전부 chat/meta 일 때만
    스킵하도록(all(...)) — join-blob(oldest 억제)·
    latest-only(ack 억제) 양쪽 오탐 해소.
  • query build 중복(extract_compact_query vs inline) → _build_compact_query 공유로
    drift 차단. itimer 를 recall 직후 disarm 해 emit/metric 이 예산 밖(injected→
    budget_timeout 오기록 차단). 복원 테스트를 distinct sentinel 로 강화(SIG_DFL-always
    mutant 도 검출).

Test count

599 (v3.4.1) → 639 (+40, 회귀 테스트 동반). mutation 15/15 GOOD, flakiness 0/5.

Migration

bash install.sh 재실행. 스키마 변경 없음. compact 경로는 SessionStart hook 에 통합돼
별도 hook 등록이 필요 없다(matcher="*" 가 compact source 도 수신). recall_core.py
RUNTIME_EXTRA_SRC~/.claude/scripts/mindvault/ 에 신규 배포된다.

Known limitations (현재 상태 — 의도적 수용)

  • numpy re-exec 인터프리터 선택: bootstrap 이 numpy 보유 인터프리터를 "첫 번째 다른"
    것으로 고르며 numpy 유무를 재검증하지 않는다(Layer 4 와 공유하는 pre-existing 패턴).
    배포 환경(framework python 3.10 우선)에선 발동 안 하고, 발동해도 compact 만 graceful
    skip 이라 그대로 둔다.
  • compact + Layer 4 1회성 중복: 압축 직후 compact 가 주입한 메모리를 다음 메시지의
    Layer 4 회수가 같은 topic 이면 1건 중복 주입할 수 있다. compaction 은 드문 1회 이벤트라
    (v1 의 매-메시지 낭비와 다름) 수용.
  • transcript O(N) parse: 최근 4개 발화를 찾으려 모든 줄을 json.loads 하지만 2s
    예산이 cover 하고 redact 는 선택분에만 적용한다.

v3.4.1 — 전체 시스템 버그 감사 (data-loss/race/파싱 강건성)

29 May 04:34

Choose a tag to compare

v3.4.1 — 전체 시스템 버그 감사 (data-loss / race / 파싱 강건성)

Summary

8천 LOC 전반을 다중 에이전트로 감사(find → 적대적 verify)해 39건 후보 중 28건을
확정하고, 그중 23건을 수정(회귀 테스트 동반, 전체 599 passed). 핵심은 임베딩
서버 일시 장애·동시 write·LLM 출력 파싱이 조용한 영구 데이터 유실로 번지던
경로들을 닫은 것.

Fixed

동시성 / DB (HIGH·MEDIUM)

  • WAL/busy_timeout 미설정 (indexer.open_db): index.db 가 기본 DELETE 모드라
    동시 reader/writer 가 unable to open database file(운영 370건+)·database is locked
    를 빈발. PRAGMA journal_mode=WAL + busy_timeout=5000 설정 (extractor_cache 선례).
    WAL 은 파일 단위 영속이라 embed_cache 등 sub-conn 도 상속.
  • 세션 인덱서 무 lock: trigger_background_indexer 가 락 없이 detach spawn →
    동시 실행 중복 작업. incremental_index 에 flock(LOCK_NB) 추가 (session-indexer.lock).
  • alias _save 고정 tmp명 race: 동시 SessionEnd 가 같은 tmp 충돌/orphan-unlink →
    alias_index 손상. PID-고유 tmp 로 분리.
  • contradiction queue append-vs-rewrite race: _mark_resolved 가 tmp FD 에만
    flock 해 read~replace 사이 concurrent append 가 유실. _mark_resolved
    append_to_review_queue 양측이 공용 <queue>.lock 을 LOCK_EX 로 잡아 직렬화
    (v3.4.0 의 "single-writer 가정" 한계 중 append-loss 경로 개선).

임베딩 서버 일시 장애 → 영구 누락 (HIGH·MEDIUM)

  • 세션 vec backfill sentinel: 서버 다운 중 backfill 이 영구 빈-blob sentinel 을
    박아 복구 후에도 세션이 semantic recall 에서 영구 제외. EmbedUnavailable
    '본문 없음'(영구 skip)과 '서버 장애'(재시도)를 분리.
  • 메모리 vec 재인덱싱 mtime: embed 실패 시에도 mtime 을 갱신해 다음 run 이 skip,
    메모리가 영구히 vec 없이 남음. embed 실패면 mtime/FTS/vec 미갱신 → 다음 run 재시도.
    (retry/backoff 는 embed_text 가 recall 핫패스 공용이라 미추가 — mtime-skip 이 안전한
    재시도 형태.)

데이터 유실 가드 (HIGH·MEDIUM·LOW)

  • dedup merge: Gemma 통합 실패 시에도 non-canonical 삭제(ok:true 위장). 통합
    성공한 파일만 삭제하고, 전부 실패/본문 빈 경우 overwrite·삭제 없이 중단.
  • dedup canonical overwrite: name/description/type 만 다시 써서 supersedes/
    deprecated_by audit link 소실. 나머지 frontmatter flow-style 보존 재방출.
  • dedup _backup: 기존 .bak 무조건 덮어쓰기 → 직전 복구본 소실. .bak 존재 시
    타임스탬프 회전.
  • extractor 부정 캐시: Gemma 호출 실패 빈 결과를 영구 캐시 → 복구 후 추출 영구
    스킵. 호출 실패(out is None)면 빈 결과 캐시 안 함. 정상 빈 응답("[]")은 캐시.
  • turns_cache wipe: append-only jsonl 의 일시 read 실패([] 반환)가 기존 turns 를
    삭제+mtime 갱신해 영구 소실. st_size>0 + 기존 turn 존재면 이번 회차 건너뛰고 재시도.

파싱 강건성 (MEDIUM·LOW)

  • staged frontmatter 줄바꿈: LLM 값(title/reason/evidence)의 줄바꿈/---
    frontmatter 구조를 깨거나 조기 종료. _fm_oneline 으로 단일 라인 정규화 (따옴표
    미사용 — naive 파서 호환). promote 경로(cmd_approve) 영구 메모리 write 에도 적용.
  • contradiction scalar-dup: _patch_frontmatter_list 가 scalar key: value
    flow/block 어느 정규식에도 못 잡아 중복 key append(YAML 손상). scalar 감지 후 거부.
  • extractor greedy JSON: re.search(r"\[[\s\S]*\]") 가 배열 밖 산문 대괄호까지
    삼켜 후보 폐기. 코드펜스 strip 후 직접 파싱 1순위 + balanced [...] 후보 선택.
  • alias frontmatter 따옴표: description: "x" 의 따옴표가 alias 프롬프트로 유입.
    양끝 짝 따옴표만 제거.

훅 동작 (MEDIUM·LOW)

  • 서브에이전트 SessionStart 게이팅: agent_type 미게이팅으로 모든 서브에이전트
    시작마다 동기 요약 생성이 블로킹. agent_type 있으면 즉시 종료 (메인 세션 무영향).
  • alias claude subprocess recursion guard: provider="claude" 시 자식 claude 의
    훅 재귀. subprocess env 에 MV3_HOOK_RECURSION_GUARD=1 주입.
  • SessionStart MISS 경로 staged 청소 누락: HIT 경로만 purge_staged_memory()
    호출하던 것을 양쪽 모두로.
  • turns_cache dead import: self_eval(flat-deploy 미배포) import 를 guarded 하여
    배포 환경에서 import-time 폭발 방지.
  • recall alias 후보 게이트 통과: alias fallback 후보(score=0)가 score_threshold
    게이트에서 탈락해 "임베딩 약한 메모리 alias 복구" 무력화. normalize 후 게이트 직상
    sentinel 부여 (raw_cosine 정렬이 최종 ranking 결정 — 순위 왜곡 없음).

Test count

533 (v3.4.0) → 599 (+66, 회귀 테스트 동반)

Migration

bash install.sh 재실행. 스키마 변경 없음. WAL 은 인덱서 첫 실행에서 index.db
1회 전환(이후 모든 conn 상속), -wal/-shm 사이드카 파일이 ~/.claude/mindvault-v3/
에 생긴다 (로컬 FS 라 무해).

Known limitations (현재 상태 — 미수정 항목과 사유)

  • recall score_threshold 게이트 vs 정렬 키 불일치 (recall-hot-path-1): 게이트는
    normalized score 기준인데 정렬은 raw_cosine 우선이라, score 는 낮지만 raw_cosine 이
    가장 강한 후보가 게이트에서 탈락할 수 있다. alias 후보 무력화(-5)는 이번에 sentinel
    로 고쳤으나, 일반 게이트 동작은 그대로 둔다SCORE_THRESHOLD=0.50 이 현재
    게이트 의미에 맞춰 hit rate 66.3% 로 튜닝된 값이라, 정렬·게이트 키를 통일하려면
    recall eval 로 FP rate 재측정이 선행돼야 한다 (사용자 결정으로 보류).
  • SIGALRM(ITIMER_REAL) 하드 타임아웃 (recall-hot-path-2/3): recall_memory 전체
    구간이 알람 무장 상태라, 드물게 pathlib/sqlite 비-재진입 구간을 인터럽트하면 객체가
    반파괴될 수 있다 (PosixPath '_str' 에러, 운영 1건). 단, 이 알람은 400ms wall-clock
    하드월의 유일한 강제 수단이고 thread/subprocess 격리 재설계는 핫패스 회귀 위험이
    과도해 그대로 둔다. 실패는 이미 graceful(FATAL 로그 후 빈 회수).
  • supersede _apply_supersede half-state (contradiction-halfstate-5): 두 파일
    순차 mutation 중 NEW write 가 OLD 성공 후 실패하면 OLD 만 deprecated 인 좁은 윈도우.
    재시도가 idempotent 로 수렴하며, 제안된 both-tmp 방식도 윈도우를 제거 못 하고 축소만
    하므로 그대로 둔다 (exit 2 + stderr 로 surfaced).
  • (v3.4.0 의 supersede audit-trace stale, backfill --memory-dir 범위 한계는 그대로.)

Memory ops

  • 신규 lock 파일: ~/.claude/mindvault-v3/session-indexer.lock,
    ~/.claude/mindvault-v3/contradictions.jsonl.lock (둘 다 flock advisory).

v3.4.0 — Contradiction Detection (Layer 5)

28 May 07:04

Choose a tag to compare

v3.4.0 — Contradiction Detection (Layer 5)

Summary

  • Memory Compiler 후속 단계로 contradiction_detector 자동 fire (session_memory_end.py:make_contradiction_aware_writer)
  • Hybrid recall (FTS5 + Arctic-ko-MLX RRF) top-5 → Gemma 4 E4B 4-way 분류 (metric_update / decision_reversal / fact_correction / no_conflict)
  • Review CLI (python -m src.contradiction_review_cli) 로 dismiss / supersede / update 결정
  • Layer 4 recall hook 이 deprecated_by 메모리의 raw_cosine + score 둘 다 × 0.3 감쇠 (primary sort key 가 raw_cosine 이므로 양쪽 감쇠 필요)

Inspiration

외부 CDSS MindVault fork 의 LLM-detected contradictions (fact-layer 4-type 중 detection 부분만 차용). 4-type 메모리 분류는 이번 범위 밖 (현재 detection 만 구현).

What's new

  • src/contradiction_detector.py — detection engine + jsonl queue
  • src/contradiction_review_cli.py — list / show / resolve apply
  • hooks/memory-recall.py + src/memory_search.py — Layer 4 deprecated_by decay
  • src/session_memory_end.pymake_contradiction_aware_writer hook integration
  • scripts/contradiction_backfill.py — one-shot pair-wise sweep for existing memories

Test count

470 (v3.3.0) → 533 (+63)

Migration

  • bash install.sh 재실행 (스키마 변경 없음)
  • 신규 메모리는 SessionEnd hook 자동 처리
  • 기존 메모리는 python scripts/contradiction_backfill.py --dry-run 으로 먼저 모집단 확인 후 본 sweep 권장

Limitations

  • Gemma cold 응답이 timeout 초과하면 silent skip (graceful fail). debug.log 에 telemetry
  • confidence < 0.7 false negative 허용 (silent abstention 정책). 보수적 retrieve 가 필요한 도메인 (e.g. CDSS 임상결정지원 fork) 은 threshold 낮춰야 함
  • _patch_frontmatter_list 는 block-style YAML list 검출 시 mutation refuse (debug.log 기록). 수동 변환 또는 flow-style 로 통일 후 재시도
  • review CLI 는 single-writer 가정: resolve --apply 의 jsonl rewrite (_mark_resolved) 와 frontmatter list mutation (_patch_frontmatter_list) 은 대상 파일에 lock 을 잡지 않는다. 동시에 두 resolve 를 실행하거나, resolve 도중 다른 세션의 SessionEnd 가 queue 에 append 하면 last-write-wins race 로 한쪽 변경이 소실될 수 있다 (드뭄 — 인터랙티브 단일 사용자 환경에서는 발생하지 않음). append 가 소실돼도 다음 세션에서 같은 메모리가 재staged 되면 재검출되므로 영구 손실은 아니다.
  • supersede audit-trace 가 staged stem 을 기록: supersede resolve 는 신규(staged) 파일의 stem (YYYYMMDD-HHMMSS_type_slug) 을 deprecated_by/supersedes 에 쓴다. promote 후 파일 stem 이 clean slug 로 바뀌므로 이 audit 링크는 더 이상 존재하지 않는 stem 을 가리킨다. decay 는 deprecated_by 키의 존재만 검사하므로 회수 down-weight 기능 자체는 정상 작동한다 — audit trace 만 부정확하다.
  • backfill --memory-dir <custom> 는 prod index DB 에 인덱싱된 디렉토리만 의미: recall_memory 가 production index DB 를 읽으므로, 인덱싱되지 않은 custom dir 을 주면 0건 검출된다 (스크립트가 stderr 경고 출력). default (prod memory dir) 사용 시 정상.

Memory ops

  • 새 jsonl path: ~/.claude/mindvault-v3/contradictions.jsonl (append-only)
  • Atomic rewrite: tmp + os.replace + fcntl.flock (single-user 가정)
  • Frontmatter mutation: tmp + os.replace per file

v3.3.0 — NEXT-37: 회수 메모리 활용률 측정 + Zep/Chain-of-Note hook contract

27 May 04:06

Choose a tag to compare

NEXT-37 — 회수 메모리 본질 결함 해결

[[recalled-memory-weight]] 결함: 회수된 메모리가 메인 Claude deliberation 에 통합 안 되는 v3 본질 약점. v4 (멀티에이전트 통합) 비전 진입 전 v3 장기기억 신뢰성 회복이 선결.

3-agent 자료조사 수렴 결론

회수 메모리를 LLM 답변에 강제 통합하는 유일한 검증된 메커니즘 = 답변에 활용 흔적을 explicit 출력하라는 contract (Self-RAG reflection token / Chain-of-Note 명시 노트 / Cursor positive instruction / Anthropic structured output 모두 같은 아이디어 수렴).

변경

Phase 1 — 측정 framework

  • 1A (hooks/memory-recall.py): _metric dict 에 recalled_ids 컬럼 추가 — 회수된 메모리 id (name 우선, path fallback) 박힘
  • 1B (src/self_eval.py): recall_utilization 메소드 신규 — 4-bucket 분류 (cited / marker_only / unused / no_response). CLI: python3 src/self_eval.py --recall-utilization --hours 168
  • 1B retroactive: --source transcripts flag — metrics.jsonl 없이 transcript jsonl 의 hook attachment.stdout 직접 파싱. Phase 1A 이전 데이터도 활용도 분석 가능

Phase 2 (Step 2) — Hook contract 변경

_format_output 변경:

옛:

<system-reminder>
# 메모리 회수 (Layer 4 hybrid)
- **name** (score 0.95, vec+fts) — desc
</system-reminder>

신규:

<system-reminder>
MEMORY CONTEXT (다음 fact 를 본 답변 reasoning 에 반드시 통합):

- [name] (score 0.95, vec+fts) — desc
  발췌: ...

답변 시작 전 한 줄로 "회수 노트: <위 메모리가 본 질문과 어떻게 관련되는가, 무관하면 '무관'>" 명시 출력 의무. 회수 fact 와 답변이 모순되면 즉시 표기.
</system-reminder>

3 메커니즘 결합:

  1. Zep "MEMORY CONTEXT:" 라벨 — production 검증 포맷
  2. Cursor positive instruction ("반드시 통합") — arxiv 2512.18925 수렴
  3. Chain-of-Note self-report ("회수 노트:") — EM +7.9 효과

Codex 5-round sweep (systematic-debugging "2 연속 0건 close")

  • Round 1 (9건): M1 (marker), M2 (substring length floor), M3 (TZ 명시), L1 (source sanitize), L2 (빈 results 가드), L3 (name ] escape), L4 (splitext multi-dot), L5 (header prefix)
  • Round 2 (5건 root-cause): A2/B4/B5 → _get_naive_tz lazy resolution + env-keyed cache, A7 (system-reminder wrapper 필수), B1 (events_source ValueError)
  • Round 3 (B3 scope 외): 3 test file env var leak — patch.dict(os.environ, {}, clear=False) 패턴
  • Round 4 (vacuous pass): integration test 의 if r.stdout.strip(): assertself.skipTest(...) 명시
  • Round 5: 자체 + codex 둘 다 0건 → close

현재 상태

  • pytest 470 passed + 25 subtests (회귀 0)
  • Production retroactive baseline (30일, N=213): cited 17 (7.62%), marker_only 2 (0.95%), unused 194 (91.43%)
  • Hook contract deploy timestamp: 2026-05-27T09:56 KST
  • 1주 dogfood 진행 중 — 정량 효과 측정은 누적 후 가능

호환성

  • Apple Silicon Mac 전용 (Intel/Linux/Windows 미지원, 로컬 LLM 은 Gemma 4 E4B 만)
  • Phase 1B retroactive 분석은 옛 (Layer 4 hybrid) + 신규 (MEMORY CONTEXT) 두 hook format 모두 backward compat
  • MV3_NAIVE_TZ env var 로 timezone override 가능 (default Asia/Seoul)

회수 결함 직접 측정

python3 src/self_eval.py --recall-utilization --source transcripts --hours 168

설치 후 1주 사용 → 위 명령으로 본인 환경 활용률 측정 가능.

v3.2.10 — /recall 결함 sweep (NEXT-36)

26 May 13:06

Choose a tag to compare

Summary

/recall 스킬 (memory_search.py + search.py + recall_cli.py) 에 대한 systematic-debugging 4-phase + codex 독립 검증 sweep. 2 fix + 4 false alarm confirm + 7 회귀 가드 + 1 cosmetic 정정. 2 연속 라운드 0 critical/medium 으로 수렴.

Critical Fix

memory_search._vec_top_k — mat ↔ meta 인덱스 미스매치

  • 증상: memories_vec 에 invalid embedding row (빈 bytes, dim mismatch) 1건만 있어도:
    • results = [(meta[i][0], ...) for ..., i in enumerate(idx_sorted)]IndexError 또는 잘못된 path 반환
    • raw_map 의 cosine 값을 잘못된 path 에 매핑 (cross-contamination, p2 cosine 0.775 → 0.0 게이트 차단, p3 cosine 0.884 → 0.775)
  • root cause: mat[i] = arr (row 인덱스) 와 meta.append(...) (valid 만) 의 인덱스 정합 깨짐. 같은 모듈의 search.vec_candidatesvalid 카운터 + mat[:valid] 안전 패턴이었으나 한 함수만 fix 누락
  • fix: valid 카운터 + mat[:valid] 패턴으로 교체. 두 모듈 안전 패턴 통일
  • silent 이유: 운영 DB invalid row 0건. 인덱서가 partial write 1회만 일으키면 recall_memory 의 outer except Exception 가드가 잡아 빈 결과 반환 → 사용자는 검색 0건으로만 보고 결함 모름

Medium Fix

_resolve_wikilink — ORDER BY path 추가

  • 증상: 다중 후보 시 SQLite 반환 순서 미보장 → 같은 wikilink 가 호출마다 다른 메모리로 resolve
  • fix: path LIKE ? 쿼리에 ORDER BY path 추가 (lexicographic 안정 결정성)

False Alarm 분류 (의도된 동작 confirm)

항목 분류
M1: 무의미 query ("1234") 5건 통과 /recall 명시 호출의 user-driven sift 정책 (score_threshold=0.0, raw_cosine_min=0.32) 일치
M3: gemma_rerank fallback 정렬 RRF rank 기준 첫 K 가 합리적, 게이트 후 절대-상대 분리 정책
L1: fts_escape 두 모듈 중복 8/8 sample 동등성 confirm. DRY 통합 비용 > 효용. skew 회귀 가드만 추가
L2: recall_cli.py top_k 옵션 없음 spec(recall.md) 의도, 명시 호출은 user-driven sift

회귀 가드 7건 (tests/test_memory_search.py)

  • TestVecTopKInvalidRowRegression × 4 (empty / bad-dim / all-invalid / all-valid backwards compat)
  • TestFtsEscapeParity × 1 (search.fts_escape vs memory_search._fts_escape 12 sample 동등성)
  • TestResolveWikilinkDeterminism × 2 (lex order + 20회 반복 안정성)

검증

  • pytest 422 → 429 passed (회귀 0)
  • 운영 본 ~/.claude/scripts/mindvault/memory_search.py md5 동기화
  • 실 query verify — 한국어/영문 query + sessions search + Gemma summary 모두 정상
  • Round 2 자체 sweep → 0 critical/medium
  • codex:codex-rescue 독립 검증 → 0 critical/medium (1 cosmetic 주석 정정만)
  • Round 3 자체 sweep (alias race / raw_cosine_map 사용 / import 순환) → 0 new
  • 2 연속 라운드 0건 close 수렴 기준 (feedback-systematic-debugging-code-review) 충족

Files changed

  • src/memory_search.py (+19/-4)
  • tests/test_memory_search.py (+170)

누적

결함 99건.