> ## 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.

# Device Meeting Flow

> Sequence diagram: how a hardware device starts and manages a session.

# Device Meeting Flow

This document describes the complete process of starting and managing a meeting session from an ESP32 device, including audio chunk upload, automatic finalisation, and coordination with recording status.

## Overview

The device meeting flow allows ESP32 devices to start recording sessions, upload audio in chunks for real-time transcription, and automatically finalise recordings when the session stops. The flow coordinates between device session management and audio chunk processing:

1. **Device Starts Session**: Creates a session record and sets recording status to "recording" in Redis
2. **Audio Chunk Upload**: Device uploads audio chunks during recording for real-time transcription
3. **Device Stops Session**: Automatically triggers finalisation of chunks, creates stitched audio, and sets recording status to "inactive"

The system uses Redis to coordinate recording status and prevent collisions between multiple clients attempting to record simultaneously.

## Complete Meeting Flow

```mermaid theme={null}
sequenceDiagram
    participant ESP32 as ESP32 Device
    participant API
    participant Redis
    participant DB
    participant Worker as Background Worker

    ESP32->>API: POST /devices/{id}/session/start
    API->>DB: Create session + device_session
    API->>Redis: SET session_recording_status:{id} = "recording"
    API-->>ESP32: {session_id}

    loop Recording chunks
        ESP32->>API: POST /sessions/{id}/upload-audio<br/>(chunk_sequence, is_final_chunk=false)
        API->>DB: Store chunk
        API->>Redis: Queue transcription job
        Worker->>Worker: Transcribe chunk
    end

    ESP32->>API: POST /devices/{id}/session/stop
    API->>DB: Check for pending chunks
    alt Chunks exist and not finalised
        API->>API: Call finalise_chunks()
        API->>DB: Stitch chunks, create final audio
        API->>Redis: Queue summarisation job
        Worker->>Worker: Process summarisation
    end
    API->>Redis: SET session_recording_status:{id} = "inactive"
    API->>DB: Mark device_session.ended_at
    API->>DB: Clear device.active_session_id
    API-->>ESP32: Success
```

## Device-Side Flow

### Step 1: Start Session

The device initiates a recording session by calling the start session endpoint.

**Endpoint**: `POST /devices/{device_id}/session/start`

**Prerequisites**:

* Device must be paired (have a `user_id`)
* Device should not have an active session (`active_session_id` should be `null`)

**Request Body**:

```json theme={null}
{
  "title": "Meeting 2024-01-15 10:30"
}
```

**Response**:

```json theme={null}
{
  "success": true,
  "session_id": "session-uuid"
}
```

**Backend Process** (`start_session_impl`):

1. **Device Validation**: Verifies device exists and is paired
2. **User Verification**: Confirms user exists and has workspace access
3. **Workspace Resolution**:
   * Uses device's `default_workspace_id` if user has access
   * Otherwise selects first accessible workspace
4. **Session Creation**:
   * Creates a new `session` record with `device_id` set
   * Creates a `device_session` record to track the session lifecycle
   * Updates `device.active_session_id` to the new session
5. **Recording Status**: Sets Redis recording status to "recording" with 1-hour TTL

**Error Responses**:

* `400 Bad Request`: Device not paired, user not found, or workspace access denied
* `404 Not Found`: Device not found

**Security**: No authentication required (device endpoint)

### Step 2: Upload Audio Chunks

During the recording session, the device uploads audio in chunks for real-time transcription.

**Endpoint**: `POST /sessions/{session_id}/upload-audio`

**Request Format**: Multipart form data

* `audio`: Audio file (WAV format, PCM, mono, 16 kHz, 16-bit)
* `chunk_sequence`: Sequence number (0, 1, 2, ...)
* `is_final_chunk`: Boolean (set to `false` during recording)

**Example Request**:

```
POST /sessions/{session_id}/upload-audio
Content-Type: multipart/form-data

audio: <binary audio data>
chunk_sequence: 0
is_final_chunk: false
```

**Response**:

```json theme={null}
{
  "success": true,
  "mode": "chunked",
  "audio_id": null,
  "chunk_id": "chunk-uuid",
  "transcription_id": "transcription-uuid",
  "chunk_sequence": 0,
  "finalised": false,
  "total_chunks": null
}
```

**Backend Process** (`upload_audio_impl`):

1. **Session Validation**: Confirms session exists
2. **Chunk Processing**:
   * Stores chunk to S3 (or local filesystem)
   * Creates `audio_chunks` record with sequence number
   * Prevents duplicate sequence numbers
3. **Transcription Job**:
   * Creates transcription job for immediate chunk processing
   * Queues job in Redis for background worker
4. **Real-time Processing**: Worker transcribes chunk independently for live feedback

**Key Points**:

* Chunks are processed independently for real-time transcription
* Each chunk gets its own transcription job
* Sequence numbers must be sequential and unique per session

### Step 3: Stop Session

The device stops the recording session, which triggers automatic finalisation of uploaded chunks.

**Endpoint**: `POST /devices/{device_id}/session/stop`

**Request**: No body required

**Response**:

```json theme={null}
{
  "success": true,
  "message": "Session stopped"
}
```

**Backend Process** (`stop_session_impl`):

1. **Device Validation**: Verifies device exists and has active session
2. **Chunk Finalisation** (if chunks exist):
   * Checks for pending chunks (`is_final_stitch = false`)
   * Calls `finalise_chunks()` to:
     * Stitch all chunks into single audio file
     * Create final `audio` record
     * Create transcription job for stitched audio
     * Create summarisation job automatically
     * Link all chunks to final audio via `parent_audio_id`
   * Handles gracefully if no chunks exist or already finalised
3. **Recording Status**: Sets Redis status to "inactive"
4. **Session Cleanup**:
   * Marks `device_session.ended_at` timestamp
   * Clears `device.active_session_id`

**Error Handling**:

* If finalisation fails, logs error but continues with session stop
* If Redis update fails, logs warning but continues (Redis is for coordination, not critical)

**Security**: No authentication required (device endpoint)

## Redis Recording Status Coordination

### Recording Status Lifecycle

The recording status is managed in Redis to coordinate between multiple clients and prevent collisions:

**Status Values**:

* `inactive`: No recording in progress (default)
* `recording`: Active recording session
* `paused`: Recording temporarily paused (not used by devices currently)

**Redis Key Format**:

```
session_recording_status:{session_id}
```

**TTL**: 3600 seconds (1 hour)

### Status Lifecycle

1. **Session Start**: Status set to `"recording"` when device starts session
2. **During Recording**: Status remains `"recording"` (can be checked by web clients)
3. **Session Stop**: Status set to `"inactive"` after finalisation completes

### Collision Prevention

The recording status prevents multiple clients from recording simultaneously:

* **Web Interface**: Checks status before starting recording, shows "Recording in progress" if active
* **Device**: Sets status when starting, ensuring other clients see active recording
* **Coordination**: Redis acts as distributed lock for recording state

**Example Check** (web client):

```typescript theme={null}
const status = await getRecordingStatus(sessionId);
if (status.recording_status === "recording") {
  // Show "Recording in progress" banner
  // Prevent starting new recording
}
```

## Audio Chunk Upload and Finalisation

### Chunked Upload Process

During recording, chunks are uploaded independently:

1. **Chunk Upload**: Each chunk is stored as a separate `audio_chunks` record
2. **Immediate Transcription**: Each chunk gets its own transcription job for real-time feedback
3. **Sequential Processing**: Chunks are processed in parallel, not dependent on order

### Finalisation Process

When the session stops, finalisation is automatically triggered:

**Process** (`finalise_chunks`):

1. **Chunk Collection**: Retrieves all chunks for session (`is_final_stitch = false`)
2. **Validation**:
   * Ensures chunks exist
   * Prevents duplicate finalisation (checks for existing `is_final_stitch = true`)
3. **Audio Stitching**:
   * Downloads all chunks from S3 (if configured)
   * Stitches chunks sequentially using audio processing pipeline
   * Uploads stitched file to S3 or local storage
4. **Database Updates**:
   * Creates final `audio` record
   * Creates final chunk record (`is_final_stitch = true`, `chunk_sequence = -1`)
   * Links all original chunks to final audio via `parent_audio_id`
5. **Transcription Job**: Creates transcription job for stitched audio
6. **Summarisation Job**: Automatically creates summarisation job queued to Redis

**Result**:

* Final stitched audio file available
* All chunks linked to final audio
* Transcription and summarisation jobs queued for processing

### Finalisation Scenarios

1. **Normal Flow**: Device uploads chunks, stops session → finalisation triggered
2. **No Chunks**: Device starts and stops without uploading → no finalisation (handled gracefully)
3. **Already Finalised**: Finalisation already completed → skipped (handled gracefully)

## Data Model

### Session Table

The `session` table stores session information:

* `id`: UUID primary key
* `title`: Session title (from device request)
* `workspace_id`: Workspace association
* `device_id`: Device that created the session (set for device-created sessions)
* `owner_user_id`: Optional user owner (null for device sessions)
* `metadata`: JSON metadata
* `created_at`, `updated_at`: Timestamps

### Device Session Table

The `device_sessions` table tracks device session lifecycle:

* `id`: UUID primary key
* `device_id`: Reference to device
* `session_id`: Reference to session
* `started_at`: Session start timestamp
* `ended_at`: Session end timestamp (null until stopped)

**Purpose**: Tracks which device created which session and when it started/ended

### Audio Chunks Table

The `audio_chunks` table stores individual uploaded chunks:

* `id`: UUID primary key
* `session_id`: Session reference
* `parent_audio_id`: Reference to final stitched audio (set after finalisation)
* `chunk_sequence`: Sequence number (0, 1, 2, ... or -1 for final stitch)
* `filename`: Chunk filename
* `file_path`: Local file path or empty if S3
* `s3_key`: S3 object key (if S3 configured)
* `duration_seconds`: Optional duration
* `uploaded_at`: Upload timestamp
* `is_final_stitch`: Boolean flag (true for final stitched result)

**Relationships**:

* Multiple chunks per session (`is_final_stitch = false`)
* One final chunk per session (`is_final_stitch = true`)
* All chunks link to final audio via `parent_audio_id`

### Audio Table

The `audio` table stores final stitched audio:

* `id`: UUID primary key
* `session_id`: Session reference
* `filename`: Stitched filename
* `file_path`: Local file path or empty if S3
* `s3_key`: S3 object key
* `duration_seconds`: Optional duration
* `uploaded_at`: Upload timestamp

**Relationship**: One audio record per finalised session

### Redis Recording Status

Stored in Redis with key pattern `session_recording_status:{session_id}`:

**Value**: JSON string of `RecordingStatus` enum:

* `"inactive"` - No recording
* `"recording"` - Active recording
* `"paused"` - Paused recording

**TTL**: 3600 seconds (1 hour)

## API Endpoints Reference

### POST `/devices/:id/session/start`

Starts a new recording session for a device.

**Path Parameters**:

* `id`: Device UUID

**Request Body**:

```json theme={null}
{
  "title": "Session Title"
}
```

**Response** (200 OK):

```json theme={null}
{
  "success": true,
  "session_id": "session-uuid"
}
```

**Error Responses**:

* `400 Bad Request`: Device not paired, user not found, or workspace access denied
* `404 Not Found`: Device not found

**Side Effects**:

* Creates session and device\_session records
* Sets `device.active_session_id`
* Sets Redis recording status to "recording"

### POST `/devices/:id/session/stop`

Stops an active recording session for a device.

**Path Parameters**:

* `id`: Device UUID

**Request Body**: None

**Response** (200 OK):

```json theme={null}
{
  "success": true,
  "message": "Session stopped"
}
```

**Error Responses**:

* `404 Not Found`: Device not found

**Side Effects**:

* Triggers chunk finalisation if chunks exist
* Sets Redis recording status to "inactive"
* Marks `device_session.ended_at`
* Clears `device.active_session_id`

### POST `/sessions/:session_id/upload-audio`

Uploads an audio chunk to a session.

**Path Parameters**:

* `session_id`: Session UUID

**Request**: Multipart form data

* `audio`: Audio file (binary)
* `chunk_sequence`: Integer sequence number
* `is_final_chunk`: Boolean (optional, defaults to false)

**Response** (200 OK):

```json theme={null}
{
  "success": true,
  "mode": "chunked",
  "audio_id": null,
  "chunk_id": "chunk-uuid",
  "transcription_id": "transcription-uuid",
  "chunk_sequence": 0,
  "finalised": false,
  "total_chunks": null
}
```

**Error Responses**:

* `400 Bad Request`: Invalid request or processing error
* `404 Not Found`: Session not found
* `409 Conflict`: Duplicate chunk sequence

**Side Effects**:

* Creates audio\_chunk record
* Creates transcription job for chunk
* If `is_final_chunk=true`, triggers finalisation

### GET `/sessions/:session_id/recording-status`

Gets or updates the recording status for a session.

**Path Parameters**:

* `session_id`: Session UUID

**Query Parameters** (optional):

* `status`: Update status to this value (`"inactive"`, `"recording"`, or `"paused"`)

**Response** (200 OK):

```json theme={null}
{
  "success": true,
  "recording_status": "recording"
}
```

**Error Responses**:

* `400 Bad Request`: Invalid status or Redis error
* `404 Not Found`: Session not found

**Use Cases**:

* Check if recording is in progress (collision prevention)
* Update status (used by web interface)
* Get current status for UI display

## Key Points

### Collision Prevention Mechanism

The Redis recording status acts as a distributed lock:

1. **Session Start**: Device sets status to "recording" when starting
2. **Status Check**: Other clients (web interface) check status before starting
3. **Status Update**: Web interface can update status to "paused" or "recording"
4. **Session Stop**: Device sets status to "inactive" when stopping

**Benefits**:

* Prevents multiple simultaneous recordings
* Provides real-time status visibility
* Works across multiple API instances (distributed)

### Error Handling and Graceful Degradation

The system handles errors gracefully:

1. **Finalisation Failures**:
   * If finalisation fails, session stop still succeeds
   * Errors are logged but don't block session cleanup
   * Chunks remain available for manual finalisation if needed

2. **Redis Failures**:
   * Redis is for coordination, not critical functionality
   * If Redis is unavailable, session management continues
   * Status coordination may be delayed until Redis recovers

3. **No Chunks Scenarios**:
   * Device can start and stop without uploading chunks
   * Finalisation is skipped gracefully
   * Session record still created for tracking

### Edge Cases

1. **Session Already Finalised**: If finalisation was already triggered (e.g., via `is_final_chunk=true` in upload), stop session skips finalisation

2. **Multiple Stop Calls**: Idempotent - multiple stop calls have no effect after first one

3. **Stale Sessions**: Redis TTL ensures status expires after 1 hour if not updated

4. **Concurrent Uploads**: Sequence number validation prevents duplicate chunks

### Device Session vs Web Session

* **Device Sessions**: Created via `/devices/{id}/session/start`, have `device_id` set
* **Web Sessions**: Created via `/sessions` (POST), may have `device_id` null or set later
* **Both Support**: Chunked audio upload and finalisation work for both session types

## References

* Backend routes: `crates/api/src/routes/device/device_routes.rs`
* Backend models: `crates/api/src/routes/device/device_routes_models.rs`
* Session handlers: `crates/api/src/util/session_handlers.rs`
* Audio upload: `crates/api/src/routes/session/session_routes_audio.rs`
* Database models: `crates/core/src/db/session.rs`, `crates/core/src/db/device_session.rs`, `crates/core/src/db/audio_chunk.rs`
