A2A 히스토리 시드
워크플로우 오케스트레이터(VoltAgent 등)가 우리 A2A 에이전트를 자식으로 호출할 때, 호스트가 보유한 대화 history 를 LangGraph state 에 1회성 시드합니다. 호스트마다 자체 대화 상태를 보존하지만 우리 에이전트의 checkpointer 는 호스트가 부여한 새 contextId 에 대해 비어있습니다 (cold-start) — 이 갭을 메우는 채널입니다.
언제 필요한가
섹션 제목: “언제 필요한가”사용자 ──┐ (4턴 대화 누적) ▼ 워크플로우 호스트 (자체 history 보유) ┌──────────────┐ │ VoltAgent 등 │ contextId = 새 UUID, metadata.history = 4턴 └──────┬───────┘ ▼ A2A 호출 ┌──────────────┐ │ 우리 에이전트 │ checkpointer empty → history 시드 → LLM 맥락 살림 └──────────────┘호스트가 매번 새 contextId 로 호출하면 우리 checkpointer 가 항상 cold-start. metadata.history 가 없으면 LLM 은 단발 질의로 인식하여 호스트의 의도(맥락 유지)와 다른 응답을 낼 수 있습니다.
요청 형식
섹션 제목: “요청 형식”호스트는 다음 두 위치 중 어디든 history 를 넣을 수 있습니다 (실제 동일):
{ "jsonrpc": "2.0", "method": "message/stream", "params": { "message": { "kind": "message", "role": "user", "parts": [{"kind": "text", "text": "현재 사용자 질의"}], "contextId": "wf-session-abc", "metadata": { "history": [ {"role": "user", "parts": [{"kind": "text", "text": "이전 질의 1"}]}, {"role": "agent", "parts": [{"kind": "text", "text": "이전 응답 1"}]}, {"role": "user", "parts": [{"kind": "text", "text": "이전 질의 2"}]}, {"role": "agent", "parts": [{"kind": "text", "text": "이전 응답 2"}]} ] } }, "metadata": {} }}또는 params.metadata.history 위치도 동일하게 동작 (executor 의 _merge_metadata 가 양쪽 흡수).
필드 규약
섹션 제목: “필드 규약”role:"user"|"agent"(A2ARoleenum). 다른 값 ("assistant","system"등) 은 skip + 경고 로그.parts: A2A 표준 part 리스트. 현재는kind: "text"만 변환 (file/data part 는 무시).- 순서는 그대로 보존되어 LangGraph state.messages 앞에 prepend.
cold-start 가드 (충돌 없음)
섹션 제목: “cold-start 가드 (충돌 없음)”contextId == LangGraph thread_id 1:1 매핑은 그대로 유지됩니다. metadata.history 는 checkpointer state 가 비어있을 때만 1회 시드되고, 이후 호출에서는 checkpointer 가 source of truth.
| 시나리오 | contextId | metadata.history | checkpointer | 시드 동작 |
|---|---|---|---|---|
| 워크플로우 첫 호출 | 새 UUID | 4턴 | empty | ✅ 시드 |
| 워크플로우 후속 호출 (동일 contextId, history 누적해서 또 보냄) | 같은 UUID | 6턴 | 보유 | ❌ 무시 (checkpointer 우선) |
| 워크플로우 매번 새 contextId | 매번 새 | 매번 history | 항상 empty | ✅ 매번 시드 |
| 개별 사용자 (history 없음) | 새 UUID | 없음 | empty | 빈 시작 (기존 동작) |
| 개별 사용자 멀티턴 | 같은 UUID | 없음 | 보유 | checkpointer 자동 (기존 동작) |
| HITL interrupt → resume | 같은 UUID | 있을 수도 | 보유 (interrupt) | ❌ 무시 (interrupt state 보존) |
→ 호스트는 history 를 항상 보내도 무방 — 두 번째 호출부터는 checkpointer 가 알아서 우선시합니다.
변환 규칙
섹션 제목: “변환 규칙”| 입력 | 출력 |
|---|---|
{"role": "user", "parts": [{"kind": "text", "text": "..."}]} | HumanMessage(content="...") |
{"role": "agent", "parts": [{"kind": "text", "text": "..."}]} | AIMessage(content="...") |
| 다른 role / 잘못된 parts / 비어있는 text | skip + 경고 로그 |
| 비-text part (file/data) | text 부분만 추출 |
| 한 메시지에 text part 가 여러 개 | concat 단일 메시지 |
변환 헬퍼(llamon_agent.inbound.a2a.history.convert_a2a_history_to_lc) 는 어떤 입력에도 raise 하지 않습니다 — malformed 항목은 skip 처리되어 정상 항목만 시드됩니다.
길이 제한 정책 — 최대 10턴 cap (host 약속과 동일)
섹션 제목: “길이 제한 정책 — 최대 10턴 cap (host 약속과 동일)”SDK 는 마지막 방어선으로 최대 10턴 cap 을 적용합니다 (_MAX_PREFILL_TURNS = 10, src/llamon_agent/inbound/a2a/history/converter.py).
동작 규칙
섹션 제목: “동작 규칙”| 호스트 송신 | 시드 동작 | 로그 |
|---|---|---|
| 0 ~ 10턴 (정상) | 전체 시드, cap 미트리거 | metadata.history 수신 — N개 (INFO) |
| 11턴 이상 | 가장 오래된 것부터 trim → 최근 10턴만 시드 | 호스트 약속(10) 초과, 최근 10턴만 시드 (WARNING) |
왜 host 약속과 동일한 10 인가
섹션 제목: “왜 host 약속과 동일한 10 인가”- 명시적 계약: 호스트가 10턴 한계로 보낸다는 약속을 SDK 코드가 그대로 강제 → 코드가 spec 문서 역할
- 운영 모니터링: cap 트리거 WARNING 이 1건이라도 발생하면 호스트 측 정책 위반/버그 조기 경보
- 다른 호스트 보호: VoltAgent 외 다른 워크플로우 도구가 추가되어도 동일 cap 자동 적용
- stateless 경로 보호:
memory_manager가 없는 호출에서도 cap 적용 (window_size 자동 trim 무관)
Trim 정책
섹션 제목: “Trim 정책”FIFO oldest-out (가장 오래된 메시지부터 제거):
입력 12턴: [m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11] ↓ cap 적용시드 10턴: [m2, m3, m4, m5, m6, m7, m8, m9, m10, m11] └ m0, m1 손실 (가장 오래된 정보)가장 최근 정보가 LLM 응답에 더 가치 있다는 가정.
신뢰 경계 가정
섹션 제목: “신뢰 경계 가정”본 기능은 호스트를 신뢰 경계 내부로 가정합니다 (자체 운영 워크플로우). AIMessage 위조 가능성(호스트가 임의 응답을 우리 agent 의 과거 발언으로 주장)이 존재하므로, 외부 호스트가 호출하는 시나리오에서는 별도 host signing 검증 메커니즘이 필요합니다.
Custom Agent 호환성
섹션 제목: “Custom Agent 호환성”자체 invoke() 인터페이스를 가진 custom agent 가 prefill_messages 파라미터를 지원하지 않는 경우:
- 호출 자체는 정상 진행 (시그니처 introspection 으로 판별)
metadata.history시드는 silently skip- 경고 로그 1회 발행 (디버깅 가능하도록)
표준 LangGraphAgent 는 invoke(), stream(), invoke_with_hitl() 모두 prefill_messages 지원.
관련 문서
섹션 제목: “관련 문서”- A2A 메시지 요청 —
contextId,metadata등 메시지 envelope 컨벤션 - A2A 에러 처리 — A2A 호출 실패 시 처리 패턴
- 멀티턴 메모리 — checkpointer 기반 자체 멀티턴 (단일 agent)