Analyze user interview transcripts (Granola, pasted text, or a file) through a 3-analyst-agent plus Opus-synthesis pipeline. Say 'process the interview', 'onboarding interview', 'research call'.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Analyze user interview transcripts (Granola, pasted text, or a file) through a 3-analyst-agent plus Opus-synthesis pipeline. Say 'process the interview', 'onboarding interview', 'research call'.
Analyze user interview transcripts. Uses a multi-dimensional analysis pipeline with 3 analyst agents + Opus synthesis to extract rich, structured insights from user research sessions.
Arguments: $ARGUMENTS (Optional: search term, file path, or "synthesis" for cross-interview patterns)
Token-heavy: Journey + Sentiment agents receive full transcript (~6K words each)
What to do
Canonical ingest projection (mandatory)
Write each approved interview and its extracted artifacts to one durable
egregore-knowledge-projection/v1 manifest under
memory/ingest/knowledge/interviews/, then validate and apply it:
The manifest is the authoritative graph write contract. Raw graph batches later
in this document are schema reference for legacy data and must not be executed
as a second write path. Known researchers and participants link existing
members; others require explicit source-scoped external identities.
Step 0: Source selection
Determine transcript source from $ARGUMENTS:
If $ARGUMENTS is a file path (contains / or ends in .txt/.md/.json):
Read the file directly. Parse for speaker turns.
If $ARGUMENTS is a search term (non-empty, not a path):
Search Granola via MCP: use the get_meetings MCP tool to search by title/content.
If one match, use it. If multiple, present via AskUserQuestion. If none, say so.
If $ARGUMENTS is empty, present source picker:
AskUserQuestion:
question: "Where is the interview transcript?"
header: "Source"
options:
- label: "Granola recording"
description: "Select from recent Granola meetings"
- label: "Paste transcript"
description: "Paste the interview text directly"
- label: "File path"
description: "Provide a path to a .txt, .md, or .json transcript file"
For Granola source:
Check Granola MCP availability: use ToolSearch with query "granola" to check if MCP tools are loaded.
If not available: "Granola not connected. Run /connect granola to set it up."
If available: use the list_meetings MCP tool to get recent meetings, then present via AskUserQuestion for selection. Use get_meeting_transcript to fetch the selected interview's transcript.
For paste source:
Tell the user: "Paste the interview transcript below. I'll look for speaker turns (Name: or [timestamp] patterns)."
Wait for user to paste. Parse the pasted text for structure.
For file path source:
Ask the user for the file path, then read it.
Step 0.5: Gather participant info
Ask for participant details if not inferrable from transcript:
AskUserQuestion:
question: "Who was interviewed? (first name or handle)"
header: "Participant"
options:
- label: "Enter name"
description: "The person being interviewed (not the researcher)"
Also determine:
Researcher: default to user's name from .egregore-state.json → name
Interview type: infer from context, or ask:
AskUserQuestion:
question: "What type of interview is this?"
header: "Type"
options:
- label: "Onboarding"
description: "First-time user setup and initial experience"
- label: "Feedback"
description: "Existing user sharing thoughts on the product"
- label: "Research"
description: "Exploratory conversation about needs/workflows"
- label: "Exit"
description: "User leaving or churning — understanding why"
Run 4 queries in a single bash bin/graph-batch.sh call:
bash bin/graph-batch.sh '[
{"statement": "MATCH (a:Artifact) WHERE a.origin STARTS WITH '"'"'interview:'"'"' AND a.created >= datetime() - duration('"'"'P90D'"'"') RETURN a.id, a.title, a.type, a.topics, a.interviewParticipant, a.interviewDate, a.confidence ORDER BY a.created DESC LIMIT 15"},
{"statement": "MATCH (a:Artifact) WHERE a.origin STARTS WITH '"'"'interview:'"'"' AND a.type = '"'"'friction'"'"' WITH a.title AS friction, count(*) AS mentions, collect(DISTINCT a.interviewParticipant)[..5] AS participants WHERE mentions >= 2 RETURN friction, mentions, participants ORDER BY mentions DESC LIMIT 10"},
{"statement": "MATCH (a:Artifact) WHERE a.origin STARTS WITH '"'"'interview:'"'"' AND a.topics IS NOT NULL UNWIND a.topics AS topic WITH topic, count(DISTINCT a.interviewParticipant) AS participantCount, collect(DISTINCT a.interviewParticipant)[..5] AS participants WHERE participantCount >= 2 RETURN topic, participantCount, participants ORDER BY participantCount DESC LIMIT 10"},
{"statement": "MATCH (q:Quest {status: '"'"'active'"'"'}) WHERE q.topics IS NOT NULL RETURN q.id, q.title, q.topics"}
]'
results[1] → Q2: Recurring friction points across participants
results[2] → Q3: Feature/topic patterns across participants
results[3] → Q4: Active quests (for linking)
If the batch call fails, continue without cross-interview context. It's enrichment, not required. Empty results are expected for the first few interviews — handle gracefully.
Read the transcript text (panel text if Granola, pasted text, or file contents). Produce a scaffold — a JSON array of extracted items:
[{"id":"s1","category":"friction","title":"Confused by setup step 3","brief":"Participant couldn't find where to enter API key","gap":false},{"id":"s2","category":"aha","title":"Discovered shared memory","brief":"Moment of clarity when they saw how memory persists","gap":false},{"id":"s3","category":"feature_gap","title":"Wanted team dashboard","brief":"Expected to see team activity overview","gap":false},{"id":"gap1","category":"unknown","title":"Mentioned something about permissions","brief":"Referenced but not elaborated","gap":true}]
Spawn 3 Sonnet sub-agents in parallel using the Task tool with model: "sonnet" and subagent_type: "general-purpose". Do NOT use run_in_background: true — run them as standard parallel Task calls so results are returned directly.
Agent 1: Journey Analyst
Input: transcript + scaffold from Pass 0 + Q1 results (recent interview artifacts) + participant's previous interview data (if any from Q1)
Task tool prompt (substitute actual data for placeholders):
You are the Journey Analyst for a user interview analysis pipeline. Your job is to map the participant's JOURNEY through the product — where they got stuck, where they had breakthroughs, what tasks they tried to complete, and where they dropped off or got lost.
## Participant
{INSERT PARTICIPANT NAME + INTERVIEW TYPE}
## Scaffold (from initial pass)
{INSERT SCAFFOLD JSON}
## Previous Interview Artifacts (from this and other participants)
{INSERT Q1 RESULTS — or "No previous interview data. This is an early interview." if empty}
## Transcript
{INSERT TRANSCRIPT}
## Instructions
Analyze the transcript through the lens of JOURNEY: what the participant tried to do, where they succeeded or failed, what path they took through the product.
Return a JSON object with this structure:
{
"journey_stages": [
{"stage": "...", "description": "free-form: what the participant was trying to do and what happened",
"task": "...", "outcome": "completed|abandoned|struggled|skipped",
"friction_points": [{"description": "...", "severity": "blocker|frustration|minor", "quote": "..."}],
"aha_moments": [{"description": "...", "quote": "..."}],
"time_spent_estimate": "brief|moderate|extended",
"assistance_needed": true}
],
"critical_path": [
{"step": "...", "description": "free-form: the ideal path vs what actually happened",
"deviation": "on_track|minor_detour|major_detour|lost"}
],
"drop_off_risks": [
{"point": "...", "description": "free-form: where and why the user might give up",
"severity": "high|medium|low", "quote": "..."}
],
"task_completion": {
"attempted": 0,
"completed": 0,
"abandoned": 0,
"success_rate_description": "free-form: overall assessment of task completion"
},
"enrichments": [
{"scaffold_id": "s1|new", "category": "friction|aha|feature_gap|mental_model|feature_discovery|suggestion",
"title": "...", "brief": "...", "evidence_quote": "...",
"speaker": "participant|researcher",
"severity": "blocker|frustration|minor|null",
"stage": "which journey stage this belongs to"}
],
"_raw_notes": "Multi-paragraph prose: your full read on the participant's journey. Include the narrative arc — where they started, where they ended up, what the experience felt like from their perspective. Note patterns that connect to previous interview data if available."
}
## Rules
1. Match scaffold items to transcript segments. For gap items (gap: true), try especially hard to find evidence.
2. Extract the richest quote — prioritize signal over length. Max 120 chars per quote.
3. Find items the scaffold missed — things discussed substantively but not in the scaffold. Use scaffold_id: "new".
4. Do NOT extract small talk, logistics, or off-topic tangents.
5. If a scaffold item has no transcript evidence, omit it from enrichments (don't fabricate).
6. Tag fields are optional suggestions. If no predefined tag fits, leave null and let description carry signal.
7. The _raw_notes section is critical — this is where your nuanced journey narrative goes.
Return ONLY valid JSON. No markdown fences, no explanation.
Agent 2: Sentiment Analyst
Input: transcript + participant context. NO scaffold, NO graph (fresh emotional read).
Task tool prompt:
You are the Sentiment Analyst for a user interview analysis pipeline. Your job is to read the EMOTIONAL texture of the interview — what the participant felt, where they were confused or delighted or frustrated. You receive NO prior analysis intentionally — we want a fresh emotional read without anchoring.
## Participant
{INSERT PARTICIPANT NAME + INTERVIEW TYPE}
## Transcript
{INSERT TRANSCRIPT}
## Instructions
Analyze the transcript through the lens of SENTIMENT: what the participant felt, how their emotions evolved, where they experienced confusion, delight, or frustration.
Return a JSON object with this structure:
{
"emotional_arc": {
"opening": "free-form: emotional state at the start of the interview",
"closing": "free-form: emotional state at the end",
"trajectory": "free-form: how emotions evolved throughout",
"peaks": [
{"moment": "...", "emotion": "...", "intensity": "high|medium|low", "quote": "..."}
]
},
"confusion_signals": [
{"description": "free-form: what confused the participant and how they expressed it",
"quote": "...", "resolution": "resolved|unresolved|workaround"}
],
"delight_signals": [
{"description": "free-form: what delighted or positively surprised the participant",
"quote": "..."}
],
"frustration_signals": [
{"description": "free-form: what frustrated the participant and how they expressed it",
"quote": "...", "severity": "high|medium|low"}
],
"engagement_assessment": {
"description": "free-form: how engaged the participant was overall",
"level": "high|medium|low|mixed",
"energy_moments": [
{"moment": "...", "energy": "high|low", "quote": "..."}
]
},
"trust_signals": [
{"description": "free-form: moments indicating trust or distrust in the product/team",
"direction": "trust|distrust", "quote": "..."}
],
"_raw_notes": "Multi-paragraph prose: your full emotional read of the interview. What was the participant's overall experience? Where did they feel supported vs abandoned? What was unsaid? Where did energy spike or drop? What did their tone reveal that their words didn't?"
}
## Rules
1. Read emotions from word choice, pacing, emphasis, hesitation, laughter, sighs — not just explicit statements.
2. Distinguish between expressed emotions and inferred emotions. Be explicit about which is which.
3. Look for emotional transitions — moments where the participant's state shifted.
4. The _raw_notes section is where your real analysis lives. Be honest about uncertainty.
5. Pay attention to what the participant does NOT say — avoidance can signal discomfort.
6. Note the researcher's emotional impact — did their questions help or hinder openness?
Return ONLY valid JSON. No markdown fences, no explanation.
You are the Product Insights Analyst for a user interview analysis pipeline. Your job is to extract PRODUCT insights — what features the participant discovered or wanted, what mental models they brought, what unmet needs they expressed, and how their experience maps to active product development.
## Participant
{INSERT PARTICIPANT NAME + INTERVIEW TYPE}
## Scaffold (from initial pass)
{INSERT SCAFFOLD JSON}
## Feature/Topic Patterns Across Participants
{INSERT Q3 RESULTS — or "No cross-participant patterns yet. This is an early interview." if empty}
## Active Quests
{INSERT Q4 RESULTS — or "No active quests." if empty}
## Transcript
{INSERT TRANSCRIPT}
## Instructions
Analyze the transcript through the lens of PRODUCT: what the participant's experience reveals about features, mental models, unmet needs, and product direction.
Return a JSON object with this structure:
{
"feature_discovery": [
{"feature": "...", "description": "free-form: how the participant discovered/used this feature",
"reaction": "positive|negative|neutral|confused",
"quote": "...", "discoverability": "intuitive|guided|accidental|missed"}
],
"mental_model_mismatches": [
{"expected": "what the participant thought would happen or exist",
"actual": "what actually happens or exists",
"description": "free-form: the mismatch and its impact on the experience",
"quote": "...", "severity": "high|medium|low"}
],
"unmet_needs": [
{"need": "...", "description": "free-form: what the participant needs that the product doesn't provide",
"evidence_type": "explicit_request|implicit_behavior|workaround|comparison",
"quote": "...", "priority_signal": "high|medium|low"}
],
"suggestions": [
{"suggestion": "...", "description": "free-form: what the participant suggested and the underlying need",
"quote": "...", "feasibility_note": "free-form: quick assessment of implementation complexity"}
],
"enrichments": [
{"scaffold_id": "s1|new", "category": "friction|aha|feature_gap|mental_model|feature_discovery|suggestion",
"title": "...", "brief": "...", "evidence_quote": "...",
"speaker": "participant|researcher",
"quest_link": "quest-id or null",
"priority_tag": "p0_blocker|p1_important|p2_nice_to_have|null"}
],
"competitive_signals": [
{"competitor": "...", "description": "free-form: what the participant compared to or referenced",
"quote": "...", "implication": "free-form: what this means for our product"}
],
"_raw_notes": "Multi-paragraph prose: your full product read of the interview. What does this participant's experience tell us about where the product is strong, where it's weak, and where it should go next? Connect to active quests if relevant. Note cross-participant patterns if provided."
}
## Rules
1. Match scaffold items to transcript segments. For gap items (gap: true), try especially hard to find evidence.
2. Extract the richest quote — prioritize signal over length. Max 120 chars per quote.
3. Find items the scaffold missed — things discussed substantively but not in the scaffold. Use scaffold_id: "new".
4. Do NOT extract trivia or off-topic tangents.
5. If a scaffold item has no transcript evidence, omit it from enrichments (don't fabricate).
6. Tag fields are optional suggestions. If no predefined tag fits, leave null and let description carry signal.
7. For quest_link: match artifact topics to active quest topics. Only link if genuinely relevant.
8. priority_tag: p0 = blocks adoption, p1 = significantly impacts experience, p2 = would be nice.
9. The _raw_notes section is critical — this is where your nuanced product analysis goes.
Return ONLY valid JSON. No markdown fences, no explanation.
Parse all 3 agent results: Extract the JSON from each response. If an agent returns invalid JSON or fails, log the failure and continue with whatever agents succeeded. The synthesis step works with partial input.
Step 4: Synthesis (Opus, inline)
Read the 3 agent outputs + scaffold. Produce two things:
1. Enriched insight list
For each scaffold item (and new items from enrichments), merge dimensional data from all three agents:
From Journey: friction points, aha moments, task completion, drop-off risks, stage context
From Sentiment: emotional peaks, confusion/delight/frustration signals, engagement level
From Product: feature discovery, mental model mismatches, unmet needs, quest links, priority
Each merged insight gets:
category: from scaffold (or enrichment for new items)
severity: from Journey (blocker/frustration/minor) or Product (p0/p1/p2)
emotional_context: from Sentiment — what the participant felt at this moment
evidence_quote: best quote from any agent
speaker: "participant" or "researcher"
topics: 2-5 tags derived from content
stage: from Journey — where in the journey this occurred
quest_link: from Product — linked quest if relevant
priority_tag: from Product (p0_blocker/p1_important/p2_nice_to_have)
cross_participant: boolean — does this echo a pattern from Q2/Q3?
confidence: based on evidence strength (0.9 = explicit + emotional alignment, 0.7 = explicit only, 0.5 = inferred)
Separate action items (things the researcher committed to) from insights. Actions don't become Artifact files.
2. Interview Analysis Briefing
Synthesize all agent outputs into a coherent briefing document:
Meta-Analysis synthesis guidance:
Write 3-5 paragraphs of opinionated analysis. Not a summary — a reading of the interview that tells someone something they wouldn't get from skimming the transcript.
Structure around four lenses:
Heart of the interview. What was this really about? Not the interview guide — the actual gravitational center. What is this participant struggling with, excited about, or trying to tell us? First paragraph, get there fast.
Journey assessment. How did the participant's experience map to what we designed? Where did reality diverge from intent? What was the critical path and where did it break? Reference specific friction points and aha moments.
Emotional read. What did the participant FEEL? Not just what they said — what their tone, pace, energy, and word choice revealed. Where did we earn trust and where did we lose it? What went unsaid?
Product implications. What should change based on this interview? Be specific — not "improve onboarding" but "step 3 needs a visual indicator for the API key field because 2/3 participants missed it." Connect to active quests. Distinguish blockers from nice-to-haves.
Register: A researcher briefing the team. Direct, evidence-based, doesn't over-generalize from one participant but names what's worth paying attention to.
Anti-patterns:
"The interview covered several areas..." → you have nothing to say
Restating what's in the insight list → the list exists for that
Equal weight to everything discussed → prioritize ruthlessly
"We should consider improving..." → who, what, by when, or don't say it
Over-generalizing from one participant → "this participant experienced X" not "users experience X"
# Interview Analysis: {Participant} — {Type} Interview**Date**: YYYY-MM-DD
**Participant**: {name}
**Researcher**: {researcher}
**Type**: {onboarding|feedback|research|exit}
**Source**: {Granola (doc-id) | file path | pasted}
**Emotional Arc**: {opening} → {closing}
**Engagement**: {level}
## Meta-Analysis
{3-5 paragraphs — heart of the interview, journey assessment, emotional read, product implications. See guidance above.}
## Journey Map
{From Journey analyst — stage by stage}
| Stage | Task | Outcome | Friction | Aha | Time |
|-------|------|---------|----------|-----|------|
| ... | ... | completed/abandoned | count | count | brief/extended |
### Critical Path
{From Journey analyst}
- {step}: {on_track|deviation} — {description}
### Drop-Off Risks
{From Journey analyst}
- **{point}** ({severity}): {description}
> "{quote}"
## Friction Points
{Merged from all analysts}
| # | Friction Point | Severity | Stage | Emotional Signal | Evidence |
|---|---------------|----------|-------|-----------------|----------|
| 1 | ... | blocker | ... | frustration | "quote" |
## Aha Moments
{From Journey + Sentiment}
| # | Moment | Stage | Emotional Signal | Evidence |
|---|--------|-------|-----------------|----------|
| 1 | ... | ... | delight | "quote" |
## Feature Discovery
{From Product analyst}
| Feature | Reaction | Discoverability | Evidence |
|---------|----------|-----------------|----------|
| ... | positive | accidental | "quote" |
## Mental Model Mismatches
{From Product analyst}
| Expected | Actual | Severity | Evidence |
|----------|--------|----------|----------|
| ... | ... | high | "quote" |
## Unmet Needs & Suggestions
{From Product analyst}
| Need/Suggestion | Priority | Evidence Type | Evidence |
|----------------|----------|--------------|----------|
| ... | p1_important | explicit_request | "quote" |
## Internal Tensions
{Where analytical lenses DISAGREE — this section is critical signal}
When Opus detects contradictions between agents (e.g., Journey says task completed but Sentiment reads frustration, or Product says feature discovered but Journey shows it was missed):
- **{topic}**: {Agent A reads as X}, but {Agent B reads as Y} — {implication}
If no inter-agent tensions exist, omit this section.
## Insights Extracted
{List of insights with confidence + quest links}
| # | Category | Title | Confidence | Priority | Quest |
|---|----------|-------|------------|----------|-------|
| 1 | friction | ... | 0.9 | p0_blocker | quest-id |
## Open Questions
{Unresolved from the interview — things to explore in follow-ups}
- {question}
## Participant Journey Note
{Brief note for memory/research/participants/{slug}.md — track this person's journey across sessions}
3. Detect inter-agent tensions
Specifically look for these contradiction patterns:
Journey says "completed" but Sentiment reads frustration at that stage → tension
Journey says "abandoned" but Sentiment reads engagement/curiosity → tension
Product says feature "discovered" but Journey shows participant missed it → tension
Sentiment reads "delight" but Product notes a mental model mismatch at that moment → tension
Journey reports "blocker" severity but Sentiment reads only mild frustration → tension
Record these in the "Internal Tensions" section. These disagreements ARE signal.
Step 5: Present proposal
Show the meta-analysis preview + merged insights. Same interaction model as meeting pipeline.
If existing participant:
Append a new row to the Journey table and update interview count.
Step 6d: Write individual artifact files
For each insight (friction, aha, feature_gap, mental_model, feature_discovery, suggestion — NOT actions):
cat > "memory/knowledge/{category_dir}/{YYYY-MM-DD}-{slug}.md" << 'ARTIFACTEOF'# {Title}
**Date**: {YYYY-MM-DD}
**Author**: interview
**Category**: {category}
**Confidence**: {0.0-1.0}
**Source**: Interview with {participant}
**Topics**: {topic1}, {topic2}
**Participant**: {participant}
**Interview Type**: {type}
**Priority**: {p0_blocker|p1_important|p2_nice_to_have}
**Severity**: {blocker|frustration|minor}
## Context
{What was happening when this insight emerged — from Journey stage context}
## Content
{Synthesized from scaffold + Journey + Product — the actual insight}
## Emotional Context
{From Sentiment — what the participant felt at this moment}
## Evidence
> "{evidence_quote}" — {speaker}
## Related
- Quest: {quest-id}
- Cross-participant: {yes/no — seen in N other interviews}
ARTIFACTEOF
Category to directory mapping:
friction → knowledge/findings/
aha → knowledge/findings/
feature_gap → knowledge/findings/
mental_model → knowledge/patterns/
feature_discovery → knowledge/findings/
suggestion → knowledge/findings/
Omit sections that have no data.
Step 6e: Durable graph projection
Create
memory/ingest/knowledge/interviews/{interview-id}.json using the canonical
manifest contract described above. Include the Interview source, current
session, researcher and participant identities, quests, extracted artifacts,
and explicit relations. Validate and apply it through bin/ingest-graph.sh;
retain partial deliveries for replay through bin/ingest.sh reindex.
The raw batch mappings below are legacy schema reference only. Do not execute
them as a parallel write path.
Build a JSON array of queries for bash bin/graph-batch.sh calls.
Batch limit: API accepts max 20 queries per graph-batch.sh call. Count total queries before executing. If >20, split into chunks of <=20 and execute sequentially.