A2A Task Routing
Captures task and context ownership from SendMessage JSON responses and SendStreamingMessage / SubscribeToTask SSE responses, then routes follow-up requests back to the backend cluster that created the task or owns the context
Category: Setup-dependent integration
Task: Captures task and context ownership from SendMessage JSON responses and SendStreamingMessage / SubscribeToTask SSE responses, then routes follow-up requests back to the backend cluster that created the task or owns the context
Prerequisites: The external service, credentials, or certificates referenced by this configuration.
Run it: Use ghcr.io/praxis-proxy/ai:0.4.1 and follow the container quickstart to mount and start the configuration.
This configuration comes from the selected release. The example has not been run here; external services are not bundled.
Download the source file.
# A2A Task and Context-Owner Routing
#
# Captures task and context ownership from SendMessage JSON responses and
# SendStreamingMessage / SubscribeToTask SSE responses, then routes
# follow-up requests back to the backend cluster that created the task or
# owns the context.
#
# This example uses `local` storage mode. This is useful for development and
# testing or single-instance deployments of Praxis, but multi-replica routing
# requires an external state store (e.g. Valkey).
#
# Flow:
# 1. SendMessage and SendStreamingMessage are routed to agent-a by
# static router rules.
# 2. The a2a filter captures task IDs and context IDs from JSON response
# bodies (SendMessage) and SSE data frames (SendStreamingMessage,
# SubscribeToTask), recording:
# - task_id -> cluster_name
# - context_id -> cluster_name
# in local in-process stores.
# 3. Follow-up GetTask, CancelTask, SubscribeToTask, or push-notification
# config calls with a known task ID inject x-praxis-a2a-route-cluster,
# which the router matches to select the owning cluster.
# 4. ListTasks, SendMessage, or SendStreamingMessage carrying a known
# contextId are also routed to the owning cluster via context-owner
# routing. Task ID routes take precedence over context ID routes.
# 5. Unknown task IDs and context IDs follow the static fallback route.
listeners:
- name: a2a
address: "127.0.0.1:8080"
filter_chains: [a2a-task-routing]
filter_chains:
- name: a2a-task-routing
filters:
- filter: a2a
max_body_bytes: 65536
on_invalid: continue
headers:
method: x-praxis-a2a-method
family: x-praxis-a2a-family
context_id: x-praxis-a2a-context-id
task_id: x-praxis-a2a-task-id
streaming: x-praxis-a2a-streaming
task_routing:
enabled: true
store: local
route_cluster_header: x-praxis-a2a-route-cluster
ttl_seconds: 3600
terminal_ttl_seconds: 300
max_response_body_bytes: 65536
- filter: router
routes:
# Task route hit: route to the cluster that owns the task
- path_prefix: "/"
headers:
x-praxis-a2a-route-cluster: "agent-a"
cluster: "agent-a"
- path_prefix: "/"
headers:
x-praxis-a2a-route-cluster: "agent-b"
cluster: "agent-b"
# Initial SendMessage goes to the primary agent
- path_prefix: "/"
headers:
x-praxis-a2a-method: "SendMessage"
cluster: "agent-a"
# Streaming methods to streaming-capable backend
- path_prefix: "/"
headers:
x-praxis-a2a-streaming: "true"
cluster: "agent-a"
# Default fallback for unknown tasks and non-A2A traffic
- path_prefix: "/"
cluster: "agent-b"
- filter: load_balancer
clusters:
- name: "agent-a"
endpoints:
- "127.0.0.1:9001"
- name: "agent-b"
endpoints:
- "127.0.0.1:9002"
insecure_options:
allow_private_endpoints: true # example proxies to local backends