> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kair.is/llms.txt
> Use this file to discover all available pages before exploring further.

# Queue System

> How audio becomes a transcript becomes a summary — the job pipeline.

## Overview

All heavy processing (transcription, summarisation, HiRAG indexing) happens asynchronously. The API never blocks waiting for these — it accepts the upload, creates a job record, pushes to Redis, and returns immediately. Workers process jobs in the background.

## The pipeline

```mermaid theme={null}
flowchart TD
    %% ─── Session ───────────────────────────────────────────────────────────
    subgraph SESSION["Session"]
        A([Client]) -->|POST /api/sessions| B[(Session created\nin PostgreSQL)]
    end

    %% ─── Audio Upload ────────────────────────────────────────────────────────
    subgraph UPLOAD["Audio Upload"]
        B --> C{Upload mode?}

        C -->|Single file\nno chunk_sequence| D[Save audio\nto S3 / local]
        D --> E[Create Audio record\n+ Transcription job\nstatus=Pending]
        E --> F[Push → transcription_queue]

        C -->|Chunked\nchunk_sequence present| G[Save chunk\nto S3 / local]
        G --> H[Create AudioChunk record\n+ Transcription job\nstatus=Pending]
        H --> I[Push → transcription_queue]
        I --> J{is_final_chunk?}
        J -->|false\nnext chunk| G
        J -->|true| K[Stitch all chunks\ninto single audio file]
        K --> L[Create Audio record\n+ final Transcription job\nstatus=Pending]
        L --> M[Push → transcription_queue]
        M --> N[Create Summarisation job\nstatus=Pending]
        N --> O[Push → summarisation_queue]
    end

    %% ─── Transcription Worker ────────────────────────────────────────────────
    subgraph TRANSCRIPTION["Transcription Worker"]
        P[BLPOP transcription_queue] --> Q[Download audio\nfrom S3 if needed]
        Q --> R[Decode audio → PCM\nSymphonia: WAV/MP3/M4A/AAC/FLAC/OGG\nresample to 16 kHz mono]
        R --> S[Whisper inference\nBeamSearch beam=5\nauto language detect]
        S -->|Progress callback\n0→100%| T[Stream progress\nto PostgreSQL]
        S --> U[Store transcription text\nUpdate job → Completed]
    end

    %% ─── Summarisation Worker ────────────────────────────────────────────────
    subgraph SUMMARISATION["Summarisation Worker"]
        V[BLPOP summarisation_queue] --> W[Fetch all transcription\ntexts from PostgreSQL]
        W --> X[Generate summary via rig\nconfigured agent_client:\nOllama / OpenAI / Anthropic / Gemini]
        X --> Y[Store summary text\nUpdate job → Completed]
        Y -.->|if summary_evaluation.enabled| YE[Push → summary_evaluation_queue\nsame worker, lower priority]
    end

    %% ─── HiRAG Indexing Worker ───────────────────────────────────────────────
    subgraph HIRAG["HiRAG Indexing Worker"]
        Z[BLPOP hirag_indexing_queue] --> AA[Fetch source text\ntranscriptions + summaries]
        AA --> P1

        subgraph PHASES["Indexing Phases"]
            P1["Phase 1 — Entity Extraction\nLLM chunks text → extracts named entities"]
            P1 --> P2["Phase 2 — Relation Extraction\nLLM identifies relations between entities"]
            P2 --> P3["Phase 3 — Hierarchical Layer Building\nCluster entities into layers"]
            P3 --> P4["Phase 4 — Community Detection\nDetect communities across graph layers"]
            P4 --> P5["Phase 5 — Concern Linking\nLink session entities to workspace-wide concerns"]
        end

        P5 --> AB[Update job → Completed\nKG queryable via RAG]
    end

    %% ─── Job Recovery ────────────────────────────────────────────────────────
    subgraph RECOVERY["Job Recovery (on worker startup)"]
        RC[Query interrupted jobs\nstatus=interrupted] -->|LPUSH front of queue| RQ[Re-queue for priority\nprocessing]
    end

    %% ─── Connections between subgraphs ───────────────────────────────────────
    F --> P
    I --> P
    M --> P
    O --> V
    Y -->|Trigger| ZT[Create HiRAG job\nstatus=Pending\nPush → hirag_indexing_queue]
    ZT --> Z
```

## Job types

| Job type                | Triggered by                                                                  | Output                                                            |
| ----------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `transcription` (chunk) | Each audio chunk upload                                                       | Partial transcript for that chunk                                 |
| `transcription` (final) | Session finalisation                                                          | Full session transcript (stitched audio)                          |
| `summarisation`         | Session finalisation (automatically), or `POST /sessions/:id/summarise`       | Session summary text                                              |
| `summary_evaluation`    | A completed summarisation, when `summary_evaluation.enabled`                  | 0–100 health score in `summary_evaluations`                       |
| `hirag_indexing`        | After summarisation completes, or `POST /sessions/:id/trigger-hirag-indexing` | Knowledge graph nodes/edges in Neo4j                              |
| `document_processing`   | Document upload (session- or workspace-scoped)                                | Extracted text, page chunks, a document summary, then a HiRAG job |

### The five queues

| Redis key                   | Drained by                                                 |
| --------------------------- | ---------------------------------------------------------- |
| `transcription_queue`       | `kair-voice-transcription-worker`                          |
| `summarisation_queue`       | `kair-voice-summarisation-worker` (priority)               |
| `summary_evaluation_queue`  | `kair-voice-summarisation-worker` (same multi-key `BLPOP`) |
| `hirag_indexing_queue`      | `kair-voice-hirag-worker`                                  |
| `document_processing_queue` | `kair-voice-document-worker`                               |

Pub/sub channels are separate from the queues: `kair:chunk_transcribed`,
`kair:job_status_changed`, `kair:moderator_insight`.

## HiRAG indexing phases

| Phase | Name                        | What happens                                                            |
| ----- | --------------------------- | ----------------------------------------------------------------------- |
| 1     | Entity Extraction           | LLM reads text chunks and extracts named entities                       |
| 2     | Relation Extraction         | LLM identifies relationships between entities                           |
| 3     | Hierarchical Layer Building | Entities are clustered into a multi-layer knowledge graph               |
| 4     | Community Detection         | Graph communities are detected across layers; summary reports generated |
| 5     | Concern Linking             | Session entities are linked to workspace-wide concerns (non-fatal)      |

## Chunk vs final transcription

Sessions are recorded in **chunks** (short audio segments sent as the recording progresses). Each chunk gets its own transcription job immediately — this gives live feedback in the moderator view.

When the session ends, all chunks are **stitched** into one audio file. A final transcription job runs on the full audio. This final transcript is what gets summarised and indexed — chunk transcripts are for live display only.

<Info>
  The final transcription is also where **speaker diarisation** runs (when enabled). Chunks are never diarised individually — only the full stitched audio.
</Info>

## Job states

```
pending → processing → completed
                  ↘ failed
```

Failed jobs are visible in the admin panel. Individual failed chunks can be re-run from the admin UI. If the final transcription fails, summarisation and HiRAG indexing do not run.

## Transcription backends

Two backends are supported, switchable at runtime via the admin panel:

| Backend     | How it works                                    | When to use                               |
| ----------- | ----------------------------------------------- | ----------------------------------------- |
| `local`     | Whisper.cpp running on the worker's machine     | Air-gapped / self-hosted setups           |
| `foresight` | Audio POSTed to an OpenAI-compatible remote API | Better accuracy, faster, requires network |

Switching backend takes effect per-job — no worker restart needed (except switching back to `local` on a Foresight-booted worker, which requires a restart to load the Whisper model).

<Warning>
  Foresight rejects audio uploads larger than 200 MiB. Very long sessions should always use chunked upload rather than a single-file upload.
</Warning>

## Monitoring

* **Admin panel → Jobs**: shows all transcription jobs, chunk groups, status, and progress
* **Failed jobs**: visible in admin with a "re-run" action per chunk
* **Logs**: worker processes log job pickup and completion with session/job IDs
