An A2A (Agent-to-Agent) example: a CrewAI pod exposes an A2A JSON-RPC server, and a LangGraph pod acts as an orchestrator that calls the Crew specialist over HTTP/A2A inside the cluster (or locally). One container image is built from a single Dockerfile; A2A_ROLE in the pod (crew vs langgraph) selects which process runs.
- uv — Python package manager
- Podman or Docker — for local container builds (
Makefileuses whichever is onPATH) - oc — for OpenShift deployment
- Helm — for deploying to Kubernetes/OpenShift
- GNU Make and a bash-compatible shell — on Windows, use WSL (recommended) or Git Bash
cd agents/a2a/langgraph_crewai_agent
make init # creates .env from template.env if missingEdit .env with your configuration (see Configuration below).
Install dependencies with make env, same idea as the Google ADK agent (Creating environment):
make envThis runs uv sync --python 3.12 and creates or updates .venv.
You can use placeholders for container images if you only run Python locally:
API_KEY=your-key-or-not-needed
BASE_URL=http://localhost:8321/v1
MODEL_ID=ollama/llama3.1:8b
CONTAINER_IMAGE=not-needed
CREW_A2A_PUBLIC_URL=http://127.0.0.1:9100
LANGGRAPH_A2A_PUBLIC_URL=http://127.0.0.1:9200
CREW_A2A_URL=http://127.0.0.1:9100
CREW_A2A_PORT=9100
LANGGRAPH_A2A_PORT=9200See Local Development for Ollama + Llama Stack setup for local model serving.
Edit .env with your keys and registry image(s):
API_KEY=your-api-key-here
BASE_URL=https://your-llama-stack.example.com/v1
MODEL_ID=your-model-id
CONTAINER_IMAGE=quay.io/your-org/a2a-langgraph-crewai:latestInstall dependencies and configure .env, then either use Make (same idea as the Google ADK agent) or run the two Python modules by hand.
Option A — one terminal (make run-app)
make init # .env from template.env if needed — then edit .env
make env # uv sync into .venv (once)
make run-app # Crew in background, LangGraph in foreground (Ctrl+C stops both)If ports 9100 / 9200 are stuck: make run-app-fresh.
Option B — two terminals (uv run)
uv sync
set -a && source .env && set +a# Terminal 1 — CrewAI + A2A
uv run python -m a2a_langgraph_crewai.crew_a2a_server
# Terminal 2 — LangGraph orchestrator
uv run python -m a2a_langgraph_crewai.langgraph_a2a_serverSingle-process shortcuts: make run-crew or make run-langgraph.
Default ports: 9100 (Crew), 9200 (LangGraph). Do not set PORT unless you mirror the container (8080).
With the LangGraph server running (terminal 2), open http://127.0.0.1:9200/ in a browser. The chat uses A2A JSON-RPC on POST / with message/send. The server also exposes POST /chat/completions (OpenAI-style). For local curl, use http://127.0.0.1:9200.
Uses Helm + Makefile. Chart: charts/a2a-langgraph-crewai/ (two Deployments, two Services, two Routes, one Secret).
cd agents/a2a/langgraph_crewai_agent
make initEdit .env with your model endpoint and container image(s).
Option A — one registry path; make build / make push produce :crew and :langgraph from the same stem:
API_KEY=your-api-key-here
BASE_URL=https://your-model-endpoint.com/v1
MODEL_ID=your-model-id
CONTAINER_IMAGE=quay.io/your-username/a2a-langgraph-crewai:latestOption B — two full image refs (e.g. separate repos or explicit tags):
API_KEY=your-api-key-here
BASE_URL=https://your-model-endpoint.com/v1
MODEL_ID=your-model-id
CONTAINER_IMAGE_CREW=quay.io/your-username/a2a-crewai:latest
CONTAINER_IMAGE_LANGGRAPH=quay.io/your-username/a2a-langgraph:latestNotes:
-
API_KEY— your API key or contact your cluster administrator -
BASE_URL— should end with/v1 -
MODEL_ID— model identifier available on your endpoint -
CONTAINER_IMAGE_CREWandCONTAINER_IMAGE_LANGGRAPHinstead.Examples (stem style): Quay.io
quay.io/your-username/a2a-langgraph-crewai:latest, Docker Hub, GHCR — same pattern astemplate.env.Note: OpenShift must be able to pull both image refs. Make them public, or configure an image pull secret for private registries.
Log in to the cluster and registry:
oc login --token=<token> --server=https://<cluster-api-url>
oc project <namespace>
docker login -u='login' -p='password' quay.ioRequires Podman or Docker (with buildx where needed) and a registry account (e.g. Quay.io).
make build loads the image locally and tags :crew and :langgraph (one build, second ref via tag). make push uploads both refs to the registry.
make build # build once, tag :crew and :langgraph locally
make push # push both tags to the registryRun make build && make push after image or Dockerfile changes, then make deploy.
make dry-run # preview rendered Helm manifests (secrets redacted)make deployThe LangGraph pod uses in-cluster CREW_A2A_URL=http://a2a-crew-agent:8080.
Register agents with Kagenti for unified discovery and management.
Prerequisites:
- Kagenti installed:
oc get pods -n kagenti-system - Namespace labeled:
oc label namespace <your-namespace> kagenti-enabled=true
Apply AgentRuntime:
NOTE: The AgentRuntime.yaml file will be applied
Apply:
oc apply -f AgentRuntime.yamlVerify:
oc get agentcard -n <your-namespace>AgentCards for both LangGraph and CrewAI will appear in Kagenti UI.
After deploying, the application may take about a minute to become available while the pods start.
Route hosts are printed after make deploy. Retrieve them manually:
oc get route a2a-langgraph-agent -o jsonpath='{.spec.host}'make undeploySee OpenShift Deployment for more details.
Use the LangGraph orchestrator for every curl below — Route a2a-langgraph-agent on OpenShift, or http://127.0.0.1:9200 locally. That is the public entrypoint (playground, Agent Card, /health, A2A JSON-RPC, /chat/completions).
Do not aim curl at the Crew specialist (a2a-crew-agent / port 9100): Crew is the peer the LangGraph pod calls over A2A in-cluster; clients should talk to LangGraph so the orchestrator can delegate when needed.
On OpenShift, routes terminate TLS at the edge; use https://. Replace <YOUR_ROUTE_URL> with the LangGraph route host, e.g.:
oc get route a2a-langgraph-agent -o jsonpath='{.spec.host}'curl -sS "https://<YOUR_ROUTE_URL>/.well-known/agent-card.json"curl -sS "http://127.0.0.1:9200/health"curl -sS "https://<YOUR_ROUTE_URL>/health"Same protocol as the browser playground: JSON-RPC 2.0 body = SendMessageRequest (see a2a-sdk). Use a unique messageId per request.
curl -sS -X POST "https://<YOUR_ROUTE_URL>/" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"jsonrpc":"2.0","method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"Use the web search tool to find the Red Hat encrypted string."}],"messageId":"0123456789abcdef0123456789abcdef"}},"id":"curl-req-1"}'The response is JSON-RPC (result or error), not OpenAI chat format.
Non-streaming:
curl -sS -X POST "https://<YOUR_ROUTE_URL>/chat/completions" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Use the web search tool to find the Red Hat encrypted string."}],"stream":false}'Streaming (SSE; use -N / --no-buffer so chunks print as they arrive):
curl -sS -N -X POST "https://<YOUR_ROUTE_URL>/chat/completions" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"messages":[{"role":"user","content":"Use the web search tool to find the Red Hat encrypted string."}],"stream":true}'For local runs, use http://127.0.0.1:9200 instead of https://<YOUR_ROUTE_URL>. The stream may include leading a2a.protocol trace events before chat.completion.chunk lines.
| Resource | Role |
|---|---|
Helm chart charts/a2a-langgraph-crewai |
Secret, Services, Routes, Deployments |
Deployment a2a-crew-agent |
CrewAI + A2A server, port 8080 |
Deployment a2a-langgraph-agent |
LangGraph + tool → CREW_A2A_URL |
Service + Route ×2 |
HTTPS; hosts feed Agent Card URLs |
| In-cluster | CREW_A2A_URL=http://a2a-crew-agent:8080 |
- A2A Python SDK
- Deploying to OpenShift (generic)
- Local Development
- Related patterns:
agents/google/adk/(Helm +make build/make push/make deploy),agents/llamaindex/websearch_agent/,agents/langgraph/react_agent/