A2A Memory DB and Sync Contract
이 문서는 A2A 메모리 기능을 실제 구현으로 옮기기 위한 DB schema, sync cursor, privacy/delete 정책을 정의한다.
현재 구현 상태는 세션/voice_context 중심이다. 이 문서는 그 위에 장기 메모리를 얹기 위한 설계 기준이며, 아직 제품 DB 구현 완료를 의미하지 않는다.
1. 설계 목표와 제약
목표:
- 최소 단위는 user/assistant pair로 관리한다.
- session은 최대 8 pair를 묶지만, 항상 8 pair가 찬다고 가정하지 않는다.
- 하루 단기, 일주일 중기, 장기 profile memory를 분리한다.
- Planner는 memory를 참고하지만 현재 발화를 우선한다.
- 장기 memory는 candidate와 confirmed state를 분리한다.
제약:
- 온디바이스는 로컬 세션과 최근 turn cache를 관리한다.
- Cloud는 요약, 장기 후보 추출, profile 확정을 관리한다.
- 민감 정보는 저장 전 분류와 삭제 정책이 필요하다.
- 네트워크 단절 시 on-device local cursor가 재동기화 기준이 된다.
2. 아키텍처 개요
On-device local store
conversation_pairs
conversation_sessions
active_workflow_cache
sync_cursor
Cloud Memory DB
short_term_summaries
mid_term_summaries
long_term_profile_candidates
long_term_profile_items
memory_audit_log
Planner Input
recognized_text
voice_context.recent_turns
memory_snapshot.session_summary
memory_snapshot.short_term
memory_snapshot.mid_term
memory_snapshot.profile_hints
device_context
3. 컴포넌트 경계와 책임
| 컴포넌트 | 책임 | 하지 말아야 할 것 |
|---|---|---|
| On-device | 최근 pair/session 보관, sync cursor 관리, Cloud 요청 시 compact snapshot 전달 | 장기 profile을 독단적으로 확정 |
| Cloud Runtime | memory snapshot을 Planner 입력으로 조립, memory update를 응답에 포함 | 현재 발화보다 memory를 우선 적용 |
| Memory Summarizer | session/day/week summary 생성 | 원문 전체를 무기한 보존 |
| Profile Extractor | 장기 후보 생성, confidence와 source 근거 보존 | 단발성 발화를 즉시 confirmed profile로 승격 |
| Privacy/Retention Worker | 삭제, 만료, 민감정보 마스킹 | audit 없이 silent delete |
3.1 현재 구현 상태
현재 repo에서 실제 구현되어 있는 부분은 DB가 아니라 runtime contract다.
| 구분 | 현재 구현 | 구현 근거 |
|---|---|---|
| Memory context 수신 | voice_context.memory_snapshot 또는 voice_context.memory_context 수용 |
gemini/a2a/runtime/memory_context.py |
| Memory context 정규화 | a2a-memory-context-v1 형태로 short/mid/long/sync를 정규화 |
normalize_memory_context |
| Planner 전달 | 정규화된 memory를 voice_context.memory_context에 삽입 |
hydrate_memory_context, orchestrator.py |
| 입력 크기 제한 | recent_pairs=8, daily_summaries=7, profile_items=20 |
test/test_memory_context.py |
| Sync 보존 | cursor/version 보존 | normalize_memory_context, build_memory_update |
| Write candidate 생성 | write_candidates.short_term_pair, device_cache_update 생성 |
build_memory_update |
| Unit test | 정규화, limit, runtime 전달, memory_update 생성 검증 | test/test_memory_context.py |
현재 구현되지 않은 부분:
| 구분 | 미구현 내용 |
|---|---|
| DB migration | 실제 table 생성 migration 없음 |
| Repository | pair/session/summary/profile/cursor/audit repository 없음 |
| Summarizer | session/day/week 압축 worker 없음 |
| Profile extractor | long-term candidate 자동 추출 없음 |
| Promotion gate | candidate -> confirmed/rejected/expired 승격 없음 |
| Privacy worker | retention/delete/mask/audit 실행 없음 |
| Sync worker | on-device/cloud cursor conflict 처리 없음 |
따라서 현재 memory는 “Planner에 안전하게 전달되는 context layer + 응답에 포함되는 write 후보” 단계로 보는 것이 정확하다.
4. 데이터 모델/저장소 전략
4.0 저장소 구현 단위
v1 구현은 아래 repository/worker 단위로 나누는 것이 좋다.
| 구현 단위 | 책임 | 주요 table |
|---|---|---|
PairRepository |
pair append, recent pair read, retention target 표시 | conversation_pairs |
SessionRepository |
session open/close, 최대 8 pair 관리, active workflow 연결 | conversation_sessions |
ShortTermSummaryRepository |
day summary read/write, open task 유지 | short_term_summaries |
MidTermSummaryRepository |
weekly digest, repeated pattern 관리 | mid_term_summaries |
ProfileCandidateRepository |
long-term 후보 생성/검토/거절/만료 | long_term_profile_candidates |
ProfileItemRepository |
confirmed profile read/write/delete | long_term_profile_items |
MemorySyncRepository |
device/cloud cursor, conflict 상태 관리 | memory_sync_cursors |
MemoryAuditRepository |
create/update/delete/mask/export audit | memory_audit_log |
Worker는 repository 위에서 동작한다.
| Worker | Trigger | 역할 |
|---|---|---|
SessionSummarizer |
session close, 8 pair 도달, timeout | pair를 session summary로 압축 |
DailyMemorySummarizer |
day close, 일정 pair 수 초과 | session summary를 short-term day summary로 압축 |
WeeklyPatternSummarizer |
week close, daily summary 누적 | 반복 topic/device pattern을 mid-term으로 압축 |
ProfileCandidateExtractor |
weekly pattern, proactive feedback, explicit preference | long-term candidate 생성 |
MemoryPromotionGate |
candidate confidence 상승, 명시 확인, 반복 증거 | pending candidate를 confirmed/rejected/expired로 변경 |
PrivacyRetentionWorker |
retention 만료, user delete request | mask/delete/audit 처리 |
4.1 conversation_pairs
최소 대화 원자다.
| 필드 | 타입 | 설명 |
|---|---|---|
pair_id |
string pk | user/assistant pair id |
user_id |
string | 사용자 또는 household scope |
device_id |
string | 기기 id |
session_id |
string | session id |
turn_index |
integer | session 내 순서 |
user_text |
text | STT normalized user utterance |
assistant_text |
text | assistant response summary 또는 full text |
route_family |
string | DEF/ODL/FRG/SCH 등 |
planner_summary |
json | selected route, steps, confidence |
created_at |
timestamp | 생성 시각 |
retention_until |
timestamp | 원문 보존 만료 시각 |
sensitive_flags |
json | 민감정보 분류 결과 |
4.2 conversation_sessions
최대 8 pair를 묶는 단기 흐름 단위다.
| 필드 | 타입 | 설명 |
|---|---|---|
session_id |
string pk | session id |
user_id |
string | 사용자 scope |
device_id |
string | 기기 scope |
started_at |
timestamp | 시작 시각 |
ended_at |
timestamp nullable | 종료 시각 |
pair_count |
integer | 포함 pair 수 |
active_workflow_id |
string nullable | 진행 중 workflow |
session_summary |
text | compact summary |
last_route |
string | 마지막 route family |
status |
string | active/completed/expired |
4.3 short_term_summaries
하루 단위 최근 맥락이다.
| 필드 | 타입 | 설명 |
|---|---|---|
summary_id |
string pk | summary id |
user_id |
string | 사용자 scope |
device_id |
string | 기기 scope |
date_key |
string | YYYY-MM-DD |
summary_text |
text | 하루 단기 summary |
open_tasks |
json | unresolved tasks |
recent_preferences |
json | 당일 선호/반복 표현 |
source_session_ids |
json | 근거 session id 목록 |
created_at |
timestamp | 생성 시각 |
expires_at |
timestamp | 만료 시각 |
4.4 mid_term_summaries
일주일 단위 반복 주제와 최근 패턴이다.
| 필드 | 타입 | 설명 |
|---|---|---|
summary_id |
string pk | summary id |
user_id |
string | 사용자 scope |
week_key |
string | ISO week |
summary_text |
text | 주간 summary |
repeated_topics |
json | 반복 주제 |
device_usage_patterns |
json | 기기 사용 패턴 |
unresolved_items |
json | 미해결 항목 |
source_summary_ids |
json | short-term 근거 |
expires_at |
timestamp | 만료 시각 |
4.5 long_term_profile_candidates
장기 profile 후보이며, 바로 확정하지 않는다.
| 필드 | 타입 | 설명 |
|---|---|---|
candidate_id |
string pk | 후보 id |
user_id |
string | 사용자 scope |
category |
string | preference/constraint/habit/device_pattern |
claim |
text | 후보 내용 |
confidence |
float | 신뢰도 |
source_refs |
json | pair/session/summary 근거 |
status |
string | pending/confirmed/rejected/expired |
created_at |
timestamp | 생성 시각 |
reviewed_at |
timestamp nullable | 검토 시각 |
4.6 long_term_profile_items
확정된 장기 memory다.
| 필드 | 타입 | 설명 |
|---|---|---|
profile_item_id |
string pk | profile item id |
user_id |
string | 사용자 scope |
category |
string | preference/constraint/habit/device_pattern |
value |
json | 확정 memory 값 |
confidence |
float | 확정 confidence |
source_candidate_ids |
json | 후보 근거 |
last_confirmed_at |
timestamp | 마지막 확인 시각 |
expires_at |
timestamp nullable | 만료 시각 |
delete_policy |
string | user_delete/retention_expire/manual_review |
4.7 memory_sync_cursors
온디바이스와 Cloud 동기화 위치다.
| 필드 | 타입 | 설명 |
|---|---|---|
cursor_id |
string pk | cursor id |
user_id |
string | 사용자 scope |
device_id |
string | 기기 id |
last_pair_seq |
integer | 마지막 업로드 pair sequence |
last_cloud_update_seq |
integer | 마지막 다운로드 memory update sequence |
last_sync_at |
timestamp | 마지막 sync 시각 |
sync_status |
string | ok/pending/conflict/error |
conflict_state |
json nullable | 충돌 상세 |
4.8 memory_audit_log
memory 변경 이력이다.
| 필드 | 타입 | 설명 |
|---|---|---|
audit_id |
string pk | audit id |
user_id |
string | 사용자 scope |
event_type |
string | create/update/delete/export/mask |
target_table |
string | 대상 table |
target_id |
string | 대상 row id |
actor |
string | device/cloud/user/admin |
reason |
string | 변경 사유 |
created_at |
timestamp | 발생 시각 |
5. API 계약 및 시퀀스
5.1 Runtime read path
Cloud Planner는 DB 전체를 직접 읽지 않고, runtime이 조립한 compact snapshot만 받는다.
request arrives
-> normalize_voice_context
-> hydrate_memory_context
-> memory_context injected into voice_context
-> planner prompt input
현재 구현 근거:
| 파일 | 역할 |
|---|---|
gemini/a2a/runtime/memory_context.py |
memory snapshot 정규화 |
gemini/a2a/runtime/orchestrator.py |
turn 시작 시 hydrate_memory_context 호출 |
gemini/a2a/planner/main_router_api.py |
planner input에 memory_context 포함 |
test/test_memory_context.py |
limits, contract version, runtime 전달 검증 |
5.2 Runtime write path
Turn 종료 후에는 실제 DB write가 아니라 write candidate를 먼저 만든다.
route result / session_state
-> build_memory_update
-> write_candidates.short_term_pair
-> repository write 또는 on-device cache update
-> summarizer/promotion worker
현재 build_memory_update가 만드는 최소 형태:
{
"contract_version": "a2a-memory-context-v1",
"source": "cloud_a2a_runtime",
"sync": {
"cursor": "cur-2",
"version": "v2"
},
"write_candidates": {
"short_term_pair": {
"user_text": "나는 거실을 자주 청정해",
"assistant_text": "",
"route_family": "DEF",
"workflow_id": ""
},
"profile_candidates": []
},
"device_cache_update": {
"active_workflow_id": "",
"last_route": "DEF",
"session_summary": ""
}
}
후속 구현에서는 assistant_text, planner_summary, device_task_result, task_event_summary, sensitive_flags를 채워야 한다.
현재 write candidate를 DB에 연결할 때의 최소 mapping:
| write candidate field | DB target | 비고 |
|---|---|---|
short_term_pair.user_text |
conversation_pairs.user_text |
STT normalized text |
short_term_pair.assistant_text |
conversation_pairs.assistant_text |
현재는 빈 값, 후속에서 route result summary 필요 |
short_term_pair.route_family |
conversation_pairs.route_family |
마지막 route 기준 |
short_term_pair.workflow_id |
conversation_pairs.planner_summary.workflow_id 또는 별도 field |
active workflow 추적 |
device_cache_update.active_workflow_id |
on-device active workflow cache | interruption/cancel 처리 |
device_cache_update.last_route |
on-device session cache | 다음 turn context |
device_cache_update.session_summary |
conversation_sessions.session_summary 후보 |
현재는 session_state 값 의존 |
sync.cursor |
memory_sync_cursors.last_pair_seq 또는 opaque cursor |
cursor 해석 정책 필요 |
sync.version |
memory_sync_cursors.last_cloud_update_seq 또는 schema version |
현재는 string 보존 |
5.3 On-device -> Cloud request
{
"recognized_text": "안방으로 가서 청정해줘",
"voice_context": {
"session_id": "session-123",
"recent_turns": [],
"history_hint_strength": "weak",
"active_workflow_id": ""
},
"memory_snapshot": {
"session_summary": "오늘 사용자는 청정 관련 요청을 여러 번 했다.",
"short_term": [],
"mid_term": [],
"profile_hints": []
},
"memory_sync": {
"device_cursor": "pair_seq_42",
"last_cloud_update_seq": 18
}
}
5.4 Cloud -> On-device response
{
"session_state": {
"session_id": "session-123",
"workflow_context": {}
},
"memory_update": {
"cloud_update_seq": 19,
"session_summary_delta": "안방 청정 요청이 진행 중이다.",
"profile_candidates": [
{
"candidate_id": "cand-001",
"category": "device_pattern",
"claim": "사용자는 안방 청정을 자주 요청한다.",
"confidence": 0.62,
"status": "pending"
}
]
}
}
5.5 Sync API 방향
On-device와 Cloud는 항상 같은 시점에 완벽히 동기화된다고 가정하면 안 된다.
| API 방향 | 목적 | 예시 payload |
|---|---|---|
| On-device -> Cloud | local pair/session upload | pairs[], device_cursor, session_id |
| Cloud -> On-device | compact memory update download | cloud_update_seq, session_summary_delta, profile_hints |
| On-device -> Cloud | delete/mask request | delete_scope=user|device|session|pair, target_id |
| Cloud -> On-device | conflict response | sync_status=conflict, expected_cursor, server_cursor |
동기화 원칙:
- 같은 pair가 두 번 업로드되어도 idempotent해야 한다.
device_cursor가 뒤처지면 Cloud는 missing range 또는 conflict를 반환한다.- Cloud가 내려주는 profile hint는 on-device local cache에 compact copy로만 유지한다.
- 삭제 요청은 Cloud DB와 on-device cache 양쪽에 반영되어야 한다.
6. Planner 사용 원칙
Planner는 memory를 아래 우선순위로 사용한다.
- 현재
recognized_text. - active workflow / pending follow-up.
recent_turns.session_summary.- short-term / mid-term summary.
- confirmed long-term profile hints.
금지:
- memory preference가 현재 발화의 명확한 route family를 덮어쓰면 안 된다.
- long-term candidate를 confirmed profile처럼 쓰면 안 된다.
- 민감 정보나 삭제 요청된 memory를 prompt에 넣으면 안 된다.
7. Memory 충돌/오염 방지 규칙
Memory는 도움이 되지만 잘못 쓰면 planner를 오염시킨다.
| 상황 | 잘못된 처리 | 올바른 처리 |
|---|---|---|
| 현재 발화가 명확한 ODL | 과거에 사용자가 잡담을 많이 했다는 이유로 DEF 선택 | 현재 발화 우선, memory는 target/slot 보조만 |
| long-term candidate만 있음 | candidate를 confirmed 선호처럼 prompt에 주입 | pending candidate는 planner에 숨기거나 낮은 신뢰 hint로 제공 |
| 삭제 요청된 profile | local cache에 남아 계속 사용 | delete audit 후 Cloud/on-device cache 모두 제거 |
| noisy STT | memory로 과하게 의미를 복원 | current_turn_priority=required, 불확실하면 STT_NULL/clarify |
| 반복 패턴 1회 | profile 확정 | short/mid-term candidate까지만 |
8. Retention / Privacy 기본값 후보
제품 정책 확정 전까지는 아래를 기본 후보로 둔다.
| 데이터 | 보존 후보 | 이유 |
|---|---|---|
| raw pair text | 7~30일 | 디버깅/요약 근거, 장기 보존은 부담 |
| session summary | 30~90일 | 단기/중기 압축 근거 |
| short-term summary | 7~14일 | 하루 맥락 유지 |
| mid-term summary | 4~8주 | 반복 패턴 판단 |
| long-term candidate | 30~90일 pending 후 expire | 오염 방지 |
| confirmed profile | 사용자 삭제/만료 정책까지 | 개인화 핵심 |
| audit log | 제품/법무 정책 기준 | 삭제/수정 추적 |
민감정보 후보:
| 유형 | 처리 |
|---|---|
| 전화번호/주소/식별번호 | 기본 mask 또는 저장 제외 |
| 건강/민감 생활 패턴 | candidate로 자동 승격 금지, explicit confirmation 필요 |
| 어린이/가족 구성원 정보 | profile 저장 전 별도 정책 필요 |
| 위치/방 이름 | household device 기능에 필요한 범위로 제한 |
9. 구현 단계
| 단계 | 작업 | 검증 |
|---|---|
| M0 | 현재 runtime contract 고정 | test/test_memory_context.py 유지, schema fixture 추가 |
| M1 | pair/session repository | append/read/session-close unit test |
| M2 | sync cursor repository | duplicate/conflict/retry test |
| M3 | short-term/day summarizer | session -> day summary fixture |
| M4 | mid-term/week summarizer | day summaries -> weekly digest fixture |
| M5 | profile candidate extractor | repeated pattern -> pending candidate |
| M6 | promotion gate | pending -> confirmed/rejected/expired |
| M7 | privacy/delete worker | delete/mask/audit integration test |
| M8 | planner integration | memory_context가 prompt에 들어가지만 current utterance를 덮지 않는 regression |
10. 비기능 요구 대응
| 요구 | 정책 |
|---|---|
| 개인정보 최소화 | pair 원문은 짧은 retention, summary/profile은 목적 제한 |
| 삭제권 | user/device 단위 delete request가 memory_audit_log에 남아야 함 |
| 네트워크 단절 | on-device cursor 기준으로 pending upload 유지 |
| 충돌 처리 | Cloud update seq와 device cursor가 불일치하면 sync_status=conflict |
| 관측성 | memory update, deletion, masking은 audit event로 기록 |
11. 리스크와 의사결정 로그
| 리스크 | 대응 |
|---|---|
| memory가 router 판단을 오염 | 현재 발화 우선 원칙과 prompt guard 유지 |
| 장기 profile 오검출 | candidate/confirmed 분리와 confidence threshold |
| 민감 정보 저장 | sensitive flag, masking, retention policy |
| 온디바이스/Cloud 불일치 | sync cursor, cloud update sequence, conflict state |
| 과도한 DB 복잡도 | v1은 pair/session/summary/profile/cursor/audit 최소 테이블로 시작 |
12. 구현 전 결정 필요
| 질문 | 제안 |
|---|---|
| user scope는 개인인가 household인가 | v1은 user_id와 device_id 둘 다 저장하고 권한 정책은 제품 정책에서 결정 |
| long-term confirmation은 자동인가 수동인가 | v1은 confidence 기반 pending 생성, confirmed는 명시 policy 후 승격 |
| pair 원문 retention은 얼마인가 | v1은 7~30일 후보, 제품 개인정보 정책에 맞춰 확정 |
| LangGraph/LangMem을 쓸 경우 schema가 바뀌는가 | 내부 orchestration은 바뀔 수 있지만 제품 DB schema는 유지 |