Back to catalog

youtube-content

YouTube and Pocket Casts transcripts to summaries, threads, blogs, with automatic LLM Wiki ingestion. Optimized for low token cost — transcripts go to disk, subagents read from file. Auto-resolves Pocket Casts URLs to YouTube.

Category 🎬 Media

YouTube Content Tool

When to use

Use when the user shares a YouTube URL, Pocket Casts URL (pca.st/episode/...), or video link and asks to extract, summarize, or ingest the transcript. The default workflow is: resolve URL (if Pocket Casts) → fetch transcript → save to disk → dispatch subagents for wiki extraction → validate → report.

Optimized Pipeline (token-cost aware)

This skill is designed to minimize API token costs. Key design decisions:

  • Transcript never enters the main agent's context. It's saved to a temp file. Subagents read it from disk.
  • Maximum 3 subagents dispatched in parallel (entity, concepts, highlights). No more.
  • v4-flash for mechanical operations. v4-pro only for content analysis.
  • Cost tracking DISABLED. OpenCode Go is flat-rate ($10/month). Phase 0/6 are commented out — uncomment when switching back to per-token provider.

Setup

The skill requires youtube-transcript-api. The setup handles PEP 668 (externally-managed Python) automatically:

cd SKILL_DIR

One-time setup — creates .venv if needed, installs deps

if [ ! -d ".venv" ]; then uv venv 2>/dev/null uv pip install youtube-transcript-api 2>&1 fi

To verify: uv run python3 -c "from youtube_transcript_api import YouTubeTranscriptApi; print('ready')"

The .venv persists across sessions — this only runs once.

Full Workflow

When the user says "extract transcript from [url] and add to wiki" (or similar):

Phase 0 — DISABLED (cost tracking moot with flat-rate OpenCode Go)

<!--

UNCOMMENT WHEN SWITCHING BACK TO OPENROUTER OR PER-TOKEN PROVIDER:

Phase 0 and Phase 6 provide cost delta tracking via check_credits.py.

With OpenCode Go ($10/month flat-rate), this is unnecessary.

See Phase 6 for the matching end-of-pipeline block.

uv run python3 SKILL_DIR/scripts/check_credits.py

Queries Hermes state.db for cumulative estimated cost (provider-agnostic — works with OpenCode, OpenRouter, etc.). Save the total_usage value for the delta calculation at the end.

NOTE: OpenCode has no credit-balance API, so we cannot check "remaining balance." The snapshot is informational — it gives us a baseline for computing the cost delta after ingestion. If the script returns "total_credits": null, that's expected for OpenCode. Estimated costs by transcript length (based on actual measured costs, Jul–Aug 2026, DeepSeek v4-pro):
  • <30 min: $0.05–0.10 (10-15 concepts, ~5 subagent calls)
  • 30–60 min: $0.10–0.20 (15-20 concepts, ~10 subagent calls)
  • 60–90 min: $0.25–0.45 (20-25 concepts, ~15 subagent calls)
  • 90+ min: $0.40–0.70 (25-30 concepts, ~20 subagent calls)

Longer videos cost disproportionately more because subagents re-read the long transcript for each page they create.

-->

SKIPPED: OpenCode Go is flat-rate ($10/month) — per-video cost tracking is meaningless. If you switch back to a per-token provider (OpenRouter, etc.), uncomment Phase 0 and Phase 6.

Phase -1 — Pocket Casts URL Resolution (auto-detected)

When the user shares a pca.st/episode/... URL (or any Pocket Casts URL), resolve it to a YouTube URL BEFORE running the normal pipeline:

cd SKILL_DIR && uv run python3 scripts/resolve_pocketcasts.py "URL"

The script outputs JSON with podcast and episode_title. Use these to search YouTube:

curl -s "https://www.youtube.com/results?search_query=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("<podcast> <episode_title>"))')" \
  -H "User-Agent: Mozilla/5.0" | grep -oP '"videoId":"[^"]+"' | head -1

Extract the video ID, verify with oembed:

curl -s "https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=<VIDEO_ID>&format=json"

If the author_name matches the podcast and the title is similar (YouTube often re-titles), proceed. If ambiguous, confirm with the user using clarify.

CRITICAL: YouTube-only pipeline with Whisper fallback. This skill's primary pipeline is YouTube → youtube-transcript-api. When a Pocket Casts episode has a matching YouTube video, use it. When it does NOT:

1. STOP the YouTube search. Don't spend more than 3-4 search attempts.

2. Check if audio is available. Pocket Casts pages usually have a downloadable MP3 (use browser console: document.querySelector('a[download]').href). The download link is also visible on the page as "Download file."

3. If audio exists, transcribe with Whisper:

   # Download audio
   curl -sL --max-time 120 -o /tmp/<slug>.mp3 "<audio_url>"
   
   # Transcribe with Whisper small model (English-only for speed)
   export PATH="/opt/data/home/.local/bin:$PATH"
   whisper /tmp/<slug>.mp3 --model small --language en --output_dir /tmp --output_format txt
   
  • Whisper is pre-installed via uv tool install openai-whisper
  • The small model is ~461MB (downloaded on first use) and takes ~5-10 min for a 45-min episode on CPU
  • Output goes to /tmp/<slug>.txt

4. Use the transcript for the rest of the pipeline (Phase 2 onwards) — save to wiki with transcription_method: whisper-small in frontmatter.

5. If no audio is available either, tell the user: "This episode has no YouTube mirror and no downloadable audio. Cannot transcribe."

Do NOT attempt Vimeo API, Pocket Casts API, or any other workaround. The approved fallback is: YouTube transcript → Whisper from downloadable audio. Nothing else.

Once confirmed, use the YouTube URL for the rest of the pipeline (Phase 1 onwards).

IMPORTANT: The Pocket Casts source URL still goes into source_url in the transcript frontmatter (Phase 2). YouTube is only used for fetching the transcript.

Phase 0.5 — Deduplication Check

Check memory FIRST (fast, deterministic, near-zero token cost). Only fall back to file search if memory doesn't have it.

Step 1: Check memory via recall
# Use mnemosyne_recall with the video ID
mnemosyne_recall(query="tivaWTTVRhY", limit=1)

If the result contains the video ID (e.g., "Video tivaWTTVRhY ingested 2026-07-31"), read the ingested: date from the result and jump to the clarify step below.

If memory doesn't contain it, use a deterministic triple lookup as a second check:

mnemosyne_triple_query(subject="tivaWTTVRhY", predicate="ingested")
Step 2: Fall back to file search (only if memory has no record)
search_files(path="$WIKI_PATH/consume/raw", pattern="$VIDEO_ID", target="content", output_mode="files_only")

Note: consume/raw/ may be empty if previous raw files were deleted after processing. If no file is found here but memory also has no record, the video hasn't been ingested — proceed to Phase 1.

If found (any method): Tell the user:

> "This video was already ingested on [date]. The entity page, concept pages, and highlights page already exist in the wiki. Do you want me to re-extract (overwriting existing pages) or skip?"

Use clarify with choices: ["Re-extract (overwrite)", "Skip (keep existing)"].

If the user chooses skip: Stop here. Report "Skipped — already in wiki." If the user chooses re-extract or nothing found: Proceed to Phase 1.

Phase 1 — Fetch Transcript

cd SKILL_DIR && uv run python3 scripts/fetch_transcript.py "URL" --text-only --timestamps
  • The script accepts any YouTube URL format (standard, shorts, youtu.be, embeds, raw video ID).
  • If it fails with "Transcript disabled" — tell the user. If it fails with "Video unavailable" — ask to verify the URL. If language is wrong, retry with --language en.
  • Do NOT read the transcript into your context. Pipe it directly to a file:
cd SKILL_DIR && uv run python3 scripts/fetch_transcript.py "URL" --text-only --timestamps > /tmp/yt-transcript-<slug>.txt

Phase 2 — Save Raw Transcript to Wiki

CANONICAL WIKI BASE: /opt/data/wiki/consume. All wiki paths below are relative to this base. Staging directory: consume/raw/ — All raw source files (YouTube transcripts, article PDFs, etc.) go here. This directory is intentionally ephemeral: files land here for processing, then get deleted after Joseph confirms the extracted outputs (concepts, highlights, entities) are good. The directory will be empty most of the time — that's by design.

1. Compute sha256 of the transcript file: sha256sum /tmp/yt-transcript-<slug>.txt

2. Write to /opt/data/wiki/consume/raw/<video-slug>.md with frontmatter:

---
source_url: <original YouTube URL>
ingested: YYYY-MM-DD
sha256: <hex digest>

Then append the full transcript content below the frontmatter.

3. VERIFY FILE PLACEMENT: Confirm the file is at consume/raw/:

test -f /opt/data/wiki/consume/raw/<video-slug>.md && echo "✓ correct path" || echo "✗ WRONG PATH — investigate"

4. POST-PROCESSING DELETION: After Phase 4 (spot-check) confirms the extracted outputs are good, delete the raw transcript:

import os
os.unlink("/opt/data/wiki/consume/raw/<video-slug>.md")

Tell the user: "Raw transcript deleted. Extracted outputs (concepts, highlights, entities) are permanent."

Phase 3 — Dispatch Subagents (parallel, max 3)

Use delegate_task with tasks array to dispatch all three subagents simultaneously. Each subagent gets the file path to the transcript — NOT the transcript text.

CRITICAL RULES for dispatching:

1. Do NOT poll live logs. The delegation system auto-delivers consolidated results when all subagents finish. Reading /opt/data/cache/delegation/live/.../task-*.log wastes tokens and turns — just wait.

2. Self-contained subagents. Each subagent MUST update index.md and log.md for its own pages. This eliminates 3-4 parent-agent validation turns (the single biggest cost after the transcript).

3. Context is file-path ONLY. Do NOT paste the transcript content into the context field. This is the single biggest cost saver.

The three subagent tasks:

Task 1: Entity Page

  • Goal: Load references/subagent-entity.md from this skill, follow its instructions, read the transcript from the provided file path, create entity page at /opt/data/wiki/consume/entities/<person-slug>.md, then update /opt/data/wiki/consume/index.md and /opt/data/wiki/consume/log.md.
  • Context: The file path to the transcript, the wiki path (/opt/data/wiki/consume), and any relevant wiki conventions. DO NOT include the transcript text itself.

Task 2: Concept Pages

  • Goal: Load references/subagent-concepts.md from this skill, follow its instructions, read the transcript from the provided file path, create concept pages at /opt/data/wiki/consume/concepts/<concept-slug>.md, then update /opt/data/wiki/consume/index.md and /opt/data/wiki/consume/log.md. CRITICAL: Do NOT abbreviate paths — always use /opt/data/wiki/consume/ not /opt/data/wiki/. Do NOT put anything under /opt/data/wiki/raw/.
  • Context: Same as above — file path, wiki path (/opt/data/wiki/consume), conventions. NO transcript text. Include the transcript line count so the subagent knows how large the source is.
  • Concept page caps by transcript size:
  • <1,200 lines (~45 min): No cap — extract all substantive concepts
  • 1,200–2,000 lines (~45–75 min): Cap at 20 concept pages — prioritize the most distinctive/reusable ideas; fold minor concepts into related pages
  • 2,000+ lines (~75+ min): Cap at 25 concept pages — be selective; only create pages for ideas the speaker spends 3+ minutes on

Task 3: Highlights Page

  • Goal: Load references/subagent-highlights.md from this skill, follow its instructions, read the transcript from the provided file path, create highlights page at /opt/data/wiki/consume/highlights/<video-slug>.md, then update /opt/data/wiki/consume/index.md and /opt/data/wiki/consume/log.md.
  • Context: Same as above — file path, wiki path (/opt/data/wiki/consume), conventions. NO transcript text.
Context template for all subagents:
TRANSCRIPT_PATH=/tmp/yt-transcript-<slug>.txt
WIKI_PATH=/opt/data/wiki/consume
Read the transcript from TRANSCRIPT_PATH using the read_file tool.
Follow the instructions in the youtube-content skill's references/subagent-*.md.
Use [[wikilinks]] to cross-reference pages. Minimum 2 outbound links per page.
ALL file writes go under /opt/data/wiki/consume/ — NEVER under /opt/data/wiki/raw/.

Phase 4 — Spot-Check & Finalize

After all subagents complete (they've already updated index.md and log.md for their pages):

1. Quick spot-check: Read 1-2 concept pages at random. Verify:

  • Quotes are verbatim (search the transcript to confirm 1-2)
  • Acronyms/frameworks are correct (subagents frequently get these wrong — e.g., LOCK vs LOCKS)
  • Cross-references exist (wikilinks to other pages)

2. Fix broken wikilinks across pages: Entity and highlights subagents run in parallel with concepts — they may link to concept slugs that don't exist. Compare [[concepts/...]] links in the entity and highlights pages against the actual files in /opt/data/wiki/consume/concepts/. Fix any mismatches. Do this check every time.

3. Fix any issues found in spot-check (use patch). Do NOT do exhaustive validation of every page — trust subagent self-validation.

3. Read /opt/data/wiki/consume/index.md to verify subagents added their entries correctly. Fix only if broken.

4. Prepend to /opt/data/wiki/consume/log.md with the ingest record:

## [YYYY-MM-DD] ingest | [video title]
- Pages created: [count] (entities: X, concepts: Y, highlights: 1)
- Source: [YouTube URL]
DO NOT re-read every page. The subagents already self-validated and updated navigation. Parent agent's role is a quick sanity check, not exhaustive audit.

Phase 5 — Wiki State Snapshot

Store a compact wiki summary in Mnemosyne so future sessions can skip re-reading SCHEMA.md, index.md, and log.md:

Use mnemosyne_remember with:

content: "Wiki at $WIKI_PATH: N pages. Entities: [list]. Concepts: [list]. Highlights: [list]. Tracking: [list]. Last ingest: [video title] on [date]. Video $VIDEO_ID ingested $DATE → entity: [name], highlights: [slug], concepts: [count]."
importance: 0.7
scope: global
source: tool

And store a deterministic triple for fast lookup:

mnemosyne_triple_add(subject="$VIDEO_ID", predicate="ingested", object="$DATE | $video_title | entity:$entity_slug | highlights:$highlight_slug | concepts:$count", source="youtube-content")

This gives two lookup paths: semantic recall (mnemosyne_recall("tivaWTTVRhY")) and deterministic fact lookup (mnemosyne_triple_query(subject="tivaWTTVRhY")).

Phase 6 — DISABLED (cost tracking moot with flat-rate OpenCode Go)

<!--

UNCOMMENT WHEN SWITCHING BACK TO OPENROUTER OR PER-TOKEN PROVIDER.

This is the matching end-of-pipeline block for Phase 0.

uv run python3 SKILL_DIR/scripts/check_credits.py

Compute delta: ending_usage - starting_usage = cost of this transcription (in USD).

Report to the user:

Wiki ingest complete: [video title]
Pages created: [count] (entities: X, concepts: Y, highlights: 1)
Cost: $Z.ZZ (estimated, via state.db delta)

-->

SKIPPED: See Phase 0 note.

Cleanup

Remove the temp transcript file AND the raw wiki file using execute_code (avoids terminal approval prompt for rm):

import os
os.unlink("/tmp/yt-transcript-<slug>.txt")
os.unlink("/opt/data/wiki/consume/raw/<slug>.md")  # raw transcript deleted after processing

Subagent Prompt Templates

Located in references/:

File Subagent What it extracts
subagent-entity.md Entity page Speaker bio, career, philosophy, key relationships, notable quotes
subagent-concepts.md Concept pages All frameworks, mental models, heuristics, and ideas from the video
subagent-highlights.md Highlights page Narrative summary, top quotes with timestamps, theme index, cross-links

Each template includes:

  • Exact output format (full markdown with YAML frontmatter)
  • Verification checklist (check these claims against the transcript)
  • Wiki conventions to follow (file naming, wikilinks, tags)

Other Output Formats (non-wiki)

If the user does NOT have a wiki or asks for a different format:

  • Chapters: Group by topic shifts, output timestamped chapter list
  • Summary: Concise 5-10 sentence overview of the entire video
  • Thread: Twitter/X thread format — numbered posts, each under 280 chars
  • Blog post: Full article with title, sections, and key takeaways
  • Quotes: Notable quotes with timestamps

Pitfalls

  • NEVER put the transcript in context. Always save to file and pass the file path to subagents. This single rule saves 80-90% of token costs.
  • Never dispatch more than 3 subagents. Entity + concepts + highlights is sufficient. More subagents = more overhead, not better output.
  • Do NOT poll live logs. The delegation system auto-delivers results. Reading /opt/data/cache/delegation/live/... wastes tokens and turns — just wait for the consolidated result.
  • Subagents self-update navigation. Each subagent updates index.md and log.md for its own pages. This eliminates the single biggest parent-agent cost.
  • Parent agent does quick spot-check only. Do NOT re-read every page. Spot-check 1-2 random pages for verbatim quotes and acronym correctness. Trust subagent self-validation.
  • Always spot-verify acronyms and frameworks. Subagents frequently get these wrong (e.g., LOCK vs LOCKS, EV>TV>Me vs CV>EV>TV>Me).
  • Use v4-flash for mechanical operations. File writes, index updates, log appends, and credit checks don't need v4-pro.
  • The .venv in SKILL_DIR persists. Don't recreate it every session. Check with test -d .venv first.
  • Cost tracking is DISABLED. OpenCode Go is flat-rate ($10/month) — per-video cost delta is meaningless. Phase 0 and Phase 6 are commented out with HTML comments. Uncomment both when switching back to a per-token provider (OpenRouter, etc.). The check_credits.py script is still maintained and provider-agnostic.
  • <!-- Cost tracking caveats (uncomment when re-enabling Phase 0/6):
  • Credit check script is provider-agnostic. It auto-detects the billing provider from state.db and adapts: OpenRouter → API, OpenCode → state.db. No manual configuration needed.
  • state.db cost tracking is cumulative. The delta between Phase 0 and Phase 6 snapshots gives the estimated cost of the current ingestion. Caveat: if other LLM activity happens during ingestion, the delta will include that too. For precise per-session tracking, use the hermes-cost-monitoring skill.

-->

  • Transcript disabled? Tell the user. Private video? Ask to verify. No subtitles available in English? Try without --language flag.
  • Pocket Casts URLs are auto-resolved. Paste a pca.st/episode/UUID link and Phase -1 extracts podcast + episode title, searches YouTube, and confirms the match. No manual YouTube searching needed. If multiple videos match or results are ambiguous, confirm with the user.
  • Wiki paths are CANONICAL — never invent new ones. /opt/data/wiki/consume/ is the only consume path. Raw source files go to consume/raw/, concepts to consume/concepts/, entities to consume/entities/, highlights to consume/highlights/. The consume/raw/ directory is ephemeral — files are deleted after processing. Don't worry if it's empty.
  • Raw transcripts are deleted after processing. After Phase 4 confirms extracted outputs are good, delete the raw file from consume/raw/. The extracted concepts, highlights, and entities are the permanent artifacts. The sources: frontmatter in those files references the YouTube URL directly, not the raw file.
  • Parallel subagents can pick different concept slugs. The entity, concept, and highlights subagents run in parallel and independent — they may invent different filenames for the same concept (e.g., voice-first-workflow vs voice-note-first-architecture). In Phase 4 spot-check, compare all [[concepts/...]] wikilinks across entity and highlights pages against what the concepts subagent actually created. Fix mismatches. Do this BEFORE finalizing.
  • Cost scales with transcript length, not linearly. A 90-min video costs ~$0.50-0.70, not just 3x a 30-min video (~$0.08). This is because subagents re-read the full transcript for each concept page. Set user expectations accordingly.
  • Cap concept pages for long transcripts. In the subagent context, include the transcript line count and enforce the cap: <1200 lines = unlimited, 1200-2000 = max 20, 2000+ = max 25. This prevents cost blowout from dense long-form content.
  • Every concept page must link back to the highlights page. The highlights page is the canonical navigational hub for a video. If a reader lands on a concept page, they need a path back to the full conversation context. The concept subagent's ## Origin section must include [[highlights/video-slug]] and the ## Related section must also list it.
  • Highlights summary must be SHORT — 2-3 sentences max. The highlights page is structured with numbered Key Themes sections, not a blog post. A long narrative summary at the top defeats the purpose. Tony Fadell's highlights page is the reference template: metadata intro → straight into numbered sections. Dianne Penn's highlights (13-line narrative summary) is the anti-pattern.
  • YAML frontmatter titles must be quoted when they contain colons. An unquoted title like Timothy Keller — "Your Plans: God's Plans" breaks YAML parsing because : inside the value is read as a key-value separator. Obsidian shows the raw YAML text with a red highlight instead of rendering properties. Fix: use single quotes around the entire title value and escape internal apostrophes as '' (e.g., title: 'Timothy Keller — "Your Plans: God''s Plans" — Key Highlights'). The highlights subagent template already enforces this; if you're writing a page manually, apply the same rule. Check memory FIRST: mnemosyne_recall("$VIDEO_ID") or mnemosyne_triple_query(subject="$VIDEO_ID"). These are near-zero token cost and deterministic. Only fall back to searching consume/raw/ files if memory has no record (e.g., for videos ingested before this dedup feature was added). Note that consume/raw/ may be empty if previous raw files were deleted after processing — an empty directory doesn't mean the video wasn't ingested.