Cloud storage is a graveyard. Files go in, but nothing connects them. We wanted Obsidian's magic β€” bidirectional links, graph visualization β€” but for R2 buckets.

The Problem

Our AI agents store memories, docs, and context across 17 R2 buckets. Finding anything meant knowing exactly where it was. No discovery. No connections. Just flat file storage.

Obsidian solved this for local notes with [[wikilinks]]. Why couldn't we have the same for cloud storage?

The Solution: R2 Vault

We built a Worker that:

  1. Parses [[wikilinks]] from markdown files across buckets
  2. Indexes links in D1 for fast graph queries
  3. Renders an interactive force-directed graph
  4. Provides a file browser and viewer

Live at: r2-brain.srvcflo.workers.dev

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Worker    │────▢│     D1      │────▢│   Graph     β”‚
β”‚  (Parser)   β”‚     β”‚   (Links)   β”‚     β”‚    (UI)     β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚
       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    R2 Buckets                        β”‚
β”‚  atlas-docs β”‚ devflo-workspace β”‚ minte-blog β”‚ ...   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Wikilink Syntax

We extended standard wikilinks for cross-bucket references:

[[file]]                    β†’ same bucket
[[bucket/path/file.md]]     β†’ cross-bucket
[[bucket/file|Display]]     β†’ aliased link

The Parser

const WIKILINK_REGEX = /\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g;

function parseWikilinks(content, sourceBucket, sourcePath) {
  const links = [];
  let match;
  
  while ((match = WIKILINK_REGEX.exec(content)) !== null) {
    const [, target, alias] = match;
    links.push({
      source: `${sourceBucket}/${sourcePath}`,
      target: resolveTarget(target, sourceBucket),
      alias: alias || null
    });
  }
  
  return links;
}

D1 Schema

CREATE TABLE links (
  id INTEGER PRIMARY KEY,
  source_bucket TEXT NOT NULL,
  source_path TEXT NOT NULL,
  target_bucket TEXT NOT NULL,
  target_path TEXT NOT NULL,
  alias TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_source ON links(source_bucket, source_path);
CREATE INDEX idx_target ON links(target_bucket, target_path);

Graph Visualization

D3.js force-directed graph with:

  • Nodes = files
  • Edges = wikilinks
  • Colors = buckets
  • Size = connection count

Click a node to open the file viewer. Hover for metadata.

Results

  • 17 buckets connected
  • 936 files indexed
  • 165 links discovered
  • Sub-second graph rendering

Why This Matters

AI agents need connected knowledge, not isolated files. R2 Vault turns dumb storage into a knowledge graph that agents can traverse.

Next: semantic search via Vectorize, so agents can find related content even without explicit links.


Built with Cloudflare Workers, D1, R2, and D3.js. The second brain for cloud storage.