데모에서는 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에서는 네 지점을 먼저 잠근다.
- Ingress: 사용자 요청 또는 배치 job이 root span을 만든다.
- A2A client: agent 위임 전
traceparent를 주입하고, 응답의taskId를 현재 span attribute로 기록한다. - A2A server: 수신한 context를 추출해 task 생성 span의 parent로 삼는다.
- 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 Protocol v1.0 공식 문서: https://a2a-protocol.org/v1.0.0/
- A2A specification, Task와
contextId/taskIdsemantics: https://a2a-protocol.org/dev/specification/ - MCP 2025-11-25 specification: https://modelcontextprotocol.io/specification/2025-11-25
- MCP
tools/call공식 문서: https://modelcontextprotocol.io/specification/2025-11-25/server/tools - MCP 2026 roadmap, transport scalability와 enterprise readiness: https://blog.modelcontextprotocol.io/posts/2026-mcp-roadmap/
- OpenTelemetry context propagation: https://opentelemetry.io/docs/concepts/context-propagation/
- OpenTelemetry specification overview: https://opentelemetry.io/docs/specs/otel/overview/
정리하면, A2A와 MCP를 붙이는 일은 프로토콜 연동이 아니라 실행 인과관계 설계다. task ID를 span에 붙이고, tool call을 child span으로 만들고, 비동기 재개를 link로 보존해야 운영자가 원인을 본다. 이 설계를 PoC 이후에 붙이면 늦다. agent 제품 설계 첫 스프린트에서 관측성 계약부터 고정해야 한다. AX LABS는 이 계약을 제품 구조와 운영 체계 안에 같이 심는다: AX Ops 방법론 →
