Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configures Dapr pub/sub components for event-driven microservices with Kafka or Redis. Use when wiring agent-to-agent communication, setting up event subscriptions, or integrating Dapr sidecars. Covers component configuration, subscription patterns, publishing events, and Kubernetes deployment. NOT when using direct Kafka clients or non-Dapr messaging patterns.
.claude/skills/aiskillstore-configuring-dapr-pubsub/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 95% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 140% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 81% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 578% | 0% |
Wire event-driven microservices using Dapr pub/sub with Kafka or Redis backends.
yaml# components/pubsub.yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub spec: type: pubsub.kafka version: v1 metadata: - name: brokers value: "my-cluster-kafka-bootstrap.kafka.svc.cluster.local:9092" - name: authType value: "none" - name: disableTls value: "true"
bash# Apply component kubectl apply -f components/pubsub.yaml # Test with Dapr CLI dapr run --app-id publisher -- dapr publish --pubsub pubsub --topic test --data '{"msg":"hello"}'
yamlapiVersion: dapr.io/v1alpha1 kind: Component metadata: name: kafka-pubsub spec: type: pubsub.kafka version: v1 metadata: # Required - name: brokers value: "my-cluster-kafka-bootstrap.kafka.svc.cluster.local:9092" - name: authType value: "none" # Consumer settings - name: consumerGroup value: "{namespace}-{appId}" # Templated per deployment - name: consumeRetryInterval value: "100ms" - name: heartbeatInterval value: "3s" - name: sessionTimeout value: "10s" # Performance - name: maxMessageBytes value: "1048576" # 1MB - name: channelBufferSize value: "256"
yamlapiVersion: dapr.io/v1alpha1 kind: Component metadata: name: kafka-pubsub-secure spec: type: pubsub.kafka version: v1 metadata: - name: brokers value: "kafka.example.com:9093" - name: authType value: "password" - name: saslUsername value: "dapr-user" - name: saslPassword secretKeyRef: name: kafka-secrets key: password - name: saslMechanism value: "SCRAM-SHA-256"
yamlapiVersion: dapr.io/v1alpha1 kind: Component metadata: name: redis-pubsub spec: type: pubsub.redis version: v1 metadata: - name: redisHost value: "redis-master.redis.svc.cluster.local:6379" - name: redisPassword secretKeyRef: name: redis-secrets key: password
yaml# subscriptions/task-events.yaml apiVersion: dapr.io/v2alpha1 kind: Subscription metadata: name: task-created-subscription spec: pubsubname: pubsub topic: task-created routes: default: /dapr/task-created scopes: - triage-agent - concepts-agent
pythonfrom fastapi import FastAPI, Request app = FastAPI() @app.get("/dapr/subscribe") async def subscribe(): """Dapr calls this to discover subscriptions.""" return [ { "pubsubname": "pubsub", "topic": "task-created", "route": "/dapr/task-created" }, { "pubsubname": "pubsub", "topic": "task-completed", "route": "/dapr/task-completed" } ] @app.post("/dapr/task-created") async def handle_task_created(request: Request): """Handle incoming CloudEvent.""" event = await request.json() # CloudEvent wrapper - data is nested task_data = event.get("data", event) task_id = task_data.get("task_id") # Process event print(f"Task created: {task_id}") return {"status": "SUCCESS"}
pythonimport httpx DAPR_URL = "http://localhost:3500" async def publish_event(topic: str, data: dict): """Publish event through Dapr sidecar.""" async with httpx.AsyncClient() as client: response = await client.post( f"{DAPR_URL}/v1.0/publish/pubsub/{topic}", json=data, headers={"Content-Type": "application/json"} ) response.raise_for_status() # Usage await publish_event("task-created", { "task_id": "123", "title": "Learn Python", "user_id": "user-456" })
pythonasync def publish_cloudevent(topic: str, data: dict, event_type: str): """Publish with explicit CloudEvent fields.""" async with httpx.AsyncClient() as client: await client.post( f"{DAPR_URL}/v1.0/publish/pubsub/{topic}", json=data, headers={ "Content-Type": "application/cloudevents+json", "ce-specversion": "1.0", "ce-type": event_type, "ce-source": "triage-agent", "ce-id": str(uuid.uuid4()) } )
Limit component access to specific apps:
yamlapiVersion: dapr.io/v1alpha1 kind: Component metadata: name: pubsub spec: type: pubsub.kafka version: v1 metadata: - name: brokers value: "kafka:9092" scopes: - triage-agent - concepts-agent - debug-agent
yamlapiVersion: apps/v1 kind: Deployment metadata: name: triage-agent spec: replicas: 2 selector: matchLabels: app: triage-agent template: metadata: labels: app: triage-agent annotations: dapr.io/enabled: "true" dapr.io/app-id: "triage-agent" dapr.io/app-port: "8000" dapr.io/enable-api-logging: "true" spec: containers: - name: triage-agent image: myapp/triage-agent:latest ports: - containerPort: 8000 env: - name: DAPR_HTTP_PORT value: "3500"
python# triage_agent.py from fastapi import FastAPI, Request import httpx app = FastAPI() DAPR_URL = "http://localhost:3500" @app.post("/api/question") async def handle_question(request: Request): data = await request.json() question = data["question"] # Route based on content if "python" in question.lower() or "code" in question.lower(): topic = "concepts-request" elif "error" in question.lower() or "bug" in question.lower(): topic = "debug-request" else: topic = "concepts-request" # Default # Publish to appropriate agent async with httpx.AsyncClient() as client: await client.post( f"{DAPR_URL}/v1.0/publish/pubsub/{topic}", json={ "question": question, "user_id": data["user_id"], "session_id": data["session_id"] } ) return {"status": "routed", "topic": topic}
python# concepts_agent.py from fastapi import FastAPI, Request import httpx app = FastAPI() DAPR_URL = "http://localhost:3500" @app.get("/dapr/subscribe") async def subscribe(): return [{"pubsubname": "pubsub", "topic": "concepts-request", "route": "/dapr/handle"}] @app.post("/dapr/handle") async def handle_concepts_request(request: Request): event = await request.json() data = event.get("data", event) # Process with LLM response = await process_with_llm(data["question"]) # Publish response async with httpx.AsyncClient() as client: await client.post( f"{DAPR_URL}/v1.0/publish/pubsub/response-ready", json={ "session_id": data["session_id"], "response": response, "agent": "concepts" } ) return {"status": "SUCCESS"}
bash# Start subscriber first dapr run --app-id concepts-agent --app-port 8001 --dapr-http-port 3501 \ --resources-path ./components -- uvicorn concepts:app --port 8001 # Start publisher dapr run --app-id triage-agent --app-port 8000 --dapr-http-port 3500 \ --resources-path ./components -- uvicorn triage:app --port 8000
yamlversion: "3.8" services: triage-agent: build: ./services/triage ports: - "8000:8000" triage-agent-dapr: image: daprio/daprd:latest command: ["./daprd", "--app-id", "triage-agent", "--app-port", "8000", "--dapr-http-port", "3500", "--resources-path", "/components" ] volumes: - ./components:/components network_mode: "service:triage-agent" depends_on: - triage-agent kafka: image: confluentinc/cp-kafka:latest # ... kafka config
bash# View sidecar logs kubectl logs deploy/triage-agent -c daprd # Check component registration curl http://localhost:3500/v1.0/metadata
| Error | Cause | Fix | |-------|-------|-----| | component not found | Component not loaded | Check --resources-path or K8s namespace | | connection refused | Kafka not reachable | Verify broker address in component | | consumer group rebalance | Multiple instances | Use unique consumerGroup per app | | event not received | Wrong topic/route | Check subscription config |
bash# Publish test event dapr publish --pubsub pubsub --topic test --data '{"test": true}' # Check consumer logs kubectl logs deploy/my-app -c daprd | grep -i subscribe
Run: python scripts/verify.py
deploying-kafka-k8s - Kafka cluster setup with Strimziscaffolding-fastapi-dapr - FastAPI services with Daprscaffolding-openai-agents - Agent orchestration patterns| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-13 | pass→pass | 15,816 | 7,974 | -50% | 1 | 1 | 0% | 2,241 | 4,948 | +121% | 0 | 0 | — |
case-01 | fail→fail | 18,782 | 12,730 | -32% | 1 | 1 | 0% | 2,764 | 4,661 | +69% | 0 | 0 | — |
case-02 | fail→pass | 17,555 | 9,218 | -47% | 1 | 1 | 0% | 2,547 | 4,966 | +95% | 0 | 0 | — |
case-03 | pass→fail | 24,868 | 19,065 | -23% | 1 | 1 | 0% | 3,097 | 5,513 | +78% | 0 | 0 | — |
case-08 | fail→pass | 13,392 | 4,091 | -69% | 1 | 1 | 0% | 1,544 | 3,698 | +140% | 0 | 0 | — |
case-04 | fail→pass | 17,807 | 4,268 | -76% | 1 | 1 | 0% | 2,954 | 3,646 | +23% | 0 | 0 | — |
case-05 | fail→pass | 11,086 | 9,629 | -13% | 1 | 1 | 0% | 2,059 | 3,724 | +81% | 0 | 0 | — |
case-06 | fail→fail | 17,587 | 12,878 | -27% | 1 | 1 | 0% | 2,100 | 4,424 | +111% | 0 | 0 | — |
case-07 | pass→pass | 15,555 | 10,294 | -34% | 1 | 1 | 0% | 1,772 | 4,461 | +152% | 0 | 0 | — |
case-09 | pass→pass | 7,043 | 8,605 | +22% | 1 | 1 | 0% | 1,180 | 3,571 | +203% | 0 | 0 | — |
case-10 | pass→pass | 9,577 | 7,768 | -19% | 1 | 1 | 0% | 732 | 3,341 | +356% | 0 | 0 | — |
case-11 | pass→fail | 16,163 | 12,444 | -23% | 1 | 1 | 0% | 2,125 | 5,071 | +139% | 0 | 0 | — |
case-12 | fail→pass | 5,041 | 8,304 | +65% | 1 | 1 | 0% | 526 | 3,566 | +578% | 0 | 0 | — |
case-14 | pass→pass | 6,040 | 4,817 | -20% | 1 | 1 | 0% | 1,140 | 3,676 | +222% | 0 | 0 | — |
case-15 | pass→pass | 7,270 | 3,817 | -47% | 1 | 1 | 0% | 1,032 | 3,668 | +255% | 0 | 0 | — |
case-16 | fail→pass | 6,904 | 2,645 | -62% | 1 | 1 | 0% | 1,229 | 3,302 | +169% | 0 | 0 | — |
case-17 | pass→pass | 12,142 | 5,348 | -56% | 1 | 1 | 0% | 1,311 | 3,643 | +178% | 0 | 0 | — |
case-18 | pass→pass | 12,513 | 4,632 | -63% | 1 | 1 | 0% | 1,434 | 3,494 | +144% | 0 | 0 | — |
case-19 | pass→pass | 16,452 | 9,484 | -42% | 1 | 1 | 0% | 2,379 | 4,897 | +106% | 0 | 0 | — |
case-20 | pass→pass | 10,093 | 9,412 | -7% | 1 | 1 | 0% | 1,940 | 4,604 | +137% | 0 | 0 | — |
case-21 | fail→pass | 36,231 | 18,712 | -48% | 1 | 1 | 0% | 1,809 | 5,986 | +231% | 0 | 0 | — |
case-22 | pass→pass | 11,256 | 5,740 | -49% | 1 | 1 | 0% | 998 | 3,847 | +285% | 0 | 0 | — |
case-23 | pass→pass | 12,838 | 8,972 | -30% | 1 | 1 | 0% | 1,356 | 3,476 | +156% | 0 | 0 | — |
case-24 | pass→pass | 12,395 | 5,200 | -58% | 1 | 1 | 0% | 1,400 | 3,732 | +167% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted, and 23 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +21 percentage points is the difference between those two pass rates over the 23 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.