AX LABS
← 블로그 에이전트 제품 설계

Trace가 끊기면 agent는 멈춘다

A2A taskId와 MCP 호출은 같은 span 안에 있어야 한다

데모에서는 agent가 똑똑해 보인다. 운영에서는 질문이 달라진다. 어떤 agent가 어떤 task를 만들었는지, 그 task가 어떤 MCP tool을 불렀는지, 실패가 모델 판단인지 권한 문제인지 외부 API 지연인지 바로 보여야 한다. 이 선이 끊기면 장애 대응은 다시 채팅 로그 읽기로 돌아간다.

taskId는 traceId가 아니다

A2A의 taskId는 업무 단위의 식별자다. 서버가 새 Task를 만들 때 생성하고, 이후 상태 조회·구독·취소의 기준이 된다. contextId는 여러 task와 message를 같은 맥락으로 묶는다. 둘 다 중요하지만 OpenTelemetry의 traceId를 대체하지 않는다.

traceId는 실행 경로를 묶는다. spanId는 그 경로 안의 개별 작업을 가리킨다. 그래서 설계 원칙은 명확하다. A2A 식별자는 span attribute로 남기고, 실행 인과관계는 OpenTelemetry parent-child 관계와 link로 표현한다.

agent 관측성의 기준은 “무엇을 했는가”가 아니라 “누가 무엇을 시켜서 어디까지 갔는가”다.

계층 운영 식별자 OTel 표현 쓰임
사용자 요청 request id root span 전체 실행 시작점
A2A 위임 contextId span attribute 대화·업무 맥락 그룹
A2A 작업 taskId span attribute 또는 span name 일부 상태 추적·재시도·취소
MCP 호출 tools/call id, tool name child span 외부 시스템 호출 원인 분석
비동기 재개 polling, SSE, callback span link 부모 span이 끝난 뒤의 인과관계 보존

MCP tool call은 로그가 아니라 child span이다

MCP의 tools/call은 agent가 외부 시스템에 손을 뻗는 지점이다. DB 조회, 문서 검색, 티켓 생성, 사내 API 호출이 여기서 일어난다. 이 지점을 단순 로그로 남기면 지연 시간, 실패 위치, 재시도, 권한 거절이 한 화면에 묶이지 않는다.

권장 span 구조는 단순하다.

  • a2a.SendMessage 또는 a2a.SendStreamingMessage를 remote agent 호출 span으로 둔다.
  • 반환된 taskId, contextId, TaskState를 attribute로 붙인다.
  • agent 내부 계획·검증은 internal span으로 남긴다.
  • 각 MCP tools/call은 tool name을 가진 child span으로 남긴다.
  • tool 결과의 isError, JSON-RPC error, timeout, HITL 거절을 span status와 event로 분리한다.

여기서 중요한 것은 span 이름을 예쁘게 짓는 일이 아니다. attribute 키를 고정하는 일이다. 예를 들어 agent.protocol=a2a, a2a.task.id, a2a.context.id, mcp.method=tools/call, mcp.tool.name, tool.result.is_error 같은 내부 표준을 먼저 정한다. OpenTelemetry semantic convention이 모든 agent 프로토콜 세부까지 대신 정해주기를 기다리면 운영 설계가 늦어진다.

전파 규칙은 네 곳에서 고정한다

cross-agent trace context propagation은 코드 한 줄의 문제가 아니다. 경계마다 carrier가 다르다. HTTP 헤더가 있는 곳은 W3C traceparent를 쓴다. JSON-RPC payload나 비HTTP transport를 쓰는 곳은 조직 표준 metadata 위치를 정하고, 수신 측에서 추출한 뒤 업무 payload로 흘러가지 않게 제거한다.

AX Ops에서는 네 지점을 먼저 잠근다.

  1. Ingress: 사용자 요청 또는 배치 job이 root span을 만든다.
  2. A2A client: agent 위임 전 traceparent를 주입하고, 응답의 taskId를 현재 span attribute로 기록한다.
  3. A2A server: 수신한 context를 추출해 task 생성 span의 parent로 삼는다.
  4. MCP client/server: tools/call 직전 child span을 만들고, MCP server 내부의 실제 API·DB 호출까지 이어 붙인다.

비동기 task는 특히 조심해야 한다. SendMessage 응답이 끝난 뒤 task가 계속 돌면 parent span은 이미 닫혀 있다. 이때 억지로 긴 span 하나를 열어두지 않는다. task worker가 새 span을 만들고 원래 span context를 link로 연결한다. 그래야 queue, polling, SSE, push notification이 섞여도 시간축이 무너지지 않는다.

참고

정리하면, A2A와 MCP를 붙이는 일은 프로토콜 연동이 아니라 실행 인과관계 설계다. task ID를 span에 붙이고, tool call을 child span으로 만들고, 비동기 재개를 link로 보존해야 운영자가 원인을 본다. 이 설계를 PoC 이후에 붙이면 늦다. agent 제품 설계 첫 스프린트에서 관측성 계약부터 고정해야 한다. AX LABS는 이 계약을 제품 구조와 운영 체계 안에 같이 심는다: AX Ops 방법론 →