Status: Accepted

Date: 2026-07-15

Author: parakram shekhawat


Context

When a Claim is denied, two independent AI tasks are required:

  1. DenialAnalyzer — diagnose the denial reason code and produce corrective actions.
  2. AppealDrafter — write a formal appeal letter for the payer.

These two tasks are fully independent: neither needs the other’s output to do its job. The user experience goal is to have both outputs available as quickly as possible after the denial transition.

Decision

In the claim denial workflow transition’s done() method, dispatch two separate zango_task_executor.delay() calls:

def done(self):
    from zango.core.tasks import zango_task_executor
    tenant = self.object_instance.created_by.UserTenantObject.all()[0].tenant.name
    zango_task_executor.delay(
        tenant,
        "backend.agents.tasks.run_denial_analyzer",
        claim_id=str(self.object_instance.id)
    )
    zango_task_executor.delay(
        tenant,
        "backend.agents.tasks.run_appeal_drafter",
        claim_id=str(self.object_instance.id)
    )

Each call queues an independent Celery task. With multiple Celery workers (the default Docker Compose setup runs a worker with concurrency based on CPU count), they execute on separate workers simultaneously. Results are written back to separate fields on the Claim model by each agent’s own update_claim_ai_result tool call.

Alternatives Considered

Sequential execution (chain): AppealDrafter waits for DenialAnalyzer to finish before starting. Slower with no benefit, since the tasks are independent.

Single combined agent: One agent that does both diagnosis and appeal writing. Harder to prompt reliably (two distinct output formats), harder to iterate on each independently, one failure kills both outputs.

Celery group/chord: Explicit Celery primitives for parallel execution. More complex, requires understanding Celery internals. zango_task_executor.delay() called twice achieves the same parallelism more simply.

Consequences