A2A Memory and Personalization
메모리 설계의 최종 목적은 A2A가 이전 맥락과 사용자 특성을 바탕으로 더 정확한 추론을 하게 만드는 것이다.
메모리 단위
| 단위 | 기간/범위 | 목적 |
|---|---|---|
| Pair | user/assistant 한 쌍 | 최소 대화 원자 |
| Session | 최대 8 pair | 짧은 대화 흐름 유지 |
| Short-term | 하루 정도 | 최근 맥락과 진행 중인 작업 유지 |
| Mid-term | 일주일 정도 | 반복 주제, 최근 선호, unresolved task 유지 |
| Long-term | 장기 | 사용자 특성, 선호, 기기 사용 패턴 |
저장 단위별 의미
메모리는 “대화를 많이 저장하는 기능”이 아니라, 다음 A2A 판단에 필요한 근거를 계층화하는 기능이다.
| 계층 | 무엇을 저장하는가 | 저장하지 말아야 할 것 | Planner에 주는 방식 |
|---|---|---|---|
| Pair | 한 번의 user text, assistant response summary, route family, workflow id | 불필요한 원문 장기 보존 | 최근 8개 이내의 compact recent pair |
| Session | 최대 8 pair의 요약, 마지막 route, active workflow, pending slot | 세션 밖 장기 선호 확정 | session_summary, last_route, active_workflow_id |
| Short-term | 하루 동안의 주요 요청, open task, 최근 선호 후보 | 일주일 이상 유지할 확정 profile | day summary, recent preference hints |
| Mid-term | 1주 단위 반복 패턴, 반복 room/device/action, unresolved item | 단발성 감정/잡담을 profile처럼 저장 | weekly digest, repeated pattern |
| Long-term candidate | 반복 증거가 있는 선호/습관/제약 후보 | 한 번 말한 내용을 즉시 확정 | pending candidate로만 제공하거나 숨김 |
| Long-term confirmed | 명시 확인되었거나 충분히 반복된 안정 profile | 민감하거나 삭제 요청된 정보 | profile hint로 제한 제공 |
현재 구현된 최소 계약
현재 Cloud repo에는 완성된 DB memory system이 아니라, planner-safe memory context를 주고받는 최소 runtime 계약이 구현되어 있다.
| 구현 위치 | 현재 동작 |
|---|---|
gemini/a2a/runtime/memory_context.py |
voice_context.memory_snapshot 또는 memory_context를 정규화한다. |
normalize_memory_context(...) |
short/mid/long/sync 구조를 a2a-memory-context-v1로 맞춘다. |
hydrate_memory_context(...) |
정규화된 memory를 voice_context.memory_context에 넣어 planner에 전달한다. |
build_memory_update(...) |
runtime 결과에서 short-term pair write candidate와 device cache update를 만든다. |
gemini/a2a/runtime/orchestrator.py |
turn 시작 시 memory context를 hydrate하고 응답 orchestration에 memory update를 포함한다. |
test/test_memory_context.py |
recent pair 8개, daily summary 7개, profile item 20개 제한과 runtime 전달을 검증한다. |
현재 계약의 중요한 제한:
- Cloud DB migration/repository/sync worker는 아직 구현 완료 상태가 아니다.
profile_candidates는 구조만 있고 실제 extractor/promotion은 후속 구현 대상이다.- 현재 구현은 memory를 “증거”로 전달하지만, 장기 profile 확정 로직은 아직 없다.
- 따라서 지금 단계에서는 memory가 route family를 직접 바꾸는 기능이 아니라 planner-safe support context다.
현재 실제로 되는 동작
현재 소스 기준으로 “된다”고 말할 수 있는 범위는 아래와 같다.
| 동작 | 현재 가능 여부 | 근거 |
|---|---|---|
On-device/요청 payload가 memory_snapshot을 보내는 형태 수용 |
가능 | normalize_memory_context가 voice_context.memory_snapshot을 우선 읽음 |
기존 memory_context 형태도 fallback으로 수용 |
가능 | memory_snapshot이 없으면 voice_context.memory_context를 읽음 |
| Planner-safe memory shape 생성 | 가능 | contract_version=a2a-memory-context-v1, current_turn_priority=required |
| short/mid/long tier limit 적용 | 가능 | recent pairs 8개, daily summaries 7개, profile items 20개 |
| sync cursor/version 보존 | 가능 | sync.cursor, sync.version을 normalized context에 유지 |
| Planner input으로 전달 | 가능 | hydrate_memory_context가 voice_context.memory_context에 삽입 |
| Turn 응답에 memory update 후보 생성 | 가능 | build_memory_update가 write_candidates.short_term_pair 생성 |
| 실제 DB 저장 | 아직 아님 | repository/migration 미구현 |
| session/day/week summary 자동 생성 | 아직 아님 | summarizer worker 미구현 |
| long-term profile 자동 승격 | 아직 아님 | extractor/promotion gate 미구현 |
| 삭제/마스킹/retention 실행 | 아직 아님 | privacy worker 미구현 |
현재 입력 예시
현재 runtime은 아래와 같은 memory snapshot을 받을 수 있다.
{
"voice_context": {
"recognized_text": "나는 거실을 자주 청정해",
"session_id": "memory-session",
"memory_snapshot": {
"source": "ondevice_cache",
"short_term": {
"recent_pairs": [
{
"user": "거실 청정해줘",
"assistant": "시작할게요"
}
],
"session_summary": "오늘 거실 청정을 자주 요청함"
},
"mid_term": {
"daily_summaries": [
{
"day": "2026-06-24",
"summary": "저녁에 청정을 자주 요청"
}
],
"weekly_digest": "저녁에 청정을 자주 요청"
},
"long_term": {
"profile_items": [
{
"key": "preferred_room",
"value": "거실"
}
]
},
"sync": {
"cursor": "cur-2",
"version": "v2"
}
}
}
}
현재 normalized memory output
Planner에는 아래처럼 정규화되어 들어간다.
{
"contract_version": "a2a-memory-context-v1",
"source": "ondevice_cache",
"current_turn_priority": "required",
"short_term": {
"recent_pairs": [
{
"user": "거실 청정해줘",
"assistant": "시작할게요"
}
],
"session_summary": "오늘 거실 청정을 자주 요청함"
},
"mid_term": {
"daily_summaries": [
{
"day": "2026-06-24",
"summary": "저녁에 청정을 자주 요청"
}
],
"weekly_digest": "저녁에 청정을 자주 요청"
},
"long_term": {
"profile_items": [
{
"key": "preferred_room",
"value": "거실"
}
]
},
"sync": {
"cursor": "cur-2",
"version": "v2"
}
}
현재 runtime memory update output
Turn 실행 후 runtime은 아래 형태의 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": ""
}
}
이 output의 의미:
- 현재 turn을 short-term pair 후보로 남길 수 있다.
- cursor/version을 보존해 on-device cache 또는 Cloud DB write 경로로 넘길 수 있다.
- 아직 profile 후보는 자동 생성하지 않는다.
assistant_text는 현재 최소 구현에서는 비어 있으며, 후속 구현에서 route result summary를 채워야 한다.
Cloud와 온디바이스 역할
온디바이스:
- 최근 turn과 session context를 보관한다.
- Cloud 요청 시 compact memory snapshot을 보낸다.
- Cloud가 내려준 memory update를 local cache에 반영한다.
Cloud:
- Planner가 필요한 수준으로 memory를 요약한다.
- long-term candidate를 추출한다.
- LangGraph/LangMem 같은 orchestration 도구를 사용하더라도 제품 schema는 유지한다.
Read / Write 흐름
Turn 시작 전 read
On-device local session cache
-> voice_context.memory_snapshot
-> Cloud normalize_memory_context
-> planner input memory_context
Planner에 들어가는 memory는 반드시 compact해야 한다.
| 입력 | 제한 |
|---|---|
short_term.recent_pairs |
최대 8개 |
mid_term.daily_summaries |
최대 7개 |
long_term.profile_items |
최대 20개 |
sync.cursor |
중복 write 방지용 |
Turn 종료 후 write
route execution result
-> build_memory_update
-> write_candidates.short_term_pair
-> On-device local cache update
-> Cloud DB write 또는 pending sync
-> summarizer / promotion worker
write는 즉시 long-term 확정이 아니다.
| 결과 | 저장 위치 | 이유 |
|---|---|---|
| 방금 user/assistant pair | pair store / short-term candidate | 다음 turn 맥락 |
| 진행 중 workflow | device cache / session state | interruption, cancel, slot fill 처리 |
| 반복 사용 패턴 | mid-term candidate | 일주일 단위 반복 판단 |
| 안정 선호 후보 | long-term candidate | confirmed 전 오염 방지 |
| 명시 선호/동의 | long-term candidate 또는 confirmed 후보 | 정책 기반 승격 필요 |
압축 정책
메모리 압축은 Pair -> Session -> Day -> Week -> Profile 순서로 진행한다.
| 압축 단계 | Trigger | 입력 | 출력 |
|---|---|---|---|
| Pair append | 매 turn 종료 | user text, assistant summary, route, workflow | conversation_pairs |
| Session summarize | 8 pair 도달, session timeout, workflow 종료 | session pairs | conversation_sessions.session_summary |
| Day summarize | 하루 종료 또는 일정량 이상 pair 누적 | sessions of day | short_term_summaries |
| Week summarize | 주간 배치 또는 충분한 daily summary | daily summaries | mid_term_summaries |
| Profile extraction | 반복 패턴/명시 선호 감지 | mid-term + feedback + confirmed signal | long_term_profile_candidates |
| Profile promotion | 명시 확인 또는 반복 증거 충족 | candidates | long_term_profile_items |
압축 결과에는 항상 source reference가 필요하다.
profile item
-> source_candidate_ids
-> source_summary_ids
-> source_session_ids
-> source_pair_ids
이 chain이 있어야 잘못된 profile을 나중에 되돌릴 수 있다.
중요한 원칙
- Memory는 현재 발화를 덮어쓰면 안 된다.
- Long-term memory는 candidate와 confirmed state를 분리한다.
- 민감 정보는 저장 정책과 삭제 정책이 있어야 한다.
- A2A route family 판단에 memory를 쓰더라도, 현재 발화의 명확한 의미가 우선이다.
Planner 입력 예시
{
"recognized_text": "안방으로 가서 청정해줘",
"voice_context": {
"session_id": "session-123",
"last_route": "ODL",
"active_workflow_id": ""
},
"memory_context": {
"contract_version": "a2a-memory-context-v1",
"current_turn_priority": "required",
"short_term": {
"recent_pairs": [
{
"user": "거실 청정해줘",
"assistant": "거실 청정을 시작할게요.",
"route_family": "ODL"
}
],
"session_summary": "오늘 사용자는 방 단위 청정 요청을 여러 번 했다."
},
"mid_term": {
"daily_summaries": [
{
"date": "2026-06-24",
"summary": "저녁 시간대에 안방/거실 청정 요청이 반복됨"
}
],
"weekly_digest": "최근 일주일간 방 단위 청정 요청이 반복됨"
},
"long_term": {
"profile_items": [
{
"category": "device_pattern",
"value": {
"preferred_cleaning_rooms": ["거실", "안방"]
},
"confidence": 0.83
}
]
},
"sync": {
"cursor": "cur-42",
"version": "v1"
}
},
"device_context": {
"current_room": "거실",
"battery": 82
}
}
Planner 사용 해석:
- 현재 발화가
안방으로 가서 청정해줘이므로 ODL/device action이 우선이다. - memory의 “방 단위 청정 반복”은 보조 근거일 뿐, 발화를 다른 family로 바꾸지 않는다.
- profile hint는 slot 추론이나 응답 자연성에는 도움을 줄 수 있지만, 실행 대상은 현재 발화의
안방이 우선이다.
예시: 장기 선호 승격
Day 1: "안방 청정해줘"
Day 2: "안방 좀 깨끗하게 해줘"
Day 4: "잘 때는 안방 먼저 청정해"
Day 6: proactive 추천 "안방 청정을 시작할까요?" -> 사용자 수락
처리:
| 단계 | 처리 |
|---|---|
| Pair | 각 user/assistant pair를 저장 |
| Short-term | 당일 청정 요청 summary 생성 |
| Mid-term | 안방 청정 반복 패턴 감지 |
| Candidate | 사용자는 취침 전 안방 청정을 선호할 수 있음 pending candidate 생성 |
| Confirmation | 명시 수락 또는 반복 수락이 누적되면 confirmed 후보 |
| Long-term | confirmed profile item으로 승격 |
이때 단발성 “안방 청정해줘” 하나만으로 장기 선호를 확정하면 안 된다.
다음 구현 방향
| 우선순위 | 작업 | 완료 증거 |
|---|---|---|
| P0 | 현재 memory_context contract를 schema로 고정 |
a2a-memory-context-v1 JSON schema와 fixture |
| P0 | pair/session write repository 구현 | pair append, session close unit test |
| P0 | build_memory_update 결과 저장 경로 연결 |
runtime output -> repository write test |
| P1 | session/day/week summarizer 구현 | summary 생성 fixture |
| P1 | profile candidate extractor 구현 | repeated pattern -> pending candidate test |
| P1 | promotion gate 구현 | candidate -> confirmed/rejected/expired test |
| P1 | sync cursor 구현 | duplicate write, conflict, retry test |
| P2 | privacy/delete worker 구현 | delete/mask/audit log test |
| P2 | LangGraph/LangMem orchestration 적용 검토 | 제품 DB schema를 유지한 adapter proof |
상세 DB schema, sync cursor, retention/delete 정책은 Memory DB and Sync Contract를 기준으로 관리한다.