How Rowboat Implements an Obsidian-Compatible Markdown Vault and Backlink System

Rowboat stores its knowledge base as a local Obsidian-compatible Markdown vault in ~/.rowboat/knowledge/, using wiki-style [[...]] backlinks that resolve to canonical file paths through helper utilities in wiki-links.ts.

The rowboatlabs/rowboat repository maintains a fully functional Obsidian-compatible Markdown vault that enables bidirectional linking between notes. This local-first architecture organizes knowledge into categorized markdown files while preserving compatibility with standard Obsidian workflows and syntax.

Vault Structure and Organization

Directory Layout in ~/.rowboat/knowledge/

Rowboat creates a dedicated workspace directory inside the user's home folder at ~/.rowboat/. Within this workspace, the vault resides under the knowledge/ subdirectory. According to the assistant instructions defined in apps/x/packages/core/src/application/assistant/instructions.ts (lines 41-45), this folder contains "plain markdown with Obsidian-style backlinks" organized into specific categories.

The Four Top-Level Categories

The vault enforces a strict four-folder taxonomy that maps directly to markdown sub-folders:

  • People (knowledge/People/) — One markdown file per person (e.g., John Smith.md)
  • Organizations (knowledge/Organizations/) — One file per company or team
  • Projects (knowledge/Projects/) — Files describing ongoing initiatives
  • Topics (knowledge/Topics/) — Subject-matter notes and recurring themes

Each note is a standard .md file that may contain Obsidian-style wiki links ([[…]]) pointing to other notes in the same vault.

The core backlink logic resides in apps/x/apps/renderer/src/lib/wiki-links.ts. This module provides canonicalization functions that ensure every wiki link resolves to a valid vault path:

  • stripKnowledgePrefix — Removes the leading knowledge/ prefix if present (lines 3-5)
  • normalizeWikiPath — Trims leading slashes or ./ and then strips the prefix (lines 6-9)
  • ensureMarkdownExtension — Guarantees the path ends with .md, adding the extension if missing (lines 11-15)
  • toKnowledgePath — Transforms a raw wiki link (e.g., [[People/John]]) into a full workspace path (knowledge/People/John.md). Returns null for illegal paths (empty, containing .., or trailing slash) (lines 17-21)
  • wikiLabel — Generates a human-readable label from a link path (file name without extension) (lines 23-27)

These utilities guarantee that any backlink is always a valid, markdown-backed, Obsidian-compatible reference within the vault.

Serializing ProseMirror Nodes to Markdown

When the rich-text editor serializes its ProseMirror document, the markdown-editor.tsx component (located at apps/x/apps/renderer/src/components/markdown-editor.tsx) converts wikiLink nodes into literal Obsidian syntax:

else if (node.type === 'wikiLink') {
  const path = (node.attrs?.path as string) || ''
  blocks.push(`[[${path}]]`)
}

This logic (lines 33-36) ensures that backlink data is stored exactly as Obsidian expects — a double-bracket link referencing another markdown file inside the vault.

The application handles backlink interactions through two primary flows in App.tsx:

Opening an existing link — When a user clicks a wiki link, the application resolves the clicked text using toKnowledgePath, then ensures the target file exists via ensureWikiFile before opening it in the editor (lines 2020-2027).

Creating a new note — The Create option in the wiki-link command palette invokes wikiLinks.onCreate, which downstream calls workspace-writeFile with the computed knowledge path.

UI components such as mention-popover.tsx (line 71) utilize wikiLabel to render human-readable link titles without file extensions.

Practical Usage Examples

Creating New Notes Programmatically

To create a new person note from a React component:

// Creates knowledge/People/Aria Lee.md if it doesn't exist
wikiLinks?.onCreate?.('People/Aria Lee')

The onCreate callback internally invokes toKnowledgePath('People/Aria Lee') to generate knowledge/People/Aria Lee.md, ensures the .md extension is present, and writes the file via the workspace API.

Inside any vault note (e.g., knowledge/Projects/Alpha.md):

We discussed the timeline with [[People/John Smith]] and [[Organizations/Acme Corp]].

When the editor serializes, each [[…]] reference becomes a wikiLink node that preserves the original syntax, maintaining full Obsidian compatibility.

import { toKnowledgePath, wikiLabel } from '@/lib/wiki-links'

const raw = 'People/John Smith'               // user input from command palette
const fullPath = toKnowledgePath(raw)          // → 'knowledge/People/John Smith.md'
const label = wikiLabel(raw)                   // → 'John Smith'

The fullPath variable can be passed to workspace-readFile to fetch note contents, while label provides UI display text.

Key Source Files and Responsibilities

File Role
apps/x/packages/core/src/application/assistant/instructions.ts Defines the vault layout conventions and required path structures for the assistant.
apps/x/apps/renderer/src/lib/wiki-links.ts Core utilities for normalizing, validating, and translating wiki links to canonical vault paths.
apps/x/apps/renderer/src/components/markdown-editor.tsx Serializes ProseMirror documents, converting wikiLink nodes into [[…]] markdown syntax.
apps/x/apps/renderer/src/App.tsx Orchestrates opening and creating wiki-linked notes, ensuring target files exist before editing.
apps/x/apps/renderer/src/components/mention-popover.tsx Renders human-readable link labels using wikiLabel for command palette and mention interfaces.
apps/x/packages/core/src/knowledge/* Implements read/write operations for the knowledge base used by the assistant's memory agents.

Summary

  • Rowboat maintains an Obsidian-compatible Markdown vault in ~/.rowboat/knowledge/ with four top-level categories: People, Organizations, Projects, and Topics.
  • Backlinks use standard Obsidian wiki-link syntax ([[Category/Note Name]]) stored as plain text in markdown files.
  • The wiki-links.ts utility library canonicalizes paths, ensures .md extensions, and prevents directory traversal attacks by rejecting paths containing ...
  • ProseMirror serialization in markdown-editor.tsx converts rich-text wiki-link nodes into literal [[…]] strings for persistent storage.
  • Runtime resolution in App.tsx automatically creates missing target files when users click backlinks, ensuring the graph remains navigable even for dangling references.

Frequently Asked Questions

Where does Rowboat store the Obsidian-compatible vault locally?

Rowboat stores the vault inside the user's home directory at ~/.rowboat/knowledge/. This location is treated as the workspace root, with four subdirectories (People, Organizations, Projects, Topics) organizing the markdown files according to the conventions defined in apps/x/packages/core/src/application/assistant/instructions.ts.

When a user clicks a wiki-link in the UI, App.tsx invokes toKnowledgePath to resolve the link to an absolute vault path, then calls ensureWikiFile (lines 2020-2027) to verify the file exists. If the target is missing, Rowboat automatically creates an empty markdown file at the resolved path before opening it in the editor, ensuring seamless navigation even for new or "dangling" references.

What are the four main categories in the Rowboat knowledge vault?

The vault organizes content into four top-level folders under knowledge/: People (individuals), Organizations (companies and teams), Projects (ongoing initiatives), and Topics (subject-matter notes and recurring themes). Each category contains individual markdown files representing specific entities within that classification.

The primary normalization logic resides in apps/x/apps/renderer/src/lib/wiki-links.ts. This module exports toKnowledgePath, normalizeWikiPath, ensureMarkdownExtension, and wikiLabel, which collectively handle path canonicalization, extension enforcement, security validation (rejecting .. traversal), and human-readable label generation for the Obsidian-compatible backlink system.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →