All architecture decisions for the 3-hour MLP sprint, locked via
/grill-me.
| # | 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 |
┌─────────────────────────────────────────────────────┐
│ 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)
-- 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.
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
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.
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
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:
- Cut Obsidian vault setup → print path, user adds manually
- Cut browser OAuth → paste token from dashboard instead
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.
- Connect — Minho adds Raeyoung by GitHub username in the dashboard
- Share — Raeyoung's
index.mdH2 sections appear as shared nodes - Reference — Minho asks Claude Desktop a question → answer cites
[Borrowed from @raeyoung · ...]→ file appears in Obsidianshared/raeyoung/ - Visualize — Dashboard shows imbalance warning + knowledge edge between users
- 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 scores —
Unverifiedtag 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