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.5.0 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