A2A protocol hub that registers agents, routes messages between them, and serves a unified agent card for discovery.
Claude Code ──MCP──→ a2a-bridge ──A2A──→ blablo hub ──A2A──→ Agent B
│
Agent C ─────────A2A──→ │ ←──A2A── Agent D
# Build
go build -o blablo ./cmd/blablo
# Run with default config (reads config.yaml if present)
./blablo
# Run with explicit config file
./blablo config.yamldocker compose up # from parent directory — runs blablo + a2a-bridge- Agents register via the management API — the hub resolves their agent card
- Hub serves a dynamic agent card at
/.well-known/agent-card.jsonaggregating all registered agent skills - Clients send messages to
POST /a2a— the hub routes to the correct downstream agent viametadata["target_skill"] - Single-agent shortcut — if only one agent is registered, no target metadata needed
| Endpoint | Description |
|---|---|
GET /.well-known/agent-card.json |
Dynamic agent card with all registered skills |
POST /a2a |
A2A JSON-RPC endpoint (message/send, etc.) |
| Method | Endpoint | Description |
|---|---|---|
POST /agents |
{"url": "http://agent:9001"} |
Register an agent (resolves its card) |
GET /agents |
— | List all registered agents |
DELETE /agents/{id} |
— | Unregister an agent |
GET /health |
— | Health check |
# Register an agent
curl -X POST http://localhost:9000/agents \
-H 'Content-Type: application/json' \
-d '{"url": "http://localhost:9001"}'
# List registered agents
curl http://localhost:9000/agents
# View hub's agent card
curl http://localhost:9000/.well-known/agent-card.json
# Remove an agent
curl -X DELETE http://localhost:9000/agents/echo-agentWhen the hub receives a message, it determines the target agent in this order:
message.metadata["target_skill"]— matches by skill ID or skill Name (case-insensitive)message.metadata["target_agent"]— matches by agent ID- If exactly 1 agent is registered — routes to it automatically
- Otherwise — returns a routing error
The companion a2a-bridge automatically injects target_skill metadata when sending messages.
All settings live in a single YAML file. The hub looks for config in this order:
- CLI argument:
./blablo myconfig.yaml CONFIG_FILEenv var:CONFIG_FILE=myconfig.yaml ./blablo- Default:
config.yamlin the working directory
# config.yaml
port: "9000"
agents:
- url: http://localhost:9001
- url: http://localhost:9002See config.example.yaml for a full example.
blablo/
├── cmd/blablo/main.go # Entry point, wiring, HTTP mux
├── internal/
│ ├── registry/registry.go # Agent registry + dynamic AgentCard producer
│ ├── hub/
│ │ ├── executor.go # AgentExecutor: route → proxy → respond
│ │ └── router.go # Message metadata → target agent resolution
│ ├── proxy/proxy.go # Outbound a2aclient pool per agent
│ └── api/management.go # REST management API
├── test/
│ └── integration_test.go # E2E tests with fake agents
├── go.mod
└── go.sum
- Registry — stores agents, indexes skills, implements
AgentCardProducerfor dynamic card generation - Router — resolves message metadata to a target agent ID
- Executor — implements
AgentExecutor(A2A SDK interface); routes inbound messages and proxies responses - Proxy — manages outbound
a2aclient.Clientper registered agent - Management API — REST endpoints for agent registration/removal
# Integration tests (spins up fake agents + hub, 18 tests)
go test ./test/ -v
# All tests
go test ./...- a2aproject/a2a-go — A2A Go SDK (client + server)
- golang.org/x/sync — errgroup
- gopkg.in/yaml.v3 — config file parsing