Video memories show "Untitled memory" when no transcript
Metadata
- Date:
2026-05-22 - Status:
fixed - Severity:
high - Related issue/ticket:
N/A - Owner:
N/A
About
Overview: - Video memories captured without audio (or with inaudible audio that produces an empty transcript) are saved with title=None and display as "Untitled memory" in the feed. Audio-only memories work correctly because they always have a transcript. Visual memories generate a caption from frames, but the caption is never used as a fallback source for title generation. - This affects every video session recorded in a quiet environment or where the microphone captures no usable audio. All resulting memories land in the feed as "Untitled memory", making them indistinguishable and unsearchable.
Technical Questions: - The title generation block at step 5 of lambda_handler only runs when transcript is truthy. After the GPU generates a caption (line ~686), title is still None if transcript was absent. The update_segment call at the end of the visual enrichment path passes title=title — still None. - The caption is a rich description of the visual content (e.g. "Person typing on laptop in a coffee shop") and is 20+ characters, so generate_title would produce a valid title from it. - The generate_title guard len(transcript.strip()) < 20 exists in title_generator.py — captions easily exceed this threshold. - Bug is present since title generation was first introduced. It was latent as long as all test sessions had audio.
Resources: - main/server/worldmm/pipeline/ingest_window.py — lambda_handler, step 5 title block (line ~630) and visual enrichment update_segment call (line ~767) - main/server/worldmm/llm/title_generator.py — generate_title function - CloudWatch log group: /aws/lambda/server-IngestWindowFunction-44v8BXyEGwOz
Steps to cause failure
flowchart LR
Start["Video session\n(no/empty audio)"] --> NoAudio["transcript = ''"]
NoAudio --> TitleSkip["if transcript: False\ntitle = None"]
TitleSkip --> CaptionGen["GPU generates caption\n(frames only)"]
CaptionGen --> UpdateSegment["update_segment(title=None)"]
UpdateSegment --> Feed["Feed shows\n'Untitled memory'"] System
flowchart TD
S3Frames["S3 frames"] --> IngestWindow["IngestWindow\nlambda_handler"]
S3Audio["S3 audio (optional)"] --> IngestWindow
IngestWindow --> Transcribe["Whisper transcription\n(if audio present)"]
Transcribe --> TitleGen["Title generation\n(if transcript)"]
IngestWindow --> GPU["GPU worker\ncaption()"]
GPU --> Caption["caption string"]
TitleGen --> UpdateDB["update_segment(title=...)"]
Caption --> UpdateDB caption is generated after title generation and is never used as a title source.
Reproduction Details
- Invoke
lambda_handlerwithframeCount > 0and no audio bytes (or audio that produces an empty transcript). - GPU generates a non-empty caption.
update_segmentis called withtitle=None.- Segment in DB has
title=None.
Reproduction test: main/server/tests/integration/test_ingest_window_titles.py::TestTitleGenerationHappyPath::test_title_generated_from_caption_when_no_transcript
Notes for PR
Root cause: title generation (step 5) only uses transcript. For video sessions without audio, transcript="" so the if transcript: guard is False and title stays None. The GPU later produces a descriptive caption that could be used as the title source, but the code never attempts this fallback.
Fix: after caption is produced by gpu_worker.caption(...), add a caption-fallback block — if title is None and caption is non-empty, call generate_title(caption, ...). This mirrors the identical try/except pattern used for the transcript-based path.
Audit Log
| ID | Action | Note | Context |
|---|---|---|---|
| 1 | Create audit log | Initialize bug investigation | visual memories show "Untitled memory" in feed |
| 2 | Read source files | Confirmed ingest_window.py lines 629-639 and 767-773 | step 5 only runs when transcript is truthy |
| 3 | Read title_generator.py | Confirmed generate_title accepts any 20+ char string | caption would pass the length guard |
| 4 | Identified root cause | title is never updated from caption after GPU enrichment | no fallback path exists |
| 5 | Write failing test | test_title_generated_from_caption_when_no_transcript | confirms title is None before fix |
| 6 | Confirm test fails | pytest red before patch | baseline confirmed |
| 7 | Apply fix | Add caption fallback block after caption_generated log line | ingest_window.py ~line 694 |
| 8 | Confirm test passes | pytest green after patch | fix verified |
Verification
- [x] Reproduced failure before fix
- [x] Reproduction test fails before fix
- [x] Root cause identified with evidence
- [x] Fix applied at source (no workaround-only patch)
- [x] Reproduction test passes after fix
- [x] Reproduction path now passes
- [x] Regression test added/updated
- [x] Verified no duplicate solved-bug log exists for same root cause