| name | research-notebook |
| description | TAD Research Notebook Manager โ NotebookLM multi-source knowledge base for Alex *discuss and research workflows. 19 sub-commands for full research lifecycle management. |
/research-notebook Command (NotebookLM Integration)
Overview
*research-notebook manages NotebookLM notebooks as persistent knowledge assets for TAD workflows.
Use for research-intensive topics requiring cross-source synthesis (YouTube + PDF + web).
Key principle: NotebookLM is a knowledge asset (stateful, persistent), not a stateless tool call.
Primary use: Alex design/discuss phase. Also usable standalone (without /alex) via CLAUDE.md ยง2 research routing โ see "Standalone Usage" below.
Standalone Usage (without /alex)
When invoked via CLAUDE.md research routing (without /alex active):
- Run preflight check (same as below)
- Use the same CLI commands (create / ask / source add-research)
- Skip Alex-specific protocols (Socratic, handoff, Gate, capability_pack_awareness)
- After research completes โ soft suggestion of next steps (see below)
- When /alex IS active, Alex's own research protocols (research_notebook_awareness, research_plan_protocol) take precedence
After Research Completes (Standalone)
Inform user: "็ ็ฉถๅฎๆใFindings saved to {path}."
Then suggest (non-blocking text, NOT AskUserQuestion):
"ๆฅไธๆฅไฝ ๅฏไปฅ๏ผ็จ /alex *analyze ่ฟๅ
ฅ่ฎพ่ฎก / ๆทปๅ ๅฐ NEXT.md / ็ปง็ปญๆทฑๅ
ฅ็ ็ฉถ / ไฟๅญๅฐ project-knowledgeใ"
Preflight Check (runs before every sub-command)
preflight:
venv_path: "~/.tad-notebooklm-venv"
notebooklm_bin: "~/.tad-notebooklm-venv/bin/notebooklm"
checks:
- "notebooklm CLI available: test -x ~/.tad-notebooklm-venv/bin/notebooklm"
- "Version check: ver=$(~/.tad-notebooklm-venv/bin/notebooklm --version | awk '{print $NF}'); printf '%s\\n0.3.4\\n' \"$ver\" | sort -V | head -1 | grep -qx '0.3.4'"
- "Auth valid: ~/.tad-notebooklm-venv/bin/notebooklm auth check --test 2>&1 | grep -q 'authenticated'"
on_fail_missing: "Output: 'โ ๏ธ NotebookLM not ready. Run: bash .tad/cross-model/setup-notebooklm.sh'"
on_fail_version: "Output: 'โ ๏ธ notebooklm-py < 0.3.4 has broken AI endpoints โ re-run: bash .tad/cross-model/setup-notebooklm.sh'"
on_fail_auth: "Output: 'โ ๏ธ NotebookLM auth expired. Run: bash .tad/cross-model/setup-notebooklm.sh'"
on_pass: "Proceed to sub-command"
invocation_pattern: "~/.tad-notebooklm-venv/bin/notebooklm <subcommand>"
Note: All notebooklm CLI invocations use the absolute path ~/.tad-notebooklm-venv/bin/notebooklm
(not notebooklm bare) to avoid PATH/venv activation dependency. The binary lives in the
venv created by setup-notebooklm.sh at ~/.tad-notebooklm-venv/.
Commands
*research-notebook create <topic>
Create a new notebook, add initial sources, register to REGISTRY.
Step 1: Create NotebookLM notebook
โ ~/.tad-notebooklm-venv/bin/notebooklm create "<topic>"
โ Capture notebook_id from output
Step 2: Guide source addition (AskUserQuestion)
โ "What sources would you like to add?"
Options:
- "I'll provide URL list"
- "Search for conference/official YouTube videos on this topic"
- "Both"
- "Add later"
Step 3: If user selects YouTube search:
โ WebSearch: "{topic} conference talk OR official channel 2026 site:youtube.com"
โ Display video list (title + URL)
โ User selects which to add
โ ~/.tad-notebooklm-venv/bin/notebooklm source add <url> (one by one)
Step 4: If user provides URL list:
โ For each URL: ~/.tad-notebooklm-venv/bin/notebooklm source add <url>
โ Report success/failure per URL
Step 5: Register to REGISTRY
โ Update .tad/research-notebooks/REGISTRY.yaml
โ Record: notebook_id, topic, source_count, sources list, created date, status=active
Step 6: Confirm
โ "โ
Notebook '{topic}' created, {N} sources added.
Query with: *research-notebook ask 'your question'"
*research-notebook add <url> [--notebook <id>]
Add a source to the active (or specified) notebook.
Step 1: Resolve target notebook
โ If --notebook <id> specified โ use that
โ Else read REGISTRY.yaml active_notebook field
Step 2: Check source limit
โ Read source_count from REGISTRY
โ If source_count >= max_sources_per_notebook (config-workflow.yaml):
โ "โ ๏ธ Notebook at source limit ({max}). Run *research-notebook curate first."
โ Exit
Step 3: Add source
โ ~/.tad-notebooklm-venv/bin/notebooklm use <notebook_id>
โ ~/.tad-notebooklm-venv/bin/notebooklm source add <url>
โ Capture success/failure
Step 4: Update REGISTRY
โ Append source entry (url, type, added date, title if detectable)
โ Increment source_count
โ Output: "โ
Source added. Total: {N} sources."
*research-notebook add-smart <url> [--notebook <id>]
Import a source with automatic type detection and preprocessing. Handles X/Twitter threads,
Bilibili videos, academic papers, paywalled articles (Substack/Medium), and generic web content.
Uses .tad/cross-model/source-preprocessor.sh for URL routing and handler dispatch.
Step 1: Resolve target notebook (same as `add`)
โ If --notebook <id> specified โ use that
โ Else read .tad/research-notebooks/REGISTRY.yaml active_notebook field
โ If no active notebook โ AskUserQuestion: "Which notebook to import into?"
Options: list of active notebooks + "Cancel"
Step 2: Validate + detect URL type
โ Validate: echo '<url>' | bash .tad/cross-model/source-preprocessor.sh validate
If exit 1 โ "โ Invalid URL: contains unsafe characters or missing http(s)://" + EXIT
โ Detect: url_type=$(echo '<url>' | bash .tad/cross-model/source-preprocessor.sh detect)
โ Set output_dir: .research/preprocessed/<notebook_id>/
mkdir -p "$output_dir"
Step 3: Capture existing source IDs before import (BA-P0-1 fix)
โ ids_before=$(~/.tad-notebooklm-venv/bin/notebooklm source list --json -n <notebook_id> | jq -r '.[].id' | sort)
Step 4: Dispatch handler + import based on url_type
[Direct path โ proven arXiv PDF, no preprocessing]
If url_type == "arxiv_pdf":
โ ~/.tad-notebooklm-venv/bin/notebooklm source add <url> -n <notebook_id>
โ handler_label="arxiv-direct"
[Handler paths โ preprocessing โ local .md or remote URL โ source add]
If url_type in [x_article, x_tweet, bilibili, arxiv_abs, scholar, substack, medium]:
โ result=$(bash .tad/cross-model/source-preprocessor.sh dispatch <url> <notebook_id> <output_dir>)
โ dispatch_exit=$?
โ If dispatch_exit == 2: "โ Missing dependency: {stderr}" + EXIT
โ If dispatch_exit == 1: "โ ๏ธ Extraction failed. {stderr}" + EXIT
โ If dispatch_exit == 10: source add <result> -n <notebook_id> [result = remote URL]
โ If dispatch_exit == 0: source add <result> -n <notebook_id> [result = local .md path]
โ Else (exit $dispatch_exit, e.g., 124=timeout / 127=missing-tool):
"โ Handler dispatch failed (exit $dispatch_exit). Install coreutils or check stderr." + EXIT
โ handler_label="$url_type"
[Generic web path โ try direct first, Jina fallback triggered by quality failure in Step 6]
If url_type == "generic_web":
โ ~/.tad-notebooklm-venv/bin/notebooklm source add <url> -n <notebook_id>
โ handler_label="generic-direct"
โ Note: if verify_import_quality returns FAIL โ Jina fallback runs inside Step 6
Step 5: Identify new source_id via set-difference (BA-P0-1 fix โ avoids unreliable .[-1])
โ ids_after=$(~/.tad-notebooklm-venv/bin/notebooklm source list --json -n <notebook_id> | jq -r '.[].id' | sort)
โ new_source_id=$(comm -13 <(echo "$ids_before") <(echo "$ids_after") | head -1)
โ If new_source_id is empty โ "โ Could not identify newly-added source id. Source may still be importing." + EXIT
โ Capture: source_title=$(notebooklm source list --json -n <notebook_id> | jq -r --arg id "$new_source_id" '.[] | select(.id == $id) | .title // "unknown"')
Step 6: verify_import_quality passing new_source_id (see [HELPER] below)
โ Wait 30s for NotebookLM indexing
โ Run structured quality probe on new_source_id (not .[-1])
โ On PASS: "โ
Source '{title}' imported with good content quality."
โ On WARN: "โ ๏ธ Source '{title}' has mixed quality. Content may include navigation noise."
โ On FAIL (QUALITY:NONE or status=="error"):
a. Delete bad source (guard against failure โ BA-P1-1 fix):
del_exit=0
~/.tad-notebooklm-venv/bin/notebooklm source delete "$new_source_id" -n <notebook_id> --yes || del_exit=$?
If del_exit != 0: "โ ๏ธ Could not delete bad source (id=$new_source_id, exit=$del_exit). Stopping to avoid duplicates." + EXIT
b. If url_type == "generic_web":
โ Jina fallback: jina_result=$(bash .tad/cross-model/handlers/jina-handler.sh <url> <output_dir>)
โ If jina exit 0: source add "$jina_result" -n <notebook_id> โ re-run verify_import_quality
โ If Jina also fails: "โ ๏ธ Could not import useful content from this URL." + EXIT
c. If url_type != "generic_web": "โ ๏ธ Handler content failed quality check." + EXIT
Step 7: Update REGISTRY + metadata
โ Update REGISTRY.yaml: increment source_count, append source entry (url, type, added date)
โ Update .research/preprocessed/<notebook_id>/metadata.yaml:
BA-P1-2 fix โ schema + append protocol:
File schema (top-level `entries` list, YAML-valid):
# metadata.yaml
entries:
- file: "x-article-12345.md" # path relative to output_dir, or null for URL-direct
original_url: "https://..."
handler: "x_article"
extracted_at: "2026-05-09T12:00:00Z"
source_id: "<NotebookLM source UUID from Step 5>"
quality_verified: true
Create if absent: write header `entries:\n` to new file.
Append entry: yq -i '.entries += [{"file": "...", "original_url": "...", "handler": "...", "extracted_at": "...", "source_id": "...", "quality_verified": true}]' metadata.yaml
Concurrency: v1 assumes single-writer โ document as v1 constraint.
Recover: metadata.yaml is an audit log; if lost, rebuild from `notebooklm source list` + dir scan.
Step 8: Report result
โ "โ
Imported via {handler_label}: '{source_title}'"
โ Quality: HIGH / LOW (WARN) / UNKNOWN
โ If preprocessed .md was saved: "๐ Cached: {out_file}"
[HELPER] verify_import_quality procedure:
Caller passes: notebook_id + source_id (from set-diff in Step 5 โ do NOT use .[-1])
verify_import_quality(notebook_id, source_id):
1. sleep 30 [Wait 30s โ NotebookLM indexing takes ~30s]
2. Fetch source record by source_id:
source_json=$(~/.tad-notebooklm-venv/bin/notebooklm source list --json -n <notebook_id>)
source_record=$(echo "$source_json" | jq --arg id "$source_id" '.[] | select(.id == $id)')
source_title=$(echo "$source_record" | jq -r '.title // "unknown"')
source_status=$(echo "$source_record" | jq -r '.status')
2b. BA-P1-3 fix โ retry if status is "preparing" or "processing" (indexing can take up to ~90s):
If source_status in ["preparing", "processing"]:
sleep 60 [second wait, total 90s]
Re-fetch source_record + source_status (same jq query above)
If still preparing/processing after retry:
โ return WARN "Source still indexing โ re-verify later via *ask"
3. If source_status == "error" โ return FAIL immediately (no probe needed)
4. If source_status == "ready":
4a. Structural pre-check โ skip LLM if content too short (< 500 chars):
content_len=$(echo "$source_record" | jq -r '.content_length // .char_count // ""' 2>/dev/null || true)
If content_len is a non-empty integer AND content_len < 500:
โ return FAIL "content too short (<500 chars) โ stub or error page (no LLM call needed)"
(If content_len field absent from source record โ skip pre-check, proceed to 4b)
4b. LLM probe (force fresh conversation for reliable response):
response=$(~/.tad-notebooklm-venv/bin/notebooklm ask \
"Rate the content quality of the most recently added source titled '${source_title}'.
Respond with ONLY one of these exact labels:
QUALITY:HIGH โ contains substantive article/paper/video text with โฅ3 substantive paragraphs
QUALITY:LOW โ contains some useful paragraphs but heavily mixed with navigation noise
QUALITY:NONE โ if content consists PRIMARILY of: navigation menus, sidebar links, footer
elements, table-of-contents listings without article body text, cookie/privacy banners,
login/paywall prompts, or fewer than 3 substantive paragraphs of actual content" \
-n <notebook_id> -c 00000000-0000-0000-0000-000000000000)
Parse first line of response:
If starts with "QUALITY:NONE" โ return FAIL
If starts with "QUALITY:LOW" โ return WARN (keep source)
If starts with "QUALITY:HIGH" โ return PASS
If no QUALITY: prefix found โ return WARN (probe inconclusive โ keep source)
*research-notebook ask <question> [--notebook <id>] [--no-follow]
Query a notebook (cross-source reasoning). By default, triggers dynamic multi-round research (step3_5).
Pass --no-follow to preserve single QโA behavior (no auto follow-up).
Step 0: Parse flags
โ If --no-follow in args: set dynamic_follow = false; remove --no-follow before passing to CLI
โ Else: set dynamic_follow = true (default โ dynamic protocol runs after answer)
Step 1: Resolve target notebook
โ If --notebook <id> specified โ use that
โ Else read REGISTRY.yaml active_notebook field
โ If no active notebook โ AskUserQuestion: "Which notebook to query?"
Options: list of active notebooks + "Create new"
Step 2: Activate notebook
โ ~/.tad-notebooklm-venv/bin/notebooklm use <notebook_id>
Step 2b (NEW): Auto-refresh stale sources (with latency cap)
Field location: `last_refreshed` lives in .tad/research-notebooks/REGISTRY.yaml,
per-notebook entry (sibling to last_queried and status fields).
(last_refreshed tracks SOURCE REFRESH events; last_queried tracks ASK events โ distinct)
Guard: Check .tad/research-notebooks/REGISTRY.yaml for this notebook's last_refreshed field.
If last_refreshed is set AND was updated < 24h ago โ SKIP entire Step 2b.
If last_refreshed field is ABSENT โ treat as "needs refresh" (bootstrap path for new notebooks).
If guard passes (refresh needed):
โ ~/.tad-notebooklm-venv/bin/notebooklm source list --json -n <notebook_id>
โ Filter: only sources where type == "SourceType.WEB_PAGE"
โ Cap: check at most 10 sources, refresh at most 5 stale ones
โ Wall-clock timeout: record start_ts=$(date +%s) before loop
abort loop when $(($(date +%s) - start_ts)) -ge 30 (30s ceiling)
โ Wrap each CLI call with `timeout 10` to protect against hung subprocess:
if timeout 10 ~/.tad-notebooklm-venv/bin/notebooklm source stale <source_id> -n <notebook_id>; then
timeout 10 ~/.tad-notebooklm-venv/bin/notebooklm source refresh <source_id> -n <notebook_id>
fi
(timeout 10: exit 124 on hang โ treat as failure โ continue loop)
โ On any CLI failure or timeout โ skip silently, proceed to ask
โ After refresh loop completes: update last_refreshed in .tad/research-notebooks/REGISTRY.yaml:
yq -i '(.notebooks[] | select(.id == "<notebook_id>") | .last_refreshed) = "<YYYY-MM-DD>"' \
.tad/research-notebooks/REGISTRY.yaml
Fallback if yq unavailable: skip the write and log "last_refreshed not updated (yq absent)"
Step 2.5 (NEW): Source targeting (optional)
โ If user specifies --source <id>:
โ Build source flags: --source <id1> [--source <id2> ...]
โ Display: "๐ฏ Querying specific sources: {source_titles}"
โ If no --source: query all sources (existing behavior)
Step 3: Execute query (stale conversation fallback)
Note: --save-as-note flag is CALLER'S RESPONSIBILITY (Alex decides when to use).
Research-notebook SKILL supports the flag but does NOT auto-add it.
Use --no-save to suppress auto-save in privacy-sensitive contexts.
Layer 1 (normal):
โ ~/.tad-notebooklm-venv/bin/notebooklm ask "<question>" {source_flags} {save_flags}
where {source_flags} = "--source <id>" if Step 2.5 specified, else empty
where {save_flags} = "--save-as-note --note-title 'TAD Research: {first 40 chars of question}'" if caller passed --save-as-note
= "" if --no-save passed or no flag
โ If exit 0 and output non-empty โ proceed to Step 4
โ If exit != 0:
Layer 2 (force fresh conversation โ retry on any non-zero exit):
โ ~/.tad-notebooklm-venv/bin/notebooklm ask "<question>" {source_flags} {save_flags} -c 00000000-0000-0000-0000-000000000000
โ If exit 0 โ proceed to Step 4
โ If still fails: "โ ๏ธ Query failed. Check auth or notebook state."
(Note: --new flag does NOT exist in 0.3.4; -c 00000000... is the only fresh-conversation mechanism)
Step 3.5: Dynamic Follow-up Protocol (skip entirely if dynamic_follow == false)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
TRIGGER: Every ask where dynamic_follow == true (default)
MAX DEPTH: max_depth = 4 rounds total (including initial ask in Step 3)
TRACK: current_depth = 1 (after Step 3); new_citations_this_round; prev_zero_citation_rounds = 0;
strategies_used: [] (append strategy name each round โ for tunnel detection)
EXTRACT four dimensions from the Step 3 answer:
surprising: "ๆไปคไบบๆ่ฎถ็ 1 ไธชๅ็ฐ๏ผไธๅธธ่ฏ/้ขๆไธๅ็๏ผ"
gap: "็ญๆกๆ็คบไบไปไนๆช่ขซ่ฆ็็้ขๅ ('sources do not contain' signals)"
conflict: "ไธๅๆบไน้ดๆฏๅฆๆ็็พ ('Source A says X, but Source B says Y')"
actionable: "ๆฏๅฆๆๅฏไปฅ็ดๆฅ่ฝฌๅไธบๅณ็ญ/ไปฃ็ ๅๆด็ไฟกๆฏ"
COUNT new citations (saturation signal):
extract all [N] markers from current answer โ count unique markers not seen in prior rounds
new_citations = count of unique [N] markers not previously seen
CHAIN STORAGE โ incremental writes (compact-safe):
Path: .tad/evidence/research/{notebook_topic}/{date}-chain-{topic_slug}-{uid}.md
{notebook_topic}: read from .tad/research-notebooks/REGISTRY.yaml โ this notebook's topic field
{topic_slug}: first 30 chars of seed question, alphanum+hyphens only, lowercase
(CJK stripped by alphanum filter โ if slug โค4 chars after filter, use "q" prefix e.g. "q-ab12")
{uid}: first 4 hex chars of md5(seed_question_full_text); prevents same-day same-slug collision
bash: uid=$(printf '%s' "$seed_question" | { md5 2>/dev/null || md5sum 2>/dev/null; } | cut -c1-4)
Collision guard: if file exists at chain init AND first line does NOT match same seed_question,
append "-2" suffix (then "-3" etc.) until a free path is found.
Append each completed round immediately after it finishes (do NOT batch-write at chain end)
Saturation state in each round's Analysis block: include `new_citations: N` and `prev_zero_streak: N`
so compact-recovery can rebuild counter by reading the last round's Analysis block.
File format (frontmatter + round blocks per ยง4.3 of HANDOFF-20260509-dynamic-research-strategies):
--- type: research-chain, notebook_id, seed_question, depth, strategies_used, total_citations, created_at,
seed_origin: original | dynamic, dynamic_index: N (0-based for dynamic seeds; omit for original) ---
## Seed Question / ## Round N โ {strategy} / ### Analysis (surprising, strategy chosen, new_citations: N, prev_zero_streak: N)
## Chain Summary (key finding + action items + sources cited)
Compact recovery for dynamic_seeds_added: count chain files with `seed_origin: dynamic` in current Phase 4 output dir.
bash: dynamic_seeds_added=$(grep -rl 'seed_origin: dynamic' .tad/evidence/research/ | wc -l | tr -d ' ')
STRATEGY SELECTION (priority order โ 6 strategies):
1. saturated (hard stop โ evidence-based)
2. contradiction (cross-source conflict resolution)
3. follow_thread (chase surprising findings)
4. perspective_shift (break tunnel vision โ NEW)
5. gap_enrichment (standalone only, NOT inside *research --deep)
6. so_what (budget-forced close, TERMINAL)
# 1. Hard stop โ checked FIRST (evidence-based: nothing new to find)
IF new_citations == 0 AND prev_zero_citation_rounds >= 1: [saturated: 0 new citations ร 2 consecutive rounds]
โ strategy = "saturated"
โ Finalize chain .md. Report: "๐ฌ Saturated after {current_depth} rounds. Chain saved to {path}"
โ EXIT step 3.5 โ go to Step 4.
# 2. Highest-value: cross-source conflict resolution
ELIF conflict detected:
โ strategy = "contradiction"
โ Build self-contained follow-up (embed claims as quotes โ NEVER use "ไฝ ๆๅฐ..." referential phrasing):
"ๅ
ณไบ'{topic}'๏ผไธไบๆบ่ฎคไธบ'{claim_A}'๏ผ่ๅฆไธไบๆบ่ฎคไธบ'{claim_B}'ใ
ๅบไบไฝ ็ๆๆๆบ๏ผๅชไธช่ฏดๆณๆๆดๅผบ็่ฏๆฎๆฏๆ๏ผ่ฏทๅผ็จๅ
ทไฝๆฎต่ฝๅนถ่งฃ้็็พ็ๅๅ ใ"
โ Execute: ~/.tad-notebooklm-venv/bin/notebooklm ask "{follow_up}" -n <notebook_id>
USE -n flag only. DO NOT use -c 00000000... fresh conversation (follow-ups need prior context).
If ask fails: fail-fast. Save chain as-is. Do NOT retry. Exit step 3.5.
โ Count new citations in follow-up answer; update saturation counter:
If new_citations == 0: prev_zero_citation_rounds += 1; else: prev_zero_citation_rounds = 0
โ Append "contradiction" to strategies_used
โ sleep 1; increment current_depth; append round to chain .md; loop back to step 3.5.
# 3. Chase surprising findings deeper
ELIF surprising finding AND current_depth < max_depth:
โ strategy = "follow_thread"
โ Build self-contained follow-up (embed the finding as quoted context):
"ๅ
ณไบ'{surprising_finding}'๏ผๆฅ่ชๅ
ณไบ'{topic}'็็ ็ฉถ๏ผ๏ผ่ฟๅ
ทไฝๆฏๆไนๅฎ็ฐ็๏ผ
ๆๅชไบๅทฒ็ฅ็ๅฑ้ๆๅคฑ่ดฅๆกไพ๏ผ่ฏทไปไฝ ็ๆบไธญๆพๅฐๅ
ทไฝไพๅญใ"
โ Execute follow-up ask (same -n flag rule and fail-fast rule as contradiction above)
โ Count new citations; update saturation counter:
If new_citations == 0: prev_zero_citation_rounds += 1; else: prev_zero_citation_rounds = 0
โ Append "follow_thread" to strategies_used
โ sleep 1; increment current_depth; append round to chain .md; loop back to step 3.5.
# 4. Perspective shift โ break tunnel vision when same strategy repeats
# Requires โฅ2 prior strategy entries (current_depth >= 3, strategies_used has โฅ2 elements)
ELIF current_depth >= 3 AND current_depth < max_depth
AND len(strategies_used) >= 2
AND strategies_used[-1] == strategies_used[-2] [last 2 rounds used same strategy]
AND strategies_used[-1] != "perspective_shift" [prevents consecutive perspective_shifts]
AND NOT conflict AND NOT gap:
โ strategy = "perspective_shift"
โ Derive expert perspectives from project context (3-tier fallback):
Tier 1 โ If OBJECTIVES.md exists:
Extract stakeholder roles implied by KR descriptions
e.g. KR about "user retention" โ perspective_role="PM", perspective_focus="retention, onboarding, churn signals"
e.g. KR about "cold-start latency <2s" โ perspective_role="SRE", perspective_focus="latency, reliability, observability"
Tier 2 โ Elif Capability Packs are loaded (.claude/skills/*/SKILL.md):
Use reviewer persona from the most relevant Capability Pack
Tier 3 โ Else use 3 generic perspectives:
- engineer โ perspective_focus="implementation feasibility, technical debt, performance"
- end-user โ perspective_focus="usability, onboarding friction, error recovery"
- skeptic โ perspective_focus="assumptions being made, missing counter-evidence, claims without data"
โ Select the perspective LEAST represented in strategies_used (favor unexplored angle)
โ Build self-contained follow-up (embed perspective identity in question):
"ไฝไธบไธไธช{perspective_role}๏ผๅ
ณๆณจ{perspective_focus}๏ผ๏ผๅ
ณไบ'{topic}'๏ผ
ๆๆๅ
ณๅฟ็้ฎ้ขๆฏไปไน๏ผ็ฐๆๅ็ฐไธญๅชไบๅฏน{perspective_role}ๆ้่ฆ๏ผๅชไบ่ขซๅฟฝ็ฅไบ๏ผ"
โ Execute: ~/.tad-notebooklm-venv/bin/notebooklm ask "{follow_up}" -n <notebook_id>
(same -n flag rule and fail-fast rule as contradiction above โ raw CLI, not *research-notebook ask)
โ Count new citations; update saturation counter:
If new_citations == 0: prev_zero_citation_rounds += 1; else: prev_zero_citation_rounds = 0
โ Append "perspective_shift" to strategies_used
โ sleep 1; increment current_depth; append round to chain .md; loop back to step 3.5.
# 5. Gap enrichment โ standalone only (NOT inside *research --deep Phase 4)
ELIF gap detected AND NOT inside_research_deep:
โ strategy = "gap_enrichment"
โ inside_research_deep detection (two conditions BOTH required):
(a) .research/research-state.yaml exists AND phase field == "ask"
(b) the current notebook_id appears in research-state.yaml's notebook_ids list
If either condition false โ inside_research_deep = false (standalone ask context)
โ Trigger Phase 4b CRAG Judge Loop (source add-research fast โ re-ask)
โ (gap_enrichment DISABLED inside *research --deep โ Phase 4b already handles gaps; would double-loop)
# 6. Budget-based forced close โ checked AFTER saturation
ELIF current_depth >= max_depth:
โ strategy = "so_what" [TERMINAL โ always exits after one so-what round, never loops back]
โ Read project_context (capped at ~600 chars total):
If OBJECTIVES.md exists: extract KR bullet descriptions only (max 200 chars/KR, max 3 KRs, skip โ
)
Elif PROJECT_CONTEXT.md exists: extract "## Current Goal" section only (max 500 chars)
Else: use the notebook topic string (always โค 100 chars)
โ Build self-contained so-what question:
"ๅบไบๅฐ็ฎๅไธบๆญขๅ
ณไบ'{topic}'็ๆๆๅ็ฐ๏ผๅฏนไบ{project_context}๏ผ
ๆ้่ฆ็ 3 ไธช่กๅจๅปบ่ฎฎๆฏไปไน๏ผๆฏไธชๅปบ่ฎฎ่ฏทๅฏนๅบๅ
ทไฝ็ๆบๅผ็จใ"
โ Execute so-what ask (same -n flag rule)
โ DO NOT loop back. so_what is ALWAYS terminal. Append final round. Finalize chain .md.
โ Report: "๐ฌ Research chain complete: {current_depth} rounds, strategies used: {list}. Chain saved to {path}"
โ EXIT step 3.5 โ go to Step 4.
ELSE:
โ strategy = "continue" (no auto-follow-up; chain may be partial; user can manually ask next)
โ EXIT step 3.5 โ go to Step 4.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Step 4: Return results
โ Output query result to user
โ Update REGISTRY.yaml: last_queried = today, status = active
(if was dormant โ auto-transition back to active; archived โ warn user first)
Note: If called from Alex *discuss context โ result feeds directly into discussion.
If called from research_decision_protocol step2_5 โ result supplements WebSearch findings.
Status transition rules:
โ If notebook status == "archived": AskUserQuestion "This notebook is archived. Query anyway?" before proceeding
โ If notebook status == "dormant": query succeeds โ set status = active (last_queried update implies reactivation)
โ If notebook status == "active": normal path, no status change needed
*research-notebook list
List all registered notebooks + status + source count. Runs lightweight sync.
Step 1: Read REGISTRY.yaml
Step 2: Lightweight sync (single cloud call โ do NOT call per notebook)
โ cloud_json=$(~/.tad-notebooklm-venv/bin/notebooklm list --json)
โ For each REGISTRY active/dormant notebook:
โ Check membership: echo "$cloud_json" | jq -e --arg id "$notebook_id" '.notebooks[] | select(.id == $id)' >/dev/null
โ If not found (jq exit 1) โ mark with โ ๏ธ "cloud-deleted"
(cloud_json schema: {"notebooks": [{"id": "<uuid>", "title": "...", ...}]})
Step 3: Apply lifecycle rules (from config-workflow.yaml research_notebook section)
โ active: last_queried within dormant_after_days โ show normally
โ dormant: last_queried between dormant_after_days and archive_suggest_after_days
โ show with ๐ค badge + "consider curate or archive"
โ archived: show with ๐ฆ badge (collapsed)
Step 4: Output table
| Notebook | Status | Sources | Last Queried | Notes |
|----------|--------|---------|--------------|-------|
| {topic} | โ
/๐ค/๐ฆ | {N} | {date} | {flags} |
*research-notebook sync
Full sync: compare REGISTRY with NotebookLM cloud state.
Step 1: Read REGISTRY.yaml (all active/dormant notebooks)
Step 2: Single cloud call to get all notebooks
โ ~/.tad-notebooklm-venv/bin/notebooklm list --json
โ Compare each REGISTRY notebook: source count, existence
Step 3: Classify discrepancies
โ Local only (cloud deleted) โ โ ๏ธ "REGISTRY outdated"
โ Source count mismatch (web UI edits) โ โ ๏ธ "sources changed"
โ Consistent โ โ
Step 4: Present sync report + AskUserQuestion
Options:
- "Update REGISTRY to match cloud"
- "Keep local state"
- "Confirm each discrepancy individually"
Step 5: Apply user choice โ update REGISTRY.yaml
*research-notebook curate [--notebook <id>]
Audit and maintain source quality for a notebook.
Step 1: Read REGISTRY.yaml sources for target notebook
Step 1b: Auto-clean error sources (NEW โ fully automatic)
โ ~/.tad-notebooklm-venv/bin/notebooklm source list --json -n <notebook_id>
โ Parse JSON: each source object has an `id` field (server-side UUID string).
Filter sources where `status` field contains "error" (explicit error state only).
Do NOT delete sources with status "preparing" or "processing" โ these may complete successfully.
โ Step A โ Collect error IDs (single Bash call):
error_ids=$(~/.tad-notebooklm-venv/bin/notebooklm source list --json -n <notebook_id> | \
jq -r '.[] | select(.status | test("error")) | .id')
โ Step B โ Parallel delete (single Bash call):
echo "$error_ids" | xargs -P5 -n1 sh -c '
~/.tad-notebooklm-venv/bin/notebooklm source delete "$1" -n <notebook_id> --yes 2>&1 | \
grep -q "error\|429" && echo "FAIL:$1" || echo "OK:$1"
sleep 0.2
' _
โ If any FAIL: lines in output: "โ ๏ธ {N} deletes failed โ consider reducing to -P3 or -P1"
โ Report: "๐งน Cleaned {N} error sources ({M} remaining)"
โ If N == 0: "โ
No error sources found"
โ โ ๏ธ DEFENSIVE: If `source list --json` output structure is unexpected (no `id` field,
different JSON shape), STOP and report: "source list JSON format changed โ manual curate needed"
Step 1c: Auto-deduplicate (NEW โ fully automatic)
โ From remaining sources, group by (lowercase(title), extract_domain(url))
โ extract_domain: parse URL to domain only (e.g., "arxiv.org", "developer.apple.com")
โ Sources without URL (type=text/file) โ skip dedup (unique by definition)
โ For each group with count > 1: keep FIRST source (by add date), collect rest as dedup_ids
โ Parallel delete (single Bash call):
echo "$dedup_ids" | xargs -P5 -n1 sh -c '
~/.tad-notebooklm-venv/bin/notebooklm source delete "$1" -n <notebook_id> --yes 2>&1 | \
grep -q "error\|429" && echo "FAIL:$1" || echo "OK:$1"
sleep 0.2
' _
โ If any FAIL: lines in output: "โ ๏ธ {N} deletes failed โ consider reducing to -P3 or -P1"
โ Report: "๐ Removed {N} duplicates ({M} unique sources remain)"
โ If N == 0: "โ
No duplicates found"
Step 2: Check each source (age-based staleness)
โ Added >90 days ago (source_stale_after_days) โ โ ๏ธ possibly stale
โ URL unreachable (if WebFetch available) โ โ broken
โ Same type >5 sources โ suggest pruning (quality > quantity)
Step 2b (content-staleness check โ URL-type sources only):
โ For each URL-type source (skip type=youtube/text/file; max 20 to avoid slowness):
โ ~/.tad-notebooklm-venv/bin/notebooklm source stale <source_id> -n <notebook_id>
โ โ ๏ธ INVERTED exit codes (shell `if` compatible):
exit 0 = stale (content changed at source URL)
exit 1 = fresh (no change)
โ Display combined age + content staleness:
| Source | Age-Stale | Content-Stale | Action |
| {title} | ๐ข/๐ด (>90 days) | ๐ข/๐ด (CLI check) | โ / "Refresh?" |
Step 2c (refresh stale sources โ URL-type only):
โ If content-stale URL sources found:
โ AskUserQuestion: "Found {N} content-stale sources. Refresh?"
Options:
- "Refresh all content-stale" โ ~/.tad-notebooklm-venv/bin/notebooklm source refresh <source_id> -n <id> for each
(Note: refresh only works for URL/Drive sources, not YouTube/text/file types)
- "Skip, review manually" โ continue
- "Skip all refreshes" โ continue
Step 3: Output curation report with quality tier
Tier classification rules (by URL pattern):
tier1_patterns: [".gov", ".edu", "arxiv.org", "pubmed", ".who.int", "fda.gov",
"developer.apple.com", "developers.google.com", "docs.anthropic.com",
"owasp.org", "w3.org", "ietf.org"]
tier2_patterns: ["medium.com", "dev.to", "stackoverflow.com", "docs.*", "blog.*",
".readthedocs.io", "github.com/*/wiki"]
tier3_patterns: ["reddit.com", "x.com", "twitter.com", "forum.*", "community.*",
"news.ycombinator.com"]
unknown: everything else โ โ
Tier values: ๐๏ธ T1 (official/academic) / ๐ฐ T2 (industry) / ๐ฌ T3 (community) / โ Unknown
| # | Source | Type | Tier | Added | Age-Stale | Content-Stale | Suggestion |
|---|--------|------|------|-------|-----------|---------------|------------|
Step 4: AskUserQuestion (for removal suggestions)
Options:
- "Apply all removal suggestions"
- "Decide each manually"
- "Skip, no changes"
Step 5: Execute confirmed removals
โ ~/.tad-notebooklm-venv/bin/notebooklm source delete <source_id> -n <notebook_id> --yes
โ Update REGISTRY.yaml source list + source_count
*research-notebook archive [--notebook <id>]
Archive a notebook: export history โ update registry โ mark archived.
Step 1: Confirm with user (AskUserQuestion)
โ "Archive notebook '{topic}'? It will remain in NotebookLM but marked archived in REGISTRY."
Options: "Yes, archive" / "Cancel"
Step 2: Ensure archive directory exists
โ mkdir -p .tad/research-notebooks/archived/
Step 3: Export query history (if any recorded in REGISTRY)
โ Write to .tad/research-notebooks/archived/{notebook_id}-history.md
โ If Write fails โ ABORT (do NOT proceed to Step 4); report error to user
Step 4: Update REGISTRY.yaml (only after Step 3 succeeds)
โ Set status: archived
โ Record archived_date
โ If active_notebook == this notebook_id โ clear active_notebook field (set to null)
Step 5: Confirm
โ "๐ฆ Notebook '{topic}' archived. REGISTRY updated."
*research-notebook use <notebook_id>
Set active notebook for this session (session-scoped override).
Step 1: Verify notebook_id exists in REGISTRY.yaml
โ If not found โ "โ ๏ธ Notebook not found. Run *research-notebook list to see available."
Step 2: Update REGISTRY.yaml active_notebook field
Step 3: Confirm
โ "โ
Active notebook set to '{topic}' ({notebook_id})"
*research-notebook research <topic> [--mode fast|deep]
High-level command: automated source discovery + import + summary in one step.
Step 0: Resolve target notebook
โ If --notebook <id> specified โ use that
โ Else read REGISTRY.yaml active_notebook
โ If no active notebook โ AskUserQuestion: "Which notebook?"
Options: list of active notebooks + "Create new notebook for '{topic}'"
Step 1: Mode selection
โ If --mode fast explicitly specified by user โ skip AskUserQuestion, proceed directly
โ If --mode deep explicitly specified by user:
โ AskUserQuestion confirmation:
"Deep mode ๅฐๆ็ดข 50+ ๆบๅนถๆฐธไน
ๅฏผๅ
ฅ (~3-4min)ใ็กฎ่ฎค๏ผ"
Options: "็กฎ่ฎค Deep" / "ๆน็จ Fast" / "Cancel"
โ If no --mode specified โ AskUserQuestion:
"ๅณๅฐ่ฎฉ NotebookLM ๆ็ดข '{topic}' ๅนถ่ชๅจๅฏผๅ
ฅๆบใ"
Options: "Fast (10 sources, ~1s)" / "Deep (50+ sources, ~3-4min)" / "Cancel"
Step 2: Execute
โ If fast mode:
โ ~/.tad-notebooklm-venv/bin/notebooklm source add-research "{topic}" --mode fast --import-all -n <id>
โ If deep mode:
โ ~/.tad-notebooklm-venv/bin/notebooklm source add-research "{topic}" --mode deep --no-wait -n <id>
โ ~/.tad-notebooklm-venv/bin/notebooklm research wait -n <id> --timeout 600 --import-all
โ (--timeout 600: native CLI flag, max 10min. --import-all: required to import sources after wait)
โ If exit != 0 (including timeout): "โ ๏ธ Deep research timed out or failed. Sources may be partially imported. Check: *research-notebook list" + EXIT
โ Capture output (source count + titles)
โ โ ๏ธ ERROR HANDLING:
- If exit code != 0 (non-timeout): "โ Research failed: {stderr}" + EXIT (do NOT proceed to Step 3)
- If source_count == 0: "โ ๏ธ No sources found for '{topic}'. Try broader keywords." + EXIT
Step 3: Summary
โ ~/.tad-notebooklm-venv/bin/notebooklm summary --topics -n <id>
โ Display: "โ
{N} sources added. Notebook summary: {summary}"
โ Display: "Suggested topics to explore: {topics}"
Step 4: Post-research source review (deep mode only)
โ If --mode deep AND source_count > 20:
โ ~/.tad-notebooklm-venv/bin/notebooklm source list -n <id>
โ AskUserQuestion: "Deep research ๆทปๅ ไบ {N} ไธชๆบใ่ฆ็ฐๅจๆธ
็ไธ็ธๅ
ณ็ๆบๅ๏ผ"
Options:
- "ๆฅ็ๅนถๆธ
็" โ display source titles, user picks which to delete
โ For each selected: ~/.tad-notebooklm-venv/bin/notebooklm source delete <source_id> -n <id> --yes
- "ๅ
จ้จไฟ็" โ continue
- "็จๅ็จ *research-notebook curate ๆธ
็" โ continue
โ If fast mode: skip (10 sources, low cleanup urgency)
Step 5: Update REGISTRY
โ Update source_count (after any deletions), last_queried, status=active
Source Strategy Note (GitHub-First)
When building a notebook for a new topic, source quality matters more than quantity.
Preferred order:
- GitHub awesome-lists (
source add "https://github.com/org/awesome-topic")
- GitHub sub-pages from awesome-lists (explore with
gh api tree, add key .md files)
- Real company repos (how production systems actually do it)
- Tool repos (official source, not blog posts about the tool)
source add-research --mode deep (LAST RESORT for gaps only)
10 curated GitHub repos > 350 deep research articles.
*research-notebook report <description>
Generate a structured report + download as local markdown.
Step 0: Resolve target notebook (same as ask command)
Step 1: Validate download capability (first-time only per session)
โ If preflight version check passed (0.3.4+), `download report` is available โ proceed directly
โ Preflight already ensures CLI capability; no additional check needed
โ Cache validation result: skip this step entirely on subsequent *report calls in same session
(Note: `download report` ships in all notebooklm-py 0.3.4+ builds)
Step 1.5 (NEW): Customize report (optional)
โ AskUserQuestion: "่ฆ็ปๆฅๅ่ฟฝๅ ็นๆฎ่ฆๆฑๅ๏ผ"
Options:
- "ไฝฟ็จ้ป่ฎคๆจกๆฟ" โ no --append, no --source filter
- "่ฟฝๅ ่ชๅฎไนๆไปค" โ user types instruction โ append_flag="--append '{instruction}'"
- "้ๅฎ็นๅฎๆบ" โ show source list from notebooklm source list -n <id> โ user picks โ source_flags="--source <id>" (repeatable)
- "่ฟฝๅ ๆไปค + ้ๅฎๆบ" โ combine both
Step 2: Generate
โ ~/.tad-notebooklm-venv/bin/notebooklm generate report "{description}" {append_flag} {source_flags} --retry 3 -n <id> --wait
โ Display: "Generating report... (typically 30-90s)"
โ If exit code != 0: "โ Report generation failed: {stderr}" + EXIT
Step 3: Download with exponential backoff retry
โ output_path: .tad/evidence/research/{notebook_topic}/{YYYY-MM-DD}-{slug}.md
where {slug} = first 50 chars of {description}, lowercase, non-alphanumeric โ "-",
collapse multiple "-", trim leading/trailing "-"
(collision: if file exists, append "-2", "-3", etc.)
(mkdir -p the directory if missing)
โ ~/.tad-notebooklm-venv/bin/notebooklm download report --latest -n <id> "{output_path}"
โ If download returns empty or error:
โ Wait 20s, retry (attempt 2)
โ Wait 30s, retry (attempt 3)
โ If still fails: "โ ๏ธ Report generated but download failed. View in NotebookLM web UI."
Step 4: Display
โ Read first 20 lines of downloaded file
โ Count lines + words
โ Output: "โ
Report saved: {path} ({line_count} lines, {word_count} words)"
*research-notebook guide [--source <id>]
Per-source AI summary: understand what each source contributes.
Step 0: Resolve target notebook (same as ask command)
Step 1: Source selection
โ If --source <id> specified โ use that source directly
โ If no --source: ~/.tad-notebooklm-venv/bin/notebooklm source list -n <id>
โ AskUserQuestion: "Which source(s) to summarize?" (display numbered list)
Step 2: For each selected source:
โ ~/.tad-notebooklm-venv/bin/notebooklm source guide <source_id> -n <id> --json
โ Parse JSON: {summary, keywords[]}
Step 3: Display formatted summary + keywords
โ "๐ Source: {title}"
โ Summary paragraph
โ "Keywords: {kw1}, {kw2}, ..."
*research-notebook configure [--persona <text>] [--mode <mode>]
Set notebook research persona and query mode.
Step 0: Resolve target notebook (same as ask command)
Step 1: If no flags โ show current config + AskUserQuestion for what to change
Options:
- "Set custom persona (up to 10,000 chars)"
- "Use preset mode (learning-guide / concise / detailed)"
- "Reset to default"
- "Cancel"
Step 1b: Resolve action from flags OR menu
โ After flags/menu, resolve to a tuple: (persona_action, mode_action)
persona_action: one of {none, set:"<text>", reset}
mode_action: one of {none, set:<mode>, reset}
โ If user chose "Reset to default" from menu: persona_action=reset, mode_action=reset
โ If user chose "Set custom persona" from menu: prompt for text โ persona_action=set:"<text>", mode_action=none
โ If user chose "Use preset mode" from menu: prompt for mode โ persona_action=none, mode_action=set:<mode>
โ If --persona flag passed: persona_action=set:"<text>"
โ If --mode flag passed: mode_action=set:<mode>
Step 2: Execute based on resolved action tuple
Case A โ Reset (persona_action=reset AND/OR mode_action=reset):
โ ~/.tad-notebooklm-venv/bin/notebooklm configure --mode default --persona "" -n <id>
(Use both flags together โ required to reset both axes per spike T2 evidence)
Case B โ Set both (persona_action=set AND mode_action=set):
โ ~/.tad-notebooklm-venv/bin/notebooklm configure --persona "{text}" --mode {mode} -n <id>
Case C โ Persona only (persona_action=set, mode_action=none):
โ ~/.tad-notebooklm-venv/bin/notebooklm configure --persona "{text}" -n <id>
Case D โ Mode only (persona_action=none, mode_action=set):
โ ~/.tad-notebooklm-venv/bin/notebooklm configure --mode {mode} -n <id>
Step 3: Confirm
โ "โ
Notebook configured."
โ If persona set: "Persona: {first 50 chars}..."
โ If mode set: "Mode: {mode}"
Note: persona up to 10,000 chars. Useful for domain-specific framing (e.g., "You are a security researcher
reviewing offensive AI techniques from a defensive blue-team perspective...").
*research-notebook topics
Quick notebook overview + suggested query topics. Display-only, returns to standby.
Step 0: Resolve target notebook (same as ask command)
Step 1: Fetch summary + topics (stale conversation fallback)
Layer 1 (normal):
โ ~/.tad-notebooklm-venv/bin/notebooklm summary --topics -n <id>
โ If exit 0 and output non-empty โ proceed to Step 2
โ If exit != 0:
Layer 2 (force fresh conversation โ retry on any non-zero exit):
โ ~/.tad-notebooklm-venv/bin/notebooklm summary --topics -n <id> -c 00000000-0000-0000-0000-000000000000
โ If still fails: report error to user
Step 2: Display
โ Output formatted summary paragraph
โ Output numbered topic list: "1. {topic} 2. {topic} ..."
โ Return to standby โ no AskUserQuestion (user invokes *research-notebook ask themselves)
Step 3: Update REGISTRY
โ Update last_queried = today, status = active
(topics consumes AI quota like ask โ lifecycle should track it equally)
*research-notebook ingest <file_path>
Add a local research finding (.md or .txt) as a notebook source.
Knowledge loop status: VERIFIED GO โ source add with local file paths is empirically
confirmed to participate in ask context within ~30s (verified 2026-05-04, TASK-20260504-002).
Step 0: Resolve target notebook (same as ask command)
Step 1: Validate file
โ Check file exists: test -f "{file_path}"
โ Check extension is .md or .txt
โ Check file size < 500KB: du -k "{file_path}" | awk '{print $1}'
โ If any check fails: report error + EXIT
Step 2: AskUserQuestion confirmation
โ "ๅฐ {filename} ็ๅ
ๅฎนไฝไธบๆฐ source ๅ ๅ
ฅ notebook '{topic}'ใ็กฎ่ฎค๏ผ"
Options: "็กฎ่ฎค" / "ๅๆถ"
Step 3: Execute
โ ~/.tad-notebooklm-venv/bin/notebooklm source add "{file_path}" -n <id>
โ If exit code != 0: "โ source add failed: {stderr}" + EXIT
Step 4: Verify ingestion (default: ON; skip with --no-verify)
โ Wait 30s for indexing
โ ~/.tad-notebooklm-venv/bin/notebooklm ask "summarize the content from {filename}" -n <id> -c 00000000-0000-0000-0000-000000000000
โ If answer references file content: "โ
Ingestion verified โ content is queryable."
โ If answer doesn't reference content:
โ Wait 60s, retry once more (indexing can be variable, up to ~90s)
โ If still no reference: "โ ๏ธ Content added but not yet appearing in answers. It is indexed โ try *ask again in ~60s."
(Content IS added regardless of verification result; verification is a confidence check)
โ If --no-verify flag: skip Step 4 entirely (use when batch-ingesting multiple files)
Step 5: Update REGISTRY
โ Increment source_count, add source entry (filename, type=file, added date)
โ Update last_queried = today, status = active
Step 6: Confirm
โ "โ
{filename} added as source to notebook '{topic}'. Total sources: {N}."
โ Reminder: "Content will be queryable via *ask within ~30s."
*research-notebook fulltext <source_id>
Preview or save the full text content of a source.
Step 0: Resolve target notebook (same as ask command)
Step 1: Get source ID
โ If <source_id> provided โ use it
โ If not โ ~/.tad-notebooklm-venv/bin/notebooklm source list -n <id>
โ Display numbered list
โ AskUserQuestion: "Which source to view fulltext?"
Step 2: Extract fulltext
โ ~/.tad-notebooklm-venv/bin/notebooklm source fulltext <source_id> -n <id> -o /tmp/tad-fulltext-{source_id}.txt
โ Read first 100 lines for preview
Step 3: Display + option to save
โ Display preview (first 100 lines)
โ AskUserQuestion: "Save fulltext to project?"
Options:
- "Save to .tad/evidence/research/" โ mkdir -p .tad/evidence/research/{topic}/ โ cp /tmp/tad-fulltext-{source_id}.txt .tad/evidence/research/{topic}/fulltext-{source_id}.txt
- "Just preview, don't save" โ rm -f /tmp/tad-fulltext-{source_id}.txt
Use case: Alex Research Director evaluates source quality before recommending deep dives
*research-notebook language [set|get|list]
Manage NotebookLM output language setting.
*research-notebook language set <code>:
Step 0: Resolve target notebook (same as ask command)
Step 1: โ ~/.tad-notebooklm-venv/bin/notebooklm language set <code> -n <id>
Step 2: โ "โ
NotebookLM output language set to {language_name}. Affects all future reports/quizzes."
*research-notebook language get:
Step 0: Resolve target notebook
Step 1: โ ~/.tad-notebooklm-venv/bin/notebooklm language get --local -n <id>
Step 2: โ Display current language setting
*research-notebook language list:
Step 1: โ ~/.tad-notebooklm-venv/bin/notebooklm language list
Step 2: โ Display supported languages table
*research-notebook consolidate
Merge overlapping notebooks: detect โ confirm โ execute. Execution layer for A4.
Step 1: List all active notebooks with topics
โ ~/.tad-notebooklm-venv/bin/notebooklm list โ display table
Step 2: Analyze overlap (LLM semantic judgment)
โ Group notebooks by semantic similarity of topic field
โ For each group with >1 notebook:
โ Display: "่ฟ {N} ไธช notebook ่ฏ้ข้ๅ : {list with source counts}"
Step 3: AskUserQuestion for each group:
"ๅปบ่ฎฎๆดๅ่ฟ็ป notebookใ้ๆฉๆไฝ๏ผ"
Options:
- "ๆดๅไธบไธไธช" โ Step 4
- "ไฟ็็ฌ็ซ" โ skip this group
- "ๅ ้คๅ
ถไธญ N ไธช" โ user picks which to delete โ *research-notebook archive for each selected
Step 4: Execute merge
โ AskUserQuestion: "ๅๅนถๅ็ notebook ๅ็งฐๆฏไปไน๏ผ" (suggest: combined topic summary)
โ ~/.tad-notebooklm-venv/bin/notebooklm create "{merged_topic_name}"
โ Capture new notebook_id
โ For each old notebook in group:
โ ~/.tad-notebooklm-venv/bin/notebooklm source list -n <old_id> โ get source URLs/IDs
โ For each source URL: ~/.tad-notebooklm-venv/bin/notebooklm source add <url> -n <new_id>
โ (Skip file-type sources โ these cannot be re-added by URL)
โ Register new notebook to REGISTRY.yaml
โ *research-notebook archive for each old notebook (with user confirmation)
โ "โ
Merged {N} notebooks into '{merged_topic_name}'. Old notebooks archived."
Note: A4 (notebook_consolidation_suggestion) in Alex SKILL calls this command with
pre-selected groups. Pass groups via context โ no extra parameters needed.
*research-notebook quiz [--difficulty easy|medium|hard] [--quantity fewer|standard|more]
Generate a quiz from notebook content + download as markdown.
Step 0: Resolve target notebook (same as ask command)
Step 1: Generate quiz
โ defaults: --difficulty medium --quantity standard
โ ~/.tad-notebooklm-venv/bin/notebooklm generate quiz --difficulty {d} --quantity {q} -n <id> --retry 3 --wait
โ Display: "Generating quiz... (typically 15-30s)"
โ If exit code != 0: "โ Quiz generation failed: {stderr}" + EXIT
Step 2: Download as markdown
โ output_path: .tad/evidence/research/{notebook_topic}/quiz-{YYYY-MM-DD}.md
(mkdir -p the directory if missing; collision: append -2, -3 etc.)
โ ~/.tad-notebooklm-venv/bin/notebooklm download quiz --format markdown "{output_path}" -n <id>
โ If download fails:
โ Wait 20s, retry once
โ If still fails: "โ ๏ธ Quiz generated but download failed. Try *research-notebook list to verify."
Step 3: Display + confirm
โ Read downloaded file content
โ Display full quiz to user
โ "โ
Quiz saved: {path}"
*research-notebook flashcards [--difficulty easy|medium|hard] [--quantity fewer|standard|more]
Generate flashcards from notebook content + download as markdown.
Step 0: Resolve target notebook (same as ask command)
Step 1: Generate flashcards
โ defaults: --difficulty medium --quantity standard
โ ~/.tad-notebooklm-venv/bin/notebooklm generate flashcards --difficulty {d} --quantity {q} -n <id> --retry 3 --wait
โ Display: "Generating flashcards... (typically 15-30s)"
โ If exit code != 0: "โ Flashcard generation failed: {stderr}" + EXIT
Step 2: Download as markdown
โ output_path: .tad/evidence/research/{notebook_topic}/flashcards-{YYYY-MM-DD}.md
(mkdir -p the directory if missing; collision: append -2, -3 etc.)
โ ~/.tad-notebooklm-venv/bin/notebooklm download flashcards --format markdown "{output_path}" -n <id>
โ If download fails:
โ Wait 20s, retry once
โ If still fails: "โ ๏ธ Flashcards generated but download failed."
Step 3: Display + confirm
โ Read downloaded file content
โ Display flashcards to user
โ "โ
Flashcards saved: {path}"
Notebook Lifecycle Rules
lifecycle_rules:
states:
active:
condition: "last_queried within dormant_after_days"
display: "โ
Active"
action: "Normal use"
dormant:
condition: "last_queried between dormant_after_days and archive_suggest_after_days"
display: "๐ค Dormant"
action: "Suggest curate or archive at list time"
archived:
condition: "User executed *archive"
display: "๐ฆ Archived"
action: "REGISTRY entry retained, notebook remains in NotebookLM"
thresholds: "Configured in .tad/config-workflow.yaml research_notebook section"
source_limit: "Configured in .tad/config-workflow.yaml research_notebook.max_sources_per_notebook"
status_field_semantics: |
The `status` field in REGISTRY.yaml is a HYBRID:
- "archived" is USER-SET (only changes via *archive, never auto)
- "active" and "dormant" are DERIVED from last_queried at display time (*list)
AND persisted by *ask and *topics (both set status=active on success)
RESOLUTION: *list always recomputes active/dormant from last_queried when status != "archived"
This means a persisted "dormant" that gets queried โ *ask/*topics sets it to "active" immediately
PASSIVE RECOMPUTE (Phase 4 wiring): the SessionStart hook .tad/hooks/notebook-dormant-sync.sh
also recomputes active/dormant on every session start (no command run required), via the
library function recompute_notebook_dormancy() in .tad/hooks/lib/notebook-lifecycle.sh.
It reads dormant_after_days from config-workflow.yaml, edits ONLY changed non-archived entries
via yq (atomic temp+mv, per-entry targeting), and is strictly NON-BLOCKING (derived state only โ
never blocks session start, no-ops if yq is absent). This prevents persisted status from going
stale between *list runs. Archived entries are never touched.
state_transitions:
active_to_dormant: "Computed at *list time AND passively at SessionStart (notebook-dormant-sync.sh) when last_queried > dormant_after_days"
dormant_to_active: "*ask or *topics success โ REGISTRY.yaml status = active"
active_to_archived: "*archive command only"
dormant_to_archived: "*archive command only"
archived_to_active: "NOT automatic โ user must *ask with explicit confirmation prompt"
topics_updates_last_queried: "YES โ *topics consumes AI quota like *ask and updates last_queried"
Integration Notes
- Auth expiry: NotebookLM sessions expire (Google cookies). When CLI returns auth error,
prompt user: "Run bash .tad/cross-model/setup-notebooklm.sh to refresh auth."
- YouTube sources: Only videos with captions work via CLI. Use conference talks
(CCC, RSAC, Black Hat) or official channels (Anthropic, Google) for reliable ingestion.
- Query latency: 23-43s per query. Normal for research tasks (not for real-time workflows).
- REGISTRY is local index, cloud is canonical: Use
*sync when discrepancies appear. Local metadata (notes, titles, added dates) is preserved during sync โ only source presence/count is compared to cloud.
- Cross-topic isolation: Different topics โ different notebooks. Cross-source redundancy is OK.
- URL curate skip: Sources with
url starting with (web-UI added are exempt from reachability checks โ these were added via NotebookLM web UI and URL was not captured at add-time.
- Stale conversation: Layer 2 retry (
-c 00000000...) fires ONLY on stale-specific stderr signals ("timeout", "stale", "conversation not found", "expired") โ NOT on all exit != 0.
- Minimum version: notebooklm-py 0.3.4+. Earlier versions (0.1.1) have deprecated RPC endpoints โ all AI-dependent commands fail.
- Local file ingestion:
source add /path/to/file.md is VERIFIED GO โ local .md and .txt files are accepted and queryable via ask within ~30s.
- source refresh: Only works for URL/Drive source types. YouTube, text, and file sources cannot be refreshed.
Quick Reference
| Command | Purpose |
|---|
*research-notebook create <topic> | New notebook + guide sources + register |
*research-notebook add <url> | Add source to active notebook (direct) |
*research-notebook add-smart <url> | Auto-detect URL type + preprocess + import + quality verify |
*research-notebook ask <question> | Query notebook (cross-source reasoning) |
*research-notebook list | List all notebooks + lightweight sync |
*research-notebook sync | Full cloud sync |
*research-notebook curate | Audit + prune + content-staleness check |
*research-notebook archive | Archive notebook |
*research-notebook use <id> | Set active notebook (session) |
*research-notebook research <topic> | Auto source discovery + import + summary |
*research-notebook report <desc> | Generate + download report as local .md |
*research-notebook guide | Per-source AI summary + keywords |
*research-notebook configure | Set notebook persona / query mode |
*research-notebook topics | Quick overview + suggested query topics |
*research-notebook ingest <file> | Add local .md/.txt as source (knowledge loop GO) |
*research-notebook fulltext <source_id> | Preview or save full source text content |
*research-notebook language [set|get|list] | Manage NotebookLM output language |
*research-notebook consolidate | Merge overlapping notebooks (A4 execution layer) |
*research-notebook quiz | Generate quiz from notebook โ download as .md |
*research-notebook flashcards | Generate flashcards from notebook โ download as .md |