Record your coding session. Stop. Your PR explains itself.
trace gives your pull requests a memory. It watches your screen and mic while you code, indexes everything through VideoDB, and when you open a PR it posts a narrated walkthrough video, a context-aware description, a reviewer Q&A bot, and a human vs AI contribution map - all from one session recording.
Demo Repo: trace-test
trace gives your pull requests a memory. Run trace start before you code and trace stop when you're done. That's it - everything else is automatic.
During the session, trace captures your screen and mic, streaming 15-second chunks to VideoDB in real time. On stop, it muxes the audio, uploads the full session, and runs index_spoken_words + index_scenes with a custom classifier prompt to build a tagged timeline of progress, stuck, research, and speech moments.
When you open a PR, trace generate selects the most relevant clips from that timeline, generates a narration script grounded in the scene index and spoken transcript, synthesizes voice via OmniVoice, adds a FLUX-generated intro card and ambient music, and assembles everything on a videodb.editor.Timeline with three composed tracks. The resulting HLS stream URL is posted directly to the PR.
The session stays queryable. Reviewers comment /trace <question> and get a text answer plus up to three bounded clip URLs, powered by dual semantic search across the spoken-word and scene indexes.
VideoDB is the only vendor: capture, indexing, search, generate_text, generate_voice, generate_image, generate_music, and editor.Timeline - 15 distinct API surfaces across 8 files.
trace start → trace stop → trace generate
record index ship
| Step | What happens |
|---|---|
trace start --project <dir> [--live] |
Records screen + mic. On Linux: wf-recorder + ffmpeg/pulse. On macOS/Windows: VideoDB Capture SDK. --live uploads 15s chunks to VideoDB as you code. |
trace stop |
Finalizes recording, uploads to VideoDB, runs index_spoken_words + index_scenes, builds a tagged timeline (progress / stuck / research / speech). |
trace generate <session_id> [pr_url] |
Selects clips, generates narration via OmniVoice, assembles a 3-track editor.Timeline with FLUX intro card + ambient music, posts HLS URL to PR. Omit pr_url to auto-commit, push, and open the PR. |
trace qa-poll <pr_url> <session_id> |
Long-running: polls PR comments for /trace <question>, runs dual semantic search (spoken-word + scene), replies with answer + up to 3 bounded clip URLs. |
trace serve |
FastAPI server: landing page, docs, /api/sessions. |
Additional commands: trace sessions, trace inspect, trace timeline, trace transcript, trace focus, trace contribution-map, trace pr-description.
Narrated PR video - Clips are selected from progress moments whose files appear in the diff. Narration is grounded in the scene index and spoken transcript (no hallucination). Three-track editor.Timeline: video / OmniVoice narration / ambient music. FLUX-generated intro title card. Posted to the PR as an HLS stream URL.
Reviewer Q&A (/trace) - Any reviewer can comment /trace why did you remove the cache?. trace runs semantic search across both the spoken-word and scene indexes and replies with a synthesized answer + up to 3 timestamped clip URLs.
Human vs Agent contribution map - Scans Claude Code session logs within the capture window, classifies each PR diff line as human, agent, mixed, or unknown, posts a per-file summary.
Reviewer Focus Mode - Ranks files by stuck moments and change size, posts a prioritized review guide.
Context-aware PR description - Generates What / Why / Struggles / Follow-ups from the transcript and timeline, appended to the PR description.
git clone https://github.com/crypticsaiyan/trace
cd trace
uv sync.env at repo root:
VIDEODB_API_KEY=...
GITHUB_TOKEN=...
Linux (Wayland):
sudo pacman -S --needed ffmpeg wf-recorder inotify-tools
# Ubuntu/Debian: sudo apt install ffmpeg wf-recorder inotify-toolsmacOS:
uv sync --extra macosWindows:
uv sync --extra windowsProvider: VideoDB OmniVoice (SandboxModel.OMNIVOICE)
Settings: WAV output, voice cloning via ref_audio + ref_text, 4 parallel workers for per-clip TTS.
To swap providers: Replace the three collection.generate_voice(...) calls in trace_cli/pr_video/render.py (labelled: reference voice, per-clip, intro). Upload your provider's audio file via collection.upload(file_path=..., media_type="audio") and use the returned asset id on the narration track.
# 1. Record. --live streams 15s chunks to VideoDB as you code.
uv run trace start --project /path/to/your/repo --live
# ... code, talk out loud, make commits ...
# 2. Stop + index. Uploads to VideoDB, indexes spoken words + scenes, builds timeline.
uv run trace stop
# 3a. Auto-ship: commit staged changes, push, open PR, post narrated video.
uv run trace generate <session_id>
# 3b. Or against an existing PR:
uv run trace generate <session_id> https://github.com/you/repo/pull/N
# 4. Reviewer Q&A bot - polls PR for /trace mentions, replies with clip URLs.
uv run trace qa-poll https://github.com/you/repo/pull/N <session_id>
# 5. Web server - landing page + /api/sessions.
uv run trace serve
# --- Inspection ---
# List all sessions.
uv run trace sessions
# Metadata + timeline summary + transcript head.
uv run trace inspect <session_id>
# Full tagged timeline (progress / stuck / research / speech).
uv run trace timeline <session_id>
# Full spoken-word transcript.
uv run trace transcript <session_id>
# --- Standalone PR decorations ---
# Reviewer Focus Mode: rank files by stuck moments, post review guide.
uv run trace focus <session_id> --pr https://github.com/you/repo/pull/N --post
# Human vs Agent contribution map: classify each diff line, post summary.
uv run trace contribution-map <session_id> --pr https://github.com/you/repo/pull/N --post
# What/Why/Struggles/Follow-ups PR description.
uv run trace pr-description <session_id> --pr https://github.com/you/repo/pull/N --post15 distinct surfaces across 8 files.
| API | Purpose |
|---|---|
videodb.connect |
Auth |
Collection.upload(file_path) |
Session video + 15s live chunks |
Collection.generate_text(prompt, model='pro') |
Narration scripts + PR description |
Collection.generate_voice(text) |
Per-clip TTS via OmniVoice |
Collection.generate_image(prompt) |
FLUX intro title card (16:9) |
Collection.generate_music(prompt) |
Ambient background music |
Video.index_spoken_words(SegmentationType.sentence) |
Transcript for narration + Q&A |
Video.index_scenes(SceneExtractionType.time_based, prompt=...) |
Visual classification |
Video.get_scene_index(scene_index_id) |
Scene grounding for narration |
Video.search(IndexType.spoken_word, semantic) |
Q&A spoken search |
Video.search(IndexType.scene, semantic) |
Q&A visual search |
Video.generate_stream(timeline=[(s,e)]) |
Bounded HLS clip URLs |
editor.Timeline + Track + Clip |
PR video assembly |
editor.VideoAsset / AudioAsset / ImageAsset / TextAsset |
Track assets + badges |
Timeline.generate_stream() |
Final HLS m3u8 posted to PR |
╔══════════════════════════════════════════════════════════════════════════════════╗
║ PHASE 1 · trace start ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ Developer machine VideoDB (--live only) ║
║ ───────────────── ────────────────────── ║
║ screen ──► wf-recorder ──► screen.mp4 every 15s: ║
║ mic ──► ffmpeg/pulse ──► audio.wav chunk.mp4 ──► Collection.upload ║
║ │ ──► index_scenes ║
║ ▼ ──► index_spoken_words ║
║ LiveIndexer thread ────────────────────────────────────────────────► ║
║ ║
║ SaveWatcher (inotify) ──► events_saves.jsonl ║
║ WindowPoller (hyprctl) ──► events_windows.jsonl ║
║ HeartbeatThread ──► heartbeat.json (5s) ║
║ ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ PHASE 2 · trace stop ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ mux screen.mp4 + audio.wav ──► session.mp4 ║
║ │ ║
║ ▼ ║
║ Collection.upload(session.mp4) ──► Video ║
║ │ ║
║ ├──► Video.index_spoken_words ──► transcript.json ║
║ │ (SegmentationType.sentence) ║
║ │ ║
║ └──► Video.index_scenes ──────► scene_index_id ║
║ (time_based, custom JSON classifier prompt) ║
║ │ ║
║ ▼ ║
║ TimelineBuilder ║
║ ├── progress classifier (file saves in diff) ║
║ ├── stuck classifier (long gaps, error scenes) ║
║ ├── research classifier (browser visible) ║
║ └── speech classifier (transcript density) ║
║ │ ║
║ ▼ ║
║ timeline.json (gap-free tagged moments) ║
║ ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ PHASE 3 · trace generate ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ GitHub PR diff ║
║ │ ║
║ ▼ ║
║ ClipSelector ──► ranked clips (progress moments matching diff files) ║
║ │ ║
║ ├──► Video.get_scene_index ──► per-clip scene context ║
║ │ ║
║ ├──► Collection.generate_text (model=pro) ║
║ │ grounded in scene + transcript ──► per-clip narration ║
║ │ ║
║ └──► editor.Timeline assembly ║
║ ├── VideoTrack: VideoAsset (source clips, muted) ║
║ ├── AudioTrack: AudioAsset (OmniVoice TTS, voice-cloned) ║
║ │ AudioAsset (ambient music) ║
║ │ Collection.generate_voice ──► per-clip WAV (x4 parallel)║
║ │ Collection.generate_music ──► background track ║
║ ├── ImageTrack: ImageAsset (FLUX intro card, 16:9) ║
║ │ Collection.generate_image ──► title card ║
║ └── BadgeTrack: TextAsset (category + filename overlays) ║
║ │ ║
║ ▼ ║
║ Timeline.generate_stream() ──► HLS m3u8 URL ║
║ │ ║
║ ▼ ║
║ GitHub PR comment ◄── HLS URL + PR description + contribution map ║
║ + focus mode comment ║
║ ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ PHASE 4 · trace qa-poll (live) ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ reviewer comments "/trace <question>" ║
║ │ ║
║ ├──► Video.search(spoken_word, semantic) ──► top hits ║
║ ├──► Video.search(scene, semantic) ──► top hits ║
║ │ dedupe + rank by score ║
║ ├──► Collection.generate_text ──► synthesized answer ║
║ └──► Video.generate_stream(timeline=[(s,e)]) ──► up to 3 clip URLs ║
║ │ ║
║ ▼ ║
║ GitHub PR reply ◄── answer + bounded clip URLs ║
║ ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ LOCAL SESSION STORE ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ ~/.trace/sessions/<session_id>/ ║
║ metadata.json status, video_id, started_at, project_dir ║
║ screen.mp4 raw screen capture ║
║ audio.wav raw mic capture ║
║ transcript.json spoken-word segments with timestamps ║
║ timeline.json tagged moments (progress/stuck/research/speech) ║
║ events_saves.jsonl file save events (path, timestamp) ║
║ events_windows.jsonl active window samples ║
║ qa_replied.json comment ids already answered ║
║ ║
╚══════════════════════════════════════════════════════════════════════════════════╝
Why chunked live upload instead of CaptureSession: VideoDB's Capture SDK has no Linux wheel. On Linux Wayland, trace runs a LiveIndexer thread that cuts the in-progress mp4 every 15s, uploads via Collection.upload, and indexes each chunk. Same API surfaces, no RTSP tunnel needed.
Why scene-grounded narration: Earlier builds hallucinated function names and errors the developer never mentioned. The narration prompt now receives the per-clip scene index slice (label, files, errors, summary) with explicit anti-hallucination rules.
MIT.
