# ADR-001: Async Celery Bridge via New Event Loop Per Task

<!-- SCOPE: adr-001 | owner: ln-120 | generated: 2026-04-29 -->

**Status:** Accepted
**Date:** 2026-04-29

## Context

FastAPI routes are async (asyncio). Celery workers run in synchronous Python processes. MongoDB (Motor) and other async clients cannot be shared across asyncio event loop boundaries. Tasks need to call async services (Gemini, GCS, MongoDB, TTS) that only have async APIs.

## Decision

Each Celery task creates a new `asyncio.EventLoop` via `asyncio.new_event_loop()` and runs its async implementation with `loop.run_until_complete(task_impl())`. The async implementation can freely use `await` with Motor, httpx, and other async clients. The event loop is closed in a `finally` block when the task completes.

## Consequences

**Benefits:**
- Async services work correctly inside Celery tasks
- No shared mutable state between tasks
- Each task is isolated — a failure does not corrupt another task's loop

**Trade-offs:**
- Every task creates a new MongoDB connection (no connection pool reuse across tasks)
- This is a known performance limitation; mitigation is connection pooling within the task's event loop lifetime
- The pattern requires discipline: never `await` outside the task's own loop

**Known violations:** `ingest_and_ai.py` creates `AsyncIOMotorClient(settings.mongodb_uri)` directly instead of using a shared factory — should be extracted to a `get_task_db()` context manager.

## Maintenance

**Update triggers:** If Celery adds native asyncio worker support, this ADR should be marked Deprecated and replaced.

<!-- END SCOPE: adr-001 -->
