notion-ingest
Pull a Notion page into the Supernova vault and route its contents to the right place. The Notion side stays read-only by default — vault is the source of truth for synthesized output.
Pre-flight checks
- Notion MCP authenticated. Test with
mcp__claude_ai_Notion__notion-search(lightweight call). If 401 / not configured, surface to Adam: "Run/mcpand authenticate Notion first." url_or_idlooks like a Notion URL (containsnotion.so) or a 32-char hex ID with optional dashes. Normalize to the page ID before fetching.Raw/Notion/exists; create if missing.
Procedure
1. Fetch the page
Call mcp__claude_ai_Notion__notion-fetch with the URL or ID. Capture:
titlebody(markdown)created_time,last_edited_timeproperties(any structured fields — date, attendees, tags, status, etc.)url(canonical Notion URL)
If fetch fails: report and abort. Don't fall back silently.
2. Save raw provenance copy
If preserve_raw == true (default):
- Slug = kebab-case of title, truncated to 60 chars.
- Path:
Raw/Notion/{YYYY-MM-DD}-{slug}.md - Frontmatter:
--- type: raw-input source: notion notion_url: {canonical url} notion_page_id: {id} fetched: {today} original_title: {title} original_last_edited: {last_edited_time} --- - Body: the page markdown verbatim.
3. Classify the content type
Use heuristics first; ask Adam if ambiguous. Heuristics (in priority order):
| Signal | Type | Routing target |
|---|---|---|
| Title contains "1:1", "call notes", "meeting", "sync", "weekly", "standup"; OR body has attendee list / agenda / action items section | Meeting notes | role doc + active-todos |
| Title contains "decision", "should we", "vs."; OR body has pros/cons / options / "TBD" | Decision | Wiki/Decisions/ + active-todos |
| Title contains "strategy", "roadmap", "OKR", "plan", "Q1/Q2/Q3/Q4 plan" | Strategy | role/brand doc update |
| Title contains "lesson", "learning", "takeaway", "retro", "post-mortem" | Learning | Wiki/Learnings/ |
| Title contains "idea", "brainstorm", "raw"; OR body unstructured / thoughts in flow | Brainstorm | Raw/Inbox/ |
| Otherwise | Ambiguous | Ask via AskUserQuestion |
If the caller passed target_hint, that wins over heuristics.
4. Determine scope (brand / role / cross-cutting)
Detect from content:
- Mentions of brand names from
Context/Brands.md→#brand/{slug} - Mentions of role-specific terms (e.g., "performance", "Meta ads", "creative team", channel names) AND brand context → role-scoped
- No clear brand → cross-cutting (vault-wide / GMG-level)
If multiple plausible targets: AskUserQuestion with the candidates. Always include "Cross-cutting / no specific scope" as an option.
5. Route
Based on type × scope:
| Type | Scope | Target file |
|---|---|---|
| Meeting notes | role-scoped | Brands/{brand}/Roles/{role}.md (append to "Recent meetings" section) + new Brands/{brand}/Notes/{date}-{title-slug}.md for full notes |
| Meeting notes | brand-scoped (no role) | Brands/{brand}/Notes/{date}-{title-slug}.md |
| Meeting notes | cross-cutting | Wiki/Notes/{date}-{title-slug}.md |
| Decision | any | Wiki/Decisions/{date}-{title-slug}.md (use decision-memo template if available) |
| Strategy | role-scoped | append/update Brands/{brand}/Roles/{role}.md strategic-bets section |
| Strategy | brand-scoped | append to Brands/{brand}/knowledge-base.md or BrandContext.md quick-reference |
| Learning | any | Wiki/Learnings/{date}-{slug}.md (atomic — one insight per node) |
| Brainstorm | any | Raw/Inbox/{date}-{title-slug}.md (don't synthesize prematurely) |
6. Spawn todos
If spawn_todos == true (default) AND content has detectable action items (lines starting with [ ], TODO:, "Action:", "@adam to..."):
- For each: append to
Wiki/Notes/_active-todos.mdunder## Openwith format:- [ ] **{action title}** — {context}. {role/brand tags}. <!-- created: {today}, source: <a class="wikilink wikilink-broken" href="#">Raw/Notion/{date}-{slug}</a> --> - Bump
last_updated:in the todos file frontmatter.
7. Spawn decisions
If spawn_decisions == true (default) AND content surfaces an open decision (questions like "should we X?", "X vs Y?", explicit "Decision needed:" markers):
- Create
Wiki/Decisions/{date}-{decision-slug}.mdwithstatus: pending, link back to source Notion page + raw copy. - Append a todo under
## Openin active-todos: "Make decision on {topic}" with#dept/decisionstag.
8. Cross-link
Wherever a synthesized file is written, add wikilinks:
- Up to the role doc / brand context (so it appears in graph view)
- Down to source:
<a class="wikilink wikilink-broken" href="#">Raw/Notion/{date}-{slug}</a>(or external Notion URL ifpreserve_raw == false) - Sideways to any mentioned people (
Wiki/People/{slug}) or concepts
9. Append Log entry
Via log-operation:
- agent:
ea-orchestrator - skill:
notion-ingest - input:
notion_page="{title}", classified_as={type}, scope={scope} - output:
synthesized={count} files, todos={N}, decisions={M} - artifacts: list of all wikilinks created/updated
- notes: any classification ambiguity that required Adam's input
Failure modes
- Notion MCP not auth'd / 401: abort, instruct Adam to authenticate.
- Page is empty or 404: abort with clear message; don't write empty Raw file.
- Page is huge (>50k chars): save raw, but ask Adam before deep-synthesizing — "this is a long page; want full synthesis or executive summary only?"
- Sensitive content flagged (contains terms from
_System/SecretsRegistry.mdlike API keys, passwords, secret tokens): refuse to write to Raw or Wiki; surface to Adam, suggest redacting in Notion first. - Routing genuinely unclear: fall back to Raw/Inbox with a follow-up todo to manually classify later.
Hard rules
- Read-only on the Notion side. Don't update / delete / rename pages in Notion unless the caller explicitly asks.
- Provenance is non-negotiable — every synthesized file references the Notion page (URL or
<a class="wikilink wikilink-broken" href="#">Raw/Notion/...</a>wikilink). Adam should always be able to trace a vault note back to its origin. - Raw is sacred — once written to
Raw/Notion/, never edit. Re-fetch creates a new dated file. - Don't push back to Notion with synthesized output (yet) — the vault is the synthesis layer; Notion is an input source. Phase 2 could add bidirectional sync (write the wiki node's permalink back as a Notion property), but not v1.
- Never push sensitive vault content (
Context/MI.md,Raw/Health/, brand compliance docs) into a Notion search query — search terms go to Notion's servers.
Caller patterns
Manual ingest (most common)
Adam pastes a Notion URL → /notion ingest <url> → skill routes everything.
EA daily briefing (Phase 2)
The EA-Orchestrator's daily-briefing skill (when built) calls notion-search to find pages updated in the last 24h that match #supernova-pull or a configured tag, then loops through notion-ingest for each.
Direct from chat
Adam pastes Notion content (without URL) → caller can skip the fetch step and pass the markdown directly to step 3 (classification) onward.
Future extensions (out of scope for v1)
- Bidirectional sync: write the synthesized wiki path back to Notion as a property.
- Database ingestion: walk a Notion database and ingest each row as a structured node.
- Webhook trigger: ingest on-update via Notion webhook (requires hosted infra; defer until needed).
- Diff-aware re-ingest: if the same Notion page is ingested twice, diff against the previous Raw copy and only resynthesize the changes.