Skip to content

feat(local-session): LOCAL session type, audio ingest task and session finalization - #9

Open
Rahulkaushik01 wants to merge 1 commit into
feat/local-session-audio-enginefrom
feat/local-session-ingest-and-lifecycle
Open

feat(local-session): LOCAL session type, audio ingest task and session finalization#9
Rahulkaushik01 wants to merge 1 commit into
feat/local-session-audio-enginefrom
feat/local-session-ingest-and-lifecycle

Conversation

@Rahulkaushik01

Copy link
Copy Markdown
Collaborator

Stacked PR 2 of 4 — base feat/local-session-audio-engine. Merge PR 1 first.

Summary

Adds the LOCAL session type and the Celery task that turns queued audio into utterances,
plus the logic that ends a local session cleanly so it can be deleted.

Problem

  1. Uploads arrive out of order, get retried, and can be duplicated — naive processing splices
    the same audio in twice or out of sequence.
  2. A local session could never finish. It stayed in READY forever, and delete_data()
    refuses to run on anything not in a terminal state — so users literally could not delete
    their own recordings.
  3. Ending it naively corrupts the recording: the post-meeting transition terminates any
    still-in-progress recording as FAILED, and a local recording has no file to save it.

How it works

  • Ordering / dedupe — segments drain from a per-source FIFO gated on a monotonic
    sequence. Replays are dropped both against the tail and within the same batch, because
    a retrying uploader can land two copies before any drain runs.
  • Timeline — all times derive from offset_ms (milliseconds since the session started),
    never the desktop's wall clock, so client clock skew cannot corrupt or drop audio. A gap
    between segments is fed to the VAD as real silence instead of splicing speech together.
  • Ending, in a deliberate order — stop flushes every source's last utterance → marks the
    recording COMPLETEonly then moves READY → ENDED. Completing first is what prevents
    the post-meeting transition marking a file-less recording FAILED. The transition is
    state-guarded, so a retried or duplicate stop is a no-op rather than an
    invalid-transition error.
  • Webhooks — bot state-change webhooks are skipped for LOCAL, which has no external
    subscribers and would otherwise deliver "bot" events to meeting-bot customers sharing
    the project.
  • Deletion safety — a drain that finds the session already gone exits quietly and clears
    its Redis state instead of erroring and retrying.
  • LOCAL is excluded from the never-launched reaper, since a local recording launches no pod
    and therefore never heartbeats that way.

Changes made

File Lines Purpose
bots/tasks/process_local_audio_segment_task.py +153 drain + finalize orchestration
bots/models.py +30 −13 SessionTypes.LOCAL, local_ id prefix, LOCAL_SESSION_ENDED event + transition, webhook skip
bots/migrations/0088, 0089 +36 choices-only
bots/management/commands/clean_up_bots_… +4 −2 exclude LOCAL from never-launched reaper
bots/tasks/deliver_webhook_task.py +3 LOCAL webhook payload branch

Total: +226 / −15 (241 changed lines), 1 commit.

Testing performed

  • ruff checkAll checks passed; ruff format --checkclean
  • python manage.py checkno issues
  • Real-audio lifecycle: session reaches ENDED, recording is COMPLETE not FAILED,
    transcript populated
  • Idempotent double-stop verified — no invalid-transition error
  • delete_data verified → DATA_DELETED, utterances removed
  • Redis cleanup verified — queue TTL set; a drain against a deleted session does not raise
    and clears leftover state
  • python manage.py test bots.tests.test_cleanup21/21 OK

Coding-guidelines compliance

  • File size (§2): new file 153 lines (≤ 250 preferred). ⚠️ bots/models.py is
    pre-existing at ~3300 lines, over the 350 hard cap; this PR adds 30 lines to it because
    a Django enum value and a state transition must live in the model module. Flagged per §2 —
    see Refactoring recommendation below.
  • Functions (§6): ⚠️ _drain is 69 lines (over the 50 guidance). It was already long;
    this PR's change to it is ~6 lines. Recommend extracting the segment-merge loop in a
    follow-up, with tests.
  • Constants (§5): MAX_SEGMENTS_PER_DRAIN, TTL constants, no magic numbers.
  • Error handling (§12): never fails silently — a vanished session is logged and dropped
    deliberately rather than retried into a loop.
  • Commit format (§14): type(scope): description.
  • No dead code / debug logs / unused imports (§15).

Refactoring recommendation (§2 flag)

bots/models.py breaches the file cap. I recommend not splitting it in this fork: the
history shows ~3810 commits from the upstream author versus a handful of ours, and models.py
is the most-edited file upstream — restructuring it would turn every future upstream sync into
a large manual conflict, permanently. Our footprint there is deliberately minimal (30 lines),
with all genuinely new logic in new, compliant files. If the split is wanted, the right move is
to propose it upstream and inherit it.

Risks / notes

  • Migrations 0088/0089 are choices-only (no data change). Numbering may need rebasing
    at devprod convergence.
  • Repo-wide ruff format --check is already red on two pre-existing test files on dev
    (test_zoom_bot.py, test_bot_cpu_request.py) — unrelated to this PR.

…n finalization

Turns queued audio into utterances and ends a local session cleanly.

* SessionTypes.LOCAL so a Bot row can represent a local recording.
* Ordering and exclusivity: segments drain from a per-source FIFO gated on a
  monotonic sequence, under a per-source lock, so exactly one drain owns the
  tail. A gap between segments is fed to the VAD as real silence rather than
  splicing speech together. Times derive from offset_ms, never a wall clock,
  so a skewed client clock cannot corrupt the timeline.
* Ending: stop flushes every source's last utterance, marks the recording
  COMPLETE and only then moves READY -> ENDED. That order matters, otherwise
  the post-meeting transition marks a file-less local recording FAILED. The
  transition is state-guarded, so a retried stop is a no-op rather than an
  invalid-transition error. Bot state-change webhooks are skipped for LOCAL,
  which has no external subscribers.
* A drain that finds the session already deleted exits quietly and clears its
  Redis state instead of erroring and retrying. LOCAL is excluded from the
  never-launched reaper, since a local recording launches no pod.

Co-Authored-By: Claude <noreply@anthropic.com>
@Rahulkaushik01
Rahulkaushik01 requested a review from hd1801 July 20, 2026 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant