# 메모리·지식·페르소나 통합 아키텍처 설계서 > 한신대 AI 심리상담 시뮬레이션 훈련 플랫폼 · **단일 종합 설계서** > 작성 2026-06-25 · 트웬티온스 산학협력 > 종합 대상 4개 영역: ① 회기 간 연속성 메모리(최우선 갭) · ② 지식 데이터베이스(KB) · ③ 페르소나 일관성 · ④ 3-AI 메모리 뷰/정보비대칭/종단평가 > 상위 SoT: `HANSHIN_AI_PLATFORM_MASTERPLAN.md` + `MASTERPLAN_REVISIONS.md`(BLUE TEAM 패치). **본 문서는 두 상위 문서의 확정 전제를 깨지 않는 하위 통합 설계다.** --- ## 0. 확정 전제 (바꾸지 않음) + 설계 철학 ### 0.1 변경 불가 기술 전제 | 항목 | 확정값 | |---|---| | 엔진 | 로컬 `claude -p` 상주 멀티턴 풀(`--input-format stream-json`), Messages API는 가용성 폴백 | | RAG | BGE-M3(dense+sparse+ColBERT 단일모델) + pgvector(HNSW) + 하이브리드 + Anthropic Contextual Retrieval + BGE-reranker-v2-m3 | | DB | NAS PostgreSQL 16 + pgvector, RLS 이중강제 | | 백엔드 | FastAPI(상태머신·가드레일·엔진 어댑터 소유) | | 3-AI | 상담사AI(보조/선택) · 내담자AI · 백그라운드 평가AI | | 정보비대칭 | DB `visible_to[]` 컬럼이 강제(프롬프트 신뢰 X), 인간 RBAC×cohort와 2-레이어 AND | ### 0.2 통합 설계 철학 — 람다 자신의 메모리 시스템에서 차용한 3대 원리 이 플랫폼의 메모리·지식·페르소나는 **람다(이 AI)의 실제 운영 메모리 구조**를 1차 레퍼런스로 삼는다. 검증된 운영 패턴이라 추측이 아니다. | 람다 시스템 원리 | 통합 설계 적용 | |---|---| | **계층 압축** (daily 원본 → weekly 요약 → monthly 요약, 원본 archive 절대보존, 숫자 무손실) | 메모리 4계층(working/episodic/summary/semantic) + KB 3계층(청크/Contextual prefix/community 요약). 원본 append-only, 압축은 파생 테이블에만 | | **역할 분리** (rag-memory=BGE-M3 sqlite 시맨틱 ↔ graphify=GraphRAG 구조, "둘 다 유지, 충돌 X, 질문 유형에 맞게 선택") | 에피소드 메모리(내담자 일관성) ↔ 정적 지식 KB를 **물리적으로 다른 스키마·다른 검색정책**으로 분리. 충돌 시 우선순위 룰로 결정 | | **부트의 큰그림→세부 읽기** (monthly→weekly→daily, "본문 인용 필요할 때만 직접 Read") | 회기 시작 회상도 case_digest(큰그림)→직전 summary(중간)→episodic recall(세부) 순으로 토큰 예산 배분. 매 발화 전체 덤프 금지 | | **IDENTITY/SOUL 불변 + daily 가변** (정체성이 일지 압축에 오염 안 됨) | 페르소나 = 불변 카드(persona_card) + 가변 메모리(persona_session_memory)를 다른 테이블·다른 프롬프트 레이어·다른 권한으로 물리 분리 → drift 구조적 차단 | ### 0.3 4대 대원칙 (전 영역 관통) - **P1. 원본은 절대 삭제 안 한다** — `turns`는 append-only(람다 archive). "망각"은 물리삭제가 아니라 회상 우선순위 배제(salience 하위 → 검색 랭킹 하위 → system 미주입). - **P2. 숫자(상태 수치)는 무손실로 넘긴다** — working state(저항·라포·openness)는 LLM 요약이 아니라 **결정론 상태머신이 DB 컬럼으로** 직접 carry-over. LLM에 수치를 맡기면 hallucination. - **P3. 큰 그림 → 세부 순서로 회상** — 토큰 예산을 case→summary→episodic 순으로 배분. - **P4. 압축은 LLM, carry-over는 결정론** — 서사(narrative)는 LLM 압축(손실 허용), 상태 수치·pinned fact는 코드가 직접 복사(무손실). --- ## 1. 통합 메모리 계층도 (working/episodic/semantic/summary + 지식KB) ``` ═══════════════════════════════════════════════════════════════════════════════ 통합 메모리·지식 아키텍처 — 5개 저장 계층 (메모리 4 + 지식 1), 물리 분리 ═══════════════════════════════════════════════════════════════════════════════ 계층 수명 저장소(스키마.테이블) 채우는 주체 람다 대응물 ───────────────────────────────────────────────────────────────────────────────── ┌──── 에피소드 메모리 (내담자 연속성) ── app 스키마 ────────────────────────────┐ │① WORKING 회기 내 app.session_state 상태머신(결정론) L3+L4 │ │ 회기내상태 (체크포인트) +단기버퍼(JSONB) 매 턴 UPSERT 상태머신 │ │ │ │② EPISODIC 영구 app.turns + 발화 로깅(원자) daily │ │ 회기별사건 (append-only) app.turn_embedding(HNSW) 회기 중 임베딩 (원본) │ │ │ │③ SUMMARY 영구 app.session_summary 회기종료 압축(LLM) weekly │ │ 회기종료요약 (+digest 임베딩) 회기당 1행 │ │ │ │④ SEMANTIC 영구(evolving) app.case_profile 케이스누적 압축 monthly │ │ 누적임상프로파일 + app.pinned_fact 회기마다 갱신/병합 +MEMORY │ └───────────────────────────────────────────────────────────────────────────────┘ ┌──── 정적 지식 KB (DSM/이론/taxonomy) ── kb 스키마 (독립, FK 없이 read-only) ──┐ │⑤ KNOWLEDGE 영구(버전드) kb.chunk(멀티벡터 HNSW) 오프라인 인덱싱 graphify │ │ 정적임상지식 +kb.document +kb.source (배치, 런타임 아님) references│ │ 3계층: 원본청크 → Contextual prefix → community 요약(예산초과 fallback) │ └───────────────────────────────────────────────────────────────────────────────┘ ───────────────────────────────────────────────────────────────────────────────── 데이터 흐름: 회기종료: ②turns ──압축(LLM)──► ③session_summary ──병합(LLM+rule)──► ④case_profile + pinned_fact(rule) 회기시작: ④case_profile+pinned ──┐ ③직전 summary ──┼─로드(큰그림→세부)──► 내담자AI system L2-EP ②episodic recall(top-k)─┘ (BGE-M3 하이브리드+reranker, ~1.5~2K토큰) ⑤KB 청크 ───────────────────로드(정책별 필터)──► system L2-KB (캐시친화) 모순 시 우선순위: pinned(L4) > 에피소드(이미 한 말) > 상태머신수치 > KB > LLM사전지식 ("KB는 사전이지 판사가 아니다" — 내담자는 에피소드를 따르고 KB는 보정용) ═══════════════════════════════════════════════════════════════════════════════ ``` ### 1.1 계층별 역할 한 줄 정의 - **① WORKING**: 상태머신 결정론 수치(stage·effective_openness·rapport_credit·resistance·ideation_stage) + 최근 K턴 버퍼. 매 턴 체크포인트 UPSERT(프로세스 죽어도 복원). - **② EPISODIC**: 발화=최소 원자단위, 저장 시 BGE-M3 임베딩 → 회기 시작 회상의 검색 대상. append-only(재귀학습·audit 원천). - **③ SUMMARY**: 회기당 1행. 구조화 수치 snapshot(무손실) + narrative(LLM 압축) 분리. 다음 회기 carry-over 1차 단위. - **④ SEMANTIC**: 케이스 전체 궤적 누적 압축(CCD 추정·호소문제·라포궤적·치료동맹). pinned_fact=절대 흘리면 안 되는 핵심(모순 처리 기준점). - **⑤ KNOWLEDGE**: DSM/이론/taxonomy/논평. 버전드, 거의 불변. 3-AI가 같은 물리 테이블을 다른 정책으로 검색. --- ## 2. 회기 라이프사이클 시퀀스 ``` ═══════════════════════════════════════════════════════════════════════════════ 회기 라이프사이클: 회기시작 회상 → 진행(턴 사이클) → 회기종료 압축 → 다음 회기 ═══════════════════════════════════════════════════════════════════════════════ 【A. 회기 시작 — 회상 + 상태 복원】 (람다 부트 monthly→weekly→daily 차용) ──────────────────────────────────────────────────────────────────────────── 학습자가 P1 내담자와 N번째 회기 시작 │ ├─1. case_profile + pinned_fact 로드 (큰 그림, ~400+300토큰) [P3] │ load_pinned_facts(case_id, status IN stable/evolving/locked) │ ├─2. 직전 session_summary 로드 (중간, ~500토큰) │ {digest, open_threads, homework, end_state} │ ├─3. episodic recall (세부, top-5, ~600토큰) │ query = open_threads + homework (+학습자 첫 발화) │ → hybrid_search(case_id 스코프, dense+sparse RRF, top-50) │ → BGE-reranker-v2-m3 → top-5 │ ├─4. 상태 복원 (carry-over, 결정론) [P2] │ init_session_state_from_history(): │ stage = '라포' (매 회기 라포부터 재시작 — 상담 구조) │ rapport_credit = prev.rapport_credit × 0.7 (주1회 간격 일부 이월) │ resistance = inter_session_drift(prev, alliance) │ ideation_stage = prev (보수적 유지 — 안전 R5) │ ├─5. KB 정적 청크 로드 (페르소나 증상 단서, 정책 필터) │ visible_to='client' AND sensitivity<=1, 본문 비노출(행동단서만) │ └─6. 상주 claude -p 프로세스 점유 + system 주입 (불변, cache_control) L0 가드레일 + L1 페르소나카드(불변) + L2-EP 회상 + L2-KB 지식 → session_state.recall_context에 캐시 (회기 내 재검색 0) 【B. 회기 진행 — 턴 사이클】 (마스터플랜 §2.2, 매 발화) ──────────────────────────────────────────────────────────────────────────── 학습자 발화 U_t │ ├─ [입력 가드레일] Presidio PII 마스킹 + 위기분류(실제위기 vs 연기 분기) ├─ [라우터] route_memory_vs_knowledge(turn, ai_role) [람다 "필요할때만 Read"] │ needs_episodic = 지시어("아까/저번에") OR role=client OR 단계전이 │ needs_knowledge = role=evaluator OR 임상용어 OR role=counselor ├─ [상태머신] effective_openness(t) 계산 (결정론, LLM 아님) [P2] │ = clamp(stage.openness + rapport_credit×unlock_rate - resistance×decay) ├─ [모순 검사] handle_potential_contradiction(new_turn, pinned) │ locked 사실모순 → 차단·재생성 / clinical 변화 → 수용·갱신+이력 ├─ [내담자 AI] stdin JSON 주입 → 페르소나 응답 (CCD 직접노출 금지) │ L3 상태수치 + L4 fact hard-pin + L5 episodic recall + L6 최근N턴 ├─ [출력 가드레일] 자살수단 차단, ideation≤3, drift 감지(임베딩 일관성≥0.79) ├─ [평가 AI 2-tier] │ fast-loop(로컬/Haiku): U_t만 4차원 태깅 (실시간) │ deep-loop(Opus 상주): 단계전환·회기말 0-5 채점 + 대안발화 ├─ [working 갱신] session_state UPSERT (체크포인트, 단기버퍼 append) └─ [로깅] turns → Postgres + 임베딩 (재귀학습 JSONL 원천) ↑ K턴 반복 (recall_context는 회기 내 재사용, 재검색 안 함) 【C. 회기 종료 — 압축】 (람다 daily→weekly 컴프레션, 비동기 비블로킹) ──────────────────────────────────────────────────────────────────────────── stage='정리' 완료 OR 명시 종료 │ ├─(A) 무손실 carry-over: end_state = working 종료 snapshot (코드 복사, LLM 미경유) [P4] ├─(B) salience 산출 → 망각/유지 결정 │ compute_salience(t): pinned/위기/최초개방/정서급변 ↑, 잡담 ↓ │ salience ≥ 0.5 → episodic 영구유지(검색 우선), 미만 → digest 흡수 ├─(C) narrative 압축(LLM, 상주 claude -p 재사용): digest 6~10문장 + open_threads │ pinned 사실은 프롬프트 hard-pin → digest 보존 강제 │ 입력 = turns.text_masked만 (F-03 마스킹 강제) ├─(D) digest 임베딩 + session_summary UPSERT + episodic 임베딩 백필 ├─(E) case_profile 병합: rapport_trajectory append, CCD 추정 갱신, │ case_digest 재압축(N회기마다 or major shift), alliance EWMA ├─(F) pinned_fact 모순/갱신 처리 + pinned_fact_history 이력화 └─(G) RAG 동기화 (람다 storeDocument→embed): 다음 회기 회상 대비 │ ├─ 상주 프로세스 회수 (누적 컨텍스트 리셋 → 말투표류 D-c 회기경계서 차단) └─ 종단 평가: learner_profile EWMA(현재수준)+slope(성장속도) 갱신 【D. 다음 회기】 → 【A】로 복귀 (case_profile·summary·pinned 누적된 상태로) 토큰 ~4K 고정 (100회기여도 선형증가 X) ═══════════════════════════════════════════════════════════════════════════════ ``` --- ## 3. 통합 Postgres 스키마 (메모리·지식·페르소나·평가, DDL 수준) > 스키마 4분할: `app`(운영·메모리·페르소나) / `kb`(정적 지식, 독립) / `audit`(append-only 감사) / `ds`(재귀학습). **Phase 0에 전부 선반영(nullable), cross-session·KB 채우기는 Phase 2a**(MASTERPLAN_REVISIONS §3.5 재마이그레이션 폭탄 방지 원칙). ### 3.1 페르소나 — 불변 카드 (app.persona_card) ```sql -- ════ 불변 정체성 (람다 IDENTITY/SOUL, 버전드, 교수만 편집) ════ CREATE TABLE app.persona_card ( persona_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code TEXT NOT NULL, -- 'P1','P2','P3' version INT NOT NULL DEFAULT 1, -- 편집 시 +1 (진행 세션은 옛 version 핀) status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','review','approved','archived')), display_name TEXT NOT NULL, difficulty TEXT NOT NULL CHECK (difficulty IN ('easy','moderate','hard')), theory_target TEXT[], -- {'humanistic','cbt'} demographics JSONB NOT NULL, -- 범주화(재식별 방지 F-25): age_band,sex,grade... presenting JSONB NOT NULL, -- 표층 호소(내담자가 입으로 말함) history JSONB NOT NULL, -- 과거사 big5 JSONB NOT NULL, -- {O,C,E,A,N} 0~1 (말투·반응 수학적 앵커) resistance JSONB NOT NULL, -- {base_resistance,unlock_rate,decay_floor,silence_prob,deflection_prob} speech_style JSONB NOT NULL, -- {register,avg_sentence_len,fillers,honorific,verbal_tics} affect_baseline JSONB NOT NULL, -- {negative_affect,hopelessness,anhedonia,sleep,anxiety,suicide_ideation_stage} hard상한=3 ccd JSONB NOT NULL, -- Patient-Ψ 8요소 (★직접발화 금지 R4, 행동으로만) dsm5_dimensional JSONB NOT NULL, -- criteria_behavior_matrix (진단명 비노출, 내부 엄밀성 F-22) source_provenance TEXT NOT NULL, -- '0615 합성변형' (원문 미적재 F-05) is_synthetic BOOL NOT NULL DEFAULT TRUE, created_by UUID, approved_by UUID, approved_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (code, version) ); CREATE INDEX idx_persona_card_status ON app.persona_card(status) WHERE status='approved'; -- 음성 매핑 (provider-agnostic, 카드 버전 정합) CREATE TABLE app.persona_voice_map ( persona_id UUID NOT NULL REFERENCES app.persona_card(persona_id) ON DELETE CASCADE, version INT NOT NULL, voice_id TEXT NOT NULL, -- 'voice_p1_teen_m' provider TEXT NOT NULL CHECK (provider IN ('openai','higgs','melotts')), base_params JSONB NOT NULL, prosody_map JSONB NOT NULL, -- 상태(stage/affect/resistance)→prosody 함수 PRIMARY KEY (persona_id, version) ); -- 상담사 AI 페르소나 (데모/self-play용, 이론 충실도 일관) CREATE TABLE app.counselor_profile ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code TEXT, theory TEXT CHECK (theory IN ('humanistic','cbt','integrative')), skill_level TEXT, reasoning_chain JSONB, -- CoE: PCT/CBT 추론체인 allowed_microskills TEXT[], speech_style JSONB ); ``` ### 3.2 메모리 ① WORKING (app.session_state, 체크포인트) ```sql CREATE TABLE app.session_state ( session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE, stage TEXT NOT NULL DEFAULT '라포' CHECK (stage IN ('라포','탐색','개입','정리')), turn_seq INT NOT NULL DEFAULT 0, effective_openness REAL NOT NULL DEFAULT 0.15 CHECK (effective_openness BETWEEN 0 AND 1), rapport_credit REAL NOT NULL DEFAULT 0.0, -- 회기말 carry 대상 (0.7 이월) resistance REAL NOT NULL DEFAULT 0.65 CHECK (resistance BETWEEN 0 AND 1), ideation_stage SMALLINT NOT NULL DEFAULT 1 CHECK (ideation_stage BETWEEN 1 AND 5), affect_state JSONB NOT NULL DEFAULT '{}', short_buffer JSONB NOT NULL DEFAULT '[]', -- 최근 K턴 rolling window recall_context JSONB, -- 회기시작 1회 로드, 회기내 재사용 캐시 updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); ``` ### 3.3 메모리 ② EPISODIC (app.turns 확장 + app.turn_embedding) ```sql -- 마스터플랜 §3.2 turns에 연속성 컬럼 추가 ALTER TABLE app.turns ADD COLUMN IF NOT EXISTS salience REAL DEFAULT 0.0, -- 망각/우선순위 ADD COLUMN IF NOT EXISTS is_pinned BOOL DEFAULT FALSE, -- pinned_fact 승격 발화 ADD COLUMN IF NOT EXISTS contradicts UUID[] DEFAULT '{}', -- 모순추적: 뒤집은 과거 turn_id[] -- 음성 paralinguistic (MASTERPLAN_REVISIONS §3.5 선반영) ADD COLUMN IF NOT EXISTS audio_ref TEXT, ADD COLUMN IF NOT EXISTS silence_ms INT, ADD COLUMN IF NOT EXISTS speech_rate REAL, ADD COLUMN IF NOT EXISTS barge_in BOOL; CREATE TABLE app.turn_embedding ( turn_id UUID PRIMARY KEY REFERENCES app.turns(id) ON DELETE CASCADE, case_id UUID NOT NULL, -- denormalized: 케이스 스코프 회상 session_id UUID NOT NULL, seq INT NOT NULL, dense vector(1024) NOT NULL, -- BGE-M3 dense sparse JSONB, -- BGE-M3 sparse {token_id:weight} context_prefix TEXT, -- Contextual Retrieval 1문장 created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_turn_emb_hnsw ON app.turn_embedding USING hnsw (dense vector_cosine_ops) WITH (m=16, ef_construction=64); CREATE INDEX idx_turn_emb_case ON app.turn_embedding (case_id, session_id); ``` ### 3.4 메모리 ③ SUMMARY (app.session_summary) ```sql CREATE TABLE app.session_summary ( session_id UUID PRIMARY KEY REFERENCES app.sessions(id) ON DELETE CASCADE, case_id UUID NOT NULL, session_no INT NOT NULL, -- (A) 무손실 carry-over (상태 수치 snapshot, LLM 미경유) [P2] end_state JSONB NOT NULL, -- {stage,openness,rapport_credit,resistance,ideation_stage,affect} rapport_delta REAL, -- (B) narrative 압축 (LLM, 손실 허용) digest TEXT NOT NULL, -- 6~10문장, 다음 회기 system 주입 open_threads JSONB NOT NULL DEFAULT '[]', -- 미해결 [{topic,last_stance,raised_session_no}] homework JSONB, emotional_arc TEXT, -- (C) 검색용 digest_embedding vector(1024), token_count INT, compressed_by TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (case_id, session_no) ); CREATE INDEX idx_sess_sum_case ON app.session_summary (case_id, session_no DESC); CREATE INDEX idx_sess_sum_emb ON app.session_summary USING hnsw (digest_embedding vector_cosine_ops) WITH (m=16, ef_construction=64); ``` ### 3.5 메모리 ④ SEMANTIC (app.case_profile + app.pinned_fact) ```sql -- 케이스 = 한 가상내담자의 다회기 전체 (persona_id=템플릿, case=학습자별 인스턴스) CREATE TABLE app.case_profile ( case_id UUID PRIMARY KEY, persona_id TEXT NOT NULL, -- 시드 페르소나 learner_id UUID NOT NULL, -- ★ 학습자별 독립 연속체 last_session_no INT NOT NULL DEFAULT 0, ccd_estimate JSONB NOT NULL DEFAULT '{}', -- CCD 8요소 현재추정 +confidence presenting_arc JSONB NOT NULL DEFAULT '[]', -- 호소문제 궤적 rapport_trajectory JSONB NOT NULL DEFAULT '[]', -- [{session_no,end_rapport,end_openness}] alliance_level REAL DEFAULT 0.2, -- 치료동맹 누적 case_digest TEXT NOT NULL DEFAULT '', -- 전체 궤적 8~12문장 (큰그림, 람다 monthly) digest_embedding vector(1024), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- pinned facts (람다 MEMORY.md, 절대 흘리면 안 되는 핵심, 모순 기준점) CREATE TABLE app.pinned_fact ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), case_id UUID NOT NULL REFERENCES app.case_profile(case_id) ON DELETE CASCADE, key TEXT NOT NULL, -- 'name','family','suicide_attempt_history' value TEXT NOT NULL, -- 가명처리/마스킹된 fact_type TEXT NOT NULL CHECK (fact_type IN ('identity','history','relationship','clinical','agreement')), status TEXT NOT NULL DEFAULT 'stable' CHECK (status IN ('stable','evolving','contradicted','locked')), source_turn UUID, confidence REAL NOT NULL DEFAULT 1.0, version INT NOT NULL DEFAULT 1, updated_session_no INT, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (case_id, key) ); CREATE INDEX idx_pinned_case ON app.pinned_fact (case_id, status); -- pinned 변경 이력 (append-only, 모순/갱신 audit + 평가AI 정답신호) CREATE TABLE app.pinned_fact_history ( id BIGSERIAL PRIMARY KEY, fact_id UUID NOT NULL, case_id UUID NOT NULL, old_value TEXT, new_value TEXT, reason TEXT, -- 'contradiction','clarification','progression' session_no INT, turn_id UUID, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); ``` ### 3.6 지식 ⑤ KB (kb 스키마, 독립) ```sql CREATE SCHEMA kb; CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS pg_trgm; -- 출처 + 라이선스 (DSM-5 저작권 1차 통제) CREATE TABLE kb.source ( source_id TEXT PRIMARY KEY, -- 'dsm5','theory','taxonomy_0615' title TEXT NOT NULL, kb_kind TEXT NOT NULL CHECK (kb_kind IN ('diagnostic','theory','technique','taxonomy','supervisor_pattern','template','ko_context')), license_class CHAR(1) NOT NULL CHECK (license_class IN ('A','B','C','D')), -- C=저작권민감 D=미성년파생 origin_path TEXT, citation TEXT, external_llm_ok BOOLEAN NOT NULL DEFAULT false, -- C/D면 false → 국내 라우팅 created_at TIMESTAMPTZ DEFAULT now() ); -- 문서 + 버전 (graphify content_hash 증분 인덱싱 차용) CREATE TABLE kb.document ( doc_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, source_id TEXT NOT NULL REFERENCES kb.source(source_id), doc_uri TEXT NOT NULL, version INT NOT NULL DEFAULT 1, content_hash TEXT NOT NULL, -- SHA256 → 변경감지(증분) superseded_by BIGINT REFERENCES kb.document(doc_id), is_active BOOLEAN NOT NULL DEFAULT true, indexed_at TIMESTAMPTZ, UNIQUE (source_id, doc_uri, version) ); -- 청크 + 멀티벡터 (검색 본체) CREATE TABLE kb.chunk ( chunk_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, doc_id BIGINT NOT NULL REFERENCES kb.document(doc_id) ON DELETE CASCADE, source_id TEXT NOT NULL, kb_kind TEXT NOT NULL, -- 비정규화(필터 가속) seq INT NOT NULL, heading_path TEXT, chunk_text TEXT NOT NULL, -- 표시·LLM 주입(prefix 미포함) context_prefix TEXT, -- Contextual Retrieval 주입문 embedding vector(1024), -- BGE-M3 dense sparse_vec JSONB, colbert_vecs JSONB, -- 멀티벡터(단일 호출 산출) visible_to TEXT[] NOT NULL DEFAULT '{client,counselor,evaluator}', -- 정보비대칭 sensitivity SMALLINT NOT NULL DEFAULT 0, -- 0공개 1비공개내부 2평가전용 3원천격리 label_id BIGINT, -- taxonomy면 technique_label_def FK meta JSONB DEFAULT '{}', -- {dsm_category,theory,bias_weight...} token_count INT, created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX idx_chunk_hnsw ON kb.chunk USING hnsw (embedding vector_cosine_ops) WITH (m=16, ef_construction=64); CREATE INDEX idx_chunk_fts ON kb.chunk USING gin (to_tsvector('simple', chunk_text)); CREATE INDEX idx_chunk_trgm ON kb.chunk USING gin (chunk_text gin_trgm_ops); CREATE INDEX idx_chunk_visible ON kb.chunk USING gin (visible_to); CREATE INDEX idx_chunk_route ON kb.chunk (kb_kind, sensitivity); ``` ### 3.7 평가 + 종단 추적 + 드리프트 감사 (app + audit) ```sql -- 발화별 평가 (마스터플랜 §3.2, visible_to로 학습자 비노출) CREATE TABLE app.feedback_scores ( turn_id UUID NOT NULL, dimension TEXT NOT NULL, -- 공감/개방질문/검증/이론부합 score REAL, -- 0-5 (CTRS 0-6) rationale TEXT, top1_score REAL, -- CRAG 게이트(임계미달→관찰프레이밍 F-06) visible_to TEXT[] NOT NULL DEFAULT '{evaluator}', PRIMARY KEY (turn_id, dimension) ); -- 종단 학습자 프로파일 (회기 거듭하며 나아지는지 — 윤찬 직접 답) CREATE TABLE app.learner_profile ( learner_id UUID PRIMARY KEY, -- 4차원 EWMA(현재수준) + slope(성장속도) 동시 dim_ewma JSONB NOT NULL DEFAULT '{}', -- {empathy:0.6, open_q:0.5, validation:..., theory:...} dim_slope JSONB NOT NULL DEFAULT '{}', -- 성장속도 (잘하는데정체 vs 못하지만급성장 구분) persistent_gaps JSONB DEFAULT '[]', -- deep-loop만 맥락화 코칭 (fast-loop 절대 미주입) session_count INT DEFAULT 0, updated_at TIMESTAMPTZ DEFAULT now() ); -- 페르소나 드리프트 감사 (KPI 임베딩일관성≥0.79 측정) CREATE TABLE audit.persona_drift_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), session_id UUID NOT NULL, turn_id UUID, persona_id UUID NOT NULL, drift_type TEXT NOT NULL, -- 'big5_violation'|'ccd_leak'|'fact_contradiction'|'style_drift' embedding_sim REAL, detail JSONB, severity TEXT CHECK (severity IN ('warn','block')), created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- KB 검색 로그 (환각측정·캐시검증, 람다 cost.json 차용) CREATE TABLE kb.retrieval_log ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, session_id UUID, turn_id UUID, ai_role TEXT NOT NULL, query_text TEXT, policy TEXT NOT NULL, hit_chunk_ids BIGINT[], rerank_scores REAL[], top1_score REAL, used_in_answer BOOLEAN, latency_ms INT, created_at TIMESTAMPTZ DEFAULT now() ); ``` > **핵심 설계 결정 — `(persona_id, learner_id)` 복합키**: 한 페르소나(P1)를 20명이 각자 연습한다. 같은 카드(불변) + 다른 연속체(가변). `case_profile.learner_id`가 "이 학습자에게 이 내담자가 겪은 회기 히스토리"를 정의 → 학습자 간 기억 오염 차단. --- ## 4. 3-AI 정보비대칭이 메모리·KB 레이어에서 강제되는 방식 ### 4.1 핵심 원리 — 위험한 건 turn 표면이 아니라 파생 메모리 turn 자체는 대화 표면이라 덜 위험하다. **위험한 건 CCD·정답라벨·점수·종단요약 같은 파생 메모리.** 여기에만 `visible_to[]`를 달고, **컨텍스트 조립기가 역할별로 물리적으로 다른 쿼리 분기**를 탄다. ``` 2-레이어 강제 (MASTERPLAN_REVISIONS §5.1 F-30): ┌─ 레이어1: AI 정보비대칭 (current_ai_view enum) ── visible_to[] WHERE 강제 │ CLIENT_AI 분기엔 load_ccd()·load_evaluator_answer() 함수 자체가 없음 │ → "필터 누락"이 아니라 "코드 경로 부재"가 1차 방어 └─ 레이어2: 인간 RBAC×cohort (current_role) ── RLS DB레벨 방어선 인간이 turn 읽을 때 두 게이트 AND (단일 미들웨어 + RLS 이중강제) ``` ### 4.2 메모리 레이어 — 3-AI 뷰 격리 매트릭스 | 메모리/KB 항목 | ② 내담자 AI | ① 상담사 AI(보조) | ③ 평가 AI | |---|---|---|---| | ① working(상태수치) | 자기 stage·openness만 (수치 주입, 메타노출 X) | 표면 대화만 | 전체 (저항엔진 검증) | | ② episodic(자기 발화) | 자기 케이스 recall만 (case 스코프 강제) | 회상 없음(표면만) | 전체 (정답 대조) | | ③ session_summary | digest·pinned는 "자기기억"으로만 표현 | 미노출 | 전체 + open_threads | | ④ case_profile.ccd_estimate | **절대 비노출** (R4 메타노출 금지) | **절대 비노출** | 전체 (정답 CCD) | | ④ pinned_fact(clinical) | 자기 사실로만 | 미노출 | 전체 (변화 정답신호) | | ⑤ KB diagnostic(DSM) | 내부 파라미터 (본문 비노출, 행동단서만) | **차단** | 전체 | | ⑤ KB taxonomy 정답라벨 | 차단 (sensitivity≥2) | 차단 | 전체 (채점 기준) | | learner_profile | 차단 | 차단 | deep-loop만 (fast 미주입, 사전편향 차단) | ### 4.3 KB 레이어 — 같은 물리 테이블, 다른 retrieval 정책 4-튜플 검색 정책 = **사전필터(WHERE) + 회수 가중치 + 리랭킹 목표 + 주입 방식**. | 차원 | ② 내담자 AI | ① 상담사 AI | ③ 평가 AI | |---|---|---|---| | 사전필터 | `kb_kind IN (diagnostic,theory,technique)` `AND 'client'=ANY(visible_to)` `AND sensitivity<=1` | `kb_kind IN (theory,technique,microskill,ko_context)` `AND 'counselor'=ANY(visible_to)` `AND sensitivity=0` | `'evaluator'=ANY(visible_to)` `AND sensitivity<=2` (taxonomy·supervisor 정답 포함) | | 회수 가중치 | dense 0.7 / sparse 0.3 (증상 의미) | dense 0.5 / sparse 0.5 (기법명) | sparse 0.6 / dense 0.4 (라벨명·논평) | | 주입 방식 | 본문 비노출, "행동단서 요약"만 | 본문+예시 | 본문+label_id+bias_weight | | 절대 차단 | sensitivity≥2, 타 페르소나 CCD | DSM diagnostic, 평가정답 | (전부 봄, 학습자 비노출은 응답 가공단) | ```sql -- 하이브리드 검색 SQL 계약 (정책별 파라미터화, visible_to DB 강제) WITH dense AS ( SELECT chunk_id, 1-(embedding <=> :q_dense) AS s_dense FROM kb.chunk WHERE kb_kind=ANY(:kinds) AND :role=ANY(visible_to) AND sensitivity<=:sens_max ORDER BY embedding <=> :q_dense LIMIT 50), sparse AS ( SELECT chunk_id, ts_rank_cd(to_tsvector('simple',chunk_text),:q_terms) AS s_sparse FROM kb.chunk WHERE kb_kind=ANY(:kinds) AND :role=ANY(visible_to) AND sensitivity<=:sens_max AND to_tsvector('simple',chunk_text) @@ :q_terms LIMIT 50) SELECT c.chunk_id, c.chunk_text, c.context_prefix, c.label_id, COALESCE(d.s_dense,0)*:w_dense + COALESCE(s.s_sparse,0)*:w_sparse AS fused FROM kb.chunk c LEFT JOIN dense d USING(chunk_id) LEFT JOIN sparse s USING(chunk_id) WHERE d.chunk_id IS NOT NULL OR s.chunk_id IS NOT NULL ORDER BY fused DESC LIMIT :k; -- → BGE-reranker-v2-m3 top-5 ``` ### 4.4 누수 방지 자동 테스트 (F-17, 람다 직접검증) - **T1 RLS 부정테스트**: 타 AI뷰 SET → 0행 반환 확인 - **T2 조립기 화이트리스트**: CLIENT_AI 분기에 ccd/정답 로드 함수 부재 정적검증 - **T3 CCD-메타 누설**: "제 핵심신념은…" 정규식 게이트 + 분류기 - **T4 cross-trainee 누수**: 학습자A의 case가 B에 새지 않는지 (case_id 스코프) - **람다 직접**: IDOR 403 스크린샷, ai_view 위조 요청 차단 검증 --- ## 5. 기존 마스터플랜 §3.6과의 정합/대체점 | 마스터플랜 §3.6 항목 | 본 설계 | 정합 / 대체 | |---|---|---| | 페르소나 메모리=하이브리드(상태수치+rolling summary+episodic 벡터) | 4계층(working/episodic/summary/semantic)으로 **구체화** | **정합·확장** (§3.6 추상을 테이블·압축 알고리즘으로 실체화) | | 피드백 근거=하이브리드+리랭킹(+CRAG) | KB ③평가 경로 = 하이브리드+Contextual+reranker, top1_score 게이트 | **정합** (CRAG를 ③경로에만 우선 도입) | | 지식=정적 KB+Contextual Retrieval | kb 스키마 + 3계층(청크/prefix/community) + license_class | **정합·확장** (DSM 저작권 통제 추가) | | 임베딩=BGE-M3 단일모델 멀티벡터 | episodic·summary·KB 전부 BGE-M3 | **정합** (동일 모델 재사용) | | pgvector(HNSW) | 모든 벡터 테이블 HNSW | **정합** | | 하이브리드 dense+sparse top-50 | episodic·KB 동일 RRF | **정합** | | GraphRAG=2차(이론 교차개념) | 본 설계도 graphify 2차 (theory KB 커질 때만) | **정합** | | (§3.6 미언급) 회기 간 연속성 | **본 설계가 신규 추가** (summary/case_profile/recall/모순처리) | **대체점=신규 보강** (§3.6은 단일회기 메모리만, 본 설계가 cross-session 채움) | | (§3.6 미언급) rag_chunks vs episodic 분리 | 에피소드(app)↔정적KB(kb) **물리 스키마 분리** | **대체점=명확화** (§3.6 rag_chunks를 KB 전용으로 좁히고 메모리는 별도) | > **핵심 대체점**: 마스터플랜 §3.6은 "페르소나 메모리=하이브리드 메모리"라고만 했지 **회기 간 연속성(cross-session)을 다루지 않았다.** 본 설계가 summary→case_profile 계층 압축 + 회기 시작 회상 + 모순 처리를 신규로 채운다. §3.6의 RAG 스택 자체는 하나도 바꾸지 않는다(BGE-M3/pgvector/하이브리드/Contextual/reranker 그대로). --- ## 6. 20주·n=1 제약 하 MVP 메모리 범위 ### 6.1 Phase 매핑 (단일 로드맵 정합, 산발 task 금지) | 항목 | Phase | 소유(REVISIONS §6.1) | DoD 검증(람다 직접) | |---|---|---|---| | **스키마 전체 선반영** (§3 전부, nullable) | **Phase 0** | 람다 직접(인프라) | 마이그레이션 통과 + nullable 확인 | | 페르소나 카드 4테이블 + 음성맵 | Phase 0 | 람다 직접 | DDL 적용 | | ① working + 체크포인트 | Phase 1 | A2(상태머신) | 프로세스 kill 후 상태 복원 재현 | | ② episodic + 임베딩 | Phase 1 | A1(엔진)+데이터 | 발화 저장 시 임베딩 적재 DB쿼리 | | ⑤ KB (하이브리드+Contextual+reranker) | Phase 1 | A1+데이터 | ②/③ 다른 청크 회수 SQL 대조 | | ③ 회기종료 압축 | **Phase 2a** | A2+데이터 | 회기 완주→summary 생성, 토큰예산 실측 | | ④ case_profile+pinned+모순처리 | **Phase 2a** | A2+데이터 | 2회기→carry-over+모순처리 재현 | | 회기시작 회상 | **Phase 2a** | A1+B(UI카드) | N회기째 "지난번~" 정확 회상 + 정답대조 | | 종단 learner_profile EWMA+slope | Phase 2a~3 | D(데이터) | 다회기 성장곡선 대시보드 | ### 6.2 MVP 메모리 범위 결정 (정직한 단계화) - **Phase 1 = 단일 회기 메모리만**: working(상태머신 carry-over 체크포인트) + episodic(발화 임베딩) + KB(정적 지식). **cross-session 없음.** 0615를 1회기 episodic 시드로. - **Phase 2a = cross-session 추가**: summary 압축 + case_profile 누적 + 회기 시작 회상 + 모순 처리. 다회기 진화는 데이터가 아니라 **결정론 상태머신 + 합성 시나리오**로 보장. - **스키마는 Phase 0에 전부 선반영** (재마이그레이션 폭탄 방지). ### 6.3 n=1 정직 표기 (절대 "검증됨" 보류) - **0615은 사실상 단일 회기 사례** → 다회기 연속성 학습 코퍼스 없음. 압축 품질(정보보존율)·회상 정확도는 **0615로 검증 불가.** - Phase 1엔 **룰 기반으로 시작**(salience 룰, inter_session_drift 룰, EWMA α=0.4·FAST_WINDOW=6 전부 가정값). - 정량검증은 **Phase 3 파일럿(20명×다회기)에서 first measure** — "압축 전후 사실 보존율 / 다음회기 회상 정확도 / 성장곡선 타당도". 그 전엔 "검증됨" 표기 금지(`feedback_measure_before_report`). - **추가 축어록 수급이 전제** — n=1 해소는 한신대 공문(블로킹 의존성, F-04)에 달림. --- ## 7. 핵심 리스크 (통합) | # | 리스크 | 영향 | 완화 | |---|---|---|---| | **M1** | 압축 정보손실로 페르소나 일관성 균열 | 페르소나 붕괴 | pinned 무손실+episodic 검색 백업, digest는 서사용. Phase3 보존율 측정 | | **M2** | 상태수치 LLM hallucination | 저항엔진 오작동 | end_state는 코드 복사(P4), LLM 미경유 | | **M3** | n=1로 압축/회상/성장곡선 품질 검증 불가 | 정량근거 부재 | 정직표기, 룰 시작, Phase3 first measure. α·window·TTFT 전부 "실측 필요" | | **M4** | 모순처리 오판 (정당변화↔버그 혼동) | 잘못 차단/오염 | fact_type별 분기(locked만 차단), 전건 이력화 사후교정 | | **M5** | 회상 검색 오류 (엉뚱한 과거발화) | "지난번~" 환각 | reranker top-5, open_threads 우선쿼리, case 스코프 강제 | | **M6** | CCD 메타노출 (회상·KB가 정답 누설) | 추론훈련 무력화 | visible_to 분리, 내담자="자기기억"만, KB 본문 비노출(단서만), 출력 가드레일 | | **M7** | 회기 경계 상태 리셋 버그 | 진화 손실 | carry-over 명시(0.7 이월), rapport_trajectory DB 회귀테스트 | | **M8** | 페르소나 drift (가변이 불변 덮음) | 페르소나 깨짐 | 불변 카드 압축 대상 제외, COMPRESS_SYSTEM hard rule+금칙어 정규식 이중방어 | | **M9** | DSM-5 verbatim 저작권 (license C) | 법적 책임 | 원문 미적재 환언본만, verbatim 검출기 게이트, 국내 라우팅 | | **M10** | 한국어 sparse/BM25 토크나이징 (PG simple 형태소 미분리) | 검색 품질 | pg_trgm+BGE sparse 병용, 부족 시 mecab-ko 2차. 실측 전 "해결됨" 금지 | | **M11** | 정보비대칭 누수 (내담자가 정답청크 봄) | 시뮬레이션 가치 0 | visible_to+sensitivity DB WHERE 강제, 코드경로 부재 1차방어, T1~T4 자동테스트 | | **M12** | 마스킹 게이트 우회 (claude -p stdin 직송) | IRB·법적 | C/D 청크 external_llm_ok=false, Presidio 선통과(F-03), KB는 인덱싱 시점 환언 | | **M13** | 압축/HNSW 재인덱싱 비용 누적 | 예산 | 정액 한계비용 0, content_hash 증분, case_digest 재압축 N회기마다, salience 입력축소 | | **M14** | 임베딩 일관성 0.79 임계가 임의값 | KPI 무근거 | self-play 회귀로 분포 측정 후 확정, 파일럿 전 "검증됨" 금지 | --- ## 8. 통합 요약 (한 장) - **5계층**: ①working(상태머신 수치+버퍼, 코드 carry-over·체크포인트) ②episodic(turns+BGE-M3, append-only) ③summary(회기종료 LLM 압축 ~600토큰) ④semantic(case_profile evolving+pinned_fact) ⑤knowledge(kb 스키마, 3-AI 다른 정책). 메모리(app)↔지식(kb) **물리 스키마 분리**. - **회기 라이프사이클**: 시작(case_digest→summary→episodic recall, 큰그림→세부, ~4K 고정) → 진행(턴 사이클, 상태머신 결정론, 모순검사) → 종료(수치 코드복사+서사 LLM압축+salience 망각/유지+case 병합) → 다음 회기(누적 상태). - **회기 간 연속성(최우선 갭)**: 상태 수치 carry-over(rapport 0.7 이월, ideation 보수적 유지), pinned hard-pin 무손실, 모순 처리(locked=차단·재생성 / clinical=수용·갱신+이력), 람다 daily→weekly→monthly 압축 직이식. - **페르소나 일관성**: 불변 카드 + 가변 메모리 물리 분리(L1 캐시 vs L3~L6 턴주입), drift=가변이 불변 덮을 때 → 구조적 차단. 음성 prosody=텍스트 말투와 같은 상태머신 입력 동기화. - **정보비대칭**: 파생 메모리(CCD·정답·점수·종단)에만 visible_to, 코드경로 부재 1차방어+RLS 2차, 인간 RBAC×cohort AND. KB는 같은 테이블 다른 4-튜플 정책. - **§3.6 정합**: BGE-M3/pgvector/하이브리드/Contextual/reranker 스택 그대로, 회기 간 연속성만 신규 보강. - **MVP·n=1**: Phase 0 스키마 전체 선반영, Phase 1 단일회기, Phase 2a cross-session. 0615 검증 불가→룰 시작+Phase3 first measure, 추가 축어록 수급(공문 블로킹) 전제. "검증됨" 표기 보류. --- ## 부록. 종합 근거 자료 (절대경로) - 마스터플랜: `C:/Users/encep/Dropbox/Obsidian/Yun Chan Repository/Projects/영업/2026/한신대학교 산학협력 프로젝트/HANSHIN_AI_PLATFORM_MASTERPLAN.md` (§2 3-AI, §3 데이터모델, §3.6 RAG) - BLUE TEAM 패치: `…/적대검증_2026-06-25/MASTERPLAN_REVISIONS.md` (§1 stream-json 상주풀, §3 마스킹·스키마, §5 visible_to/RBAC 레이어분리, §6 RACI·DoD검증) - 데이터 원천: `…/데이터/README.md` (0615 n=1 시드, 파란색=발화별 기법라벨+슈퍼바이저논평 taxonomy) - 람다 메모리 시스템(1차 레퍼런스): `…/Lambda/BOOT.md`(부트 큰그림→세부), `…/Lambda/procedures/memory-compression.md`(daily→weekly→monthly 압축 알고리즘), `…/CLAUDE.md`(graphify=GraphRAG / rag-memory=BGE-M3 sqlite 역할분리) - KB 자료원: `…/Lambda/references/counseling/`(00~07 DSM·이론·마이크로스킬·위기·한국맥락, 출처인용·가드레일 완비 환언본) - 자매 설계서: `…/설계/연속성메모리_정보비대칭_종단평가_설계서.md`(3-AI 메모리뷰 상세) **설계 차별점 4줄**: ① 같은 물리 KB(`kb.chunk`) 하나를 visible_to+sensitivity+정책 매트릭스로 3-AI가 다르게 검색(DB 강제, 코드경로 부재 1차방어). ② 람다 daily→weekly→monthly 계층압축을 메모리 4계층(working/episodic/summary/semantic) + KB 3계층(청크/prefix/community)으로 이식. ③ 람다 rag-memory↔graphify 역할분리를 에피소드 메모리↔정적 KB 물리분리로, 모순 시 "KB는 사전이지 판사가 아니다"(에피소드>KB) 우선순위로 페르소나 일관성 보호. ④ 회기 간 연속성=수치는 결정론 carry-over(무손실)+서사는 LLM 압축(손실허용)+pinned hard-pin, ideation은 보수적 유지(안전).