Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Async communication patterns using message brokers and task queues. Use when building event-driven systems, background job processing, or service decoupling. Covers Kafka (event streaming), RabbitMQ (complex routing), NATS (cloud-native), Redis Streams, Celery (Python), BullMQ (TypeScript), Temporal (workflows), and event sourcing patterns.
.claude/skills/ancoleman-using-message-queues/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 35% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 117% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 199% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 81% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 127% | 0% |
Implement asynchronous communication patterns for event-driven architectures, background job processing, and service decoupling.
Use message queues when:
Choose message broker based on primary need:
→ Apache Kafka
→ Task Queues
→ Temporal
→ NATS
→ RabbitMQ
→ Redis Streams
| Broker | Throughput | Latency (p99) | Best For | |--------|-----------|---------------|----------| | Kafka | 500K-1M msg/s | 10-50ms | Event streaming | | NATS JetStream | 200K-400K msg/s | Sub-ms to 5ms | Cloud-native microservices | | RabbitMQ | 50K-100K msg/s | 5-20ms | Task queues, complex routing | | Redis Streams | 100K+ msg/s | Sub-ms | Simple queues, caching |
See examples/kafka-python/ for working code.
pythonfrom confluent_kafka import Producer, Consumer # Producer producer = Producer({'bootstrap.servers': 'localhost:9092'}) producer.produce('orders', key='order_123', value='{"status": "created"}') producer.flush() # Consumer consumer = Consumer({ 'bootstrap.servers': 'localhost:9092', 'group.id': 'order-processors', 'auto.offset.reset': 'earliest' }) consumer.subscribe(['orders']) while True: msg = consumer.poll(1.0) if msg is not None: process_order(msg.value())
See examples/celery-image-processing/ for full implementation.
pythonfrom celery import Celery app = Celery('tasks', broker='redis://localhost:6379') @app.task(bind=True, max_retries=3) def process_image(self, image_url: str): try: result = expensive_image_processing(image_url) return result except RecoverableError as e: raise self.retry(exc=e, countdown=60)
See examples/bullmq-webhook-processor/ for full implementation.
typescriptimport { Queue, Worker } from 'bullmq' const queue = new Queue('webhooks', { connection: { host: 'localhost', port: 6379 } }) // Enqueue job await queue.add('send-webhook', { url: 'https://example.com/webhook', payload: { event: 'order.created' } }) // Process jobs const worker = new Worker('webhooks', async job => { await fetch(job.data.url, { method: 'POST', body: JSON.stringify(job.data.payload) }) }, { connection: { host: 'localhost', port: 6379 } })
See examples/temporal-order-saga/ for saga pattern implementation.
pythonfrom temporalio import workflow, activity from datetime import timedelta @workflow.defn class OrderSagaWorkflow: @workflow.run async def run(self, order_id: str) -> str: # Step 1: Reserve inventory inventory_id = await workflow.execute_activity( reserve_inventory, order_id, start_to_close_timeout=timedelta(seconds=10), ) # Step 2: Charge payment payment_id = await workflow.execute_activity( charge_payment, order_id, start_to_close_timeout=timedelta(seconds=30), ) return f"Order {order_id} completed"
Use: Domain.Entity.Action.Version
Examples:
order.created.v1user.profile.updated.v2payment.failed.v1json{ "event_type": "order.created.v2", "event_id": "uuid-here", "timestamp": "2025-12-02T10:00:00Z", "version": "2.0", "data": { "order_id": "ord_123", "customer_id": "cus_456" }, "metadata": { "producer": "order-service", "trace_id": "abc123", "correlation_id": "xyz789" } }
Route failed messages to dead letter queue (DLQ) after max retries:
python@app.task(bind=True, max_retries=3) def process_order(self, order_id: str): try: result = perform_processing(order_id) return result except UnrecoverableError as e: send_to_dlq(order_id, str(e)) raise Reject(e, requeue=False)
python@app.post("/process") async def process_payment( payment_data: dict, idempotency_key: str = Header(None) ): # Check if already processed cached_result = redis_client.get(f"idempotency:{idempotency_key}") if cached_result: return {"status": "already_processed"} result = process_payment_logic(payment_data) redis_client.setex(f"idempotency:{idempotency_key}", 86400, result) return {"status": "processed", "result": result}
python# FastAPI endpoint for real-time job status @app.get("/status/{task_id}") async def task_status_stream(task_id: str): async def event_generator(): while True: task = celery_app.AsyncResult(task_id) if task.state == 'PROGRESS': yield {"event": "progress", "data": task.info.get('progress', 0)} elif task.state == 'SUCCESS': yield {"event": "complete", "data": task.result} break await asyncio.sleep(0.5) return EventSourceResponse(event_generator())
typescriptexport function JobStatus({ jobId }: { jobId: string }) { const [progress, setProgress] = useState(0) useEffect(() => { const eventSource = new EventSource(`/api/status/${jobId}`) eventSource.addEventListener('progress', (e) => { setProgress(JSON.parse(e.data)) }) eventSource.addEventListener('complete', (e) => { toast({ title: 'Job complete', description: JSON.parse(e.data) }) eventSource.close() }) return () => eventSource.close() }, [jobId]) return <ProgressBar value={progress} /> }
For comprehensive documentation, see reference files:
references/kafka.md for partitioning, consumer groups, exactly-once semanticsreferences/rabbitmq.md for exchanges, bindings, routing patternsreferences/nats.md for JetStream, request-reply patternsreferences/redis-streams.md for consumer groups, acknowledgmentsreferences/celery.md for periodic tasks, canvas (workflows), monitoringreferences/bullmq.md for job prioritization, flows, Bull Board monitoringreferences/temporal-workflows.md for saga patterns, signals, queriesreferences/event-patterns.md for event sourcing, CQRS, outbox patternpython# ❌ BAD: Blocks request thread @app.post("/generate-report") def generate_report(user_id: str): report = expensive_computation(user_id) # 5 minutes! return report # ✅ GOOD: Enqueue background job @app.post("/generate-report") async def generate_report(user_id: str): task = generate_report_task.delay(user_id) return {"task_id": task.id}
python# ❌ BAD: Processes duplicates @app.task def send_email(email: str): send_email_service(email) # Sends twice if retried! # ✅ GOOD: Idempotent with deduplication @app.task def send_email(email: str, idempotency_key: str): if redis.exists(f"sent:{idempotency_key}"): return "already_sent" send_email_service(email) redis.setex(f"sent:{idempotency_key}", 86400, "1")
python# ❌ BAD: Failed messages lost forever @app.task(max_retries=3) def risky_task(data): process(data) # If all retries fail, data disappears # ✅ GOOD: DLQ for manual inspection @app.task(max_retries=3) def risky_task(data): try: process(data) except Exception as e: if self.request.retries >= 3: send_to_dlq(data, str(e)) raise
python# ❌ BAD: Kafka is not designed for RPC def get_user_profile(user_id: str): kafka_producer.send("user_requests", {"user_id": user_id}) # How to correlate response? Kafka is asynchronous! # ✅ GOOD: Use NATS request-reply or HTTP/gRPC response = await nats.request("user.profile", user_id.encode())
Confluent Kafka (Python)
/confluentinc/confluent-kafka-pythonTemporal
/websites/temporal_ioPython:
bashpip install confluent-kafka celery[redis] temporalio aio-pika redis
TypeScript/Node.js:
bashnpm install kafkajs bullmq @temporalio/client amqplib ioredis
Rust:
bashcargo add rdkafka lapin async-nats redis
Go:
bashgo get github.com/confluentinc/confluent-kafka-go go get github.com/hibiken/asynq go get go.temporal.io/sdk
Use scripts for setup automation:
python scripts/kafka_producer_consumer.py for test utilitiespython scripts/validate_message_schema.py to validate event schemas| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 28,157 | 21,061 | -25% | 1 | 1 | 0% | 5,406 | 7,314 | +35% | 0 | 0 | — |
case-02 | fail→fail | 23,895 | 18,643 | -22% | 1 | 1 | 0% | 4,782 | 6,981 | +46% | 0 | 0 | — |
case-03 | fail→pass | 13,832 | 11,166 | -19% | 1 | 1 | 0% | 2,451 | 5,310 | +117% | 0 | 0 | — |
case-04 | pass→pass | 12,125 | 8,156 | -33% | 1 | 1 | 0% | 2,713 | 4,921 | +81% | 0 | 0 | — |
case-05 | pass→pass | 12,793 | 8,295 | -35% | 1 | 1 | 0% | 2,153 | 4,881 | +127% | 0 | 0 | — |
case-06 | pass→pass | 28,249 | 8,237 | -71% | 1 | 1 | 0% | 2,423 | 4,810 | +99% | 0 | 0 | — |
case-07 | pass→pass | 10,494 | 10,289 | -2% | 1 | 1 | 0% | 1,658 | 5,016 | +203% | 0 | 0 | — |
case-08 | fail→fail | 10,927 | 10,008 | -8% | 1 | 1 | 0% | 1,872 | 4,995 | +167% | 0 | 0 | — |
case-09 | pass→pass | 15,933 | 12,048 | -24% | 1 | 1 | 0% | 2,955 | 5,792 | +96% | 0 | 0 | — |
case-10 | pass→pass | 14,011 | 13,467 | -4% | 1 | 1 | 0% | 2,492 | 5,781 | +132% | 0 | 0 | — |
case-11 | pass→pass | 14,866 | 16,166 | +9% | 1 | 1 | 0% | 2,225 | 5,672 | +155% | 0 | 0 | — |
case-12 | fail→fail | 10,367 | 6,768 | -35% | 1 | 1 | 0% | 2,192 | 4,495 | +105% | 0 | 0 | — |
case-13 | pass→pass | 19,158 | 13,470 | -30% | 1 | 1 | 0% | 3,021 | 5,785 | +91% | 0 | 0 | — |
case-14 | pass→pass | 14,374 | 7,318 | -49% | 1 | 1 | 0% | 2,134 | 4,723 | +121% | 0 | 0 | — |
case-15 | pass→pass | 16,791 | 10,867 | -35% | 1 | 1 | 0% | 2,548 | 5,077 | +99% | 0 | 0 | — |
case-16 | pass→pass | 13,202 | 6,338 | -52% | 1 | 1 | 0% | 2,143 | 4,334 | +102% | 0 | 0 | — |
case-17 | fail→pass | 7,861 | 6,823 | -13% | 1 | 1 | 0% | 1,528 | 4,575 | +199% | 0 | 0 | — |
case-18 | pass→pass | 16,510 | 16,181 | -2% | 1 | 1 | 0% | 3,110 | 6,322 | +103% | 0 | 0 | — |
case-19 | pass→pass | 10,633 | 8,850 | -17% | 1 | 1 | 0% | 2,093 | 5,059 | +142% | 0 | 0 | — |
case-20 | pass→pass | 12,418 | 9,909 | -20% | 1 | 1 | 0% | 2,033 | 4,818 | +137% | 0 | 0 | — |
case-21 | pass→pass | 11,864 | 14,440 | +22% | 1 | 1 | 0% | 2,021 | 5,703 | +182% | 0 | 0 | — |
case-22 | pass→pass | 17,204 | 25,514 | +48% | 1 | 1 | 0% | 2,753 | 5,802 | +111% | 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. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
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.