# A2A

**Since Camel 4.21**

**Both producer and consumer are supported**

The A2A component implements the [Agent-to-Agent (A2A) v1.0 protocol](https://a2a-protocol.org/latest/specification/), enabling Camel routes to both expose and call A2A-compliant agents.

As a **consumer**, it turns a Camel route into an A2A agent — automatically serving an agent card at `/.well-known/agent-card.json`, exposing all A2A operations via HTTP, and managing task state. As a **producer**, it calls remote A2A agents with automatic agent card discovery, protocol wrapping, and credential management.

The component follows the same design philosophy as [REST OpenAPI](rest-openapi-component.md): it handles protocol plumbing and delegates business logic entirely to the route.

> **Note**
> **A2A Protocol**
>
> The [A2A protocol](https://a2a-protocol.org) is an open standard for communication between AI agents. It defines how agents discover each other (via agent cards), exchange messages, manage long-running tasks, stream progress updates, and deliver push notifications. The protocol supports both REST (HTTP+JSON) and JSON-RPC 2.0 bindings over HTTP.

## Preview Limitations

The A2A component is Preview in Camel 4.21. It targets the A2A v1.0 protocol, but endpoint options, model classes, generated metadata, and the task-store SPI may still change before the component reaches stable support.

Current Preview limitations:

-   Extended Agent Card / `GetExtendedAgentCard` is not implemented in this first release.
    
-   Consumers validate operation requests by default. Local-only examples in this page set `validateAuth=false`; network-exposed agents should use an agent card with `securitySchemes` and `securityRequirements` plus `apiKey`, `bearerToken`, or `oauthProfile` endpoint configuration.
    
-   The REST binding uses A2A custom-method paths containing colons, such as `/message:send`. These paths collide on Vert.x/platform-http. Use `protocolBinding=JSONRPC` with platform-http, or use `httpServerComponent=undertow` or `httpServerComponent=jetty` for REST custom-method routes.
    
-   Real-time SSE streaming is verified with Undertow and Jetty. Vert.x/platform-http buffers `InputStream` responses and does not deliver events as they are emitted.
    
-   The default task store is in-memory and single-JVM. Register a custom `A2ATaskStore` for durable or cross-node task state.
    

Maven users will need to add the following dependency to their `pom.xml`:

```xml
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-a2a</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
</dependency>
```

Consumer routes also need an HTTP server component at runtime. For example, add `camel-platform-http-vertx` for JSON-RPC agents, or `camel-undertow` / `camel-jetty` for REST custom-method routes and real-time SSE streaming. See [Dependencies](#_dependencies) and [HTTP Server Component Discovery](#_http_server_component_discovery).

## URI Format

a2a:agentCardSource\[?options\]

How **agentCardSource** determines where the agent card is loaded from:

 
| Source | Example |
| --- | --- |
| Remote URL (partial) | `a2a:\https://agent.example.com` — auto-expands to `https://agent.example.com/.well-known/agent-card.json` |
| Remote URL (full) | `a2a:\https://agent.example.com/.well-known/agent-card.json` |
| Classpath | `a2a:classpath:cards/weather.json` |
| File | `a2a:file:/etc/agents/weather.json` |
| Plain name | `a2a:weather-agent?name=Weather&description=…​` — card built from params |

The same URI works in both `from()` (consumer: "I am this agent") and `to()` (producer: "I am calling this agent").

## Configuring Options

Camel components are configured on two separate levels:

-   component level
    
-   endpoint level
    

### Configuring Component Options

At the component level, you set general and shared configurations that are, then, inherited by the endpoints. It is the highest configuration level.

For example, a component may have security settings, credentials for authentication, urls for network connection and so forth.

Some components only have a few options, and others may have many. Because components typically have pre-configured defaults that are commonly used, then you may often only need to configure a few options on a component; or none at all.

You can configure components using:

-   the [Component DSL](../../manual/component-dsl.md).
    
-   in a configuration file (`application.properties`, `*.yaml` files, etc).
    
-   directly in the Java code.
    

### Configuring Endpoint Options

You usually spend more time setting up endpoints because they have many options. These options help you customize what you want the endpoint to do. The options are also categorized into whether the endpoint is used as a consumer (_from_), as a producer (_to_), or both.

Configuring endpoints is most often done directly in the endpoint URI as _path_ and _query_ parameters. You can also use the [Endpoint DSL](../../manual/Endpoint-dsl.md) and [DataFormat DSL](../../manual/dataformat-dsl.md) as a _type safe_ way of configuring endpoints and data formats in Java.

A good practice when configuring options is to use [Property Placeholders](../../manual/using-propertyplaceholder.md).

Property placeholders provide a few benefits:

-   They help prevent using hardcoded urls, port numbers, sensitive information, and other settings.
    
-   They allow externalizing the configuration from the code.
    
-   They help the code to become more flexible and reusable.
    

The following two sections list all the options, firstly for the component followed by the endpoint.

## Component Options

The A2A component supports 3 options, which are listed below.

   
| Name | Description | Default | Type |
| --- | --- | --- | --- |
| **bridgeErrorHandler** (consumer) | Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored. | false | boolean |
| **lazyStartProducer** (producer) | Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel’s routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing. | false | boolean |
| **autowiredEnabled** (advanced) | Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching type, which then gets configured on the component. This can be used for automatic configuring JDBC data sources, JMS connection factories, AWS Clients, etc. | true | boolean |

## Endpoint Options

The A2A endpoint is configured using URI syntax:

a2a:agentCardSource

With the following _path_ and _query_ parameters:

### Path Parameters (1 parameters)

   
| Name | Description | Default | Type |
| --- | --- | --- | --- |
| **agentCardSource** (common) | **Required** The agent card source (classpath:, file:, http://, https://, or plain name). |  | String |

### Query Parameters (35 parameters)

   
| Name | Description | Default | Type |
| --- | --- | --- | --- |
| **agentCard** (common) | Agent card bean reference or inline configuration. |  | AgentCard |
| **basePath** (common) | The base path for HTTP requests. |  | String |
| **dataFormat** (common) | 
The data format for the exchange body, following the CXF DataFormat convention. PAYLOAD (default) extracts text content from message parts as a String backward compatible, simple for chatbot routes. POJO sets the body to the full Java model object (Message on consumer, Task or Message on producer) preserving all parts, metadata, and file content. RAW passes the raw JSON string without deserialization useful for forwarding, logging, or compliance.

Enum values:

-   PAYLOAD
    
-   POJO
    
-   RAW
    





 | PAYLOAD | A2ADataFormat |
| **description** (common) | The agent description (overrides agent card). |  | String |
| **historyLength** (common) | Maximum number of history messages to include in the context. |  | Integer |
| **host** (common) | The host to connect to for producers. |  | String |
| **name** (common) | The agent name (overrides agent card). |  | String |
| **port** (common) | The port to connect to for producers. |  | Integer |
| **protocolBinding** (common) | 

The protocol binding to use for communication. Legacy aliases rest and jsonrpc are also accepted.

Enum values:

-   HTTP+JSON
    
-   JSONRPC
    





 | HTTP+JSON | String |
| **returnImmediately** (common) | Whether to return immediately without waiting for task completion. | false | boolean |
| **validateAuth** (common) | Whether to validate authentication on incoming consumer operation requests. Disable explicitly only for unauthenticated A2A operation serving. | true | boolean |
| **version** (common) | The agent version (overrides agent card). |  | String |
| **bridgeErrorHandler** (consumer (advanced)) | Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the likes, will now be processed as a message and handled by the routing Error Handler. Important: This is only possible if the 3rd party component allows Camel to be alerted if an exception was thrown. Some components handle this internally only, and therefore bridgeErrorHandler is not possible. In other situations we may improve the Camel component to hook into the 3rd party component and make this possible for future releases. By default the consumer will use the org.apache.camel.spi.ExceptionHandler to deal with exceptions, that will be logged at WARN or ERROR level and ignored. | false | boolean |
| **exceptionHandler** (consumer (advanced)) | To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By default the consumer will deal with exceptions, that will be logged at WARN or ERROR level and ignored. |  | ExceptionHandler |
| **exchangePattern** (consumer (advanced)) | 

Sets the exchange pattern when the consumer creates an exchange.

Enum values:

-   InOnly
    
-   InOut
    





 |  | ExchangePattern |
| **httpServerComponent** (consumer (advanced)) | The Camel HTTP component to use for serving incoming A2A requests (consumer side). Must implement RestConsumerFactory (e.g., platform-http, jetty, netty-http, undertow, servlet). When not set, the component is auto-discovered from the classpath or falls back to the global camel.rest.component setting. |  | String |
| **maxConcurrentTasks** (consumer (advanced)) | Maximum number of tasks the agent can process concurrently. When the limit is reached, new requests are rejected with ServerBusyError (HTTP 429). Set to 0 (default) for unlimited concurrency. | 0 | int |
| **sseHeartbeatInterval** (consumer (advanced)) | Interval in milliseconds for SSE keep-alive heartbeat comments. Sent as ':' comment lines to prevent proxies from closing idle connections. Independent from asyncTimeout which controls task processing timeout. | 15000 | long |
| **sseQueueCapacity** (consumer (advanced)) | Maximum number of SSE events that can be buffered per streaming connection. When the queue is full, new events are dropped with a warning log. Prevents unbounded memory growth from slow clients. | 1000 | int |
| **taskQueueSize** (consumer (advanced)) | Maximum number of tasks that can wait in the pending queue when all concurrent slots are occupied. Only applies to async requests (returnImmediately=true). Queued tasks receive SUBMITTED status and are processed as capacity becomes available. Set to 0 (default) for no queueing requests are rejected immediately when at capacity. | 0 | int |
| **operation** (producer) | 

The A2A operation to perform.

Enum values:

-   MESSAGE\_SEND
    
-   MESSAGE\_STREAM
    
-   TASK\_GET
    
-   TASK\_LIST
    
-   TASK\_CANCEL
    
-   TASK\_SUBSCRIBE
    
-   PUSH\_CONFIG\_CREATE
    
-   PUSH\_CONFIG\_GET
    
-   PUSH\_CONFIG\_LIST
    
-   PUSH\_CONFIG\_DELETE
    





 | MESSAGE\_SEND | A2AOperations |
| **connectTimeout** (producer (advanced)) | Connect timeout in milliseconds for the HTTP client used by the producer. | 30000 | long |
| **lazyStartProducer** (producer (advanced)) | Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail during starting and cause the route to fail being started. By deferring this startup to be lazy then the startup failure can be handled during routing messages via Camel’s routing error handlers. Beware that when the first message is processed then creating and starting the producer may take a little time and prolong the total processing time of the processing. | false | boolean |
| **streamingReadTimeout** (producer (advanced)) | Read timeout in milliseconds for the producer’s SSE streaming connection. If no SSE event arrives within this period, the stream is closed with an error. Prevents indefinite blocking when a remote agent stops sending events. | 300000 | long |
| **asyncTimeout** (advanced) | Timeout in milliseconds for asynchronous task operations. | 300000 | long |
| **completedTaskTtl** (advanced) | Time-to-live in milliseconds for completed tasks before cleanup. | 3600000 | long |
| **followRedirects** (advanced) | Whether the HTTP client should follow redirects. Disabled by default to prevent credential leakage on cross-origin redirects. Enable only when the remote agent is known to issue redirects (e.g., behind a load balancer). | false | boolean |
| **maxPayloadSize** (advanced) | Maximum payload size in bytes (default 6MB). | 6291456 | long |
| **pushRetryAttempts** (advanced) | Maximum number of retry attempts for push notification webhook delivery. | 3 | int |
| **pushRetryBackoffMs** (advanced) | Initial backoff in milliseconds for push notification retry. Retries use exponential backoff with this delay multiplied by 2 to the attempt number. | 1000 | long |
| **allowLocalWebhookUrls** (security) | Whether to allow webhook URLs pointing to localhost/loopback addresses. When false (default), push notification webhook URLs targeting 127.0.0.0/8, ::1, or localhost are rejected as SSRF protection. Enable for local development only. | false | boolean |
| **apiKey** (security) | API key for authentication. |  | String |
| **apiKeyHeader** (security) | HTTP header name for API key authentication (e.g., X-API-Key, Authorization). | Authorization | String |
| **bearerToken** (security) | Bearer token for authentication. |  | String |
| **oauthProfile** (security) | OAuth profile name for obtaining an access token via the OAuth 2.0 Client Credentials grant. When set, the token is acquired from the configured identity provider and used for authentication. Requires camel-oauth on the classpath. The profile properties are resolved from camel.oauth.profile-name.client-id, camel.oauth.profile-name.client-secret, and camel.oauth.profile-name.token-endpoint. |  | String |

## Message Headers

The A2A component supports 19 message header(s), which is/are listed below:

   
| Name | Description | Default | Type |
| --- | --- | --- | --- |
| **CamelA2AOperation** (producer) Constant: [`OPERATION`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#OPERATION) | A2A operation to invoke. |  | String |
| **CamelA2ATaskId** (common) Constant: [`TASK_ID`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#TASK_ID) | Task ID. |  | String |
| **CamelA2APushConfigId** (common) Constant: [`PUSH_CONFIG_ID`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#PUSH_CONFIG_ID) | Push notification config ID. |  | String |
| **CamelA2AContextId** (common) Constant: [`CONTEXT_ID`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#CONTEXT_ID) | Context ID for multi-turn conversations. |  | String |
| **CamelA2AMessageId** (common) Constant: [`MESSAGE_ID`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#MESSAGE_ID) | Message ID. |  | String |
| **CamelA2ATaskState** (common) Constant: [`TASK_STATE`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#TASK_STATE) | Task state. |  | String |
| **CamelA2AMethod** (producer) Constant: [`METHOD`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#METHOD) | A2A method name invoked. |  | String |
| **CamelA2AResponseType** (common) Constant: [`RESPONSE_TYPE`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#RESPONSE_TYPE) | Response type: task or message. |  | String |
| **CamelA2AReturnImmediately** (common) Constant: [`RETURN_IMMEDIATELY`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#RETURN_IMMEDIATELY) | Return immediately flag. |  | Boolean |
| **CamelA2AHistoryLength** (common) Constant: [`HISTORY_LENGTH`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#HISTORY_LENGTH) | Max history messages. |  | Integer |
| **CamelA2AStreamEmitter** (consumer) Constant: [`STREAM_EMITTER`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#STREAM_EMITTER) | SSE stream emitter for route processors. |  | A2AStreamEmitter |
| **CamelA2AListContextId** (common) Constant: [`LIST_CONTEXT_ID`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#LIST_CONTEXT_ID) | Context ID filter for task listing. |  | String |
| **CamelA2AListPageSize** (common) Constant: [`LIST_PAGE_SIZE`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#LIST_PAGE_SIZE) | Page size for task listing. |  | Integer |
| **CamelA2AListPageToken** (common) Constant: [`LIST_PAGE_TOKEN`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#LIST_PAGE_TOKEN) | Page token for task listing pagination. |  | String |
| **CamelA2AListIncludeArtifacts** (common) Constant: [`LIST_INCLUDE_ARTIFACTS`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#LIST_INCLUDE_ARTIFACTS) | Whether to include artifacts in task listing. |  | Boolean |
| **CamelA2AListHistoryLength** (common) Constant: [`LIST_HISTORY_LENGTH`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#LIST_HISTORY_LENGTH) | History length for task listing. |  | Integer |
| **CamelA2AListStatusTimestampAfter** (common) Constant: [`LIST_STATUS_TIMESTAMP_AFTER`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#LIST_STATUS_TIMESTAMP_AFTER) | Filter tasks by status timestamp after this value. |  | String |
| **CamelA2AListStatus** (common) Constant: [`LIST_STATUS`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#LIST_STATUS) | Comma-separated status filter for task listing. |  | String |
| **CamelA2AExtensions** (consumer) Constant: [`EXTENSIONS`](https://javadoc.io/doc/org.apache.camel/camel-a2a/latest/org/apache/camel/component/a2a/A2AConstants.html#EXTENSIONS) | Negotiated A2A extension URIs requested by the client. |  | List |

## Consumer — Exposing an A2A Agent

The consumer turns a Camel route into an A2A agent. It automatically:

-   Serves the agent card at `GET /.well-known/agent-card.json`
    
-   Registers HTTP endpoints for A2A operations (REST routes, or a JSON-RPC dispatcher for supported methods)
    
-   Validates authentication on incoming requests (when configured)
    
-   Manages task state via an internal `A2ATaskStore`
    
-   Dispatches push notification webhooks when task state changes
    
-   Filters inbound `Camel*` and `org.apache.camel.*` headers (case-insensitive) from untrusted requests
    
-   Enforces `maxPayloadSize` on incoming request bodies (returns 413 when exceeded)
    

The route’s `setBody` output becomes the agent’s response — the consumer handles all A2A protocol wrapping. The `dataFormat` parameter controls what the route receives as body: extracted text (`PAYLOAD`, default), the full `Message` object (`POJO`), or raw JSON (`RAW`).

### Basic Agent (Local Only)

The simplest possible local A2A agent. Define your agent’s capabilities in `agent-card.json`, disable operation auth for local-only use, and let the route handle business logic:

-   YAML
    
-   Java
    

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        validateAuth: false
      steps:
        - log:
            message: "Received: ${body}"
        - setBody:
            constant: "Hello from my agent!"
```

```java
from("a2a:classpath:agent-card.json?validateAuth=false")
    .log("Received: ${body}")
    .setBody(constant("Hello from my agent!"));
```

Do not use `validateAuth=false` for agents exposed to an untrusted network. See [Authentication](#_authentication) for authenticated operation serving.

The agent card (`agent-card.json`) declares your agent’s identity and capabilities:

```json
{
  "protocolVersion": "1.0",
  "name": "my-agent",
  "description": "A simple Camel A2A agent",
  "url": "http://localhost:8080",
  "version": "1.0.0",
  "provider": {
    "name": "Example",
    "url": "https://example.com"
  },
  "capabilities": {
    "streaming": false,
    "pushNotifications": false,
    "extendedAgentCard": false
  },
  "supportedInterfaces": [
    {
      "url": "http://localhost:8080",
      "protocolBinding": "HTTP+JSON",
      "protocolVersion": "1.0"
    }
  ],
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    {
      "id": "greet",
      "name": "Greeting",
      "description": "Returns a greeting message",
      "tags": ["greeting"],
      "examples": ["Say hello to Ada"],
      "inputModes": ["text/plain"],
      "outputModes": ["text/plain"]
    }
  ]
}
```

The component supports the `HTTP+JSON` and `JSONRPC` protocol bindings. The legacy aliases `rest` and `jsonrpc` are also accepted and normalized to the v1.0 binding names. The `protocolVersion`, `defaultInputModes`, and `defaultOutputModes` fields are part of the A2A v1.0 card shape.

> **Note**
> The `AgentCard` Java model can deserialize A2A v1.0 fields such as `defaultInputModes` and `defaultOutputModes`, and it also stores unknown JSON properties. URI parameter overrides are limited to `name`, `description`, and `version`; the endpoint resolver preserves card metadata and unknown extension properties from file and bean cards, supplies preview defaults for plain-name routes, and rejects unsupported protocol bindings.

### Card from Parameters (Local Only)

For simple or test agents, the card identity fields can be built from URI parameters - no JSON file needed. Because parameter-built cards do not include security schemes, these local-only examples set `validateAuth=false`:

-   YAML
    
-   Java
    

```yaml
- route:
    from:
      uri: >-
        a2a:weather-agent
        ?name=Weather Agent
        &description=Provides weather forecasts
        &version=1.0.0
        &validateAuth=false
      steps:
        - bean:
            ref: weatherService
```

```java
from("a2a:my-agent?name=My Agent&description=A simple agent&version=1.0.0&validateAuth=false")
    .setBody(constant("Response from parameter-configured agent"));
```

The component generates the `/.well-known/agent-card.json` response dynamically from the parameters. URI parameters only cover the agent identity fields above; use an agent card file or an `agentCard` bean for capabilities, skills, security schemes, supported interfaces, and other card metadata.

### JSON-RPC Protocol Binding

By default, the consumer uses the REST binding (HTTP+JSON with colon-notation paths like `/message:send`). To use JSON-RPC 2.0 (single `POST /` endpoint), set `protocolBinding` to `JSONRPC`. The legacy aliases `rest` and `jsonrpc` are also accepted.

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        protocolBinding: JSONRPC
        validateAuth: false
      steps:
        - setBody:
            constant: "JSON-RPC response"
```

This example is local-only because it disables operation auth. Keep `validateAuth=true` and configure a card security scheme for network-exposed agents.

The REST binding exposes separate HTTP routes for the operation-serving methods listed in [Consumer REST API Paths](#_consumer_rest_api_paths). The JSON-RPC binding exposes a single dispatcher and handles `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, and the push notification config operations. Extended agent cards are outside the Preview scope for this first release.

REST requests and responses use `application/a2a+json`. JSON-RPC requests and responses use `application/json`. Streaming operations produce `text/event-stream`.

#### Vert.x Colon-in-Path Issue

The A2A REST protocol uses Google’s Custom Method convention with colons in paths (e.g., `/message:send`, `/message:stream`, `/tasks/{id}:cancel`). Vert.x’s router interprets the colon (`:`) as a path parameter delimiter, causing route collisions:

-   `/message:send` and `/message:stream` both match the pattern `/message{param}` — whichever is registered first captures ALL requests
    
-   `/tasks/{taskId}:cancel` and `/tasks/{taskId}:subscribe` can collide with `/tasks/{taskId}`
    

This means operation serving is not reliable with REST binding on Vert.x/platform-http. For example, `POST /message:stream` can be handled by the `SendMessage` route instead of the streaming route.

**Workaround:** Use `protocolBinding=JSONRPC` for agents running on Vert.x (the default `platform-http`). Alternatively, use `httpServerComponent=jetty` or `httpServerComponent=undertow`; their routers treat colons as literal characters. For real-time SSE streaming, use Jetty or Undertow because Vert.x/platform-http buffers `InputStream` responses.

### Authentication

The component supports OAuth 2.0, OpenID Connect, API key, and HTTP bearer security schemes. OAuth 2.0 and OpenID Connect use the `oauthProfile` option and the `camel-oauth` SPI; HTTP bearer can use either an OAuth profile or the static `bearerToken` option.

> **Important**
> When `validateAuth=true`, the resolved agent card must declare at least one security scheme. If the card declares no schemes, the consumer rejects every request with HTTP 401 (fail closed). Cards built purely from URI parameters contain no security schemes, so combine `validateAuth=true` with a card file or `agentCard` bean that declares the scheme, as shown in the examples below.

> **Warning**
> `validateAuth` defaults to `true`. The well-known agent card remains public, but all A2A operation endpoints require authentication by default. Set `validateAuth=false` only for deployments that intentionally expose unauthenticated A2A operation serving; in that mode the in-memory task store is open/single-tenant and does not provide per-user isolation.

> **Note**
> The component scopes built-in task and push-notification operations to the authenticated user that created the task. Tenant routing and tenant-aware authorization policy are outside the component scope; implement those rules in the Camel route or in deployment-specific infrastructure.

`securityRequirements` follows A2A v1.0 semantics: multiple requirement objects are alternatives (OR), while multiple schemes inside one requirement must all be satisfied together (AND). The `list` array contains required scopes. The legacy A2A draft field `security` is accepted on input and converted to `securityRequirements`; new cards should use `securityRequirements`.

#### OAuth / OIDC Authentication

Validate incoming bearer tokens via the `camel-oauth` SPI. When an `oauthProfile` is configured, the consumer validates JWT signatures (via JWKS), expiry, issuer, and audience — or uses RFC 7662 token introspection for opaque tokens. Requires `camel-oauth` on the classpath.

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        oauthProfile: my-agent
        validateAuth: true
      steps:
        - setBody:
            constant: "Authenticated response"
```

Configure the OIDC provider in application properties (these properties are owned by the `camel-oauth` component — see its documentation for the canonical format):

```properties
camel.oauth.my-agent.client-id=my-agent-client
camel.oauth.my-agent.client-secret=my-secret
camel.oauth.my-agent.token-endpoint=http://keycloak:8180/realms/my-realm/protocol/openid-connect/token
```

The agent card must declare the security scheme:

```json
{
  "securitySchemes": {
    "oidc": {
      "openIdConnectSecurityScheme": {
        "openIdConnectUrl": "http://keycloak:8180/realms/my-realm/.well-known/openid-configuration"
      }
    }
  },
  "securityRequirements": [
    {
      "schemes": {
        "oidc": {
          "list": []
        }
      }
    }
  ]
}
```

When authentication succeeds, the `CamelA2AUserProfile` exchange property is populated with a `Map<String, Object>` containing the authenticated user’s profile (subject, issuer, scopes, claims). When using OAuth/OIDC, the full `OAuthTokenValidationResult` is also available as the `CamelOAuthTokenValidationResult` exchange property.

#### API Key Authentication

The `apiKeyHeader` parameter controls which HTTP header carries the API key. It defaults to `Authorization`. A common override is `X-API-Key`:

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        validateAuth: true
        apiKey: "{{my.api.key}}"
        apiKeyHeader: X-API-Key      # default is Authorization
      steps:
        - setBody:
            constant: "API key protected response"
```

The agent card must declare an `apiKey` security scheme:

```json
{
  "securitySchemes": {
    "apikey": {
      "apiKeySecurityScheme": {
        "location": "header",
        "name": "X-API-Key"
      }
    }
  },
  "securityRequirements": [
    {
      "schemes": {
        "apikey": {
          "list": []
        }
      }
    }
  ]
}
```

When the scheme declares `location=header` and `name`, that header name takes precedence over `apiKeyHeader`. The component also supports `location=query` and `location=cookie` for API key schemes.

#### Bearer Token

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        bearerToken: "{{my.token}}"
        validateAuth: true
      steps:
        - setBody:
            constant: "Bearer token protected response"
```

The agent card must declare an `http` bearer security scheme:

```json
{
  "securitySchemes": {
    "bearer": {
      "httpAuthSecurityScheme": {
        "scheme": "bearer"
      }
    }
  },
  "securityRequirements": [
    {
      "schemes": {
        "bearer": {
          "list": []
        }
      }
    }
  ]
}
```

> **Note**
> The agent card endpoint (`GET /.well-known/agent-card.json`) is always public — clients need it for discovery before they can authenticate.

### SSE Streaming with `${a2a:emit()}`

Agents can emit progressive status updates during processing using the `${a2a:emit()}` Simple language function. Clients receive these as Server-Sent Events (SSE).

For `SendStreamingMessage` and `SubscribeToTask`, the agent card must explicitly set `capabilities.streaming=true`. If the `capabilities` object is missing, or `streaming` is omitted or `false`, the consumer returns `UnsupportedOperationError` (HTTP 405 for REST, JSON-RPC `-32004` for JSON-RPC).

-   YAML
    
-   Java
    

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        protocolBinding: JSONRPC
        httpServerComponent: undertow
      steps:
        - script:
            simple: "${a2a:emit('Connecting to database...')}"
        - delay:
            constant: 2000
        - script:
            simple: "${a2a:emit('Processing 1000 records...')}"
        - delay:
            constant: 3000
        - script:
            simple: "${a2a:emit('Analysis complete!')}"
        - setBody:
            constant: "Final analysis results: ..."
```

```java
import org.apache.camel.component.a2a.A2AProgress;

from("a2a:classpath:agent-card.json?httpServerComponent=undertow")
    .process(exchange -> {
        A2AProgress.emit(exchange, "Connecting...");
        //connect to the external service
        A2AProgress.emit(exchange, "Processing...");
        //process data
        exchange.getMessage().setBody("Here is the final answer.");
    });
```

Each `${a2a:emit('message')}` emits an SSE status event with `TASK_STATE_WORKING` state. The final `setBody` is automatically delivered as the completed response.

You can also emit with an explicit state:

```yaml
- script:
    simple: "${a2a:emit(INPUT_REQUIRED, 'Please provide your address')}"
```

> **Note**
> Use `script` EIP (not `setBody`) for `${a2a:emit()}` calls — `script` evaluates the expression for its side effect without changing the message body. Use `setBody` only for the final response.

### Scoped progress updates with `a2aSubTask`

Camel routes can group related work with the `a2aSubTask` EIP and emit progress updates before, after, or when the grouped steps fail. This is a regular Camel route step, not a YAML-only extension, and it can be used from Camel’s model-based DSLs when `camel-a2a` is on the classpath. The `emitBefore`, `emitAfter`, and `emitOnError` fields are optional and are evaluated as Simple expressions against the current Exchange. The nested `steps` behave like normal Camel route steps: in YAML they are configured under `steps`, and in Java DSL they are added after `.a2aSubTask()` until `.end()`. If the nested steps fail, `emitOnError` can access the original exception, for example with `$\{exception.message}`, and the original exception continues to propagate through the route. Failures while evaluating the `emitBefore`, `emitAfter`, or `emitOnError` Simple expressions are route failures. Failures while storing or notifying the resulting A2A progress event are best-effort: they are logged at debug level and do not stop nested work, fail an otherwise successful exchange, or replace the original nested-step exception. By default, emitting progress outside an active A2A task context is a no-op. Set `failIfNoTaskContext=true` when a route should fail instead if the current Exchange does not carry an active A2A task. An active task context is normally created by an A2A consumer while it is processing a task. Use stable `id` values on long-lived sub-tasks so route tracing and route-structure views keep stable node names.

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        protocolBinding: JSONRPC
        httpServerComponent: undertow
        validateAuth: false
      steps:
        - a2aSubTask:
            id: search-docs-progress
            emitBefore: "Searching docs..."
            emitAfter: "Docs found: ${body.size()}"
            emitOnError: "Error searching docs: ${exception.message}"
            failIfNoTaskContext: true
            steps:
              - to:
                  uri: "elasticsearch:docs?operation=Search"
        - a2aSubTask:
            id: draft-answer-progress
            emitBefore: "Drafting answer..."
            emitAfter: "Answer drafted: ${body}"
            emitOnError: "Error drafting answer: ${exception.message}"
            steps:
              - bean:
                  ref: answerDraftingService
                  method: draft
        - setBody:
            simple: "Final answer: ${body}"
```

The route examples in this section are local-only because they disable operation auth. Keep `validateAuth=true` and configure a card security scheme for network-exposed agents.

The same route step is also available from Java DSL:

```java
from("a2a:classpath:agent-card.json?protocolBinding=JSONRPC&httpServerComponent=undertow&validateAuth=false")
    .a2aSubTask()
        .id("search-docs-progress")
        .emitBefore("Searching docs...")
        .emitAfter("Docs found: ${body.size()}")
        .emitOnError("Error searching docs: ${exception.message}")
        .failIfNoTaskContext(true)
        .to("elasticsearch:docs?operation=Search")
    .end();
```

For advanced use cases, `A2AProgress` also supports emitting structured artifacts and intermediate messages:

_Java-only: emitting structured artifacts and intermediate messages with A2AProgress_

```java
// Emit a structured artifact (e.g., a generated file)
Artifact artifact = Artifact.builder()
    .artifactId("report-1")
    .name("Analysis Report")
    .parts(List.of(new TextPart("Report content...")))
    .build();
A2AProgress.emitArtifact(exchange, artifact, false, true);  // append=false, lastChunk=true

// Emit an intermediate agent message (not a status update)
Message msg = Message.builder()
    .role(Message.Role.ROLE_AGENT)
    .parts(List.of(new TextPart("Intermediate finding...")))
    .build();
A2AProgress.emitMessage(exchange, msg);
```

The consumer supports two SSE streaming patterns:

-   **`SendStreamingMessage`** — The stream starts with the submitted `Task`, followed by progress events from `A2AProgress` or `${a2a:emit()}`. When the route completes, the body is emitted as the final message.
    
-   **`SubscribeToTask`** — Clients subscribe via `POST /tasks/{id}:subscribe` to receive real-time SSE updates for an existing non-terminal task. The component uses an Event-to-Stream Bridge (`QueueStreamEmitter` + `SseQueueInputStream`) with heartbeat comments to keep the connection alive. The stream sends the current `Task` first and ends when the task later reaches a terminal state. Subscribing to an already-terminal task returns `UnsupportedOperationError`.
    

> **Important**
> **HTTP Server Component for Streaming**
>
> For real-time SSE streaming, use `httpServerComponent=undertow` or `httpServerComponent=jetty`. The default Vert.x `platform-http` buffers `InputStream` responses due to an `AsyncInputStream` greedy-fill loop, causing all events to arrive at once.
>
> ```yaml
> parameters:
>   httpServerComponent: undertow
> ```
>
> This requires `camel-undertow` (or `camel-jetty`) on the classpath.

SSE streaming parameters:

-   `sseHeartbeatInterval` (default 15,000 ms / 15 seconds) — interval for SSE keep-alive heartbeat comments (`:` lines) sent to prevent proxies from closing idle connections. Independent from `asyncTimeout`.
    
-   `sseQueueCapacity` (default 1,000) — maximum SSE events buffered per streaming connection. When the queue is full, new events are dropped with a warning log. Prevents unbounded memory growth from slow clients.
    

### Async Tasks with `returnImmediately`

For long-running operations, enable `returnImmediately` to return a `SUBMITTED` task immediately. The route processes in the background, and clients poll with `GetTask`:

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        returnImmediately: true
        asyncTimeout: 30000          # default is 300000 (5 minutes)
      steps:
        - delay:
            constant: 10000
        - setBody:
            constant: "Long computation result"
```

The `asyncTimeout` parameter (default: 300,000 ms / 5 minutes) controls how long the background processing can run before the task is marked `FAILED`.

The consumer manages the task lifecycle automatically: `SUBMITTED` → `WORKING` → `COMPLETED` (or `FAILED` on error / timeout).

`returnImmediately` resolution priority (highest wins):

1.  Per-request: `configuration.returnImmediately` in the `SendMessageRequest` body
    
2.  Per-request: `configuration.blocking=false` in the `SendMessageRequest` body (typed inverse of immediate return)
    
3.  Per-exchange: `CamelA2AReturnImmediately` header
    
4.  Endpoint config: `returnImmediately` URI parameter
    

`SendMessageRequest.configuration` is modeled as `SendMessageConfiguration`. The consumer interprets `returnImmediately`, `blocking`, and `historyLength`; unknown fields are retained for protocol extensions and future A2A options. `configuration.historyLength` limits the task history returned by synchronous `SendMessage` task responses.

> **Note**
> The REST consumer rejects `returnImmediately` for streaming operations (`SendStreamingMessage`) with 400 Bad Request.

### Push Notifications

Agents that use `returnImmediately` can deliver updates via webhooks. Clients register a webhook URL via `CreateTaskPushNotificationConfig`, and the component’s built-in `PushNotificationDispatcher` sends status updates to registered URLs.

The agent card should declare push notification support so clients can discover the feature:

```json
{
  "capabilities": {
    "pushNotifications": true
  }
}
```

Progress updates emitted via `${a2a:emit()}` or `A2AProgress.emit()` are automatically dispatched to all registered webhooks.

Push notification dispatch features:

-   **Parallel dispatch** — multiple webhooks per task are notified concurrently via `CompletableFuture`
    
-   **Auth headers** — `AuthenticationInfo` from the push config is sent as `Authorization: <scheme> <credentials>`
    
-   **Retry with exponential backoff** — configurable via `pushRetryAttempts` (default 3) and `pushRetryBackoffMs` (default 1s). Backoff formula: `delay × 2^attempt`. Only retries on 5xx/IOException — client errors (4xx) are not retried
    
-   **410 Gone auto-cleanup** — webhooks returning HTTP 410 are automatically deregistered
    
-   **SSRF protection** — webhook URLs are validated at registration time (blocks private IPs, loopback, link-local, wildcard, and cloud metadata addresses). Non-loopback webhook URLs must use HTTPS. Set `allowLocalWebhookUrls=true` to permit loopback URLs (plain HTTP allowed) for local development only
    

### Capacity Limiting

Control concurrent task processing and queue overflow:

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        maxConcurrentTasks: 10
        taskQueueSize: 50
      steps:
        - setBody:
            constant: "Processing..."
```

  
| Parameter | Default | Description |
| --- | --- | --- |
| `maxConcurrentTasks` | `0` (unlimited) | Maximum concurrent tasks. A shared semaphore governs all processing paths (sync, async, streaming). |
| `taskQueueSize` | `0` (no queue) | Pending queue capacity for async requests when all slots are occupied. Synchronous and streaming requests cannot be queued — they are rejected immediately. |

Behavior by processing path:

-   **Synchronous** (`returnImmediately=false`): if no permit, returns `ServerBusyError` (HTTP 429)
    
-   **Asynchronous** (`returnImmediately=true`): if no permit and queue has space, task queues as `SUBMITTED`; otherwise HTTP 429
    
-   **Streaming** (`SendStreamingMessage`): same as synchronous — rejects immediately
    

The error response follows the A2A error model: REST returns HTTP 429 with `ServerBusyError`; JSON-RPC returns code `-32000` in the JSON-RPC error envelope.

On shutdown, queued tasks are marked `FAILED` and subscribers are notified.

### Payload Size Limits

The consumer enforces `maxPayloadSize` (default 6,291,456 bytes / 6 MiB) on incoming request bodies. Requests exceeding this limit receive HTTP 413 (REST) or a JSON-RPC error.

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        maxPayloadSize: 10485760
```

### CORS Support

To enable CORS (required when browsers call your agent directly), set the REST configuration:

```properties
camel.rest.enableCors=true
```

The consumer automatically adds `Access-Control-*` headers (including A2A-specific headers like `A2A-Version` and `A2A-Extensions`) and registers OPTIONS preflight handlers for all A2A paths.

### A2A Extensions

The agent card can advertise protocol extensions in `capabilities.extensions`. For protected operation routes, clients can request extensions with the `A2A-Extensions` HTTP header as a comma-separated list of extension URIs.

The consumer validates requested extensions against the resolved agent card. If any requested URI is not advertised, or if a card extension marked `required=true` is not requested, the request fails before route processing with `UnsupportedExtensionError`. Accepted extension URIs are exposed to the route as the `CamelA2AExtensions` header and exchange property, and are echoed in the `A2A-Extensions` response header.

Routes can branch directly on `CamelA2AExtensions`. For reusable behavior, register one or more `org.apache.camel.component.a2a.extension.A2AExtensionHandler` beans in the Camel registry. A handler is matched by extension URI and is invoked before and after route processing when that extension is negotiated. The handler receives the full `AgentExtension` declaration from the agent card, including `params`.

### Consumer REST API Paths

When using the default REST protocol binding, the consumer registers these HTTP endpoints:

  
| Method | Path | Operation |
| --- | --- | --- |
| `GET` | `/.well-known/agent-card.json` | Agent card discovery (always public) |
| `POST` | `/message:send` | SendMessage |
| `POST` | `/message:stream` | SendStreamingMessage (SSE response) |
| `GET` | `/tasks` | ListTasks |
| `GET` | `/tasks/{taskId}` | GetTask |
| `POST` | `/tasks/{taskId}:cancel` | CancelTask |
| `POST` | `/tasks/{taskId}:subscribe` | SubscribeToTask (SSE response) |
| `POST` | `/tasks/{taskId}/pushNotificationConfigs` | CreateTaskPushNotificationConfig |
| `GET` | `/tasks/{taskId}/pushNotificationConfigs` | ListTaskPushNotificationConfigs |
| `GET` | `/tasks/{taskId}/pushNotificationConfigs/{configId}` | GetTaskPushNotificationConfig |
| `DELETE` | `/tasks/{taskId}/pushNotificationConfigs/{configId}` | DeleteTaskPushNotificationConfig |

All paths are prefixed with `basePath` if configured. When using JSON-RPC binding, a single `POST /` endpoint dispatches based on the JSON-RPC `method` field.

The built-in `ListTasks` consumer applies `contextId`, `status`, `statusTimestampAfter`, `pageSize`, `pageToken`, `includeArtifacts`, and `historyLength`. `pageSize` defaults to `50` and is capped at `100`. `pageToken` values returned by the consumer are opaque cursor tokens containing a snapshot of the filtered task IDs for that listing. Clients must send the token back unchanged with the same filters; numeric offsets are rejected.

### Consumer Error Responses

REST binding responses use the A2A v1.0 error wrapper with an `error` object. The `ErrorInfo.reason` detail maps back to these component error names:

  
| Condition | HTTP status | Error code |
| --- | --- | --- |
| Unsupported `A2A-Version` request header | `400` | `VersionNotSupportedError` |
| Authentication validation failure | `401` | `AuthenticationError` |
| Authenticated caller is not authorized for the task | `403` | `AuthorizationError` |
| Unsupported requested extension, or required extension not requested | `400` | `ExtensionSupportRequiredError` |
| Empty `SendMessage` or `SendStreamingMessage` body | `400` | `ContentTypeNotSupportedError` |
| Request body larger than `maxPayloadSize` | `413` | `ContentTypeNotSupportedError` |
| Streaming or task subscription requested but `capabilities.streaming` is omitted or `false` | `405` | `UnsupportedOperationError` |
| Push notification config requested but `capabilities.pushNotifications` is omitted or `false` | `405` | `UnsupportedOperationError` |
| `returnImmediately=true` on REST `SendStreamingMessage` | `400` | `UnsupportedOperationError` |
| Invalid parameters (for example negative `historyLength`, invalid `pageSize`, invalid `pageToken`, or rejected webhook URL) | `400` | `InvalidParamsError` |
| Agent capacity reached | `429` | `ServerBusyError` |
| Task or push notification config not found | `404` | `TaskNotFoundError` |
| Cancel requested for a terminal task | `409` | `TaskNotCancelableError` |
| Unhandled consumer exception | `500` | `InternalError` |

REST binding returns these error responses as `application/a2a+json` with an `error` object containing the HTTP status, status name, message, and a `google.rpc.ErrorInfo` detail reason such as `TASK_NOT_FOUND` or `VERSION_NOT_SUPPORTED`.

JSON-RPC binding returns JSON-RPC error envelopes as `application/json`. Parse errors use `-32700`, invalid requests use `-32600`, unknown methods use `-32601`, invalid parameters use `-32602`, internal errors use `-32603`, capacity and authorization errors use `-32000`, missing tasks use `-32001`, non-cancelable terminal tasks use `-32002`, push notification support errors use `-32003`, unsupported operations use `-32004`, unsupported content types use `-32005`, invalid agent responses use `-32006`, extension negotiation failures use `-32008`, and unsupported A2A versions use `-32009`.

## Producer — Calling a Remote Agent

The producer calls a remote A2A agent. It automatically discovers the agent card, selects the protocol binding, acquires credentials, and wraps/unwraps messages.

### Basic Call

-   YAML
    
-   Java
    

```yaml
- route:
    from:
      uri: direct:call-agent
      steps:
        - setBody:
            constant: "What is the weather?"
        - to:
            uri: a2a:http://remote-agent:8080
        - log:
            message: "Agent replied: ${a2a:text}"
```

```java
from("direct:call-agent")
    .setBody(constant("What is the weather?"))
    .to("a2a:http://remote-agent:8080")
    .log("Agent replied: ${a2a:text}");
```

The producer:

1.  Fetches the agent card from `http://remote-agent:8080/.well-known/agent-card.json`
    
2.  Wraps the body as an A2A `SendMessage` request
    
3.  Sends the request using the card’s declared protocol binding
    
4.  Returns the response as the exchange body — by default (`dataFormat=PAYLOAD`) as extracted text; with `dataFormat=POJO` as the full `Task` or `Message` Java object
    

### Authenticated Calls

#### OIDC (Client Credentials)

```yaml
- to:
    uri: a2a:http://remote-agent:8080
    parameters:
      oauthProfile: my-profile
```

Acquires a token via client-credentials grant, caches it, and refreshes on expiry.

#### API Key

```yaml
- to:
    uri: a2a:http://remote-agent:8082
    parameters:
      apiKey: "{{my.api.key}}"
      apiKeyHeader: X-API-Key
```

#### Bearer Token

```yaml
- to:
    uri: a2a:http://remote-agent:8080
    parameters:
      bearerToken: "{{my.token}}"
```

### JSON-RPC Producer

```yaml
- to:
    uri: a2a:http://remote-agent:8081
    parameters:
      protocolBinding: JSONRPC
```

### Producer Streaming

The producer supports two streaming modes, controlled by the `dataFormat` parameter.

#### Default (PAYLOAD/POJO): Lazy Iterator

The exchange body is a `SseEventIterator` (implements `Iterator<StreamResponse>` + `Closeable`) that parses SSE events lazily from the remote agent’s response stream. Each call to `next()` blocks until the next event arrives. Use with the Split EIP for event-by-event processing:

-   YAML
    
-   Java
    

```yaml
- setHeader:
    name: CamelA2AOperation
    constant: MESSAGE_STREAM
- to: a2a:https://agent.example.com
- split:
    simple: "${body}"
    streaming: true
    steps:
      - choice:
          when:
            - simple: "${body.statusUpdate} != null"
              steps:
                - log:
                    message: "Progress: ${body.statusUpdate.status.state}"
            - simple: "${body.message} != null"
              steps:
                - log:
                    message: "Final message received"
```

```java
from("direct:stream")
    .setHeader("CamelA2AOperation", constant("MESSAGE_STREAM"))
    .to("a2a:https://agent.example.com")
    .split(body()).streaming()
        .log("Event: ${body}")
    .end();
```

The iterator implements `Closeable`, so Camel’s Split EIP automatically releases the underlying HTTP connection after iteration.

To buffer all events into a `List<StreamResponse>`:

```yaml
- to: a2a:https://agent.example.com
- convertBodyTo:
    type: java.util.List
```

#### Raw Passthrough (RAW mode)

With `dataFormat=RAW`, the exchange body is the raw `InputStream` from the remote agent’s SSE response. No parsing or buffering — bytes flow through untouched. Ideal for proxying SSE streams to a browser:

-   YAML
    
-   Java
    

```yaml
- to:
    uri: a2a:https://agent.example.com?dataFormat=RAW
    parameters:
      operation: MESSAGE_STREAM
- setHeader:
    name: Content-Type
    constant: text/event-stream
```

```java
from("platform-http:/stream")
    .to("a2a:https://agent.example.com?dataFormat=RAW&operation=MESSAGE_STREAM")
    .setHeader("Content-Type", constant("text/event-stream"));
```

#### Subscribe to Task Updates

Subscribe to ongoing task updates from a remote agent:

```yaml
- setHeader:
    name: CamelA2AOperation
    constant: TASK_SUBSCRIBE
- setHeader:
    name: CamelA2ATaskId
    simple: "${exchangeProperty.taskId}"
- to: a2a:https://agent.example.com
- split:
    simple: "${body}"
    streaming: true
    steps:
      - log:
          message: "Task update: ${body.statusUpdate.status.state}"
```

Streaming requests use `asyncTimeout` (default 5 minutes) instead of the standard 60-second request timeout. SSE heartbeat comments from the remote agent are automatically filtered during parsing.

### Async Task Lifecycle (returnImmediately + GetTask Polling)

```yaml
# Step 1: Submit the task
- setBody:
    constant: "Start long operation"
- to:
    uri: a2a:http://remote-agent:8083
    parameters:
      oauthProfile: assistant
- setVariable:
    name: taskId
    simple: "${header.CamelA2ATaskId}"

# Step 2: Poll for completion
- removeHeaders:
    pattern: "*"
- setHeader:
    name: CamelA2AOperation
    constant: TASK_GET
- setHeader:
    name: CamelA2ATaskId
    simple: "${variable.taskId}"
- to:
    uri: a2a:http://remote-agent:8083
    parameters:
      oauthProfile: assistant
```

### Push Notification Registration

```yaml
# Step 1: Submit task
- setBody:
    constant: "Track my package"
- to: a2a:http://remote-agent:8085
- setVariable:
    name: taskId
    simple: "${header.CamelA2ATaskId}"

# Step 2: Register push notification webhook
- removeHeaders:
    pattern: "*"
- setHeader:
    name: CamelA2AOperation
    constant: PUSH_CONFIG_CREATE
- setHeader:
    name: CamelA2ATaskId
    simple: "${variable.taskId}"
- setBody:
    simple: "${ref:pushConfig}"
- to: a2a:http://remote-agent:8085
```

Configure the webhook URL as a bean:

```properties
camel.beans.pushConfig = #class:org.apache.camel.component.a2a.model.TaskPushNotificationConfig
camel.beans.pushConfig.url = https://my-server:8090/webhook
```

The full push notification config CRUD is also available. `PUSH_CONFIG_GET` and `PUSH_CONFIG_DELETE` require both `CamelA2ATaskId` and `CamelA2APushConfigId`. Use JSON-RPC binding for these two producer operations when addressing another `camel-a2a` agent with separate task and config IDs.

```yaml
# List push configs for a task
- setHeader:
    name: CamelA2AOperation
    constant: PUSH_CONFIG_LIST
- setHeader:
    name: CamelA2ATaskId
    simple: "${variable.taskId}"
- to: a2a:https://agent.example.com

# Get a specific push config
- setHeader:
    name: CamelA2AOperation
    constant: PUSH_CONFIG_GET
- setHeader:
    name: CamelA2ATaskId
    simple: "${variable.taskId}"
- setHeader:
    name: CamelA2APushConfigId
    simple: "${variable.configId}"
- to: "a2a:https://agent.example.com?protocolBinding=JSONRPC"

# Delete a push config
- setHeader:
    name: CamelA2AOperation
    constant: PUSH_CONFIG_DELETE
- setHeader:
    name: CamelA2ATaskId
    simple: "${variable.taskId}"
- setHeader:
    name: CamelA2APushConfigId
    simple: "${variable.configId}"
- to: "a2a:https://agent.example.com?protocolBinding=JSONRPC"
```

### Parallel Multicast

Call multiple agents concurrently using Camel’s `multicast` EIP:

```yaml
- multicast:
    parallelProcessing: true
    aggregationStrategy: "#class:MyAggregator"
    steps:
      - to: direct:call-weather
      - to: direct:call-news
      - to: direct:call-fortune

- route:
    id: call-weather
    from:
      uri: direct:call-weather
      steps:
        - removeHeaders:
            pattern: "*"
        - setBody:
            constant: "What is the weather?"
        - to:
            uri: a2a:http://weather-agent:8080
            parameters:
              oauthProfile: my-profile
```

> **Tip**
> Use `removeHeaders: "*"` before each producer call in a multicast branch to prevent leaking headers (like `CamelA2ATaskId`) from one branch to another.

### Address Override

Override the remote agent’s URL from the card using `host`/`port`/`basePath` config:

```yaml
- to: a2a:https://agent.example.com?host=http://localhost&port=8080
```

Priority without producer credentials: `host` config > card’s `supportedInterfaces` URL > `agentCardSource` URL. When producer credentials are configured (`apiKey`, `bearerToken`, or `oauthProfile`) and `host` is not set, the producer sends credentialed requests only to the HTTP(S) `agentCardSource` origin. This avoids sending credentials to a URL supplied by the remote card’s `supportedInterfaces` field. The `port` and `basePath` producer overrides are applied when `host` is configured. A `host` value without a scheme is treated as HTTPS.

> **Note**
> The producer does not forward arbitrary inbound `Authorization` or `Cookie` headers. Only credentials resolved from the A2A endpoint configuration are applied to outgoing requests.

### Redirect Handling

By default, the producer does not follow HTTP redirects to prevent credential leakage on cross-origin redirects. Enable only when the remote agent is known to issue redirects:

```yaml
- to: a2a:https://agent.example.com?followRedirects=true
```

> **Note**
> `followRedirects` applies to operation requests only. Agent card discovery never follows redirects — a card URL that returns a redirect fails with an error.

Producer operation responses must be 2xx. Redirect and other non-2xx responses fail before response deserialization, so a 3xx response cannot be accidentally parsed as an A2A payload.

### Request Timeouts

The producer uses different timeouts depending on the operation:

-   **Non-streaming requests**: hard-coded at 60 seconds. There is currently no configuration parameter to override this.
    
-   **Streaming requests** (`SendStreamingMessage`, `SubscribeToTask`): uses `asyncTimeout` (default 300,000 ms / 5 minutes).
    
-   **Streaming read timeout**: `streamingReadTimeout` (default 300,000 ms / 5 minutes) — if no SSE event arrives from the remote agent within this period, the stream is closed with an error. Prevents indefinite blocking when a remote agent stops sending events.
    
-   **Streaming event size**: each decoded SSE event is limited by `maxPayloadSize` (default 6 MiB). Oversized events fail the producer stream instead of accumulating unbounded memory.
    
-   **TCP connection**: `connectTimeout` (default 30,000 ms / 30 seconds).
    

### A2A-Version Header

The producer sends an `A2A-Version: 1.0` header on every outgoing operation request, per the A2A v1.0 specification. This is automatic and not configurable.

## Simple Language Functions

The component registers custom Simple language functions under the `a2a:` namespace:

 
| Function | Description |
| --- | --- |
| `${a2a:emit('message')}` | Emit a status update with `WORKING` state. Used in `script` EIP for SSE streaming side effects. |
| `${a2a:emit(STATE, 'message')}` | Emit a status update with an explicit `TaskState` (e.g., `INPUT_REQUIRED`). |
| `${a2a:text}` | Extract `TextPart` content from the body (`Task` or `Message`). If the body is a `Task`, extracts from the last history message. |
| `${a2a:text(expression)}` | Extract `TextPart` content from the result of a Simple expression. |
| `${a2a:data}` | Extract `DataPart` content as a JSON string from the body. |
| `${a2a:data(expression)}` | Extract `DataPart` content from the result of a Simple expression. |
| `${a2a:file}` | Extract `FilePart` content — the `url` value for URL file parts, or a binary-size description for inline base64 `raw` content. |
| `${a2a:file(expression)}` | Extract `FilePart` content from the result of a Simple expression. |
| `${a2a:card}` | Full agent card as JSON string. Resolves the card from the A2A endpoint (consumer’s own card, or the cached remote card on the producer). |
| `${a2a:card.name}` | Agent card name. |
| `${a2a:card.description}` | Agent card description. |
| `${a2a:card.url}` | Agent card URL. |
| `${a2a:card.version}` | Agent card version. |
| `${a2a:card.skills}` | Skills formatted as human-readable text, one per line: `* Skill Name: description`. |
| `${a2a:card.skills.json}` | Skills as a JSON array string. |
| `${a2a:card.iconUrl}` | Agent card icon URL. |
| `${a2a:card.documentationUrl}` | Agent card documentation URL. |
| `${a2a:card.provider}` | Agent provider as JSON string. |
| `${a2a:card.capabilities}` | Agent capabilities as JSON string. |
| `${a2a:card.supportedInterfaces}` | Supported interfaces as JSON array string. |
| `${a2a:card.securitySchemes}` | Security schemes as JSON object string. |
| `${a2a:card.securityRequirements}` | A2A v1.0 security requirements as JSON array string. |
| `${a2a:card.security}` | Legacy draft `security` field as JSON array string. |

The `${a2a:card}` functions resolve the agent card from the exchange context. On a consumer route, `exchange.getFromEndpoint()` provides the agent’s own card. On a producer route, the component scans registered endpoints for a cached remote card. Returns an empty string if no card is available.

> **Note**
> The card functions support flat field access only — nested property access like `${a2a:card.skills[0].name}` or `${a2a:card.provider.organization}` is not supported. For structured data, use `${a2a:card.skills.json}` or `${a2a:card}` (full JSON) and parse downstream.

Example — including remote agent skills in an LLM prompt:

```yaml
- setBody:
    simple: |
      Generate a plan using these available skills:
      ${a2a:card.skills}
```

For the full text of all parts combined, use the TypeConverter:

```yaml
- convertBodyTo:
    type: String
```

## Exchange Headers

  
| Header | Type | Description |
| --- | --- | --- |
| `CamelA2AOperation` | `String` | A2A operation to invoke. Accepts both enum names (`TASK_GET`, `PUSH_CONFIG_CREATE`) and PascalCase method names (`GetTask`, `CreateTaskPushNotificationConfig`). |
| `CamelA2ATaskId` | `String` | Task ID for `GetTask`, `CancelTask`, `SubscribeToTask`, and push notification operations. |
| `CamelA2APushConfigId` | `String` | Push notification config ID for get/delete operations. |
| `CamelA2AContextId` | `String` | Context ID for multi-turn conversations. Set automatically on consumer inbound and producer response. |
| `CamelA2AMessageId` | `String` | Message ID from the request/response. |
| `CamelA2ATaskState` | `String` | Task state from the response (e.g., `TASK_STATE_COMPLETED`, `TASK_STATE_WORKING`). |
| `CamelA2AMethod` | `String` | A2A method name invoked (set on producer response). |
| `CamelA2AResponseType` | `String` | Response type after a producer call (`task`, `message`, `taskList`, `pushConfigList`, or `stream`). On consumer routes, set this header to `message` to return a message-only `SendMessage` response; the default is a task response. |
| `CamelA2AReturnImmediately` | `Boolean` | Override `returnImmediately` per-request. Takes precedence over endpoint-level config. |
| `CamelA2AHistoryLength` | `Integer` | Producer request field for `GetTask` and `SubscribeToTask`. |
| `CamelA2AUserProfile` | `Map<String, Object>` | Authenticated user profile (set as exchange **property**, not a header). Populated when `validateAuth=true` and authentication succeeds. Access via `$\{exchangeProperty.CamelA2AUserProfile}` in Simple expressions. |
| `CamelA2AListContextId` | `String` | Context ID filter for `ListTasks`. |
| `CamelA2AListPageSize` | `Integer` | Page size for `ListTasks`. |
| `CamelA2AListPageToken` | `String` | Pagination token for `ListTasks`. |
| `CamelA2AListStatus` | `String` | Comma-separated status filter for `ListTasks`. |
| `CamelA2AListIncludeArtifacts` | `Boolean` | Whether `ListTasks` responses include artifacts. |
| `CamelA2AListHistoryLength` | `Integer` | Maximum history messages to include per task in `ListTasks` responses. |
| `CamelA2AListStatusTimestampAfter` | `String` | Status timestamp filter for `ListTasks`. Use an ISO-8601 offset date-time. |
| `CamelA2AExtensions` | `List<String>` | Consumer-side negotiated A2A extension URIs from the `A2A-Extensions` request header. Set as both an exchange property and a route header after validation. |
| `CamelA2AStreamEmitter` | `A2AStreamEmitter` | Consumer-side: the stream emitter injected for streaming routes. Available for advanced use cases — prefer `${a2a:emit()}` or `A2AProgress` API for most agents. |
> **Note**
> `CamelA2AResponseTask` and `CamelA2AUserProfile` are exchange **properties**, not headers. Access them via `$\{exchangeProperty.CamelA2AResponseTask}` and `$\{exchangeProperty.CamelA2AUserProfile}` in Simple expressions. `CamelA2AResponseTask` contains the full `Task` object from the producer response, useful in polling workflows. `CamelA2AUserProfile` contains the authenticated user’s profile map, available when `validateAuth=true`.

## Supported Operations

   
| Operation (enum) | Method Name | Producer | Consumer |
| --- | --- | --- | --- |
| `MESSAGE_SEND` | `SendMessage` | Send message, receive Task or Message | Route processes message |
| `MESSAGE_STREAM` | `SendStreamingMessage` | Lazy `Iterator<StreamResponse>` (`PAYLOAD`/`POJO`), or raw `InputStream` (`RAW`) | Route emits via `${a2a:emit()}` |
| `TASK_GET` | `GetTask` | Retrieve task from remote | Read from task store |
| `TASK_LIST` | `ListTasks` | List tasks from remote | Query task store |
| `TASK_CANCEL` | `CancelTask` | Cancel remote task | Update store + cancel in-flight async |
| `TASK_SUBSCRIBE` | `SubscribeToTask` | Lazy `Iterator<StreamResponse>` of task updates | Event-to-Stream Bridge (real-time SSE) for REST and JSON-RPC bindings |
| `PUSH_CONFIG_CREATE` | `CreateTaskPushNotificationConfig` | Register webhook | Store config + SSRF validation |
| `PUSH_CONFIG_GET` | `GetTaskPushNotificationConfig` | Retrieve config | Read from store |
| `PUSH_CONFIG_LIST` | `ListTaskPushNotificationConfigs` | List configs | Read from store |
| `PUSH_CONFIG_DELETE` | `DeleteTaskPushNotificationConfig` | Delete config | Remove from store |

The `CamelA2AOperation` header accepts both the enum name (left column) and the PascalCase method name (second column).

The built-in JSON-RPC consumer dispatches `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`, and the push notification config operations.

## Pluggable Authentication

Authentication uses a strategy-per-scheme pattern via the `A2ASecuritySchemeHandler` SPI. One handler covers both producer (apply credentials) and consumer (validate credentials) for a given security scheme type.

Default handlers:

  
| Scheme Type | Handler | Description |
| --- | --- | --- |
| `http` (bearer) | `HttpBearerSchemeHandler` | OAuth profile or static bearer token |
| `apiKey` | `ApiKeySchemeHandler` | API key in `header`, `query`, or `cookie` location. If the security scheme declares `location=header` and `name`, that header is used; otherwise `apiKeyHeader` is used. |
| `oauth2` | `OAuth2SchemeHandler` | Token via `camel-oauth` SPI |
| `openIdConnect` | `OpenIdConnectSchemeHandler` | Same as OAuth2, stores OIDC discovery URL |

Override or extend by registering a custom handler bean:

_Java-only: implementing a custom A2ASecuritySchemeHandler_

```java
@BindToRegistry("myCustomAuth")
public class MutualTlsHandler implements A2ASecuritySchemeHandler {
    public String schemeType() { return "mutualTls"; }

    public void applyCredentials(Exchange exchange, SecurityScheme scheme,
            A2AConfiguration config, CamelContext context) {
        // producer: configure mTLS
    }

    public A2AUserProfile validateCredentials(Exchange exchange,
            SecurityScheme scheme, A2AConfiguration config) {
        // consumer: extract client certificate
        return A2AUserProfile.fromMap(Map.of(
                "scheme", "mutualTls",
                "subject", "CN=client"));
    }
}
```

### Security Scheme Priority

When the agent card declares multiple security schemes, selection is based on configuration hints:

-   `oauthProfile` configured → OAuth2 / OpenID Connect schemes tried first
    
-   `bearerToken` configured → HTTP bearer schemes tried first
    
-   `apiKey` configured → API key schemes tried first
    
-   No hint → schemes tried in agent card declaration order
    

On the producer, the first matching handler applies credentials. On the consumer, all declared schemes are tried until one succeeds (or all fail with 401).

## Task State Machine

A2A tasks follow this state machine:

                    +--> COMPLETED
                    |
SUBMITTED --> WORKING +--> FAILED
                    |
                    +--> CANCELED
                    |
                    +--> INPUT\_REQUIRED --> WORKING (resumed)
                    |
                    +--> AUTH\_REQUIRED --> WORKING (after re-auth)
                    |
                    +--> REJECTED

All valid task states (wire values use the A2A v1.0 proto names shown below; the `$\{a2a:emit()}` function uses the short Java enum names — e.g., `INPUT_REQUIRED`, not `TASK_STATE_INPUT_REQUIRED`):

 
| State | Description |
| --- | --- |
| `TASK_STATE_UNSPECIFIED` | Unknown or missing task state fallback |
| `TASK_STATE_SUBMITTED` | Task accepted, not yet processing |
| `TASK_STATE_WORKING` | Task is actively being processed |
| `TASK_STATE_COMPLETED` | Task completed successfully (terminal) |
| `TASK_STATE_FAILED` | Task failed due to error or timeout (terminal) |
| `TASK_STATE_CANCELED` | Task was canceled by the client (terminal) |
| `TASK_STATE_INPUT_REQUIRED` | Agent needs additional input from the client before proceeding |
| `TASK_STATE_AUTH_REQUIRED` | Agent needs the client to re-authenticate before proceeding |
| `TASK_STATE_REJECTED` | Agent rejected the task (terminal) |

The consumer manages transitions automatically:

-   `SUBMITTED` — when `returnImmediately=true` and the task is accepted
    
-   `WORKING` — when the route starts processing (async tasks)
    
-   `COMPLETED` — when `setBody` produces a non-null response
    
-   `FAILED` — when the route throws an exception or `asyncTimeout` expires
    

Routes can emit any state explicitly via `${a2a:emit(INPUT_REQUIRED, 'Please provide your address')}` or `A2AProgress.emit(exchange, TaskState.AUTH_REQUIRED, "Re-authentication needed")`.

## Unified Body-as-Response

Both streaming and async agents use the same route pattern — `setBody` is always the final response:

```yaml
steps:
  - script:
      simple: "${a2a:emit('progress...')}"    # optional status events
  - setBody:
      constant: "final response"              # consumer handles delivery
```

The consumer handles delivery regardless of mechanism:

-   **Synchronous**: returned directly in the HTTP response
    
-   **Streaming**: emitted as an SSE message event
    
-   **Async + polling**: stored in the task’s history and status message, returned via `GetTask`
    
-   **Push notifications**: included in the `COMPLETED` status webhook payload
    

## Agent Card Resolution

Card fields are resolved with layered precedence (lowest to highest):

1.  **Agent card JSON file** — loaded from the URI source (base)
    
2.  **Bean reference** — `agentCard=#myBean` fills/overrides fields from a programmatic object
    
3.  **URI parameters** — `name`, `description`, `version` override everything
    

Bean reference example — useful when the card needs programmatic construction (e.g., dynamic skills):

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        agentCard: "#myCardBean"
```

URI parameter overrides — customize base cards per environment via properties:

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json
      parameters:
        name: "{{agent.name}}"
        version: "{{agent.version}}"
```

Security schemes from the card drive auth handler selection. Config parameters (`oauthProfile`, `bearerToken`, `apiKey`) provide the runtime credentials and influence scheme priority.

The resolver currently merges these card fields from file and bean cards: `name`, `description`, `url`, `version`, `provider`, `capabilities`, `skills`, `supportedInterfaces`, `securitySchemes`, `securityRequirements`, `iconUrl`, `documentationUrl`, `defaultInputModes`, `defaultOutputModes`, `supportsAuthenticatedExtendedCard`, and unknown extension properties. Legacy input cards using `security` are converted to `securityRequirements`. URI parameters can override only `name`, `description`, and `version`.

> **Note**
> The card is loaded once at endpoint startup and cached. Periodic card refresh is not yet implemented.

## HTTP Server Component Discovery

The consumer needs an HTTP server to register A2A endpoints. The discovery order is:

1.  **Explicit `httpServerComponent`** parameter (e.g., `undertow`, `jetty`)
    
2.  **Global `camel.rest.component`** / REST DSL component configuration
    
3.  **Already registered `platform-http` component**
    
4.  Exactly one already registered Camel component implementing `RestConsumerFactory`
    
5.  Exactly one registry bean implementing `RestConsumerFactory`
    

The preview component does not auto-create arbitrary HTTP server components and does not choose between multiple discovered server factories. Add and register one of the tested server components, or set `httpServerComponent` explicitly.

Tested behavior in this Preview release:

-   `camel-platform-http` / Vert.x - suitable for JSON-RPC agents and serving the public agent card. REST custom-method paths containing colons can collide, and SSE `InputStream` responses are buffered.
    
-   `camel-undertow` and `camel-jetty` - suitable for REST custom-method paths and real-time SSE streaming.
    

For SSE streaming, specify `httpServerComponent=undertow` or `httpServerComponent=jetty` and add the matching component dependency.

## Data Format

The `dataFormat` parameter controls what the exchange body contains, following the same convention as the [CXF component](cxf-component.md). It applies to both consumer (inbound request) and producer (response) sides.

  
| Value | Default | Description |
| --- | --- | --- |
| `PAYLOAD` | Yes | Extracts text content from message parts as a `String`. On the consumer, incoming `Message` parts are flattened via `messageToString()`. On the producer, response `Task` or `Message` objects are converted to text. Backward compatible — simple routes using `${body}` get clean text. Streaming: same as POJO (lazy iterator on producer, emitter-based on consumer). |
| `POJO` | No | Full Java model objects. On the consumer, the body is the `Message` record with all parts, metadata, and files intact — access individual parts via `$\{body.parts()[0]}`. On the producer, the body is the `Task` or `Message` response object. Streaming: body is a lazy `Iterator<StreamResponse>` parsed on demand (use with Split EIP). |
| `RAW` | No | Raw JSON string with no deserialization. On the consumer, the body is the JSON representation of the incoming `Message`. On the producer, the body is the raw JSON response string. Useful for forwarding, logging, or compliance where byte-exact fidelity matters. Streaming: body is the raw `InputStream` — true SSE passthrough with no buffering. |

Example — consumer in POJO mode for multimodal processing:

```yaml
- route:
    from:
      uri: a2a:classpath:agent-card.json?dataFormat=POJO
      steps:
        - log:
            message: "Received ${body.parts().size()} parts"
        - setBody:
            simple: "Processed ${body.parts()[0]}"
```

Example — producer in RAW mode for proxying:

```yaml
- route:
    from:
      uri: direct:proxy
      steps:
        - to: a2a:http://remote-agent:8080?dataFormat=RAW
        - log:
            message: "Raw JSON: ${body}"
```

## Task Store

The default `InMemoryTaskStore` manages task state with lazy cleanup. It is thread-safe, using concurrent maps for storage and lock-protected eviction for task-associated data such as message-id mappings, subscribers, and push configs.

The `completedTaskTtl` URI parameter (default 3,600,000 ms / 1 hour) controls the TTL for terminal tasks. Expired tasks are evicted on read or during periodic cleanup.

The following settings are configurable programmatically on the `InMemoryTaskStore` bean, not via URI parameters:

  
| Property | Default | Description |
| --- | --- | --- |
| `maxStoredTasks` | `10000` | Maximum tasks in the store. When exceeded, the oldest terminal task is evicted; if no terminal task exists, no task is evicted. Set to 0 for unlimited. |
| `cleanupInterval` | `100` | Number of `put()` calls between periodic bulk cleanup runs. |
| `stuckTaskTtlMs` | `0` (disabled) | TTL for non-terminal stuck tasks (`SUBMITTED`, `WORKING`). When set to a positive value, tasks older than this duration are marked `FAILED` on read or periodic cleanup, and subscribers are notified. This preview safety net is disabled by default; prefer `asyncTimeout` on the endpoint for normal timeout handling. |

To customize, register a bean implementing `A2ATaskStore` in the Camel registry. The preview SPI is also split into narrower parent contracts (`A2ATaskRepository`, `A2ATaskSubscriptions`, `A2APushConfigStore`, and `A2ATaskCleanup`) so custom stores can keep storage, subscription, push-config, and cleanup responsibilities separate. Registry-provided stores are wrapped by a guard that enforces terminal-state protection, delegates subscriber registration and notification to the custom store, tracks endpoint subscribers for terminal-event cleanup, validates push webhook URLs, and normalizes push config IDs/task IDs on the endpoint path. Custom stores still need to preserve durable storage and any cross-node notification semantics inside their own implementation; the dispatcher revalidates webhook URLs before delivery.

Custom subscribers can be registered on a task-store bean for audit logging, metrics, or custom delivery:

_Java-only: registering a custom A2ATaskStore with a global subscriber_

```java
@BindToRegistry
public A2ATaskStore taskStore() {
    MyAuditingTaskStore store = new MyAuditingTaskStore();
    store.addGlobalSubscriber((taskId, event) -> auditLog.record(taskId, event));
    return store;
}
```

## Model Classes

The component uses Java model classes aligned with A2A v1.0:

 
| Class | Description |
| --- | --- |
| `Task` | Task with `id`, `contextId`, `status` (`TaskStatus`), `history` (`List<Message>`), `artifacts` (`List<Artifact>`), and `metadata` (`Map<String, Object>`). `contextId` is the key for multi-turn conversations (maps to `CamelA2AContextId` header). `Task.latest()` returns the last message in history. |
| `Message` | Agent or user message with `role`, `parts`, `messageId`, `contextId`, `taskId`, `referenceTaskIds`, `metadata`, and `extensions`. The `Role` enum uses `ROLE_USER`, `ROLE_AGENT`, and `ROLE_UNSPECIFIED`; `ROLE_UNSPECIFIED` is the fallback for unknown role values during deserialization. |
| `SendMessageRequest` | Operation request wrapper with `message`, `configuration`, and `metadata`. The consumer requires `message.role` and at least one message part. |
| `SendMessageConfiguration` | Send-message runtime options with `returnImmediately`, `blocking`, `historyLength`, and retained extension properties. `returnImmediately` wins over `blocking`; `blocking=false` is treated as immediate return. |
| `SendMessageResponse` | Response wrapper containing exactly one of `task` or `message`. |
| `TaskListRequest` | List-tasks request with `contextId`, `status`, `statusTimestampAfter`, `includeArtifacts`, `historyLength`, `pageSize`, and `pageToken`. |
| `ListTasksResponse` | List-tasks response with `tasks`, `nextPageToken`, `pageSize`, and `totalSize`. |
| `StreamResponse` | SSE event wrapper containing exactly one of `task`, `message`, `statusUpdate`, or `artifactUpdate`. |
| `TextPart` | Text content part implementing `Part<String>`. |
| `DataPart` | Structured data part implementing `Part<Object>`. |
| `FilePart` | File content with `raw` for inline base64 data, `url` for referenced content, plus optional `mediaType`, `filename`, and `metadata`. |
| `TaskStatus` | Status snapshot with `state` (`TaskState`), optional `message` (`Message`), and `timestamp` (`OffsetDateTime`). |
| `Artifact` | Named output with `artifactId`, `name`, `description`, `parts` (`List<Part<?>>`), `metadata`, and `extensions`. |
| `TaskPushNotificationConfig` | Push notification webhook config with `id`, `taskId`, `url`, `token` (simple bearer token), and `authentication` (`AuthenticationInfo` with `scheme` + `credentials`). The `token` field provides a simpler alternative to full `authentication` for webhook auth. |
| `ListPushNotificationConfigsResponse` | Push notification config list response with `configs`. |

Core immutable model types such as `Task`, `Message`, `Artifact`, and `AgentCard` provide builders, for example: `Task.builder().id("t1").contextId("ctx-1").status(new TaskStatus(TaskState.WORKING)).build()`. Mutable request/configuration classes use standard getters and setters.

## Preview API and SPI Boundaries

The stable user-facing surface for this Preview release is the endpoint URI/options, exchange headers, documented model classes, type converters, and these registry SPIs: `A2ATaskStore`, `A2AExtensionHandler`, and `A2ASecuritySchemeHandler`.

The protocol and operation implementation classes under `org.apache.camel.component.a2a.protocol` and `org.apache.camel.component.a2a.operation` are public so the component can share implementation code across packages and tests, but they are not extension SPIs. `A2AProtocol` is sealed deliberately to the built-in REST and JSON-RPC bindings; select a binding with `protocolBinding` rather than implementing a custom protocol class.

The JSON mapper helper returns configured mapper copies. Customizing the returned `ObjectMapper` does not change component-wide serialization behavior.

## Dependencies

The component has zero compile-time coupling to any HTTP library. Transport is discovered at runtime:

  
| Role | SPI | Examples |
| --- | --- | --- |
| Consumer HTTP server | `RestConsumerFactory` | `camel-platform-http`, `camel-undertow`, `camel-jetty` |
| Producer HTTP client | Built-in `java.net.http.HttpClient` | No additional dependency |
| OAuth tokens | `OAuthClientAuthenticationFactory` | `camel-oauth` (optional, discovered via FactoryFinder) |
| Task state | `A2ATaskStore` | Built-in `InMemoryTaskStore` (default), custom implementations via registry |

Add the HTTP server component you want as a runtime dependency. Consumer routes require one of these dependencies; producer-only routes do not.

```xml
<!-- Consumer: JSON-RPC or non-streaming HTTP endpoint serving -->
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-platform-http-vertx</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
</dependency>

<!-- Consumer: REST custom-method routes and real-time SSE streaming -->
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-undertow</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
</dependency>

<!-- Alternative consumer transport for REST custom-method routes and real-time SSE streaming -->
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-jetty</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
</dependency>

<!-- Optional: OAuth authentication -->
<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-oauth</artifactId>
    <version>x.x.x</version>
    <!-- use the same version as your Camel core version -->
</dependency>
```

## See Also

-   [Camel 4.21 upgrade notes for A2A](../../manual/camel-4x-upgrade-guide-4_21.md)
    
-   [A2A v1.0 Protocol Specification](https://a2a-protocol.org/latest/specification/)
    
-   [A2A Protocol Definitions](https://a2a-protocol.org/latest/definitions/)
    
-   [A2A Protocol GitHub](https://github.com/a2aproject/A2A)