Skip to documentation

eli workspace

eli workspace — overview & capability map

eli is a files-first, agent-native, async-collaborative “second brain”: a single Next.js 16 codebase that runs in two modes behind one storage abstraction — plain markdown on your disk, or a multi-tenant SaaS on Supabase.

About this section

These pages document the eli workspace product (the eli-secondbrain codebase) — a sibling of the eli.ai platform documented in the other tabs. Every diagram here follows one convention: a simplified and a detailed view, at both the system (component) level and the data-flow (runtime) level. Download the full 25-page draw.io bundle for the complete per-cluster diagram set.

One codebase, two modes

  • Local mode (ELI_MODE=local, the default — env.ts:15): a guest, no-account app on http://localhost:3737 (package.jsonnext dev --turbopack -p 3737). Your notes are plain markdown files in a vault directory you pick; everything else — FTS5 full-text index, wikilink graph, embeddings, versions — lives in a rebuildable per-vault SQLite database at ~/.eli/index/<hash>.db. Delete the index and eli rebuilds it from the files.
  • SaaS mode (ELI_MODE=saas): a multi-tenant hosted product on Supabase — Postgres (+ tsvector, pgvector) with Row-Level Security as the security boundary (33 policies over 9 tables, pgTAP-verified in CI), magic-link/OAuth auth, workspaces with role-based memberships, comments, share links, and Realtime change notifications.

Both modes render the same RSC/component tree: route groups app/(local)/ and app/(saas)/ share components/* and app/_actions/*, and every surface talks to storage through the StorageAdapter interface (lib/storage/adapter.ts), resolved by getStorage() (lib/storage/index.ts:20). “Agent-native” is not a bolt-on: a 16-tool MCP server (lib/mcp/server.ts) is served over stdio (bin/eli-mcp.mjs) and streamable HTTP (app/api/mcp/[transport]/route.ts), agents that bypass MCP and edit .md files directly are picked up by a chokidar watcher and versioned with authorKind: 'agent', and an OpenAI-compatible /v1 facade (app/v1/) makes the vault a drop-in RAG backend for any OpenAI-SDK client.

The four invariants

Everything in this doc set hangs off four load-bearing invariants (docs/ARCHITECTURE.md §1):

  1. The markdown file on disk (or body_md in Postgres) is the source of truth. FTS fragments, note_links, embeddings, the folder tree — all rebuildable derived index.
  2. Every UI surface goes through getStorage(). No component, server action, API route, or MCP tool imports LocalAdapter or SupabaseAdapter directly (ADR-0004). Swapping modes is a config switch, not a refactor.
  3. No raw HTML controls outside components/ui/* — all controls are shadcn/ui primitives, so theming, a11y, and keyboard handling stay uniform.
  4. ~/.eli/config.json is the single user-scope state file (active vault, tokens, favourites, pairings). Vault contents never leak into it.

Phase status

Per the phase-file headers, docs/IMPLEMENTATION-LOG.md, and the git log (134 commits) — not the status table in docs/phases/README.md, which still claims Phases 2–7 are “not started” and is stale, as are parts of docs/ARCHITECTURE.md §11’s “not yet built” inventory. This doc set reflects code reality, cross-checked against the repo source at the current head (a8c4269).

PhaseScopeStatus
P1Local MVP (vault, editor, FTS, palette, versions, SSE live refresh)DONE (8c9b649), carry-overs closed in later sweeps
P2Git versioning + SaaS auth + workspacesDONE — all 22 tasks / 9 tracks (1708adc)
P3MCP server + agent integrationsDONE — all 13 tracks (0287cbb)
P4Async collaboration (anchors, diffs, comments, shares, conflicts)DONE — data path closed, all UI carry-overs landed
P5Clipper + AI (gateway, embeddings, hybrid search, chat, /v1)DONE in scope — only the Chrome MV3 extension declared out of scope
P6Templates + mobile + PWADONE in scope — Tauri shell and git-as-bridge sync declared out of scope
P7Stretch spikes (graph view, CRDT, voice, …)NOT STARTED, except P7-AGENT-01 usage dashboard (4db76b0)

Build health at the last log entry: 712 unit tests (+1 intentional skip), 25/25 Playwright e2e, 11 MCP harness tests, typecheck/lint/build green.

How to read this doc set

The Workspace tab is nine cluster pages plus one end-to-end journeys page, backed by the downloadable multi-page draw.io bundle. The central rule, applied uniformly:

Every subject gets BOTH a simplified view AND a detailed view, at BOTH the system (component/architecture) level AND the data-flow (runtime/sequence) level — and every cluster page explains how the subject works SEPARATELY (standalone) and TOGETHER (composed with the other clusters).

Concretely, each cluster page contains: System view — simplified (≤10-node component sketch), System view — detailed (full component map with file paths), Data-flow — simplified (the happy-path pipeline), Data-flow — detailed (sequence diagram with local-vs-SaaS branches and file:line anchors), plus a Standalone vs composed section. The draw.io bundle mirrors this: one page per cluster with labeled SIMPLIFIED and DETAILED bands, plus the four platform-level pages that correspond to the diagrams on this page. The journeys page replays three complete journeys (local-first solo loop, SaaS team loop, external-agent loop) across all clusters.

Cross-cutting surfaces that the multi-agent analysis treated separately — trash/bulk actions and the daily digest — are folded in where they live: storage modeling in core data & storage, the trash/bulk lifecycle in versioning & history, the digest builder in the AI platform, and the cron/prune machinery in platform ops.

#ClusterOne-line purposeKey dirs
C1Core data & storageStorageAdapter contract + 4 implementations (Local, Supabase, Memory, JsonFile), both DB schemas, vault scan/index pipeline, chokidar watcher, config store, mode bootstraplib/storage/, lib/db/, lib/domain/, lib/watch/, lib/config/, env.ts, instrumentation.ts
C2Authoring & navigation UXBlockNote/raw editor with debounced autosave + conflict banner, sidebar/palette/inspector shell, keybindings, theming, mobile & a11ycomponents/, app/(local)/, app/_actions/, lib/keybindings/
C3Search & embeddingsAuto-embed queue (30s debounce) → 1536-dim vectors (SQLite BLOB / pgvector); FTS + vector KNN fused with weighted RRF (k=60, fts 0.7 / vector 0.3)lib/search/, lib/ai/embeddings.ts, lib/ai/auto-embed-queue.ts, app/api/search/hybrid/, scripts/embed-backfill.mjs
C4AI platformGateway-routed model tiers, three-tier BYOK key resolver, seven /api/ai/* features, persisted RAG chat with function tools, daily digest, usage metering, /v1 OpenAI facadelib/ai/, app/api/ai/, app/v1/, components/ai/, lib/chat/
C5Agent-native surface (MCP)16 MCP tools over stdio + streamable HTTP, PAT/local-token auth, rate limits, five-agent setup wizard, AGENTS.md generator, OpenClaw memory compatlib/mcp/, bin/, app/api/mcp/, lib/setup/, lib/agents-md/, lib/openclaw/
C6Versioning, history & recoveryDebounced git auto-commit (local bookkeeping layer) + git-free section-level diff/restore UI reading the adapters’ versions table; trash/bulk lifecyclelib/git/, components/diff/, app/_actions/diff.ts, app/_actions/bulk.ts
C7SaaS collaborationSupabase auth, workspaces/memberships/invites under RLS, section-anchored comments, share links /s/[token], Realtime toasts, audit logapp/(saas)/, lib/auth/, lib/share/, components/comments/, components/realtime/, lib/email/
C8Capture & templatingBookmarklet + /api/clipingest, PWA share-target, zip/JSON import & export, template language + editor + AI generationlib/clipper/, lib/templates/, app/api/clip/, app/api/import/, app/api/export/, app/share/, app/manifest.ts
C9Platform opsCron (digest 13:00, trash-prune 04:00 UTC), events bus + SSE live refresh, dev/test endpoints, seeding & bench CLIs, native-ABI self-heal, MCP bin packaging, full test pyramid + CI gatesapp/api/cron/, app/api/events/, app/api/dev/, scripts/, tests/, .github/workflows/
10End-to-end journeysThree composed walkthroughs: local-first solo loop, SaaS team collaboration loop, external-agent (MCP) loopspans all of the above

Platform system view — simplified

The 10-second mental model: two kinds of principals (humans and agents), three kinds of surfaces (UI, MCP, HTTP APIs), one storage seam, two backends, one AI gateway.

Platform system view — simplified

Everything below elaborates this picture; nothing contradicts it. The one asymmetry worth memorising now: in local mode the .md file is written first (atomic tmp+rename) and the index catches up; in SaaS mode the write is a single transactional notes_save() RPC with optimistic concurrency on content_hash.

Platform system view — detailed

Each cluster’s 2–4 core components and every material cross-cluster edge. Colors encode cluster identity and are reused in the draw.io pages.

Platform system view — detailed

Notable structural facts this diagram encodes:

  • C1 is the only cluster everyone depends on. ADR-0004 makes StorageAdapter the binding contract; a shared conformance suite (tests/adapter/contract.ts) must pass for every adapter.
  • C5 and C4 never talk to each other’s tool registries. MCP tools (*_artifact naming) and AI-SDK chat tools (*_note naming, lib/ai/tools.ts) are deliberately parallel surfaces over the same storage (docs/tools.md:11-14).
  • The save fan-out is where clusters compose: one updateNote() in C1 triggers C3 (embed queue), C6 (git queue), C9 (events bus → SSE), and — in SaaS — C7 (Realtime notification).
  • C9’s SSE channel and C7’s Realtime toast are mode-twin mechanisms for the same UX outcome (other tabs/users see the change): in-process events bus locally, Supabase Realtime in SaaS.

Integration map — how the clusters compose

The clusters as nodes; every material runtime dependency as a labeled edge.

Integration map — every material runtime dependency as a labeled edge
ProducerConsumerMechanismCode anchor
C2C1Server actions call getStorage() for every mutation/readapp/_actions/notes.tslib/storage/index.ts:20
C5C1Every MCP tool resolves McpContextStorageAdapter; no mode branching in tool codelib/mcp/context.ts, lib/mcp/server.ts
C5C3search tool routes kind: semantic|hybrid through the hybrid orchestratorlib/mcp/tools/search.tslib/search/hybrid.ts
C5C8apply_template delegates to the adapter-level template enginelib/mcp/tools/template.tslib/templates/
C8C1handleClip / import / export write and read notes via storagelib/clipper/, app/api/clip/route.ts, app/api/import/, app/api/export/
C7C1Session cookie + eli_ws workspace cookie select the RLS-scoped SupabaseAdaptermiddleware.ts, lib/storage/supabase*
C1C3The note-save path arms a per-note 30s embed debounce (DEBOUNCE_MS = 30_000); trigger sits in the save action for UI edits — MCP/watcher edits rely on the batch embed route or backfill CLIapp/_actions/notes.ts:97-98 lib/ai/auto-embed-queue.ts
C1C6Local saves enqueue relPath into the per-vault commit queue (default debounceSec: 30)lib/storage/local/writes.ts:331 (enqueueCommit) → lib/git/commit-loop.ts:108
C1C9LocalAdapter writes and the chokidar watcher publish to the in-process events buslib/watch/events-bus.ts, lib/watch/chokidar.ts
C9C2SSE stream → <VaultEventsBridge> debounced router.refresh()app/api/events/route.ts, components/vault-events-bridge.tsx
C4C3Chat injects hybrid-search hits as RAG system contextapp/api/ai/chat/route.ts:178 (hybridSearch)
C3C4Embedding generation goes through the gateway embed tier + key resolverlib/ai/embeddings.ts, lib/ai/resolver.ts
C4C1Chat function tools (read_note, update_note, …) hit storage; writes audited, capped at 6 stepslib/ai/tools.ts
C2C4Inline AI actions and the “Ask your vault” chat UI call /api/ai/*components/editor/, components/ai/vault-chat.tsx
C9C4Vercel Cron (13:00 UTC) triggers the digest runnervercel.json, app/api/cron/digest/route.ts lib/ai/digest-runner.ts
C9C1Vercel Cron (04:00 UTC) purges trash older than 30 daysapp/api/cron/trash-prune/route.ts
C6C1Diff UI reads the adapters’ append-only versions table (never git)app/_actions/diff.ts, lib/storage/local/versions.ts
C7C2Supabase Realtime note:updated → refresh toast in other members’ tabscomponents/realtime/note-watcher.tsx

Running a cluster in isolation

The seams are real — meaningful sub-products fall out of subsets of clusters:

  • C1 + C2 = a complete offline notes app. Vault picker, BlockNote editing, buckets/tags/folders, FTS search, version history — no network, no API keys, no git. This was literally Phase 1 as shipped.
  • C1 + C5 = a headless agent vault. node bin/eli-mcp.mjs with VAULT_PATH boots a LocalAdapter directly — no Next.js server, no UI. An agent gets full CRUD, FTS search, versions, comments, and templates against plain files.
  • C1 + C3 + C4 = a RAG service over a vault. Point any OpenAI-SDK client at /v1/chat/completions with a PAT and get hybrid-search-grounded answers; no eli UI involved.
  • C1 + C7 = collaboration without AI. A team on Supabase gets workspaces, RLS-scoped notes, comments, invites, shares — no gateway key configured, AI routes simply unavailable.
  • C6’s git layer and C9 are shed-able. ELI_DISABLE_GIT=1 turns the commit loop into a no-op (versions still recorded in C1’s versions table); ELI_DISABLE_LIVE_EVENTS=1 silences SSE (the e2e suite runs this way). The product degrades gracefully, never breaks.
  • C8 standalone is thin by design — the clip handler, importers, and template engine are libraries over C1; the bookmarklet and PWA share-target are just alternative front doors.

The full loop

Composed, the clusters form one closed loop: content enters via C2 (typing), C8 (clip/share/import), or C5 (agent tools); C1 persists it file-first with a version row and fans out to C3 (embedding), C6 (git commit), and C9 (SSE/Realtime refresh); C3 makes it findable by keyword + meaning; C4 reads it back as RAG context for chat and digests, and can write back through the same storage seam (audited, attributed); C7 layers humans-in-the-loop on top (comments, shares, conflict snapshots); C6 guarantees any state is recoverable; and C9 keeps the whole thing observed, tested, and pruned. Every write — human, clipper, agent, or cron — is the same write: one StorageAdapter call, one authorKind-attributed version row, one fan-out.

Data-flow spine — simplified

The life of a note, compressed to one pipeline:

Data-flow spine — simplified

Data-flow spine — detailed

The full life of one note across all nine clusters, with local-vs-SaaS branches where the paths diverge.

Data-flow spine — detailed

Divergence summary — the spine is identical in shape in both modes; only four hops differ:

HopLocalSaaS
Persistatomic tmp+rename .md write, then index (lib/storage/local/writes.ts)transactional notes_save() RPC, OCC on content_hash
Keyword indexSQLite FTS5, bm25 (lib/db/)Postgres tsvector, ts_rank_cd
Change propagationchokidar + events bus → SSE (lib/watch/, app/api/events/)Supabase Realtime (components/realtime/note-watcher.tsx)
Cold-storage historydebounced git commit (lib/git/commit-loop.ts)none — versions table only

The diagrams on this page correspond to the four platform pages of the draw.io bundle — “Platform — System (Simplified)”, “Platform — System (Detailed)”, “Platform — Integration Map”, and “Platform — Data-Flow Spine”; per-cluster pages follow the same color coding. Download eli-workspace-architecture.drawio.