zao-research
Research skill for the ZAO OS ecosystem. Three-tier workflow (QUICK/STANDARD/DEEP) with mandatory community-source coverage. Integrates exa + context7 MCPs, Reddit/HN/X searches, staleness detection,
SKILL.md.claude/skills/zao-research/SKILL.md
GitHubZAO OS Research Skill v2
Use this skill when asked to research a topic for any ZAO ecosystem project - ZAO OS, COC Concertz, FISHBOWLZ, BetterCallZaal, or any other brand. The canonical research library lives in one place.
Research Library Location
Canonical path: /Users/zaalpanthaki/Documents/ZAO OS V1/research/
This is the single source of truth for all ZAO ecosystem research. All projects read from and write to this library.
Topic Folders
| Folder | For |
|---|---|
agents/ |
AI agents, OpenClaw, ZOE, frameworks, memory, orchestration |
music/ |
Player, NFTs, distribution, Arweave, audio, FISHBOWLZ |
dev-workflows/ |
Skills, Claude Code, testing, autoresearch, git, MCP |
infrastructure/ |
Next.js, Supabase, streaming, mobile, notifications |
governance/ |
Respect, ORDAO, Hats, ZOUNZ, fractals, Snapshot |
community/ |
ZAO guide, onboarding, member profiles, task forces |
cross-platform/ |
Bluesky, Lens, Nostr, Mastodon, Reddit, X, Twitch |
farcaster/ |
Protocol, Mini Apps, XMTP, ecosystem, social graph |
identity/ |
ZIDs, ENS, reputation, knowledge graph |
business/ |
Revenue, payments, strategy, marketplace |
events/ |
Bootcamp notes, ship logs, big wins, retros |
wavewarz/ |
Prediction markets, artist pipeline |
security/ |
Audits, testing, API verification |
Three Tiers: Pick Your Depth
| Tier | Time | Sources | Coverage | Use When |
|---|---|---|---|---|
| [QUICK] | 10 min | 1-2 | Official docs only, no web | Sanity check existing knowledge |
| [STANDARD] | 30 min | 5-7 | Docs + 1 Reddit + 1 HN + 1 GitHub + 1 verified URL | Default for most research |
| [DEEP] | 2 hours | 20+ | Full community scan (Reddit threads, HN discussions, X sentiment, blogs, GitHub issues), contradiction checks, staleness audit | Hub topics (10+ dimensions) or critical decision |
Default: [STANDARD]. For topics with 10+ dimensions (e.g., strategy, market, architecture), use [DISPATCH] pattern (see Step 4).
Mandatory Workflow
Follow these steps IN ORDER. Do not skip.
Step 1: Understand the Request
Parse what the user wants to know. Determine which ZAO ecosystem project (ZAO OS, COC Concertz, FISHBOWLZ, BetterCallZaal). If no tier specified, default to [STANDARD]. Flag if topic looks like a hub (10+ conceptual dimensions) - you may suggest [DISPATCH].
Step 1.5: Ingest media source (when the input is a podcast / video / audio)
If the research input is a Spotify episode URL, YouTube URL, Apple Podcasts URL, RSS feed, direct mp3, or a podcast transcript page (not a topic string), first turn it into a transcript via the shared ingestion engine, then research from that transcript:
~/bin/zao-ingest.sh "<url>"
# prints the transcript .txt path; also writes <transcript>.meta.json {source,type,title,url}
- The transcript becomes a primary source for the doc - cite it
[FULL]in Sources with the episode title + URL from the.meta.jsonsidecar. - For Spotify, the engine resolves the DRM episode to its podcast RSS (via the Podcast Index API - key in
~/.zao/zao.env) or a YouTube copy, then transcribes locally. If it can't resolve (Spotify-exclusive original), relay the message and ask for an alternate URL. - Then continue Step 2 onward as normal, synthesizing the transcript + any web research into the doc. This is how a podcast becomes a research doc. See the
/ingestskill for the full source matrix.
Step 2: Search Codebase + Existing Research
Before looking at web research:
# Check if research already exists (dedup)
grep -ri "<topic_keyword>" "/Users/zaalpanthaki/Documents/ZAO OS V1/research/"*/README.md
# Check codebase for real-world usage
grep -ri "<topic_keyword>" "/Users/zaalpanthaki/Documents/ZAO OS V1/src/"
Glob "**/*<topic_keyword>*" path="/Users/zaalpanthaki/Documents/ZAO OS V1"
# Check config files
read community.config.ts / <project>.config.ts
This grounds research in reality. Research docs are aspirational; code is ground truth.
Step 2.5: Cross-Repo Search (ZAO Ecosystem)
After checking local research + codebase, search across ZAO ecosystem repos (30+ public repositories in bettercallzaal org). Use this step only if topic is cross-repo relevant:
Cross-repo relevant topics: Music tech patterns, Snap/Mini App builders, governance bots, Web3 tokenomics, Farcaster integration, automation frameworks, brand consistency.
Use MCP mcp__grep__searchGitHub tool with org scope:
mcp__grep__searchGitHub(
query='<pattern>',
repo='bettercallzaal/',
language=['TypeScript', 'TSX', 'JavaScript', 'Python']
)
Rate limits: 30 requests/min, 60/hour (GitHub API). Budget 2-3 searches per [STANDARD] tier, up to 5 for [DEEP].
Result handling:
- Exact match: Include repo name + file path. Example: "WaveWarZ player at
wwbase/src/components/SonataPlayer.tsx." - Zero hits: Document as valuable negative signal. "Pattern not found across 30 ZAO repos; pattern is novel or private."
- Deduplication: If pattern appears in 3+ repos, extract as "Standard ZAO pattern" + link representative repo.
- Hit rate limit: Stop gracefully. Note "rate-limited after X of Y searches." Resume next day or use fallback:
gh search code "query" --owner=bettercallzaal.
Excluded (do NOT search): Private repos (quad-sandbox, zao-ui, zao-mono, zaoos-workspace, budget2026), archived variants (fractalbotv1old, zaaltimelinev1.1, etc.).
Step 2.6: Cowork Tracker Pre-Check (NEW v2.4 - 2026-05-24)
Before reserving a doc number, query the ZAO cowork tracker for in-flight tasks on the same topic. Two reasons:
- Avoid duplicate work - if Iman or Zaal has an in-flight task that already covers this research, link to it instead of duplicating.
- Surface context - prior decisions about this topic may have a tracker row with notes / due-date / owner that informs the research.
~/bin/zao-tracker search "<topic-keyword>" --status todo --limit 5
Optional --source filter to scope: --source research-doc shows prior research-shipped tasks; no filter = all writers (PR test plans, meeting actions, bot adds).
If matches:
- Include the matched tracker rows in the doc's
Also Seesection:- Tracker task <legacy_id> (status, due) - <title> - If the match is a research-doc tracker row from a recent shipped doc, the topic may already be covered. Consider Re-Research Mode instead of writing a new doc.
If no matches: continue to Step 3.
This step is non-blocking - if zao-tracker is unavailable (env not loaded) or returns no results, proceed normally. The pre-check is best-effort situational awareness, not a gate.
Step 3: Reserve Doc Number + Branch (Parallel-Session Safe)
Before writing anything. A claim branch does NOT reserve a number - on 2026-06-09 two terminals both computed next=819 off origin/main and shipped six colliding pairs (819/824/825/826/827/828), which had to be renumbered after merge. The max-number scan MUST cover in-flight work, not just merged dirs:
REPO="/Users/zaalpanthaki/Documents/ZAO OS V1"
git -C "$REPO" fetch origin --prune
# 1. Highest MERGED doc number (robust: leading number of NNN-slug dirs, not any digits)
merged=$(find "$REPO/research" -maxdepth 2 -type d -regextype posix-extended -regex '.*/[0-9]+-.*' 2>/dev/null \
| grep -oE '/[0-9]+-' | grep -oE '[0-9]+' | sort -n | tail -1)
# 2. Highest IN-FLIGHT number on unmerged remote branches (other terminals mid-doc)
inflight_b=$(git -C "$REPO" ls-remote --heads origin 'ws/research-*' 2>/dev/null \
| grep -oE '[0-9]{3,}' | sort -n | tail -1)
# 3. Highest number sitting in an OPEN PR (claimed, not merged)
# Use the REST endpoint, NOT `gh pr list` — gh pr list/create hit the GraphQL
# bucket (5000 pts/hr, shared with nightly automation + every terminal on the
# bettercallzaal token, and drained fast). REST `gh api .../pulls` uses the
# separate 5000-req/hr core bucket. See research doc 887 rate-limit diagnosis.
inflight_pr=$(gh api 'repos/bettercallzaal/ZAOOS/pulls?state=open&per_page=50' \
--jq '.[].title' 2>/dev/null | grep -oE 'doc[ #]?[0-9]{3,}' | grep -oE '[0-9]{3,}' | sort -n | tail -1)
# next = max(all three) + 1
next=$(printf '%s\n' "$merged" "$inflight_b" "$inflight_pr" | sort -n | tail -1)
echo "next doc number: $(( next + 1 ))"
git -C "$REPO" checkout -b ws/research-<topic-slug>
mkdir -p "$REPO/research/{topic}/$(( next + 1 ))-{doc-name}"
This catches parallel sessions whose docs are branched/PR'd but not yet merged. If you still suspect a race (multiple terminals starting within the same minute), pick next+2 and leave a gap - a gap is free; a collision costs a renumber. After the renumber incident, see also feedback_no_synthesis_from_titles sibling lesson on parallel-session hygiene.
Step 4: Conduct Research (Tier-Dependent)
[QUICK] - 10 minutes, 1-2 sources
- Check official docs only (context7 if available)
- 1 GitHub README or official example
- Zero web search
- Save + done
[STANDARD] - 30 minutes, 5-7 sources
Use MCP tools in this order:
- context7 — Official library docs (what changed in Next.js? What's new in Supabase RLS?)
- exa — Semantic web search (shows what smart people built, not just Google results)
- WebSearch — Fallback if exa doesn't cover it
- Community sources (pick 1 per source type):
- Reddit: Use WebSearch filtered to reddit.com. Query templates:
"topic" site:reddit.com/r/ClaudeAIorsite:reddit.com/r/LocalLLaMA(for tech topics)"topic" site:reddit.com/r/selfhosted(infrastructure)"topic" site:reddit.com/r/Web3(blockchain)
- HackerNews: Algolia API via WebSearch:
"query" site:news.ycombinator.com dateRange:2025or search for exact discussion threads - GitHub Discussions: Check repo official discussions,
gh api repos/OWNER/REPO/discussions - X/Twitter: Via exa semantic search, or WebSearch with
site:twitter.com(Exa covers cross-posts on Substack, Medium, etc.)
- Reddit: Use WebSearch filtered to reddit.com. Query templates:
- Verify URLs: Check each source URL resolves (no 404s). Use Wayback Machine mentally or note "last checked: [date]".
[DEEP] - 2 hours, 20+ sources, full community coverage
- All STANDARD sources
- Reddit: 3-5 threads across relevant subreddits (r/ClaudeAI, r/LocalLLaMA, topical subs). Synthesize comments, flag dissent.
- HackerNews: 3-5 threads, search both recent (last 6 months) and all-time (find historical context). Sort by points; read top 5 comments per thread.
- GitHub Discussions: 2-3 threads from official repos. Synthesize user pain points.
- X/Twitter: Semantic search via exa for sentiment (bullish/bearish on topic). 3-5 threads if relevant.
- Blogs + Substack: exa covers these; fetch 2-3 substantive articles.
- Contradiction check: If sources disagree, flag explicitly. Do NOT synthesize false consensus.
- Staleness audit: Note dates for APIs, pricing, versions. Flag if knowledge is >6 months old.
- Verify all URLs: Wayback Machine check for liveness. Note any dead links.
[DISPATCH] - For Hub Topics (10+ Dimensions)
Topics like strategic decisions, market analysis, architectural redesigns often have 10+ independent dimensions (cost, team, timeline, risk, integration points, etc.). Spawn sub-agents:
Topic: Architecture Decision for <Feature>
Dimensions: 1) cost, 2) latency, 3) security, 4) team skill, 5) vendor lock-in,
6) integration with existing stack, 7) testing strategy, 8) monitoring,
9) vendor roadmap, 10) community adoption
Sub-agents:
- Agent 1: Cost analysis (pricing tiers, hidden fees, egress costs)
- Agent 2: Performance (latency benchmarks, throughput under load)
- Agent 3: Security (audit track record, data residency, compliance)
- Agent 4: Ecosystem (adoption rate, community size, example implementations)
- Agent 5: Roadmap (planned features, vendor stability, deprecation risk)
Parent agent: Synthesizes findings into hub README with 5 cross-linked docs.
Each sub-agent runs tier=[STANDARD] or [QUICK] research on their dimension. Parent synthesizes into one hub README with "Also see" section linking all sub-docs.
Re-Research Mode (updating an existing doc)
When the task is to re-research an EXISTING doc (bring a stale or thin doc up to standard) rather than create a new one:
- Read the existing doc first. Note its number, folder, title, and what it already claims.
- Reconstruct the
original-query. If the doc has nooriginal-queryfield, write one faithfully from the title + Goal + content and suffix it" (reconstructed)". This field is permanent - it lets any future session re-run or extend the research from the same seed. Never drop it. - Re-fetch, do not trust the old text. Run fresh research (Steps 4 + 4.5) - the old doc's claims may be stale or were written off thin fetches. Climb the fetch ladder for every source.
- Preserve the doc number and folder. Re-research edits the existing
README.mdin place. Never renumber. - Rewrite to the current v2 standard - full frontmatter (incl
original-queryand a freshlast-validated), Key Decisions, Findings, Sources marked FULL/PARTIAL/FAILED, Next Actions. - Note what changed. If a material fact changed since the last version, say so in Findings ("Updated 2026-05-20: X was Y, now Z").
Large re-research campaigns: dispatch one subagent per batch of ~3-6 docs, each owning whole doc clusters; the parent commits.
Step 4.5: Fetch-Quality Gate (MANDATORY before writing)
Do NOT write the doc until every source passes this gate. This is the fix for Doc 693 - the failure where a hard-to-fetch link gets a doc written off metadata, search snippets, or a 404.
For each source, classify it:
- FULL — the actual content was fetched and read (not the title, not a preview, not a search snippet).
- PARTIAL — some content retrieved, some missing (e.g. a tweet's text but not its long-form article body; a Reddit post but not its comment tree).
- FAILED — nothing retrieved (404, empty JS shell, tombstone).
Any PARTIAL or FAILED source MUST be escalated through the full fetch ladder (WebFetch → exa web_fetch → Playwright / /browse → Wayback) before the doc is written. Only after the ladder is genuinely exhausted may a source stay PARTIAL/FAILED — and then it MUST be marked as such in the doc's Sources section (Hard Requirement #11).
Banned: writing the doc from a search snippet because the real page would not load on the first try. The page Zaal sent is the source; a snippet about it is not.
If a high-signal source (long technical post, 500+ upvotes, an actual playbook/spec/wiki) is only PARTIAL after escalation, say so loudly at the top of the doc - do not bury it.
Step 5: Save with v2 Metadata Frontmatter
Required structure:
---
topic: [category from Topic Folders, e.g. "infrastructure"]
type: [guide | comparison | decision | audit | market-research | threat-landscape | incident-postmortem]
status: [research-complete | draft | review-pending | superseded]
last-validated: [date, e.g. 2026-04-24]
superseded-by: [doc number if outdated by another doc]
related-docs: [comma-separated doc numbers, e.g. "123, 145, 289"]
original-query: "[the literal research request that spawned this doc, verbatim. For docs re-researched from an older version, a faithful reconstruction - suffix it with ' (reconstructed)'. This field exists so any future session can re-run or extend the research from the same seed.]"
tier: [QUICK | STANDARD | DEEP | DISPATCH]
---
# {Number} — {Title}
> **Goal:** {One-line description}
[... content ...]
## Also See
- [Doc 123](../123-related-topic/)
- [Doc 289](../289-other-angle/)
## Next Actions
| Action | Owner | Type | By When |
|--------|-------|------|---------|
| Update community.config.ts with finding X | @Zaal | PR | After approval |
| Create ZOE task for "monitor vendor deprecation" | @Zaal | Bot task | Next sprint |
| Schedule infrastructure review meeting | @Team | Calendar | 2026-05-01 |
## Sources
- [Source 1 Title](https://...)
- [Source 2 Title](https://...)
Step 6: Detect + Flag Staleness + Hallucinations
Before declaring research complete:
Staleness check:
- APIs, pricing, versions - note "current as of [date]" for each
- Docs >6 months old? Flag: "Source is X months old; verify with current docs"
- Contradictions between sources? Flag explicitly: "Source A claims X, Source B claims Y"
Hallucination check:
- Each URL: Does it resolve? (404? Redirected?) Note "verified: [date]"
- Citations: Author name + date match the actual page header? Or LLM-invented?
- Specific claims: Can you trace them back to a direct quote? Or inferred/synthesized?
If any hallucinations suspected, rewrite. Do not publish unverified claims.
Step 7: Add Action Bridge
Every doc ends with "Next Actions" table (see frontmatter template above).
HARD REQUIREMENT (retrospective 2026-07-03, 5.8:1 research-to-ship ratio): every Next Actions row MUST have:
- Exec owner - a person who will do it (not "team", not blank)
- A real date in By When - an absolute date or "wontfix". BANNED values: "When ready", "Next sprint", "Ongoing", "TBD"
- Shipped-criteria in the Action text - what observable thing exists when done (PR merged / page live / bot deployed)
A doc whose actions have no owner+date is insight that never ships - do not commit it until the table passes. Link research to:
- PR: "Update X config based on finding Y"
- Todo: "Investigate Z before next release"
- ZOE task: "Set up monitoring for deprecation warning"
- Calendar: "Schedule team sync on topic X"
- Other doc: "This doc supersedes Doc 234, update links"
Research without action stays archived. Link it forward.
Step 8: Commit + Push
git -C "/Users/zaalpanthaki/Documents/ZAO OS V1" add research/{topic}/{number}-{slug}/
git -C "/Users/zaalpanthaki/Documents/ZAO OS V1" commit -m "docs: {topic} research doc {number} (tier:{tier})"
git -C "/Users/zaalpanthaki/Documents/ZAO OS V1" push -u origin ws/research-{topic}
Step 9: Create PR to Main
You (the session) create the PR. Zaal reviews + merges himself.
Use the REST endpoint, NOT gh pr create. gh pr create hits the GraphQL bucket
(5000 pts/hr, shared across the nightly automation and every terminal on the one
bettercallzaal token — it gets exhausted and blocks PR creation). gh api .../pulls
uses the separate, healthy core/REST bucket (5000 req/hr). Proven fix, doc 887.
BODY=$(cat <<'EOF'
## Summary
[1-3 sentence summary of findings]
## Tier
[QUICK | STANDARD | DEEP | DISPATCH]
## Sources
[Count of unique sources, by type: docs, Reddit, HN, GitHub, blogs, etc.]
## Next Actions
[Link to doc section with action table]
EOF
)
gh api -X POST repos/bettercallzaal/ZAOOS/pulls \
-f title="doc {number}: {topic} research ({tier} tier)" \
-f head="ws/research-{topic}" \
-f base="main" \
-f body="$BODY" \
--jq '"PR #\(.number): \(.html_url)"'
(Fallback if REST is somehow also limited: open the compare URL
https://github.com/bettercallzaal/ZAOOS/compare/main...ws/research-{topic}?expand=1.)
Step 9.5: Fire research-doc tracker task (NEW v2.4 - 2026-05-24)
After the PR is created, write a tracker row into the ZAO cowork tracker so the research doc shows up in Iman / Zaal's Kanban alongside code tasks:
~/bin/zao-tracker research <doc-number> "<doc title>"
This creates a todo task owned by Zaal (default for research kind), due in 3 days, with legacy_source=research-doc:<num> and legacy_id=research-doc-<num>. Idempotent on legacy_id - re-running the same doc number is a no-op (will fail with a unique constraint; the helper will print the error and exit non-zero, no harm).
Owner override: pass a third arg for non-Zaal review (e.g. ~/bin/zao-tracker research 740 "title" Iman for an Iman-owned review). Default = Zaal.
Best-effort: if SUPABASE_SERVICE_KEY is unset in ~/.zao/zao.env, the helper exits with a clear error. Skill should NOT abort the run on tracker failure - the doc + PR are already shipped, the tracker task is a nice-to-have for the Kanban surface. Print the error to chat and continue.
Per the standing rule (feedback_pr_auto_test_task), the PR itself ALSO gets an ~/bin/zao-tracker pr <prnum> "..." row separately. The research-doc row and the pr-auto row are different writers with different legacy_source prefixes - both should exist (one tracks "review the research", one tracks "test the PR").
Hard Requirements (All Must Pass)
| # | Requirement |
|---|---|
| 1 | Recommendations FIRST in Key Decisions table; no preamble |
| 2 | At least 1 file path from relevant project codebase (Step 2 result) |
| 3 | At least 3 specific numbers (versions, prices, dates, counts, benchmarks) |
| 4 | Sources section with 3+ clickable URLs (STANDARD) or 10+ (DEEP) |
| 5 | Comparison table with 3+ options (if decision doc) OR findings table (if guide) |
| 6 | No vague language (see banned phrases below) |
| 7 | [STANDARD]/[DEEP]: At least 1 community source (Reddit, HN, GitHub Discussions, X) |
| 8 | All URLs verified for liveness; note any 404s or paywalled content |
| 9 | Metadata frontmatter with topic/type/status/last-validated/related-docs |
| 10 | Action bridge table ("Next Actions") linking research to concrete todos/PRs/tasks |
| 11 | Sources section marks each source [FULL] (content fully read), [PARTIAL - what is missing], or [FAILED - what was tried]. No doc ships with an unescalated PARTIAL/FAILED. |
| 12 | Frontmatter carries original-query - the verbatim research request, or a faithful reconstruction (suffixed " (reconstructed)") for re-researched docs. Never ship a doc without it. |
Banned Phrases
| BANNED | USE INSTEAD |
|---|---|
| "consider using" | "USE [X] because [reason]" |
| "it might be worth" | "[X] is worth it because [reason]" |
| "you could explore" | "USE [X]" or "SKIP [X]" |
| "worth investigating" | "INVESTIGATE [X] - [specific question]" |
| "it depends" | State the decision for the specific project context |
| "behind schedule" | State actual date or blockers, e.g. "blocked on Zaal's approval" |
Context: ZAO Ecosystem Projects
| Project | Stack | Community | Focus |
|---|---|---|---|
| ZAO OS | Next.js 16 + Supabase + Neynar | 188 member gated Farcaster music community | Social platform, governance, music, autonomous agents |
| COC Concertz | Next.js 16 + Firebase + Cloudinary | 13+ promoters, virtual concert community | Concert promotion, artist showcasing |
| FISHBOWLZ | Next.js + Privy + Supabase | Audio rooms for music communities | Live audio, discussions [paused Apr 16 for Juke partnership] |
| BetterCallZaal | Static HTML + Farcaster Mini App | Personal brand | Portfolio, consulting, mini app distribution |
Staleness + Supersession Rules
Every doc carries last-validated: <date>. This is your SLA. If you ship doc on 2026-04-24, it's valid until 30 days. After 30 days:
- If still accurate: Update
last-validatedto today, re-push. - If outdated but only slightly: Add note "Updated 2026-05-24 - [what changed]" and re-validate.
- If fundamentally wrong or superseded: Create new doc. Set
superseded-by: <new-doc-number>in old doc. Updaterelated-docsin new doc.
For high-churn topics (pricing, APIs, security), validate every 2-4 weeks.
Failure Modes + Graceful Degradation
| Failure Mode | Symptom | Recovery |
|---|---|---|
| Hallucinated URL (3-13% rate) | Citation resolves to 404 or wrong page | Verify each URL manually. Use Wayback Machine. If dead, note "link rot" and cite next-best source. |
| Stale Info (5-18% of content) | API doc is 6 months old; pricing changed | Check last-validated date. If >4 weeks old, re-verify. Add timestamp to every external fact: "Current as of 2026-04-24: API v3.2.1 costs $X/month." |
| JS / bot / paywall wall | WebFetch returns an empty app shell, "Notion", or thin content | Climb the fetch ladder: exa web_fetch → Playwright MCP → /browse skill. Do NOT fall back to WebSearch snippets and write the doc anyway. If a true paywall, note "premium content, not summarized" and use an alternative source. |
| Rate Limiting (Reddit, HN Algolia, WebSearch) | Search stops returning results | Stop gracefully. Note "rate-limited" in doc. Suggest manual follow-up. For [DEEP] tier, budget upfront: "max 20 API calls." |
| Context Collapse (late-stage hallucination on long research) | Synthesized claims contradict source material | Flag contradictions explicitly: "Source A: X. Source B: Y. Contradiction unresolved; needs manual review." Do not pretend consensus. |
| Metadata Fabrication (78.5% of phantom citations) | Title is correct; URL/DOI/author invented | Always extract metadata from page headers, never infer. If page missing metadata, note "metadata unavailable" and use next source. |
MCP Tool Order (Use in This Sequence)
firecrawl is NOT installed. Do not reference it. The real tool chain is below (verified 2026-05-20, Doc 693).
For searching (find the target):
- context7 — Library/framework docs. Zero wait, high signal. Use first for "what's new in X?"
- exa web_search (
mcp__plugin_everything-claude-code_exa__web_search_exa) — Semantic web search. Use for "what are smart people doing?" - WebSearch — Fallback if exa search doesn't cover it.
- grep.app — Code search across GitHub. Use for "show me implementations."
For fetching (read the target) — escalation ladder, climb it until the content is FULL:
- WebFetch — Plain articles + HTML. First try for any URL.
- exa web_fetch (
mcp__plugin_everything-claude-code_exa__web_fetch_exa) — JS-rendered pages, clean markdown, batches multiple URLs. This is the firecrawl replacement. Use when WebFetch returns an empty shell or thin content. - Playwright MCP (
mcp__playwright__*) or the/browse//gstackskills — Full headless browser. Use for Notion wikis, X long-form articles, Spotify, and any login-walled or heavily-JS page that exa cannot render. - Wayback Machine (
https://web.archive.org/web/<url>) — For 404s, moved pages, dead links. Always try before giving up on a dead URL. ~/bin/zao-fetch-reddit.sh/~/bin/zao-fetch-x.sh— Reddit threads / X tweets (see the fallback chain below).
Per-source-type escalation:
- Notion / JS app shells → WebFetch returns empty → exa web_fetch → Playwright.
- X long-form articles → zao-fetch-x.sh gets the tweet only, NOT the article body → Playwright (logged-out article view).
- Dead / 404 / moved URLs → Wayback Machine, then search for the renamed slug.
- Reddit → zao-fetch-reddit.sh, then walk the FULL comment tree, not just the top 5.
- Media (Spotify, YouTube) → Playwright for the web player, or search the ID for show/episode metadata.
Reddit / X Scraping Fallback Chain (v2.1, Doc 562 + Doc 564)
When WebFetch returns "unable to fetch from www.reddit.com" or HTTP 402 from x.com, do NOT give up. Use this chain:
- First try:
~/bin/zao-fetch-reddit.sh "<url>"(curl + Mozilla UA + .json suffix). Works for 95%+ of public threads + subreddits. - If 429 rate-limited: wait 10-15 seconds, retry once.
- If still blocked: invoke the
last30days-skillskill - it carries full Reddit auth flow plus 10 other sources. - As last resort:
WebSearchwithsite:reddit.com/r/SUB query.
X / Twitter
- First try:
~/bin/zao-fetch-x.sh "<url-or-id>"— three-tier internal fallback:- Tier 1:
cdn.syndication.twimg.com/tweet-result?id=ID&token=4(works for ~95% of public tweets) - Tier 2:
nitter.net/i/status/ID(HTML scrape; one of few mirrors alive in 2026) - Tier 3:
web.archive.orgsnapshot
- Tier 1:
- If tombstone (deleted/private): the script reports it; do NOT retry; surface to user.
- For breadth research (multiple X accounts): invoke the
last30days-skillwhich uses a saved browser session token. - As last resort:
WebSearchfor tweet text fragments to find cached copies.
Programmatic shortcut
The /fetch skill (installed at ~/.claude/skills/fetch/) auto-routes any URL to the right tool. Prefer it over manual case-handling:
# Inside another skill or at the top of /zao-research:
/fetch <url>
When to use last30days-skill instead of /fetch
| Use /fetch | Use last30days-skill |
|---|---|
| One specific URL | "What did people say about X in the last 30 days" |
| Reddit thread you know | Subreddit + topic mining |
| One tweet | An account's recent activity + sentiment cluster |
| Single source | Cross-platform synthesis (Reddit + X + YT + HN) |
Future: Extraction to MCP
When research library is extracted to a shared MCP resource server, agents (ZOE, future ZAO bots) will access docs via:
list_resources- enumerate available docs, filter by topic/type/statusread_resource- fetch a specific doc by number- Metadata queries - "show me all DEEP-tier docs from 2026-04" or "all docs about Farcaster Protocol"
For now, all access is via this skill using absolute paths.
Example: How to Invoke
User says: "Research what open-source music player libraries would work best for ZAO OS Phase 2. Give me a tier=DEEP analysis of trade-offs."
v2 Skill flow:
- Understand: tier=DEEP, topic=music/player libraries, project=ZAO OS
- Check codebase: find
/src/providers/AudioPlayer.tsx+src/lib/music/ - Check existing research: grep for "music player" across research/
- Reserve doc: [DISPATCH] suggests spawning 5 sub-agents (OSS options, licensing, community, integration, mobile-friendly)
- Sub-agents each run tier=[STANDARD]: 1) Sonata (MIT), 2) Herocast (AGPL), 3) Nook (MIT), 4) Wavesurfer, 5) Howler.js
- Parent synthesizes findings into hub README, links all 5 sub-docs
- Action bridge: "Recommend Sonata for Phase 2.1 -> PR to community.config.ts" + "Setup integration spike -> ZOE task"
- Commit + PR
- Zaal reviews, merges
Result: Doc 492 (hub) + Docs 492a-492e (sub-docs) with clear recommendations, zero ambiguity.
Lens Mode (merged from /zao-lens, 2026-07-12)
The standalone /zao-lens skill is folded into this skill. When the user types
/zao-lens, or asks to "look at ZAO OS", "what should ZAO build next", "audit the
lab", "find the non-obvious move", or "what's dead in the repo" - this is LENS MODE,
not standard research. Run the 3-lens breakthrough engine (Builder invents / Skeptic
attacks with ZAO's own boundaries / Synthesizer forges the survivor), grounded in the
real repo - every idea must cite an actual file, research doc, or operating surface.
Full engine (sections 0-N, the ground-in-reality reads, intensity slider, --drunk,
--subsystem <name>, --mode build|kill|leverage, output format) lives in the
sibling file lens-mode.md in this skill directory. Read it and follow it exactly
when in lens mode. Open every lens run with ZAO Lens on.
Lens mode does NOT produce a numbered research doc by default (it is ideation, not archival research) - only write a doc if the user asks to capture the synthesis.