Mycelium is a local-first semantic memory and knowledge graph for Chrome. It quietly extracts readable long-form pages as you browse, stores their metadata and text in your Chrome profile, and makes everything searchable from a command palette. A separate graph view turns the same browsing memory into an explorable network of related pages.
Moss is the retrieval engine. Mycelium uses @moss-dev/moss-web and a Chrome offscreen document to run Moss's embedding and vector-search runtime in WebAssembly. Once the index is loaded, query embedding and retrieval happen inside the extension without a query network round trip.
OpenRouter is optional. The complete ingestion, search, snippet, history, and graph experience works without an OpenRouter key.
Browser history is useful when you remember a page's title or URL. It is much less useful when you only remember an idea, argument, or passage.
Mycelium treats browsing history as semantic memory:
- You read a substantial article.
- Mycelium extracts its readable text after a short delay.
- The page is cached locally and upserted into a Moss index.
- You later search for a concept instead of an exact title.
- Moss returns the most relevant pages from the loaded in-browser index.
- The graph lets you inspect related pages and return to the original source.
No separate Moss tab needs to remain open. The Moss runtime is owned by the extension.
- Uses Mozilla Readability first, with
article,main, and[role="main"]as conservative fallbacks. - Waits about 1.8 seconds after page load or in-page navigation before extraction.
- Indexes only HTTP(S) pages with at least 500 readable words.
- Records the URL, title, domain, favicon, excerpt, full readable text, word count, first visit, last visit, revisit count, and search-match count.
- Uses a stable URL hash so revisiting the same URL updates one memory instead of creating duplicates.
- Detects URL changes in single-page applications and schedules another extraction.
- Opens directly over an ordinary webpage in an isolated Shadow DOM.
- Debounces input by 150 ms and returns up to eight ranked results.
- Displays query-aware snippets, favicons, timestamps, relevance scores, result count, retrieval source, and measured latency.
- Supports arrow-key navigation,
Enterto open, andEscapeto close. - Shows recently ingested pages before a query is entered.
- Keeps a non-blocking local lexical fallback available while Moss initializes or if the runtime is temporarily unavailable.
- Renders up to 300 weighted page nodes with
d3-forceand Canvas 2D. - Groups pages loosely by domain and assigns stable domain colors.
- Sizes nodes using revisit and search-match activity.
- Builds and caches local content-affinity edges using normalized shared-term overlap, an
0.08threshold, and at most five outgoing candidates per source node. - Supports pan, zoom, node dragging, hover focus, graph search, and a detailed node inspector.
- Shows related pages, source metadata, word count, cached summary or local snippet, index size, last Moss sync, and total/visible node counts.
- Preserves the full memory count even when the visualization is capped at 300 nodes.
- Generates a single compact graph-node label after a page is indexed.
- Uses OpenRouter asynchronously, so summarization never blocks ingestion or search.
- Caches one summary per URL in
chrome.storage.local. - Falls back silently to the page title or a locally generated snippet on missing keys, model errors, rate limits, or network failures.
- Is disabled by default and is not required for semantic retrieval.
| Action | macOS | Windows/Linux | Notes |
|---|---|---|---|
| Open or close search on a normal page | Command + K |
Ctrl + K |
Captured by the content script. Some sites may also use this shortcut. |
| Extension command for search | Command + Shift + K |
Ctrl + Shift + K |
Chrome-managed; configurable at chrome://extensions/shortcuts. |
| Open the knowledge graph | Command + Shift + G |
Ctrl + Shift + G |
Opens graph.html in a new extension tab. |
Clicking the Mycelium toolbar icon also opens the palette. Chrome does not permit content scripts on internal pages such as chrome://extensions, the Chrome Web Store, or certain browser-owned tabs. Use the extension command or toolbar icon from a normal HTTP(S) page.
- Google Chrome 121 or newer
- Node.js 20.19 or newer
- pnpm 11 or newer
- A Moss project ID and project key
- An OpenRouter API key only if optional AI summaries are enabled
From this directory:
pnpm install
pnpm buildThe build creates the unpacked extension in dist/.
The build runs three Vite targets:
- The graph and offscreen extension pages.
- A single-file Manifest V3 background service worker.
- An IIFE content script that can be injected into ordinary pages.
The background build deliberately rejects dynamic imports because Chrome Manifest V3 service workers must be statically loadable. Moss's WASM runtime runs in offscreen.html, where it has the lifetime and DOM-capable environment it needs.
- Open
chrome://extensions. - Enable Developer mode in the upper-right corner.
- Click Load unpacked.
- Select this project's generated
distfolder—not the repository root and notdist/assets. - Confirm that the Mycelium card appears and is enabled.
- Open or refresh a normal article tab so the content script is present.
- Press
Command/Ctrl + K, or click the toolbar icon. - Enter a Moss project ID and project key in Settings.
- Click Save & search.
If the extension was already loaded before a rebuild, click its refresh icon on chrome://extensions and then refresh any article tabs you want to test. Removing and re-adding the extension clears its chrome.storage.local data, including credentials and cached pages.
Use this checklist to verify the full path:
- Open a public article containing at least 500 readable words.
- Leave the article open for two or three seconds.
- Press
Command/Ctrl + K. - Confirm that the article appears under Recently ingested.
- Search for a specific concept expressed in the article rather than only a word from its title.
- Check the search footer:
- LOCAL WASM means Moss handled the query from the loaded index.
- LOCAL CACHE means Mycelium returned its lexical fallback while Moss was unavailable or warming up.
- Open two or three additional long articles on related topics and repeat the wait.
- Open Knowledge graph from the palette or press
Command/Ctrl + Shift + G. - Search inside the graph, select a node, inspect its related pages, and open the original source.
The first index creation, credential change, or extension reload can take longer than a warm query because the client may need to connect, synchronize documents, and load the index. The latency badge measures the completed search path; it does not include earlier index creation or synchronization work.
Moss credentials are entered at runtime, not build time:
- Open the command palette.
- Select the gear icon.
- Enter the Moss project ID and project key.
- Click Save & search.
The credentials are trimmed and stored in chrome.storage.local. They are not compiled into the JavaScript bundle and are not stored with Chrome Sync. They are sent to Moss when the extension constructs the browser client.
Changing either credential disposes the active client, resets the loaded index state, reconnects using the new project, and begins syncing already cached pages in the background.
chrome.storage.localis profile-local storage, not a dedicated encrypted secrets vault. Treat the Chrome profile and local machine as trusted, and rotate any credential that has been exposed publicly.
The Moss index name is not secret. It defaults to mycelium-history and can be changed at build time:
cp .env.local.example .env.localMOSS_INDEX_NAME=mycelium-historyRebuild the extension after changing the value. Project IDs, project keys, and OpenRouter keys should not be placed in .env.local for this extension.
To enable optional summaries:
- Open Settings.
- Turn on Enable AI summaries.
- Enter an OpenRouter API key.
- Keep
openrouter/free, select a listed model, or enter a custom OpenRouter model ID. - Save the settings.
When enabled, Mycelium sends the page title and up to 12,000 characters of readable text to https://openrouter.ai/api/v1/chat/completions. The request asks for one sentence under 20 words, uses a 60-token cap, and caches a successful response by URL.
The OpenRouter key:
- is stored only in
chrome.storage.local; - is never included in Moss documents;
- is sent only as the OpenRouter bearer token;
- is not needed for search or graph generation.
Free-model availability and rate limits can change. Check the OpenRouter model list before relying on a specific model in production.
Mycelium uses the browser SDK rather than the Node SDK:
const client = await MossClient.create(projectId, projectKey, {
model: "moss-minilm",
});
await client.createIndex(indexName, documents, {
modelId: "moss-minilm",
});
await client.addDocs(indexName, documents, { upsert: true });
await client.loadIndex(indexName);
const result = await client.query(indexName, query, { topK: 8 });The browser client does not expose the Node SDK's session API. Mycelium therefore manages a durable named index with createIndex and addDocs, then calls loadIndex after completed mutations.
- Local capture: the content script writes the extracted page to
chrome.storage.localimmediately. - Background handoff: it sends a
MOSS_UPSERT_PAGEruntime message. - Offscreen runtime: the background worker creates
offscreen.htmlif needed and forwards the request. - Index creation or update: the offscreen Moss client creates the named index or upserts documents in batches of 50.
- Snapshot load: the client loads the updated index into the browser WASM runtime.
- Warm query: query embedding and vector retrieval execute against the loaded index without a query network round trip.
Index creation, document synchronization, and snapshot loading can use Moss's service. The local-query claim applies after loadIndex has completed.
The search UI is intentionally non-blocking:
- Input is debounced for 150 ms.
- The background attempts Moss retrieval when the index is ready.
- Moss requests have a 1.2-second application timeout.
- A local lexical search can render after roughly 250 ms while the semantic path is still pending.
- If Moss returns useful hits, they replace the fallback results.
- If initialization or querying fails, the cached local results remain usable and Moss is marked for reinitialization.
This separation keeps the palette responsive without pretending that lexical fallback results came from Moss. The UI reports the active source as LOCAL WASM or LOCAL CACHE.
Each indexed page is sent to Moss as a document with the stable page ID and readable text. Metadata includes:
- URL
- title
- favicon URL
- domain
- last-visited timestamp
- revisit count
OpenRouter credentials and summaries are not included in Moss documents.
ordinary HTTP(S) page
│
├─ content script (React + Shadow DOM)
│ ├─ Readability extraction after ~1.8 s
│ ├─ sensitive-site and 500-word guards
│ ├─ immediate chrome.storage.local write
│ └─ command-palette UI
│
│ Chrome runtime messages
▼
Manifest V3 background service worker
├─ search orchestration and local fallback
├─ settings, page metadata, graph cache, and summary cache
├─ optional OpenRouter summary queue
├─ toolbar and keyboard-command handling
└─ offscreen-document lifecycle
│
▼
offscreen.html
└─ @moss-dev/moss-web
├─ createIndex / addDocs synchronization
├─ loadIndex snapshot hydration
└─ in-browser WASM query embedding + vector retrieval
graph.html
├─ React interface
├─ d3-force layout
├─ Canvas 2D renderer
└─ local affinity-edge cache + Moss-backed graph search
The offscreen document matters because Manifest V3 service workers are short-lived and do not provide the environment expected by the Moss WASM stack. The background worker remains the coordinator, while the offscreen page owns the long-lived client and loaded index.
Mycelium uses these chrome.storage.local keys:
| Key | Contents |
|---|---|
mycelium_pages |
Page records keyed by stable URL hash |
mycelium_settings |
Moss credentials, OpenRouter settings, and model selection |
mycelium_summaries |
Optional summaries keyed by URL |
mycelium_revision |
Incrementing page-data revision used for graph caching |
mycelium_last_sync |
Timestamp of the most recent completed Moss sync |
mycelium_graph_cache |
Affinity edges and the revision they were built from |
The manifest requests unlimitedStorage because readable article text can outgrow Chrome's default extension-storage quota over time.
Mycelium is local-first, but not completely offline:
- Extracted page records are cached in the current Chrome profile.
- Moss documents are synchronized to the user's configured Moss project so the durable index can be created and updated.
- Loaded Moss queries run in browser WASM without a query network round trip.
- OpenRouter receives page content only when AI summaries are explicitly enabled.
The extension skips known or likely sensitive routes, including common webmail, inbox, banking, checkout, billing, payment, wallet, patient, and medical-record URLs. It also explicitly excludes several mail and payment hosts.
These guards are conservative URL heuristics, not a formal data-loss-prevention system. Review and extend isSensitivePage and isSensitiveUrl before deploying Mycelium in a regulated or enterprise environment.
mycelium/
├── public/
│ └── manifest.json Chrome Manifest V3 definition
├── src/
│ ├── background/
│ │ ├── index.ts orchestration, search, graph data, summaries
│ │ └── storage.ts typed chrome.storage.local helpers
│ ├── content/
│ │ ├── index.tsx extraction, shortcut capture, Shadow DOM mount
│ │ ├── Palette.tsx search and settings UI
│ │ └── palette.css page-isolated palette styling
│ ├── graph/
│ │ ├── App.tsx graph search, inspector, stats, controls
│ │ ├── GraphCanvas.tsx d3-force simulation and Canvas renderer
│ │ └── graph.css graph styling
│ ├── offscreen/
│ │ └── index.ts Moss client and WASM index lifecycle
│ └── shared/
│ ├── types.ts messages, records, settings, graph types
│ └── utils.ts URL hash, snippets, timestamps, page weighting
├── graph.html graph extension-page entry
├── offscreen.html Moss offscreen-page entry
├── vite.config.ts graph/offscreen build and shared constants
├── vite.background.config.ts single-file service-worker build
└── vite.content.config.ts IIFE content-script build
# Start the Vite development server for extension pages
pnpm dev
# Type-check without emitting files
pnpm typecheck
# Build every extension target into dist/
pnpm build
# Run the complete verification sequence
pnpm checkFor normal extension testing, pnpm build plus Chrome's extension refresh button is more representative than pnpm dev, because the background worker and content script are separately bundled extension targets.
- Change the source.
- Run
pnpm check. - Open
chrome://extensions. - Click the refresh icon on the Mycelium card.
- Refresh the article tab so Chrome replaces the old content script.
- Re-test ingestion, search, and graph opening.
If a content script from an earlier build is still mounted in a tab, Chrome can report Extension context invalidated. Refreshing that webpage attaches the newly loaded extension context.
- Confirm Mycelium is enabled at
chrome://extensions. - Use a normal HTTP(S) page, not a
chrome://page, Chrome Web Store page, PDF viewer, or another restricted surface. - Refresh tabs that were already open when the extension was installed or reloaded.
- Try the toolbar icon.
- Check
chrome://extensions/shortcutsfor a conflicting Chrome-managed shortcut. - Remember that direct page capture uses
Command/Ctrl + K, while the manifest command defaults toCommand/Ctrl + Shift + K.
- Wait at least two seconds after the page becomes readable.
- Confirm the page contains 500 or more readable words.
- Refresh the page after loading a new extension build.
- Check that the URL is not caught by a sensitive-site rule.
- Some heavily scripted or paywalled pages may not expose readable article text to Readability.
This means the local lexical fallback answered the query. Verify the Moss credentials, wait for the initial sync, and search again. Saving credentials with cached pages triggers a background sync automatically.
- First confirm that pages appear under Recently ingested.
- Search for concepts present in those pages.
- Open Settings and re-save valid Moss credentials to force a sync.
- Inspect the service worker and offscreen document from
chrome://extensionsfor runtime errors. - Check the configured Moss project for credential or index-access problems.
The graph visualizes locally cached pages, so it needs at least one successfully extracted article. Visit several long articles, wait for ingestion, and reopen the graph. Related edges appear only when page pairs exceed the local affinity threshold.
That is expected: Mycelium closes the overlay after requesting graph.html in a new extension tab. If no new tab appears, check Chrome's pop-up behavior and extension runtime errors; the background message path provides a fallback when window.open is unavailable.
- Run
pnpm buildagain. - Confirm
dist/assets/background.jsexists. - Confirm
dist/manifest.jsonpoints toassets/background.js. - Remove any stale unpacked copy that points to a different folder.
- Load the generated
distfolder again.
The build verifies that the service worker is a single emitted file with no static or dynamic imports. Do not point Chrome at a partially generated dist directory.
Both Moss fields are required. The save action is disabled until the project ID and project key contain non-whitespace characters. A successful save closes Settings and returns to search immediately; index synchronization continues in the background.
- Mycelium is an unpacked development extension; it is not packaged for the Chrome Web Store.
- Ingestion is limited to readable HTTP(S) pages with at least 500 words.
- Sensitive-site detection is heuristic and may need organization-specific rules.
- The graph renders at most 300 nodes for predictable Canvas performance.
- Graph edges currently use local shared-term affinity; graph search uses the same Moss semantic path as the palette when Moss is ready.
- Initial synchronization is not the same as warm-query latency.
- There is no in-product delete, export, pause-indexing, or per-site allow/deny interface yet.
- Removing the extension clears its Chrome-local cache and settings.
- Moss machine-readable documentation index
- Moss Browser/WASM SDK
- Moss browser-extension architecture
- Official Moss JavaScript browser example
- Mozilla Readability
- Chrome offscreen documents
- Chrome extension commands
- OpenRouter chat API
No license file is currently included. Add one before distributing or accepting external contributions.