# A2A guide

Use the production Hybridbox A2A gateway for sessions, function execution, JSON-RPC, and multi-call code mode.

Hybridbox exposes an A2A gateway for agent-oriented function discovery, session management, and function execution.

## 1. Discover the agent card

Fetch the agent card first. It advertises gateway identity, auth, the function list URL, the JSON-RPC endpoint, and session bootstrap URLs.

```bash
curl -sS "https://hybridbox.io/.well-known/agent-card.json"
```

Example agent card:

```json
{
  "name": "Hybridbox A2A Agent",
  "description": "Hybridbox agent gateway",
  "url": "https://hybridbox.io/",
  "version": "current",
  "defaultInputModes": ["application/json"],
  "defaultOutputModes": ["application/json"],
  "authentication": {"type": "bearer"},
  "functions_url": "https://a2a.hybridbox.io/v1/functions",
  "supported_interfaces": [
    {"protocol_binding": "JSONRPC", "url": "https://a2a.hybridbox.io/rpc"}
  ],
  "skills": [
    {"name": "functions.list", "description": "List pseudo-functions available to the current caller"},
    {"name": "functions.explain", "description": "Explain visible pseudo-functions with detailed docs and examples"},
    {"name": "execute", "description": "Execute one or more Hybridbox pseudo-function calls"}
  ],
  "bootstrap": {
    "start_with": "message/send",
    "session_url": "https://a2a.hybridbox.io/v1/sessions",
    "login_url": "https://a2a.hybridbox.io/v1/sessions/login",
    "execute_url": "https://a2a.hybridbox.io/rpc",
    "docs_url": "https://docs.hybridbox.io"
  }
}
```

## 2. Authenticate or create a session

Most useful functions need a bearer session. Login returns both `session_id` and `session_token`; use the token in the `Authorization` header and pass the session ID in execute requests.

Login request:

```bash
curl -sS -X POST "https://a2a.hybridbox.io/v1/sessions/login" \
  -H 'Content-Type: application/json' \
  -d '{
    "identifier": "user@example.com",
    "password": "secret"
  }'
```

Login response:

```json
{
  "session_id": "session_123",
  "session_token": "hb_session_token",
  "expires_at": "2026-05-17T22:00:00Z",
  "authenticated": true,
  "user_id": "user_123",
  "email": "user@example.com"
}
```

Service accounts use the same session flow. Pass the service account token to the login endpoint, then use the returned `session_id` and `session_token` exactly like a user session:

```bash
curl -sS -X POST "https://a2a.hybridbox.io/v1/sessions/login" \
  -H 'Content-Type: application/json' \
  -d '{
    "service_account_token": "hbx_live_..."
  }'
```

Service account login response:

```json
{
  "session_id": "session_123",
  "session_token": "hb_session_token",
  "expires_at": "2026-05-17T22:00:00Z",
  "authenticated": true,
  "principal_type": "service_account",
  "auth": {
    "authenticated": true,
    "principal_type": "service_account",
    "service_account_id": "service_account_123"
  }
}
```

The service account token is verified by Hybridbox before the A2A session is created. Store the returned A2A session token securely; do not send the service account token again for normal function listing or execution.

For unauthenticated flows, create an anonymous bearer session instead:

```bash
curl -sS -X POST "https://a2a.hybridbox.io/v1/sessions" \
  -H 'Content-Type: application/json' \
  -d '{}'
```

## 3. List available functions

Use `/v1/functions` to list pseudo-functions visible to the current session.

```bash
curl -sS "https://a2a.hybridbox.io/v1/functions?session_id={session_id}" \
  -H "Authorization: Bearer {session_token}"
```

## 4. Execute code through `/v1/execute`

Use code mode for function execution. A request can contain one call or multiple calls. Code requests run synchronously by default.

One function call:

```bash
curl -sS -X POST "https://a2a.hybridbox.io/v1/execute" \
  -H "Authorization: Bearer {session_token}" \
  -H 'Content-Type: application/json' \
  -d '{
    "request_id": "req-1",
    "session_id": "session_123",
    "code": "accounts.list(page=1, page_size=25)"
  }'
```

Two function calls:

```json
{
  "request_id": "req-2",
  "session_id": "session_123",
  "code": "accounts.get(account_id='3c90c3cc-0d44-4b50-8888-8dd25736052a')\naccounts.list(page=1, page_size=25)"
}
```

Python-like syntax supported by code mode:

- one function call per line
- variables and expression reuse
- `if` statements
- `for` loops
- `range(...)`
- sequential execution in source order

```python
account = accounts.get(account_id="3c90c3cc-0d44-4b50-8888-8dd25736052a")
if auth.whoami()["authenticated"]:
    domains.list(page=1, page_size=10)
for page in range(1, 4):
    accounts.list(page=page, page_size=25)
```

Response shape for multiple calls:

```json
{
  "request_id": "req-2",
  "status": "completed",
  "result": {
    "last_value": {
      "items": ["...result from accounts.list..."]
    }
  },
  "responses": [
    {
      "index": 0,
      "function": "accounts.get",
      "status": "completed",
      "result": {"...": "result from accounts.get"}
    },
    {
      "index": 1,
      "function": "accounts.list",
      "status": "completed",
      "result": {"...": "result from accounts.list"}
    }
  ],
  "complexity_score": 2
}
```

- `responses[]` contains every function call result in order.
- `result.last_value` contains the result from the last successful function call.
- If a call fails, the response includes completed calls before the failure and identifies the failed call index.

## 5. `/v1/execute` vs `/rpc`

Both surfaces can execute the same code path, but they are meant for different clients.

- `/v1/execute` is the direct HTTP execution endpoint. Use it when your client just wants to run Hybridbox functions.
- `/rpc` is the A2A JSON-RPC message surface. Use it when your client speaks A2A `message/send` and wants requests wrapped as JSON-RPC messages.

JSON-RPC example:

```bash
curl -sS -X POST "https://a2a.hybridbox.io/rpc" \
  -H "Authorization: Bearer {session_token}" \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": "rpc-1",
    "method": "message/send",
    "params": {
      "message": {
        "operation": "execute",
        "session_id": "session_123",
        "request_id": "req-3",
        "code": "accounts.list(page=1, page_size=25)"
      }
    }
  }'
```

## 6. Subscribe to WebSocket notifications

Use WebSockets for live notifications about A2A sessions and tasks. WebSockets are notification-only; command execution still happens through `/v1/execute` or `/rpc`.

### Mint a WebSocket ticket

Before opening a socket, mint a short-lived, single-use WebSocket ticket from your normal A2A session token:

```bash
curl -sS -X POST "https://a2a.hybridbox.io/v1/ws/tickets" \
  -H "Authorization: Bearer {session_token}" \
  -H 'Content-Type: application/json' \
  -d '{"session_id":"session_123"}'
```

Response:

```json
{
  "ticket": "wst_...",
  "token_type": "Bearer",
  "session_id": "session_123",
  "expires_at": "2026-05-17T22:00:00Z"
}
```

Ticket rules:

- The public ticket endpoint does not accept a TTL override.
- The current ticket lifetime is 1 minute; treat `expires_at` as authoritative.
- Each ticket is single-use and bound to the returned `session_id`. Mint a fresh ticket for each connection attempt.
- Only WebSocket tickets are valid for WebSocket authentication. Do not use `session_token` or `hbx_live_...` service-account tokens as `ws_ticket`, in the WebSocket URL, or in `Authorization` during the WebSocket upgrade.

### Open the socket

Browser clients pass the ticket as `ws_ticket` while keeping `session_id` in the query string:

```text
wss://a2a.hybridbox.io/v1/ws?session_id=session_123&ws_ticket=wst_...
```

Non-browser clients may pass the ticket in the upgrade request instead. The `Authorization` header replaces only `ws_ticket`; `session_id` is still required in the query string.

```text
GET /v1/ws?session_id=session_123 HTTP/1.1
Host: a2a.hybridbox.io
Authorization: Bearer wst_...
Upgrade: websocket
Connection: Upgrade
```

Browser WebSocket upgrades are checked against the configured allowed origins. Missing `Origin` headers are rejected unless the deployment explicitly allows missing origins for non-browser clients.

### Send the first client message

Immediately after the upgrade succeeds, send the first client message. `session_id` is required and must match the query-string session ID. `last_sequence` is optional and is used for replay/resume.

Fresh stream:

```json
{
  "session_id": "session_123"
}
```

Resume from a known event sequence:

```json
{
  "session_id": "session_123",
  "last_sequence": 42
}
```

If the first message is invalid JSON or the session IDs do not match, the server sends an `error` event with code `INVALID_CLIENT_MESSAGE` and closes the socket.

### Server event envelope

Server events use this envelope:

```json
{
  "type": "task.running",
  "session_id": "session_123",
  "task_id": "task_456",
  "request_id": "req_789",
  "event_id": "evt_abc",
  "sequence": 43,
  "timestamp": "2026-05-17T22:00:00Z",
  "payload": {
    "status": "running"
  }
}
```

Fields:

- `type`: event name
- `session_id`: owning A2A session
- `task_id`: optional task reference
- `request_id`: optional execution request correlation ID
- `event_id`: unique event identifier
- `sequence`: replay/resume sequence number; `session.ready` and `ping` use `0`
- `timestamp`: server UTC timestamp
- `payload`: event-specific data

Standard event types:

- `session.ready`: subscription accepted and live delivery is active
- `session.replaced`: another socket connected for the same session and replaced the old socket
- `ping`: server keepalive
- `error`: protocol-level error payload with `code` and `message`
- `task.queued`, `task.running`, `task.completed`, `task.failed`, `task.cancelled`: task lifecycle notifications
- `task.progress`: reserved progress event type for long-running producers

### Keepalive

The server sends a `ping` event about every 20 seconds. Reply with:

```json
{
  "type": "pong"
}
```

If the server does not observe a pong for about 40 seconds, it closes the socket.

### Replay and replacement

Replay uses `last_sequence` from the first client message. If replay is available, the server sends missed events starting at `last_sequence + 1`, then sends `session.ready`. Replay is backed by a bounded Redis window of the last 100 events with a 15 minute TTL.

If the requested sequence is older than the retained replay window, the server sends an `error` event with code `REPLAY_NOT_AVAILABLE`. Poll task state over HTTP, then reconnect without assuming missed events were delivered.

Only one active socket is kept per `session_id`. A new connection for the same session replaces the old one. The old socket receives `session.replaced` with payload `{ "reason": "replaced" }` and is then closed.

## Function catalog

Open the function catalog when you need callable names, compact signatures, arguments, return fields, auth metadata, and public API route mappings.

### Function catalog

[Function catalog](/en/agents/a2a-function-catalog)

  Browse generated function references by domain.
