Skip to content

Latest commit

 

History

History
232 lines (186 loc) · 9.11 KB

File metadata and controls

232 lines (186 loc) · 9.11 KB

KnowledgeFabric — Grilled Decisions

All architecture decisions for the 3-hour MLP sprint, locked via /grill-me.


Decision Record

# Decision Choice
1 Access grant Private repo + GitHub PAT stored in backend
2 PAT ownership Backend holds PAT, proxies via /shared/{user}/index.md endpoint
3 User B auth Supabase GitHub OAuth + RLS allowlist (shares table)
4 MCP location Local daemon
5 index.md structure H2 sections as nodes, loose convention
6 Vector search pgvector in Supabase, server-side on manual refresh
7 Embedding API Supabase built-in AI (no external key)
8 LLM clients Claude Desktop + Obsidian
9 Obsidian sync On-demand write when read_shared_node is called
10 Connection flow Username entry OR share link — both
11 Freshness Manual refresh button in dashboard
12 Imbalance warning Node count ratio ≥ 50% borrowed → show warning
13 Context injection Structured block: [Borrowed from @user · Topic · date]
14 CLI npx knowledge-fabric, Node.js
15 Repo structure Monorepo, npm workspaces
16 Pre-sprint setup Supabase schema + shared types + demo wikis done night before

Architecture

┌─────────────────────────────────────────────────────┐
│              Next.js WebApp (packages/frontend)     │
│  Dashboard · Share (username + link) · Refresh btn  │
│  Imbalance warning · Graph view                     │
└───────────────────┬─────────────────────────────────┘
                    │ Supabase client (anon key + OAuth session)
┌───────────────────▼─────────────────────────────────┐
│              Supabase Backend                       │
│  GitHub OAuth · RLS on shares table                 │
│  PAT stored per user · proxy endpoint               │
│  pgvector + built-in AI embeddings                  │
│  Edge Function: fetch → parse H2 → embed → upsert   │
└───────────────────┬─────────────────────────────────┘
                    │ HTTPS (Supabase session token)
┌───────────────────▼─────────────────────────────────┐
│         npx knowledge-fabric (packages/cli)         │
│  Local MCP daemon · stdio transport                 │
│  Tools: list_shared_nodes · read_shared_node        │
│         search_shared_knowledge                     │
│  On read: writes to {vault}/shared/{friend}/*.md    │
│  On search: pgvector similarity query               │
└───────────────────┬─────────────────────────────────┘
                    │ MCP protocol
     ┌──────────────┴──────────────┐
     ▼                             ▼
Claude Desktop                 Obsidian
(claude_desktop_config.json)   ({vault}/shared/ folder)

Supabase Schema

-- Users (managed by Supabase Auth)
-- auth.users: id, email, raw_user_meta_data->>'user_name' = github username

-- PAT storage
create table user_pats (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users not null,
  github_pat text not null,           -- encrypted at rest
  created_at timestamptz default now()
);

-- Access control
create table shares (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid references auth.users not null,
  allowed_user_id uuid references auth.users,     -- null = invite link pending
  invite_token uuid unique,                        -- for share link flow
  created_at timestamptz default now()
);

-- Parsed knowledge sections
create table knowledge_sections (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid references auth.users not null,
  section_title text not null,        -- H2 header text
  content text not null,
  last_synced_at timestamptz default now()
);

-- Embeddings (pgvector)
create extension if not exists vector;
create table knowledge_embeddings (
  id uuid primary key default gen_random_uuid(),
  section_id uuid references knowledge_sections not null,
  owner_id uuid references auth.users not null,
  embedding vector(384),              -- Supabase built-in AI dimension
  created_at timestamptz default now()
);

RLS policies: shares table enforces that User B can only read sections where (owner_id, allowed_user_id) matches their session. User B never sees the PAT.


Monorepo Structure

knowledge-fabric/
  package.json                  # npm workspaces root
  packages/
    shared/
      types.ts                  # SharedNode, Share, KnowledgeSection
    frontend/
      package.json
      app/                      # Next.js app router
        dashboard/              # friend list, shared nodes, graph
        share/[token]/          # invite link landing page
    cli/
      package.json              # name: "knowledge-fabric", bin: "knowledge-fabric"
      src/
        index.ts                # MCP server entry
        tools/
          list_shared_nodes.ts
          read_shared_node.ts
          search_shared_knowledge.ts
        auth.ts                 # Supabase session management
        obsidian.ts             # writes to {vault}/shared/{friend}/
        claude_desktop.ts       # writes to claude_desktop_config.json
  supabase/
    migrations/
    functions/
      sync-knowledge/           # Edge Function: fetch → parse → embed → upsert

MCP Context Injection Format

When read_shared_node is called, the returned content is wrapped:

[Borrowed from @raeyoung · Kubernetes · last synced 2026-04-26]

{section content verbatim}

Claude Desktop cites the source naturally in its answer. The attribution block is what makes judges see provenance, the social layer, and product differentiation — all at once.


Imbalance Warning

Fires when borrowed sections ≥ 50% of total sections in a query context.

borrowed_count / (local_count + borrowed_count) >= 0.5
→ "This answer draws heavily on @raeyoung's knowledge (X of Y sections)."

Implemented in the MCP server before returning results. No embedding comparison needed — pure node count.


npx knowledge-fabric Onboarding Flow

$ npx knowledge-fabric

1. Opens browser → Supabase GitHub OAuth
2. Stores session token in ~/.config/knowledge-fabric/token
3. Asks: "Where is your Obsidian vault?" → creates {vault}/shared/
4. Writes MCP entry to ~/Library/Application Support/Claude/claude_desktop_config.json
5. Prints: "Restart Claude Desktop. You're connected."

If time is short during sprint, cut in this order:

  1. Cut Obsidian vault setup → print path, user adds manually
  2. Cut browser OAuth → paste token from dashboard instead

Pre-Sprint Checklist (Night Before)

Infrastructure
  ☐ Supabase project created, GitHub OAuth provider configured
  ☐ pgvector extension enabled
  ☐ Schema migrated (tables above)
  ☐ Supabase anon key + URL in shared .env

Repo
  ☐ Monorepo scaffolded with npm workspaces
  ☐ packages/shared/types.ts with SharedNode, Share, KnowledgeSection
  ☐ Next.js bootstrapped in packages/frontend
  ☐ npm package scaffolded in packages/cli

Demo content  ← most important, do not skip
  ☐ Minho's index.md written with real H2 sections
  ☐ Raeyoung's index.md written with real H2 sections
  ☐ Byungmin's index.md written with real H2 sections
  ☐ All 3 GitHub accounts connected to Supabase
  ☐ At least one share relationship pre-created

Critical: Demo index.md files must exist before the sprint starts. Content creation during the sprint kills the clock.


Demo Script (4 Scenes)

  1. Connect — Minho adds Raeyoung by GitHub username in the dashboard
  2. Share — Raeyoung's index.md H2 sections appear as shared nodes
  3. Reference — Minho asks Claude Desktop a question → answer cites [Borrowed from @raeyoung · ...] → file appears in Obsidian shared/raeyoung/
  4. Visualize — Dashboard shows imbalance warning + knowledge edge between users

Future Milestones (Out of Scope for MLP)

  • Scoped tokens — replace PAT with file-path-scoped credentials
  • Webhook-based freshness — GitHub push → auto re-embed (replace manual refresh)
  • Client-side embedding push — User A's local MCP generates and pushes embeddings
  • Confidence scoresUnverified tag on auto-ingested nodes
  • Knowledge Lifecycle — cleanup of low-connectivity nodes
  • CC License reciprocity — "you borrowed mine, share yours" enforcement
  • Obsidian plugin — native integration without filesystem sync