Hermes Agent에 A2A 배포하기: Docker Gateway 참조 스택
프로덕션에서 A2A를 실행한다는 것은 서로 통신하도록 설계되지 않은 두 프로토콜을 연결하고 — 토큰을 떨어뜨리지 않고 인증, 테넌트 격리 및 스트리밍을 처리하는 게이트웨이 뒤에서 그렇게 하는 것을 의미합니다. 이 참조 스택은 A2A 배포를 차단하는 세 가지 문제를 해결합니다: 프로토콜 변환(JSON-RPC 2.0에서 OpenAI 호환 chat completions), 스트리밍 조정(A2A 아티팩트 SSE에서 Hermes 실행 이벤트 SSE), 다중 테넌트 보안(모든 쿼리에서 PostgreSQL Row-Level Security). 다른 프레임워크의 에이전트가 서로 작업을 위임해야 하는 경우, 여기의 패턴이 배포 경로입니다.
핵심 요약
- 3계층, 1개 Docker Compose 스택 — SilvaEngine Gateway가 전송과 인증을 처리하고, A2A Daemon Engine이 프로토콜 로직을 처리하며, HermesAgentHandler가 Hermes Agent의 OpenAI 호환 API로 브리징합니다. docker-a2a-hermes-agent-gateway 저장소가 세 가지를 모두 패키징합니다.
- 한 포트에 5개 프로토콜 표면 — JSON-RPC 2.0, GraphQL, SSE 스트림, SSE 푸시, Agent Card 발견이 모두 포트 8765의 단일 게이트웨이 뒤에서 JWT 또는 AWS Cognito 인증과 함께 제공됩니다.
- PostgreSQL 행 수준 보안이 테넌트 격리를 강제 —
partition_key = "{endpoint_id}#{Part-Id}"복합 키가 4개 A2A 테이블 모두에 RLS 정책을 통해 데이터베이스 수준에서 강제되며, 애플리케이션 코드에서만 처리되지 않습니다. - A2A SDK v2의 단일 메시지 제약이 스트리밍 아키텍처를 형성 — 브리지는 토큰 청크를 SSE에 실시간으로 내보내고 스트림 완료 후 누적된 단일 Message를 SDK EventQueue로 내보내어
InvalidAgentResponseError를 회피합니다. - 5개 스크립트에 걸친 15개 E2E 테스트 검사 — 비스트리밍 스모크 테스트부터 HTTP 폴백 검증을 포함한 전체 SSE 스트리밍 파이프라인까지, 모두
pip install requests로 실행 가능합니다.
Agent2Agent Protocol (A2A)은 AI 에이전트가 JSON-RPC 2.0을 통해 서로를 발견하고, 위임하고, 작업을 스트리밍하는 방법을 정의합니다. Hermes Agent(Nous Research 제작)는 /v1/chat/completions에서 OpenAI 호환 API 서버를, /v1/runs와 /v1/runs/{id}/events에서 runs 기반 SSE 스트리밍 인터페이스를 노출합니다. 둘은 같은 언어를 사용하지 않습니다. A2A는 구조화된 파트와 함께 message/send를 보내고, Hermes는 채팅 완성 페이로드를 받습니다. A2A는 SSE를 통해 작업 아티팩트를 스트리밍하고, Hermes는 run 이벤트를 통해 토큰 델타를 스트리밍합니다.
docker-a2a-hermes-agent-gateway 저장소는 단일 컨테이너 이미지와 docker compose 스택에서 그 간극을 브리징합니다. a2a_daemon_engine 모듈만 등록된 SilvaEngine Gateway를 실행하여 전체 A2A 프로토콜 표면을 노출하고 HTTP + SSE를 통해 Hermes Agent API 서버 인스턴스로 A2A 작업을 브리징합니다. 상태는 테넌트 격리를 위한 행 수준 보안을 갖춘 번들 PostgreSQL 백엔드에 영속화됩니다.
이 글은 3계층 아키텍처, A2A 클라이언트에서 Hermes로 그리고 되돌아오는 요청 라이프사이클, 구성 및 배포 패턴, 그리고 프로덕션에서 Hermes Agent에 A2A를 운영하기 위한 운영 관심사를 매핑합니다. 이는 참조 배포 워크스루입니다 — 일반적인 패턴(임의 에이전트 프레임워크로의 게이트웨이 매개 A2A 브리지)이 주제이며, Docker 스택이 구현된 사례입니다.
3계층 아키텍처
이 스택은 관심사를 각각 별도 컴포넌트가 소유하는 세 계층으로 분리합니다:
게이트웨이가 유일한 상시 실행 서비스입니다. Hermes와 PostgreSQL 모두 프로파일 게이트 형제입니다 — 자체 완결형 스택을 위해 번들로 포함하거나, 프로파일을 끄고 HERMES_API_URL과 PG_HOST를 외부 인스턴스로 지정하세요. 이는 프로덕션에서 중요합니다: VPC에서 게이트웨이를 실행하고 관리형 Postgres(RDS, Cloud SQL)와 다른 GPU 노드에서 실행되는 Hermes 인스턴스를 가리키도록 할 수 있습니다.
계층 1: SilvaEngine Gateway — 전송과 인증
SilvaEngine Gateway는 설치된 모듈에 인증된 인-프로세스 접근을 제공하는 FastAPI 게이트웨이입니다. 구성 가능한 YAML 라우트 매니페스트를 통해 모듈 GraphQL과 REST 라우트를 노출합니다 — 새 모듈 추가는 매니페스트 변경만으로 가능하며 게이트웨이 Python 코드는 전혀 필요 없습니다. 이 스택에서는 A2A Daemon Engine만 등록되어 있습니다.
게이트웨이가 소유하는 것:
- 인증 —
GATEWAY_AUTH_PROVIDER로 선택되는 로컬 JWT(HS256) 또는 AWS Cognito(RS256 + JWKS) - 라우팅 — YAML 매니페스트가 URL 경로를 모듈 디스패치 함수로 매핑
- SSE 클라이언트 라이프사이클 —
sse_manager가 모듈별로 해결되고 장기 연결 클라이언트 연결을 관리 - 속도 제한 — IP당 인메모리 속도 제한(
GATEWAY_RATE_WINDOW초당GATEWAY_RATE_LIMIT요청) - 스레드 풀 디스패치 — 동기 모듈 디스패치 함수가 구성 가능한 스레드 풀에서 실행(
GATEWAY_DISPATCH_WORKERS, Docker 이미지에서 기본값 32)
게이트웨이는 URL 경로 세그먼트와 Part-Id 요청 헤더에서 partition_key = "{endpoint_id}#{Part-Id}"를 구성합니다. 모든 테넌트 범위 요청은 해당 헤더를 필요로 합니다. /{ep}/.well-known/agent-card.json의 Agent Card 엔드포인트는 A2A 스펙에 따라 공개(인증 없음)이지만, 카드가 파티션별로 해결되므로 여전히 Part-Id가 필요합니다.
계층 2: A2A Daemon Engine — 프로토콜 로직
a2a_daemon_engine은 독립 실행형 서비스가 아닙니다. main.py의 deploy()를 통해 등록된 게이트웨이 모듈로 로드되며, 세 개의 게이트웨이 대면 엔트리 포인트를 선언합니다:
| 엔트리 포인트 | 게이트웨이 라우트 | 메서드 | 목적 |
|---|---|---|---|
a2a_core_graphql |
POST /{ep}/a2a_core_graphql |
POST | 에이전트, 작업, 메시지, 설정에 대한 GraphQL CRUD |
a2a |
POST /{ep}/a2a |
POST | A2A JSON-RPC 프로토콜(message/send, tasks/get, tasks/cancel, tasks/list) |
sse_message |
POST /{ep}/a2a_sse |
POST | A2A JSON-RPC 메시지 + SSE 클라이언트로 푸시 |
게이트웨이는 추가로 SSE 스트림을 위해 GET /{ep}/a2a_sse를 노출합니다. 데몬은 자체 포트에서 수신하거나 프로덕션에서 자체 HTTP 서버를 실행하지 않습니다. 모든 전송, 인증, SSE 클라이언트 라이프사이클은 게이트웨이가 소유합니다.
데몬이 제공하는 것:
- A2A SDK v1.0 — 공식 A2A SDK 서버 패턴 기반의 HTTP를 통한 JSON-RPC
- 공개 Agent Card — ETag와 Last-Modified 지원을 포함한
/.well-known/agent-card.json - 작업 상태 머신 —
submitted→working→input-required|completed|failed|canceled - 이중 백엔드 영속성 — DynamoDB(PynamoDB) 또는 PostgreSQL(SQLAlchemy + Alembic). Docker 이미지는 PostgreSQL을 강제합니다.
- 다중 테넌트 격리 — PostgreSQL 행 수준 보안을 포함한 복합 파티션 키(
{endpoint_id}#{part_id}) - 플러그형 LLM 핸들러 — 에이전트 레지스트리의 에이전트별
module_name/class_name선택
계층 3: HermesAgentHandler — 브리지
Hermes 브리지 핸들러(a2a_daemon_engine/handlers/a2a_hermes_handler.py)는 유일한 프레임워크별 코드입니다. ask_model() 인터페이스를 구현합니다: A2A 메시지 파트와 컨텍스트를 수락하고, Hermes Agent API 서버를 호출하고, 응답을 다시 A2A 메시지 파트로 변환하며, 스트리밍의 경우 토큰 델타를 SSE 채널로 전달합니다.
핸들러는 두 가지 실행 모드를 지원합니다:
비스트리밍은 Hermes API 서버의 OpenAI 호환 엔드포인트인 POST /v1/chat/completions에 매핑됩니다. 요청은 변환된 A2A 메시지 파트를 채팅 완성 페이로드로 전달합니다. Hermes가 요청을 처리하고 단일 응답을 반환합니다. 핸들러는 응답을 ROLE_AGENT를 가진 A2A Message로 변환하고 SDK EventQueue로 내보냅니다. 클라이언트는 전체 에이전트 텍스트가 포함된 단일 JSON-RPC 응답을 받습니다.
스트리밍은 run을 생성하기 위해 POST /v1/runs에 매핑되고, 이후 GET /v1/runs/{id}/events로 SSE 연결을 엽니다. Hermes는 이벤트가 발생하는 대로 스트리밍합니다: 토큰 델타(message.delta), 추론 메타데이터(reasoning.available), 도구 호출/결과 알림, 승인 요청(approval.required), 라이프사이클 이벤트(run.created, run.completed, run.failed). 핸들러는 백그라운드 스레드에서 드레인 루프를 실행합니다. 각 message.delta 이벤트는 연결된 클라이언트에 실시간 전달을 위해 게이트웨이의 SSE 매니저로 푸시됩니다. run.completed가 도착하면 누적된 텍스트가 단일 A2A Message로 SDK EventQueue로 내보내집니다.
요청 라이프사이클
stream=true를 포함한 단일 message/send는 8단계를 거칩니다:
1. 클라이언트 POST /{ep}/a2a {jsonrpc, method:"message/send", params}
헤더: Authorization: Bearer *** Part-Id:
2. 게이트웨이 인증(로컬 JWT / Cognito) → routes.yaml에서 라우트 매칭
→ partition_key = "{ep}#{Part-Id}"
3. a2a_daemon dispatch_a2a → A2ADaemonExecutor
→ resolve_agent(): 에이전트 메타데이터(DB) > 설정 딕셔너리 > Config(환경변수)
4. 핸들러 HermesAgentHandler (A2A_AI_AGENT_MODULE / _CLASS)
5. Hermes POST {HERMES_API_URL}/v1/runs (Bearer HERMES_API_KEY)
GET {HERMES_API_URL}/v1/runs/{id}/events (SSE)
6. 브로드캐스트 토큰 청크 → GET /{ep}/a2a_sse의 구독자
7. 영속화 작업 + 메시지 PostgreSQL에 기록(a2a_* 테이블, RLS 범위)
8. 응답 누적된 응답은 HTTP JSON-RPC 결과에도 반환 8단계가 중요합니다: 스트리밍 중에도 HTTP 응답이 전체 응답을 전달합니다. SSE 프레임을 놓친 클라이언트도 HTTP 응답으로 폴백할 수 있습니다. E2E 테스트 스위트(test_hermes_sse_live.py)는 6단계에서 이 폴백을 명시적으로 검증합니다.
에이전트 해결 우선순위
구성 해결은 우선순위 체인을 따릅니다: 에이전트 메타데이터(DB) → 설정 딕셔너리 → Config 기본값(환경변수). 에이전트별 재정의가 전역 기본값보다 우선합니다. 두 에이전트가 다른 프레임워크를 가리킬 수 있습니다 — 추론 중심 작업에는 Hermes로, 워크플로 오케스트레이션 작업에는 다른 핸들러로 — 그리고 A2A 프로토콜 표면은 호출 에이전트에 동일하게 보입니다.
Hermes 기반 에이전트의 경우 a2a_agents 테이블에 저장된 메타데이터는 다음과 같습니다:
{
"module_name": "a2a_daemon_engine.handlers.a2a_hermes_handler",
"class_name": "HermesAgentHandler",
"hermes_api_url": "http://127.0.0.1:8642",
"hermes_api_key": "hermes-local-key",
"hermes_model": "hermes-agent",
"hermes_timeout": 300.0
}환경변수 기본값(HERMES_API_URL, HERMES_API_KEY, HERMES_MODEL)은 DB 에이전트 레코드 없이 브리지가 Hermes에 도달할 수 있게 합니다 — a2a_ai_agent_utility.py의 resolve_agent() 함수는 에이전트 레코드가 없을 때 환경변수로 폴백합니다. 즉, 에이전트를 등록하지 않고도 스택을 시작하고 message/send를 보낼 수 있으며, 환경 기본값을 사용해 Hermes로 라우팅됩니다.
A2A 상태 매핑
브리지는 Hermes SSE 이벤트를 A2A 작업 상태로 매핑합니다. 이 표가 브리지의 핵심입니다 — 모든 프레임워크 통합은 동등한 표를 생성합니다:
| Hermes SSE 이벤트 | A2A 작업 상태 | 브리지 동작 |
|---|---|---|
run.created(run_id 반환) |
WORKING |
취소 지원을 위해 run_id 등록 |
message.delta |
WORKING |
토큰 누적; 청크별 SSE로 내보냄 |
reasoning.available |
WORKING |
추론 메타데이터 — 토큰 내보냄 없음 |
tool.call / tool.result |
WORKING |
도구 실행 메타데이터만 |
approval.required |
INPUT_REQUIRED |
승인 청크 내보냄; pending_approval 저장 |
run.completed |
COMPLETED |
스트림 이벤트 설정; 최종 텍스트 누적 |
run.failed |
FAILED |
오류 청크 내보냄; FAILED 상태 설정 |
POST /v1/runs/{id}/stop |
CANCELED |
tasks/cancel을 통한 외부 취소 |
POST /v1/runs/{id}/approval |
(run 계속) | operation="approval_response"로 해결 |
a2a_daemon_engine 저장소의 HERMES_INTEGRATION.md 문서가 정확한 Hermes 이벤트 형식, 구성 키, 종단 간 흐름 세부 사항을 기록합니다.
에이전트 경계를 넘어선 인간 개입 승인
Hermes는 인간 개입 승인 게이트를 지원합니다 — 에이전트가 민감한 작업을 실행하기 위해 권한이 필요할 때 일시 중지하고 승인 요청을 내보냅니다. 브리지는 이를 A2A INPUT_REQUIRED 상태로 변환하여, 호출 에이전트(또는 인간 운영자)에게 입력이 필요함을 알립니다. 응답은 POST /v1/runs/{id}/approval을 통해 돌아오고 run이 계속됩니다.
여기서 브리지 패턴의 가치가 드러납니다. A2A는 INPUT_REQUIRED을 일급 작업 상태로 정의합니다. Hermes는 자체 승인 메커니즘을 가지고 있습니다. 브리지가 하나를 다른 것으로 매핑하며, 호출 에이전트 — 완전히 다른 프레임워크에서 실행되는 A2A 클라이언트 자체일 수도 있습니다 — 는 Hermes별 세부사항이 아닌 표준 프로토콜 상태 전환을 봅니다. 에이전트 위임 체인은 인간 승인이 필요한 단계(구매 승인, 데이터 접근 결정, 견적 승인)를 포함할 수 있으며, A2A 프로토콜이 그 게이트를 프레임워크 경계를 넘어 투명하게 전달합니다.
A2A SDK v2 제약과 이중 경로 수정
A2A SDK v2(a2a-sdk==1.0.2)는 on_message_send 경로에 두 가지 제약을 부과하여 브리지 구현을 형성했습니다:
- 단일 Message만. SDK EventQueue에 여러
Message객체를 내보내면InvalidAgentResponseError: Multiple Message objects received.가 발생합니다. - TaskStatusUpdateEvent 불가. 상태 이벤트는
InvalidAgentResponseError: Received TaskStatusUpdateEvent in message mode.를 발생시킵니다.
단순한 브리지는 토큰 델타마다 하나의 Message를 내보낼 것입니다 — 자연스러운 스트리밍 패턴입니다. SDK는 이를 거부합니다. 또한 message/send 경로에서 상태 이벤트(WORKING, COMPLETED)도 거부합니다.
수정은 이중 경로 출력 채널입니다:
- SSE(게이트웨이 관리): 토큰 청크가 실시간으로 SSE로 푸시됩니다. 연결된 클라이언트는 발생하는 대로 스트리밍 출력을 봅니다. 상태 이벤트(WORKING, COMPLETED, FAILED)도 SSE로만 갑니다.
- SDK EventQueue: 스트림 완료 후 전체 응답 텍스트가 포함된 단일 누적
Message가 SDK EventQueue로 내보내집니다. 이것이 JSON-RPCmessage/send응답이 반환하는 것입니다.
클라이언트는 SSE를 통해 실시간 스트리밍을, SDK를 통해 깔끔한 단일 메시지 JSON-RPC 응답을 받습니다. 두 채널 모두 작동하며 어느 쪽도 SDK 제약을 위반하지 않습니다.
한 포트의 프로토콜 표면
게이트웨이는 단일 포트(기본 8765)에 5개 프로토콜 표면을 노출합니다:
| 프로토콜 | 라우트 | 인증 | 목적 |
|---|---|---|---|
| GraphQL | POST /{ep}/a2a_core_graphql |
예 | A2A 코어 쿼리/뮤테이션(에이전트, 작업, 메시지, 설정) |
| JSON-RPC 2.0 | POST /{ep}/a2a |
예 | A2A 프로토콜: message/send, tasks/get, tasks/cancel, tasks/list |
| SSE(스트림) | GET /{ep}/a2a_sse |
예 | 파티션별 장기 A2A 작업 이벤트 스트림 |
| SSE(푸시) | POST /{ep}/a2a_sse |
예 | JSON-RPC 메시지 + 연결된 SSE 클라이언트로 푸시 |
| Agent Card | GET /{ep}/.well-known/agent-card.json |
공개 | A2A 발견 문서(Part-Id 헤더 여전히 필요) |
테스트 하니스가 사용하는 JSON-RPC message/send 매개변수 형태:
{
"message": {
"role": "ROLE_USER",
"parts": [{ "text": "Say hello from A2A" }]
},
"metadata": {
"operation": "task_execution",
"agent_uuid": "a2a-hermes-agent",
"stream": true,
"task_data": { "task_id": "my-task-001", "task_type": "hermes_test" },
"system_prompt": "You are a concise assistant.",
"conversation_history": []
}
}metadata.operation 필드가 실행 경로를 선택합니다: 스트리밍 지원을 포함한 에이전트 run은 task_execution, 비스트리밍 채팅은 message_response. agent_uuid는 레지스트리의 특정 에이전트를 대상으로 합니다. stream 플래그는 SSE 브로드캐스팅을 활성화합니다. task_data.task_id는 tasks/get과 tasks/cancel이 사용하는 호출자 제공 ID입니다.
SSE는 작업별이 아닌 파티션별입니다. {ep}#{Part-Id}의 모든 작업이 해당 파티션의 모든 구독자에게 브로드캐스트됩니다. 작업 순서가 중요합니다: 메시지를 보내기 전에 SSE 리스너를 연결하세요, 그렇지 않으면 초기 토큰 청크가 누락됩니다.
영속성과 다중 테넌시
Docker 이미지는 db_backend=postgresql을 강제합니다 — DynamoDB는 지원되지 않습니다. 데몬은 리터럴, 접두사 없는 테이블 이름을 사용합니다:
| 테이블 | 보관 |
|---|---|
a2a_agents |
에이전트 레코드 + 에이전트별 핸들러/모델 메타데이터 |
a2a_tasks |
작업 라이프사이클 + 상태 |
a2a_messages |
작업별 메시지 턴 |
a2a_settings |
파티션별 설정 딕셔너리 |
이름이 접두사가 없으므로 같은 이름을 사용하는 다른 모듈과 PG_DB를 공유하지 마세요.
테넌트 격리는 PostgreSQL 행 수준 보안을 사용합니다. 세션 변수 app.tenant_id가 요청의 partition_key("{endpoint_id}#{Part-Id}")로 설정되고, RLS 정책이 모든 쿼리를 해당 값으로 범위 지정합니다. 테이블과 정책은 initialize_tables=1일 때 게이트웨이 시작 시 자동 생성됩니다. 즉, 애플리케이션 코드에서 잊힌 partition_key 필터가 크로스 테넌트 행을 누출할 수 없습니다 — 데이터베이스가 경계를 강제합니다.
RLS 구현은 a2a_daemon_engine/utils/rls.py(set_rls_context와 create_rls_policies)와 마이그레이션 0005_enable_rls_policies에 있습니다. set_rls_context 함수가 연결에서 요청별 SET app.tenant_id를 실행하고, create_rls_policies가 4개 A2A 테이블 모두에 tenant_isolation 정책으로 RLS를 활성화하고 강제합니다. RLS는 DynamoDB 모드에서 비활성입니다.
Docker Compose로 배포
이 스택은 하나의 상시 실행 서비스와 두 개의 선택적 프로파일 게이트 형제를 가집니다:
| 서비스 | 컨테이너 이름 | 상시 실행? | 프로파일 | 목적 |
|---|---|---|---|---|
a2a-gateway |
a2a-hermes-gateway |
예 | — | SilvaEngine Gateway(A2A 전용 라우트) + Hermes 브리지 |
postgres |
a2a-postgres |
선택 | postgres |
번들 PostgreSQL 영속성 백엔드 |
hermes |
container-hermes |
선택 | hermes |
번들 Hermes Agent(OpenAI 호환 API + 대시보드) |
COMPOSE_PROFILES가 두 형제에 대한 단일 스위치입니다:
| 값 | 시작되는 서비스 |
|---|---|
| 비어 있음 | 게이트웨이만(외부 Postgres + 외부 Hermes) |
postgres |
게이트웨이 + 번들 Postgres |
hermes |
게이트웨이 + 번들 Hermes(외부 Postgres) |
postgres,hermes |
게이트웨이 + 번들 Postgres와 Hermes(기본값) |
형제가 번들로 포함될 때, 호스트 참조를 서비스 이름으로 유지하세요: PG_HOST=postgres와 HERMES_API_URL=http://hermes:<API_SERVER_PORT>. 형제가 외부일 때, 자체 인스턴스로 지정하세요(예: PG_HOST=host.docker.internal, HERMES_API_URL=http://host.docker.internal:8642).
빠른 시작
cp .env.example .env
# 입력: JWT_SECRET_KEY, ADMIN_PASSWORD, API_SERVER_KEY,
# HERMES_API_KEY(= API_SERVER_KEY), HERMES_MODEL_PROVIDER + 제공자 키, HERMES_MODEL
mkdir -p www/hermes www/projects
DOCKER_BUILDKIT=1 docker compose build
docker compose up -d # COMPOSE_PROFILES=postgres,hermes가 기본값
docker compose ps # (healthy) 대기
curl -f http://localhost:8765/health
pip install requests
python test_hermes_hello.py # 종단 간 스모크 테스트silvaengine_gateway와 a2a_daemon_engine 모두 git에서 이미지에 pip 설치됩니다(호스트 소스 마운트 없음). 이미지는 일반적이고 완전히 환경 기반입니다 — 시크릿이 포함되지 않습니다. 모듈은 git+https를 통해 ideabosque의 공개 GitHub 저장소에서 복제됩니다 — 자격 증명이나 SSH 배포 키가 필요 없습니다.
.env 인라인 주석 함정
Docker Compose의 env_file 파서는 인라인 주석을 제거하지 않습니다. 다음과 같은 줄은:
HERMES_API_KEY=hermes-local-key # token for HermesHERMES_API_KEY를 리터럴 문자열 hermes-local-key # token for Hermes(주석 포함)로 설정하여, 조용히 인증을 깨뜨립니다. 규칙: KEY=value 줄의 값 뒤에 아무것도 두지 마세요. 메모는 변수 위의 자체 # 주석 줄에 두세요.
검증: 5개 스크립트에 걸친 15개 E2E 검사
이 스택은 독립형 Python 테스트 하니스를 제공합니다(유일한 종속성: requests). ./.env를 로드하고, 게이트웨이 JWT를 해결하거나 발행하고, 실행 중인 스택과 통신합니다:
| 스크립트 | 종류 | 수행 작업 |
|---|---|---|
test_hermes_hello.py |
스모크 | 비스트리밍 message/send, 응답 출력 |
test_hermes_hello_sse.py |
스모크 | 한 프롬프트를 SSE로 스트리밍하여 반환 |
test_hermes_gateway_live.py |
E2E 스위트 | 9개 검사: Hermes 헬스, 게이트웨이 헬스, 에이전트 카드, GraphQL 핑, message/send, tasks/get, tasks/list, tasks/cancel, 실패 경로 |
test_hermes_sse_live.py |
E2E 스위트 | 6개 검사: 헬스 x2, SSE 연결, 실시간 토큰 청크, COMPLETED 상태, HTTP 폴백 |
test_hermes_chatbot.py |
인터랙티브 | 실시간 SSE 스트리밍을 포함한 A2A 표면에 대한 REPL |
모든 비인터랙티브 스크립트는 단계별로 PASS/FAIL을 출력하고 실패 시 0이 아닌 종료 코드를 반환하여, CI 게이트로 작동합니다. 단위 테스트 스위트(test_hermes_handler.py)는 httpx.MockTransport를 통해 모킹된 HTTP로 24개 테스트를 실행합니다 — 서비스가 필요 없습니다.
운영 패턴
재빌드 없는 라우트 변경
routes.yaml이 컨테이너에 읽기 전용으로 바인드 마운트됩니다. 호스트 파일을 편집하고 게이트웨이 프로세스를 재시작하세요 — 재빌드가 필요 없습니다:
make restart업스트림 변경 후
silvaengine_gateway와 a2a_daemon_engine가 빌드 시 git에서 pip 설치되므로, 업스트림 변경은 git 레이어가 최신 @main을 다시 복제하도록 --no-cache로 재빌드가 필요합니다:
DOCKER_BUILDKIT=1 docker compose build --no-cache
docker compose up -d --force-recreate버전 고정이 없습니다 — @main은 움직이는 대상입니다. 재현성이 필요하면 requirements-modules.txt에 태그나 커밋을 고정하세요.
한 워커 이상으로 스케일링
인메모리 작업 상태, 속도 제한 카운터, SSE 클라이언트 레지스트리는 프로세스별입니다. GATEWAY_WORKERS > 1일 때, 공유 백엔드로 전환하세요(GATEWAY_TASK_BACKEND=dynamodb, GATEWAY_RATE_LIMIT_BACKEND=dynamodb, 추가로 region_name과 aws_* 자격 증명)하고 SSE에 sticky 세션을 사용하세요. 기본 구성은 하나의 Uvicorn 프로세스를 시작합니다.
변경해야 할 보안 기본값
JWT_SECRET_KEY=change-me-in-production—openssl rand -hex 32로 교체ADMIN_PASSWORD=change-me— 실제 비밀번호로 교체POSTGRES_PASSWORD=silvaengine— 실제 비밀번호로 교체GATEWAY_CORS_ORIGINS=*는 자격 증명 없이 모든 출처를 허용합니다. 쿠키/자격 증명이 필요하면 명시적 목록을 설정하세요.- 번들 Hermes는 호스트 Docker 소켓(
/var/run/docker.sock)을 마운트합니다. 이는 해당 컨테이너 내부의 모든 것에 대해 사실상 호스트의 root입니다. 제어하는 호스트에서만hermes프로파일을 실행하고, 에이전트가 컨테이너를 시작할 필요가 없으면 마운트를 제거하세요. - Hermes 대시보드는 기본적으로 포트 9119에서 빈 basic-auth 자격 증명으로 활성화됩니다. 호스트를 노출하기 전에
HERMES_DASHBOARD_BASIC_AUTH_*를 설정하거나 포트를 localhost에 바인드하세요. - RLS가 테넌트 경계입니다. 임의
Part-Id를 설정할 수 있는 호출자는 해당 파티션의 데이터를 읽습니다 — 이 앞에 두는 모든 프론트엔드에서Part-Id를 인증 관련 입력으로 취급하세요.
이것이 가능하게 하는 것
3계층 스택은 처음부터 조립하기 어려운 세 가지 역량을 제공합니다:
1. Hermes 재작성 없는 A2A 프로토콜 준수. 모든 A2A 클라이언트가 Agent Card를 통해 Hermes 기반 에이전트를 발견하고, message/send로 작업을 보내고, SSE로 응답을 스트리밍하고, 표준 A2A 상태로 작업 라이프사이클을 추적할 수 있습니다. 클라이언트는 원격 에이전트가 Hermes를 실행한다는 것을 모릅니다 — JSON-RPC 인터페이스를 가진 A2A 엔드포인트로 보입니다.
2. 하나의 게이트웨이에서 다중 테넌트 에이전트 서빙. Part-Id 헤더와 PostgreSQL RLS의 결합은 하나의 게이트웨이 인스턴스가 하드 데이터베이스 수준 격리로 여러 테넌트를 서빙함을 의미합니다. 각 테넌트는 자체 에이전트 레지스트리, 작업 기록, 메시지 저장소를 갖습니다 — 모두 partition_key로 범위 지정된 동일한 4개 테이블에 있습니다.
3. 에이전트 경계를 넘어선 인간 개입 승인. Hermes 승인 게이트가 A2A INPUT_REQUIRED 상태로 매핑됩니다. 에이전트 위임 체인이 인간 승인이 필요한 단계를 포함할 수 있으며, A2A 프로토콜이 해당 상태 전환을 원래 에이전트나 운영자에게 되돌려 전달합니다 — 체인의 각 에이전트가 어떤 프레임워크에서 실행되든 관계없이.
참조 구현은 또한 서버리스 A2A를 위한 AWS Lambda 디스패치, 양방향 스트리밍을 포함한 실험적 gRPC 전송, 이중 백엔드 영속성(DynamoDB 또는 PostgreSQL)을 지원합니다. a2a_daemon_engine 저장소와 Hermes 통합 가이드가 전체 구현, 구성 참조, 상태 매핑 세부 사항을 포함합니다. SilvaEngine Gateway 저장소가 라우트 매니페스트 시스템, 인증 제공자, 모듈 자동 초기화를 문서화합니다.
관련 읽기
- MCP + A2A: 모든 프로덕션 에이전틱 AI 시스템 뒤의 두 프로토콜 — 2계층 프로토콜 스택에서 MCP(에이전트에서 도구로)와 A2A(에이전트에서 에이전트로)의 상호 보완적 역할
- 기존 에이전트 프레임워크와 A2A 통합: Hermes Agent 시연 — 일반 브리지 패턴과 Hermes 이외의 OpenClaw 및 다른 프레임워크에의 적용
- MCP 모듈 코드 표준 — 프로덕션 준비 에이전트 모듈을 위한 구조적 패턴, A2A 핸들러 코드에도 적용 가능
중견 유통업체는 카탈로그 에이전트와 대화하는 견적 에이전트가, 인벤토리 에이전트와 대화하는 카탈로그 에이전트가 필요합니다 — 각각 다른 프레임워크로 뒷받침되고, 각각 다른 팀이 소유합니다. A2A가 그 에이전트들에게 공유 프로토콜을 제공합니다. 브리지 계층은 Hermes Agent가 내부를 재작성하지 않고 참여할 수 있게 합니다. docker-a2a-hermes-agent-gateway가 그 브리지를 PostgreSQL 영속성, RLS 다중 테넌시, 15개 E2E 테스트 검사를 포함한 단일 컨테이너 이미지로 패키징합니다.
범위 지정 빌드 요청
1주 발견. 시스템 인벤토리, 워크플로 맵, 고정 범위를 받습니다 — 우리와 함께 빌드하든 아니든.
귀하의 시스템을 위해 이것을 구축하고 싶으신가요?
여기의 각 문서는 실제 프로덕션 작업에서 나왔습니다. 대상 시스템과 워크플로가 있다면, 1주 내에 빌드를 범위 정의할 수 있습니다.
범위 정의 빌드 요청1주 발견. 시스템 인벤토리, 워크플로 맵, 고정 범위를 받습니다 — 우리와 빌드할지 여부와 관계없이.