본문으로 건너뛰기

daiops A2A 연결 가이드

외부 에이전트/오케스트레이터에서 daiops 직원(워크스페이스)을 표준 A2A(Agent2Agent) 에이전트로 호출하는 방법. (AGENT-API-3 Phase B)

REST 면(트리거·SSE 챗)은 GET /api/v1/openapi로 문서화되며, A2A 면은 JSON-RPC 2.0이라 이 문서로 별도 안내한다(MCP 서버와 동일한 이유).

A2A는 MCP 서버(Phase A)와 나란한 두 번째 프로토콜 파사드다. 실행은 기존 v1 능력(SSE 챗·트리거)에 매핑된다.

1. 디스커버리 (Agent Card)

daiops는 멀티테넌트라 A2A Agent Card가 직원(워크스페이스)별이다. 표준 루트 /.well-known/agent.json(단일 origin)로는 특정 직원을 표현할 수 없어 워크스페이스 스코프 경로로 노출한다.

GET /api/v1/workspaces/{workspaceId}/agent-card   # 무인증 공개
  • 카드의 url 필드가 A2A JSON-RPC 서비스 엔드포인트(/a2a)를 가리킨다.
  • skills는 직원-facing 능력(chat_with_employee·run_task·query_wiki)이며 MCP 카탈로그와 단일 소스로 일치한다.
  • capabilities.streaming=true(message/stream), pushNotifications=false(A2A push 미구현 — 결과 push가 필요하면 REST 트리거의 callback_url을 사용).

2. 서비스 엔드포인트

POST /api/v1/workspaces/{workspaceId}/a2a   # JSON-RPC 2.0
  • Base URL: https://daiops.com
  • Content-Type: application/json

지원 메서드(A2A 0.2.x + 초안 별칭):

메서드별칭동작
message/sendtasks/send블로킹. 최종 Task 반환.
message/streamtasks/sendSubscribeSSE. status-update/artifact-update 스트림.
tasks/get세션 상태 조회 → Task.
tasks/cancel진행 중 세션 즉시 중지 → Task{ state:"canceled" }. 진행 중이 아니면 -32002. (§8 참조)

3. 인증

Bearer API 키(dai_live_…)를 사용하며 a2a 스코프가 필요하다. Settings → API 키에서 발급.

Authorization: Bearer dai_live_xxxxxxxx
  • rate limit: 키 단위 분당 60회 초과 시 429(+Retry-After).
  • quota: 실행 경로(message/send·message/stream)에 사용량 한도 게이트 적용. tasks/get은 무비용.
  • Agent Card(GET /agent-card)는 무인증 공개(디스커버리).

라이프사이클(직원 상태) · 에러 코드

직원은 유휴 시 자동 퇴근(중지)하고 장기 미사용 시 휴직(archived, 컴퓨트 삭제)한다. message/send·message/stream은 상태에 따라:

  • 퇴근(stopped): 자동 출근(auto-wake) 후 실행 — 첫 이벤트가 수 초~수십 초 지연될 수 있다.
  • 휴직(archived): 자동 복귀하지 않음. -32013(HTTP 409, "on leave — 복귀 필요"). 복귀(revive)는 소유자가 수행.
  • 자동 출근 꺼짐(external_auto_wake=false): -32014(HTTP 409, "수동 출근 필요").
  • 깨우는 중/일시 불가: -32015(HTTP 503). 잠시 후 재시도.
  • 활발히 호출되는 직원은 자동 퇴근되지 않는다(호출이 활동 타이머를 갱신).

에러 코드 규약

표준/A2A 예약 코드는 스펙 의미로만 쓰고, daiops 도메인 에러는 예약 구간(-32001-32006)을 침범하지 않도록 서버 정의 구간(-32010)에 둔다. error.data.reason으로 프로그램 분기를 보조한다.

코드의미
-32700Parse error (malformed JSON)
-32600Invalid Request (배치 미지원·스키마 불일치)
-32601Method not found (미지원 RPC 메서드)
-32602Invalid params (필수 파라미터 누락)
-32603Internal error (서버 내부 결함)
-32001Task not found (tasks/get·tasks/cancel·이어쓰기 대상 세션 부재)
-32002Task not cancelable (tasks/cancel 대상이 진행 중이 아님)
-32010인증 실패 (HTTP 401/403)
-32011Rate limit 초과 (HTTP 429, Retry-After 헤더)
-32012워크스페이스(직원) 없음
-32013휴직(on leave) — 복귀 필요
-32014자동 출근 꺼짐 — 수동 출근 필요
-32015일시 불가(깨우는 중 등) — 재시도 대상
-32016사용량 한도 초과
-32017세션 시작 실패. 재시도 가능 케이스error.data.code로 구분: WORKSPACE_BUSY(HTTP 503, 동시 실행 상한 초과)·RUN_ACTIVE(HTTP 409, 같은 세션 실행 진행 중). 이때 error.data.retryAfterSeconds와 HTTP Retry-After 헤더가 재시도 대기를 안내한다.

멱등: message/send는 at-least-once다. 네트워크 재시도 시 같은 messageId라도 중복 실행될 수 있으므로, 호출자는 필요 시 자체 중복 제거를 둔다.

4. Task 모델

  • Task id = 내부 세션 id(Task/세션 1:1). message.taskId로 기존 Task를 이어쓰고, caller_id(또는 message.contextId)로 호출자별 세션을 자동 재사용한다.
  • contextId: message/send·message/stream은 호출자가 보낸 message.contextId를 응답 Task·이벤트에 그대로 에코한다(미지정 시 taskId). 단 tasks/get은 원 contextId를 보관하지 않아 taskId로 반환한다.
  • 입출력 파일(FilePart): 텍스트 파트와 함께 file(uri 또는 base64 bytes)를 주고받는다. Agent Card defaultInputModes/defaultOutputModesapplication/octet-stream으로 선언. 파일 크기: uri(원격 URL)는 daiops가 서버측에서 당겨오므로 50MB까지 가능하고, bytes(base64 인라인)는 요청 바디에 실려 배포 요청 바디 한도(Vercel ~4.5MB)에 종속된다 — 대용량은 도달 가능한 URL이면 uri를 쓴다(bytes는 호스팅 수단이 없거나 daiops가 URL에 도달할 수 없는 경우의 경로). 초과·미지원 등으로 처리되지 못한 첨부는 조용히 버려지지 않고 응답 텍스트 말미에 드롭 사유로 통보된다.
  • Task state 매핑: thinking→working, plan_pending/secret_pendinginput-required, completed→completed, error/failed→failed.

세션(Task) 목록 조회 — A2A엔 없음(의도적), 크로스-프로토콜로 확보

A2A는 표준 규격상 세션(Task) 목록 메서드가 없다(tasks/get은 id를 이미 알아야 하는 단건 조회). daiops도 표준 준수를 위해 A2A에 비표준 list 메서드를 추가하지 않는다. 이어쓸 기존 세션의 id는 다음 크로스-프로토콜 경로로 얻는다(모두 같은 chat_sessions.id = A2A taskId):

  • REST: GET /api/v1/workspaces/{id}/sessions?channel=a2a (sessions:read 스코프) — 반환 items[].idmessage.taskId로 넘긴다.
  • MCP: list_sessions 도구 (mcp:read 스코프).
  • 자기 세션만 안정 재사용하면 되는 소비자는 목록 없이 caller_id(또는 message.contextId)로 자동 재사용해도 된다.

웹 뷰어: A2A 세션은 channel='a2a'로 기록되어 daiops 웹 통합 스레드 리스트/사이드바(A2A 진입점)에 read-only thread로 노출된다(REST=api, MCP=mcp와 구분).

5. message/send (블로킹)

curl -sX POST "$BASE/api/v1/workspaces/$WS/a2a" -H "$AUTH" -H 'Content-Type: application/json' -d '{
  "jsonrpc":"2.0","id":1,"method":"message/send",
  "params":{"message":{"kind":"message","role":"user","parts":[{"kind":"text","text":"이번 주 요약"}]}}
}'

응답 resultTask(kind:task). 완료면 status.state="completed" + artifacts에 응답 텍스트/구조화 결과.

응답시간 바운딩 (선택 — 짧은 콜백 창용)

카카오톡·슬랙 콜백처럼 회신 창이 짧은(≈1분) 연동은 params에 실행 예산을 실어 예측가능한 지연을 얻는다. message/send·message/stream·MCP chat_with_employee 공통.

파라미터범위동작
max_turns1~50에이전트 도구 루프 최대 turn. 낮게 주면 반복을 조기 종료. 미지정 시 50
max_response_seconds5~770벽시계 예산(초). 초과 시 러너 실행을 멈추고 종료 — 생성된 부분은 세션에 영속돼 tasks/get으로 회수. 미지정 시 모델 기본
# 45초 예산·최대 6 turn으로 제한한 message/send
curl -sX POST "$BASE/api/v1/workspaces/$WS/a2a" -H "$AUTH" -H 'Content-Type: application/json' -d '{
  "jsonrpc":"2.0","id":1,"method":"message/send",
  "params":{
    "message":{"kind":"message","role":"user","parts":[{"kind":"text","text":"이번 주 요약"}]},
    "max_response_seconds":45, "max_turns":6
  }
}'

범위 밖 값은 클램프된다(0·음수·비수치는 미지정으로 간주 = 기존 동작). 마감 초과 시 부분 텍스트를 강제로 반환하지 않고 "아직 처리 중" 신호를 준다 — 짧은 콜백 창에서는 이 신호를 받고 별도 채널(알림톡 등)로 지연 답변을 보내는 구성을 권장한다.

6. message/stream (SSE)

같은 body에 method:"message/stream". 응답은 text/event-stream이며 각 data:는 JSON-RPC 응답 봉투로 감싼 A2A 이벤트다:

  • status-update(state=working) — 진행/도구 사용
  • artifact-update(append) — 응답 텍스트 델타(artifactId="response")
  • artifact-update(lastChunk) — 구조화 결과(artifactId="structured")
  • status-update(final=true) — completed / failed / input-required

각 이벤트의 id:는 agent-runner seq다. 끊기면 표준 Last-Event-IDGET /api/v1/workspaces/{id}/sessions/{sessionId}/events에 재접속해 이어받는다.

7. input-required (결재/시크릿) 회신

에이전트가 위험 도구(결재) 또는 시크릿을 요구하면 status-update{ state:"input-required", final:true }가 오고, status.message.metadata에 회신 URL이 실린다:

  • 결재: { kind:"approval", approval_id, decide_url }decide_url{ "decision":"allow"|"deny" } POST(Bearer chat 스코프).
  • 시크릿: { kind:"secret", key_name, approval_id, secret_url }secret_url{ "action":"provide", "value":"…" } 또는 { "action":"skip" } POST(Bearer secret 스코프).

회신 후 실행은 자동 재개되며, 이어지는 이벤트는 sessions/{sessionId}/events(SSE resume)로 이어받는다. 상호작용 결재 흐름은 message/stream을 사용한다 — 블로킹 message/send는 결재 해소까지 대기한다.

8. 보급 상황 · 우선순위 (B2 재확인)

A2A는 2025년 표준화가 진행 중인 신생 프로토콜이다(Linux Foundation 이관). 현재 daiops 소비자(주로 Lattice·내부 서비스)는 REST 트리거와 MCP 서버로 충분히 커버된다. 본 Phase B는 표준 상호운용 구현(Agent Card + message/send·stream + tasks/get + tasks/cancel + input-required 배선 + FilePart 입출력)이다.

구현됨(Phase B 후속):

  • tasks/cancel — 진행 중 세션을 즉시 중지(in-process abort + runner 취소 + DB aborted). { "id": "<taskId>" } params, 결과 Task{ state:"canceled" }. 진행 중이 아니면 -32002.
  • FilePart 입출력 — 인바운드: message.parts{ kind:"file", file:{ name, mimeType, uri } }(원격 URL, 50MB) 또는 { …, bytes:"<base64>" }(인라인, 요청 바디 한도 ~4.5MB — 대용량은 uri 권장). 텍스트 지시와 함께 보내면 파일을 샌드박스에 배치해 에이전트가 읽는다(처리 실패분은 응답에 드롭 사유로 통보). 아웃바운드: 에이전트가 만든 산출물은 artifactId:"files" 아티팩트의 FilePart(file.uri = 7일 signed URL)로 반환된다(message/send는 최종 Task, message/stream은 completed 직전 artifact-update).

실제 수요가 확인되면 확장할 항목:

  • tasks/pushNotificationConfig(webhook 등록) — Agent Card capabilities.pushNotifications:false로 미지원 명시.
  • 루트 /.well-known 디렉토리형 디스커버리(다중 직원 카탈로그).

수요 미확인 시 현 구현을 유지한다(과투자 방지).