A2A · Agent-to-Agent Protocol

에이전트가 다른 회사의 에이전트에게 일을 맡긴다

QMesh는 금융 FinAllQ · 보험 InsuQ · 제조 MaintQ — 스택도 소유권도 다른 세 AI 에이전트를 A2A 프로토콜로 잇는 중립 오케스트레이션 허브입니다. 세 도메인은 서로의 DB도, 프레임워크도, 내부 프롬프트도 모릅니다. 오직 Agent Card와 표준 Task 스키마로만 대화합니다.

MCP = 에이전트가 자기 도구를 쓴다 (도메인 내부)
A2A = 독립된 타사 에이전트에게 Task를 위임한다 (QMesh가 조율)

cross-domain trace
POST /a2a/skills/assess-loan
X-A2A-Partner-Id: maintq
request_chain_id: CHAIN-LOAN-a0639920

FinAllQ :9001 LTV 판정
  → InsuQ :9002 담보 보험 검증
decision: conditional 1.3s
※ 2026-08-24 실측 캡처 값. 5억 신청 · 담보 인정액 3억 → 보장 부족으로 조건부.

하나의 request_chain_id가 홉 전체를 관통한다

3도메인 에이전트
13표준 Task 스키마
9크로스도메인 시나리오
113어댑터 테스트 (QMesh)
왜 A2A인가

도구를 부르는 것과, 남의 에이전트에게 맡기는 것은 다르다

MCP는 한 에이전트가 자기 도구를 쓰는 문제를 푼다. A2A는 소유권이 다른 조직의 에이전트에게 작업을 위임하는 문제를 푼다. 뒤쪽에는 앞쪽에 없는 것들이 붙는다 — 신원 증명, 사람의 승인 대기, 그리고 끝까지 따라가는 추적.

MCP

자기 도구를 쓴다

  • 호출 대상은 내 DB, 내 검색엔진, 내 API다. 소유권이 같다.
  • 스키마가 바뀌면 내가 바로 고친다. 조율이 필요 없다.
  • 실패하면 내 문제다. 안에서 재시도하면 끝난다.
  • MaintQ의 MCP 도구 20종이 이 층에 있다.
A2A

남의 에이전트에게 맡긴다

  • 상대의 내부를 모른다. Agent Card에 적힌 스킬만 부를 수 있다.
  • 계약(JSON Schema)을 먼저 맺어야 한다. 한쪽이 바꾸면 양쪽이 깨진다.
  • 응답이 즉시 오지 않는다. input-required — 상대 조직의 사람이 결재를 마칠 때까지 기다린다.
  • 내가 누구인지(Actor) 증명해야 하고, 무엇을 처리할지(Subject)는 따로 밝혀야 한다.
경계

경계의 물리적 증명

세 프로젝트는 기술 스택도 소유권도 다른 '독립 회사'다. 연결 코드를 어느 한쪽 안에 넣으면 그 순간 경계가 사라진다. 그래서 QMesh를 바깥에 뒀다 — 누구의 소유도 아닌 중립 프로토콜 계층.

캡슐화

블랙박스 유지

QMesh는 각 프로젝트의 DB도, 프레임워크도, 내부 프롬프트도 알지 못한다. Java Spring과 Python FastAPI와 LangGraph가 섞여 있어도, 바깥에서 보이는 것은 표준 HTTP 창구 하나뿐이다.

릴리스

독립 릴리스

각 도메인은 자기 일정으로 배포한다. QMesh는 그 위에서 크로스도메인 워크플로우만 따로 갱신한다. 실제로 세 레포는 서로 다른 스프린트를 돌면서 동시에 개발됐다.

통신 토폴로지

완전 삼각형, 그리고 한 번 더 건너가는 멀티홉

세 에이전트가 서로 직접 대화한다. MaintQ가 FinAllQ에 대출을 물으면, FinAllQ는 다시 InsuQ에 담보 보험을 확인한다 — 최초 요청자는 두 번째 홉이 일어났다는 사실조차 알 필요가 없다.

S5 · S6 S7 · S11 · S14 S8 · S13 MaintQ 제조 설비보전 · :9003 Python FastAPI · MCP FinAllQ 은행 · 증권 · :9001 Java Spring · LangGraph InsuQ 기업 · 화재보험 · :9002 Spring · FastAPI RAG QMesh 오케스트레이터 · :9000 실선 = A2A 직접 호출 · 점선 = 통합 진입점 및 분산 트레이스 수집(설계)
S5 · S6발주 승인 → 출금 요청(2단 승인) · 환헤지 상담
S7 · S11 · S14화재보험 갱신 · 자산변동 통지 · 보험금 청구
S8 · S13담보 대출 심사 중 보험 검증 — 2차 홉 릴레이
도메인 에이전트 기술 스택 A2A 창구
조율QMesh OrchestratorPython · FastAPI:9000
금융FinAllQJava 21 · Spring Boot 3 · PostgreSQL · React 19 · LangGraph:9001
보험InsuQJava Spring · Python FastAPI · Qdrant · LangGraph:9002
제조MaintQPython FastAPI · Next.js · PostgreSQL(pgvector) · MCP:9003
A2A 통신 자체에는 전용 SDK를 쓰지 않았다. 네 레포 전부 FastAPI + httpx + pydantic(Python) 또는 Spring MVC 직접 구현으로 JSON Schema 계약을 손으로 구현했다. 프로토콜을 라이브러리에 맡기지 않고 직접 짜본 것이 이 프로젝트의 목적이었다.
세 에이전트

각자 다른 문제를 풀고, 각자 다른 절대 원칙을 지킨다

세 프로젝트는 A2A를 위해 급조된 목업이 아니다. 각각 독립적으로 설계·측정·평가된 서비스이고, 그 위에 통신 계층을 얹었다.

보험:9002

InsuQ

약관을 근거 조항과 함께 답하는 RAG 에이전트

무엇을 푸나

보험 약관은 조항이 서로 얽혀 있고(참조 조항), 보장금액표·감액표가 많으며, 같은 상품도 가입 시점에 따라 보장이 다르다. InsuQ는 약관을 검색 가능한 지식베이스로 만들고 출처 조항을 명시해 답한다. 못 찾으면 '약관에서 확인 불가'라고 밝힌다 — 보험은 오답의 비용이 커서, 틀리게 답하느니 모른다고 답하는 쪽을 택했다.

스택

Python FastAPIJava SpringQdrantLangGraph 0.2Gemini 3.5 Flashnemotron-3-embed-1b (2048D)Next.js
절대 원칙 — 근거 없는 답은 하지 않는다. 방어선①은 LLM 호출 에 있다: 검색 결과가 없거나 점수 미달이면 모델을 부르지도 않는다. 방어선②는 호출 후 인용을 검증하고, 단정 표현을 판단 유보로 강등한다. 둘 다 결정론적이라 예산이 없어도 원칙은 지켜진다.

A2A 스킬 6종

verify-collateral-insuranceadvise-policy-renewalnotify-asset-changenotify-risk-changeclaim-insurancelookup-clause
0.8333검색 Hit@5
100%거부 정확도
0환각 건수
10.4s응답 p95
제조:9003

MaintQ

설비 진단부터 부품 발주까지 잇는 보전 에이전트

무엇을 푸나

중소 제조공장에서 인버터·PLC 에러가 나면 PDF 매뉴얼을 10~30분 뒤지고, 고참 정비사 경험에 의존해 진단하고, 자재담당에게 전화로 재고를 확인하고, 엑셀로 발주서를 쓴다. 매체가 계속 바뀌고 단계마다 대기가 생긴다. CMMS는 기록 시스템이고 매뉴얼은 정적 문서다 — MaintQ는 그 사이의 판단을 자동화하고, 판단 결과를 조달 액션까지 연결한다.

스택

Python FastAPINext.jsPostgreSQL · pgvectorMCP 도구 20종Gemini 2.5 Flash자체 Agent LoopSSE
절대 원칙 — AI는 초안까지만 쓴다. 쓰기 도구 3종(create_po_draft·generate_disposal_document·create_repair_record)은 전부 draft INSERT만 가능하고 UPDATE 권한이 없다. 승인·반려·서명은 사람 전용 API만 한다 — 프롬프트가 아니라 도구 권한 수준에서 강제된다.

A2A 역할

크로스도메인 시나리오 대부분의 발신자(client)다. 설비에서 시작된 사건이 금융·보험으로 번져나가는 구조라, S5~S16의 출발점이 거의 항상 MaintQ다.

90.7%근거 페이지 인용률
0%미지 코드 환각
100%권한 위반 차단
금융:9001

FinAllQ

은행과 증권을 한 곳에 모은 개인화 금융 비서

무엇을 푸나

파편화된 은행(자산·보안) 데이터와 증권(투자) 데이터를 단일 대시보드로 통합하고, 대출 신청부터 이체까지 여신 업무를 자동화한다. 코어 트랜잭션은 Spring+RDBMS로, 모델 서빙은 Python 마이크로서비스로 격리한 하이브리드 MSA다 — 탐지는 빠르고 값싼 ML이, 설명은 그래프가 맡는다.

스택

Java 21 · Spring Boot 3PostgreSQL · RedisReact 19 · VitePython FastAPILangGraph 1.2Nginx
절대 원칙 — 사람 승인 없이는 돈이 움직이지 않는다. 자기결재를 RBAC로 금지하고, 이체는 RFC 6238 TOTP 2FA로 잠근다. 그래서 A2A로 TOTP 코드를 대신 제출하는 기능은 일부러 만들지 않았다 — 서비스 계정이 코드를 내면 2FA가 1FA로 붕괴한다.

A2A 스킬 7종

request-withdrawalassess-loanadvise-hedgeadvise-financingadvise-replacement-financingassess-used-equipment-loanrequest-settlement
1,567테스트 GREEN
0회귀
0LLM 호출 (규칙기반)
FinAllQ의 LLM 호출은 0건이다. 4개 레포 패키지를 전수 조사한 결과(2026-08-24), FinAllQ의 plan·synthesize 노드는 규칙기반·템플릿이고 FDS·스미싱 탐지도 scikit-learn 고전 기법뿐이었다. 금융 판정에 생성 모델을 쓰지 않겠다는 선택이 코드에 그대로 남아 있다 — 세 에이전트가 같은 방식으로 만들어졌다고 말하지 않기 위해 적어둔다.
실동작 데모

실제로 돌아가는 세 개의 홉

아래 캡처는 전부 2026-08-24에 실제 서비스를 상대로 찍은 것이다. 목업 응답이나 재현 영상이 아니라, 실 DB·실 인증·실 A2A 호출이 통과한 화면이다.

S5 request-withdrawal MaintQ :9003 → FinAllQ :9001

발주 승인 → 출금 요청 · 자금이동 2단 승인

진단(INV-L3-02 · OCT) → 발주 초안 → 팀장 승인 → 재무 승인 → 진단보고서·발주요청서·자금집행요청서 3종이 실 데이터로 렌더 → A2A 출금 요청 전송 → /manager/a2a 이력에서 CHAIN-PO-0122-b66672d7 · ok 확인까지 전 구간 라이브. 돈이 움직이는 요청이라 요청 부서 승인 → 집행 부서 재무 승인 2단 계단을 거치고, 그 뒤로도 FinAllQ 내부 결재함에서 사람이 한 번 더 결재한다.

단일 홉 lookup-clause MaintQ :9003 → InsuQ :9002

설비 손해가 약관상 보장되는지, 보험 에이전트에게 직접 묻는다

정비사가 채팅에 "인버터 과전압 손해가 약관상 보장되나?"라고 물으면 MaintQ가 InsuQ에 lookup-clause를 던지고, InsuQ는 근거 조항 8건을 인용하며 판정은 "판단 유보"로 돌려준다. 실측 9.7초 — 처음엔 에이전트 루프의 전역 타임아웃 10초가 병목이라 계속 끊겼고, 이 도구 하나에만 예외를 둬서 풀었다.

S8 assess-loan MaintQ :9003 → FinAllQ :9001 → InsuQ :9002

멀티홉 릴레이 — 요청자는 두 번째 홉을 모른다

5억원 대출 신청(담보 BLD-A, 인정액 3억) → FinAllQ가 자체 LTV 판정을 하면서 InsuQ에 담보 보험 충분성까지 확인 → 1.3초 만에 decision: conditional(보장 부족) 반환 → /manager/a2a에서 CHAIN-LOAN-a0639920 · ok. 5천만원으로 찍으면 무조건 approved만 나와서, 판정 로직이 실제로 동작한다는 걸 보여주려고 일부러 부결 나는 금액을 골랐다.

ID 시나리오 경로 성격 스킬
S5발주 승인 → 출금 요청MaintQ → FinAllQ자금이동 (2단 승인)request-withdrawal
S6환헤지 · 여유자금 운용 상담MaintQ → FinAllQ제안 (위험도 0)advise-hedge
S7화재보험 갱신 (고장이력 연계)MaintQ → InsuQ상담 + 데이터 연계advise-policy-renewal
S8담보 대출 심사 시 보험 검증MaintQ → FinAllQ → InsuQ멀티홉 릴레이assess-loan
S11설비 개조 · 폐기 자산변동 통지MaintQ → InsuQ원장 갱신 통지notify-asset-change
S13중고설비 담보대출 (비례보상)MaintQ → FinAllQ → InsuQ멀티홉 심사assess-used-equipment-loan
S14설비 사고 보험금 청구MaintQ → InsuQ청구 접수 · 조사claim-insurance
S15전손 교체 금융 상담MaintQ → FinAllQ연쇄 금융 제안advise-replacement-financing
S16신규 설비 도입 자금조달 비교MaintQ → FinAllQ비교 상담advise-financing
관통 원칙

세 도메인을 가로질러 똑같이 지켜지는 것

에이전트끼리 자율적으로 대화하게 두면, 사람이 책임지던 판단이 조용히 자동화된다. 이 세 가지는 그 일이 일어나지 않게 하려고 프로토콜 층에 못 박아 둔 규칙이다.

01

돈과 계약이 움직이는 곳에는 반드시 사람의 승인

AI 에이전트는 요청서 · 제안서 · 심사 초안까지만 작성한다. 실제 계좌 출금, 대출 실행, 보험 계약 체결은 반드시 승인권자의 결정 뒤에 실행된다. 자금이 이동하는 작업(S5)은 요청 부서 승인 → 집행 부서 재무 승인의 2단 승인 계단을 거친다.

A2A 응답의 input-required 상태가 이 대기를 표현한다 — 요청은 성공했지만 아직 끝나지 않았고, 상대 조직의 사람이 결재를 마칠 때까지 기다린다는 뜻이다.

02

누가 부르는가(Actor)와 누구의 데이터인가(Subject)를 분리한다

Actor는 인증의 문제다 — 비대칭 서명된 파트너 액세스 토큰으로 위조 불가능한 신원을 증명한다. Subject는 인가의 문제다 — 페이로드의 finallq_company_id, building_id, policy_id가 처리 대상을 명시한다. 둘을 섞으면 '인증된 상대가 아무 데이터나 만질 수 있는' 구멍이 생긴다.

자격증명은 위험도에 따라 3단계로 쪼갰다 — 조회/상담(위험도 0, 정보 조회만) · 심사(위험도 저, 판정은 하되 실행 권한 없음) · 자금이동(위험도 고, input-required 기반 2단 승인 필수).

03

멀티홉 여정 하나를 끝까지 따라간다

모든 A2A 요청·응답 헤더에 request_chain_id를 전파한다. MaintQ → FinAllQ → InsuQ로 이어지는 여정이 세 조직의 로그에 흩어지지 않고 단일 타임라인으로 묶여 감사 로그에 남는다. 위 데모의 CHAIN-LOAN-a0639920·CHAIN-PO-0122-b66672d7이 그 값이다.

여기에 Idempotency-Key 3중 복합키를 얹어 재시도가 중복 처리로 번지지 않게 했다.

설계와 현실이 어긋났을 때 문서가 아니라 구현을 고쳤다. 인증은 원래 Basic(client_id/secret) 실토큰 교환을 붙일 계획이었다. 그런데 상대 레포의 인증 필터를 직접 열어 대조해 보니, InsuQ·FinAllQ 양쪽 다 애초에 Authorization: Bearer + X-A2A-Partner-Id 자기신고 헤더만 검사하고 있었고 — FinAllQ→InsuQ 2차 홉은 이미 그 스킴으로 실 성공 중이었다. MaintQ 쪽 credentials.py·auth_header.py를 실제 스킴에 맞춰 다시 썼다.
측정된 것

달성한 것과, 아직 목표에 못 닿은 것

세 프로젝트 모두 평가 하네스로 baseline을 숫자로 고정한 뒤 개선했다. 미달 항목을 지우면 나머지 숫자도 못 믿게 되므로 그대로 둔다.

에이전트 지표 결과 목표 상태
InsuQ검색 성능 Hit@50.83330.85미달 (1문항)
InsuQ거부 정확도100%≥ 90%초과 달성
InsuQ오탐율 (환각)0%0%달성
InsuQ응답 레이턴시 p9510.4s≤ 30s달성
MaintQ근거 페이지 인용률90.7%100%미달
MaintQ부품 특정 정확률40.0%≥ 90%미달 (원천 데이터 문제)
MaintQ미지 에러코드 환각0%0%달성
MaintQ권한 위반 차단100%100%달성
FinAllQ테스트 (백엔드 957 · 프론트 480 · 어댑터 130)1,567전부 GREEN달성
FinAllQ회귀00달성
QMesh어댑터 테스트 (finallq_a2a · insuq_a2a)113전부 GREEN달성
기각된 개선도 기록으로 남겼다. InsuQ의 하이브리드 검색(BM25+cross-encoder 리랭커)은 개선을 입증하지 못해 기각했다. 넣으면 좋아 보이는 기법이라도 전/후 지표가 움직이지 않으면 넣지 않는다는 규칙을 그대로 적용한 결과다.
진행 상태

지금 도는 것과, 아직 설계인 것

데모에서 구현된 것과 아닌 것이 섞이면 안 된다. 아래는 2026-08-24 기준 실제 상태다.

계약 확정

Agent Card 3종 · Task JSON Schema 13종

세 에이전트의 스킬 선언서와 요청/응답 스키마가 QMesh 레포에 SSOT로 고정돼 있다. 어느 도메인도 이 계약을 자기 마음대로 바꿀 수 없다.

실동작

InsuQ A2A 스킬 5종 — 전부 구현 완료

verify-collateral-insurance · advise-policy-renewal · notify-asset-change · notify-risk-change · claim-insurance. 서비스 간 인증, request_chain_id 감사로그, Idempotency-Key 인프라, Task 생명주기 상태머신까지 포함. claim-insurancerequires_human_approval:true는 우회 불가능한 하드 리터럴이다.

실동작

FinAllQ 호출부 2종 — 종단 검증 완료

assess-loan은 InsuQ 2차 홉 호출까지 연결됐고, request-withdrawal은 실 서비스 계정(CORPORATE)으로 종단 실동작을 확인했다. 폴링이 결재대기와 TOTP필요를 pending_action으로 구분한다. 그래도 Loan.status는 여전히 UNDER_REVIEW — 자동승인은 없다.

실동작

MaintQ 발신 3경로 — 브라우저 실캡처

request-withdrawal(S5) · lookup-clause · assess-loan(S8 멀티홉). 위 데모 3종이 그 캡처다.

프로토타입

QMesh 레포 안의 어댑터 2종 — 테스트 113건

finallq_a2a(:9101, request-withdrawal·assess-loan) · insuq_a2a(:9102, lookup-clause). 각 도메인 레포에 얹기 전의 참조 구현이다.

다음

Orchestrator 본체(:9000) · maintq_a2a 어댑터 · 통합 docker-compose

지금은 세 에이전트가 서로 직접 호출한다. 중앙 오케스트레이터와 통합 기동 환경은 아직 설계 단계다 — README의 레포 구조에 적힌 orchestrator/·contracts/는 목표 구조이지 현재 상태가 아니다.

의도적 보류

A2A로 TOTP 코드를 대신 제출하는 기능

기술적으로는 만들 수 있다. 만들지 않았다 — CORPORATE 서비스 계정이 결재권자를 대신해 코드를 내면 2단계 인증이 1단계로 붕괴한다. 이건 못 만든 기능이 아니라 지킨 불변식이다.

자료

더 깊이 보려면

발표자료

QMesh 발표 슬라이드

A2A 계약 체결 메커니즘, 세 도메인 아키텍처, 시나리오 데모까지 정리한 발표 자료.

설계 문서

계약 · 신원 · 시나리오 명세

Agent Card 3종, Task JSON Schema 13종, 신원 식별 및 보안 아키텍처 결정서(SSOT), S5~S16 시나리오 상세 명세.

포트폴리오

최지현 — AI Agent 엔지니어

LangGraph 기반 에이전트 시스템 설계와 근거 있는 AI를 다뤄온 기록. 다른 프로젝트도 함께 있습니다.