Roam Research + REM Labs: Bidirectional Knowledge

Roam Research organizes knowledge as a graph of interlinked pages and blocks. REM Labs adds semantic search and AI memory on top of that graph. This guide shows how to export your Roam database, import it into REM while preserving link structure, and query across your entire knowledge graph.

Why This Combination Works

Roam excels at capturing and linking ideas. But finding specific information in a large Roam graph means you either remember the page name or browse linked references. REM Labs adds semantic recall -- ask a question in natural language and get the relevant blocks, regardless of how they were linked or tagged in Roam.

Step 1: Export from Roam

In Roam, go to the menu (three dots) and select Export All > JSON. This gives you a file with every page and its nested blocks.

// roam-export.json structure: [ { "title": "Project Alpha", "children": [ { "string": "Goal: Launch by Q3", "children": [...] }, { "string": "[[Meeting Notes]] from kickoff", "children": [...] } ] }, ... ]

Step 2: Parse and Import

Flatten the Roam hierarchy and push each page to REM Labs, preserving block structure and extracting [[links]] as tags:

import { RemClient } from "@remlabs/sdk"; import fs from "fs"; const rem = new RemClient({ apiKey: process.env.REMLABS_API_KEY }); const roam = JSON.parse(fs.readFileSync("roam-export.json", "utf-8")); function flattenBlocks(children, depth = 0) { let text = ""; for (const block of children || []) { const indent = " ".repeat(depth); text += `${indent}- ${block.string}\n`; if (block.children) { text += flattenBlocks(block.children, depth + 1); } } return text; } function extractLinks(text) { const matches = text.match(/\[\[([^\]]+)\]\]/g) || []; return matches.map(m => m.slice(2, -2)); } let count = 0; for (const page of roam) { const body = flattenBlocks(page.children); if (!body.trim()) continue; const links = extractLinks(body); await rem.remember({ content: `# ${page.title}\n\n${body}`, namespace: "roam-research", tags: links, metadata: { title: page.title, source: "roam-research", linked_pages: links } }); count++; } console.log(`Imported ${count} Roam pages`);

Step 3: Query Your Knowledge Graph

// Semantic search across your entire Roam graph const results = await rem.recall({ query: "What decisions were made about the database architecture?", namespace: "roam-research", limit: 5 }); results.forEach(m => { console.log(`Page: ${m.metadata.title}`); console.log(`Links: ${m.metadata.linked_pages.join(", ")}`); console.log(`${m.content.slice(0, 200)}...\n`); });

Preserving Roam Links in REM

By storing [[links]] as both tags and metadata, you can query by connection as well as semantics:

// Find all memories linked to a specific Roam page const linked = await rem.recall({ query: "Project Alpha", namespace: "roam-research", tags: ["Project Alpha"], limit: 10 });

Daily notes: Roam daily notes are especially valuable as AI memory. They contain timestamped decisions, meeting notes, and fleeting ideas that are hard to find later. REM's temporal awareness surfaces recent daily notes more prominently than old ones.

Give your Roam graph semantic search

Free tier. Roam JSON import. Natural language recall across pages.

Get Started