diff --git a/README.md b/README.md index 7314618..0c69377 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,54 @@ DWN records ──> JWE encrypted at rest (user's DID keys) The sidecar never leaves the device. The DWN records are the portable, sovereign data. +## Search Setup + +memoryd uses **hybrid search** that combines vector similarity (sqlite-vec) with full-text keyword matching (FTS5) via reciprocal rank fusion. The quality of vector search depends on the configured embedding provider. + +Three embedding providers are available, configured via the `MEMORYD_EMBEDDING_PROVIDER` environment variable: + +### `noop` (default) + +No external dependencies required. Only keyword (FTS5) search produces meaningful results. Vector/semantic search returns arbitrary results because all embeddings are zero vectors. + +This is the default so that `memoryd init && memoryd serve` works out of the box without any extra setup, but you will want to switch to a real provider for full hybrid search. + +### `ollama` — free, local, private + +Requires [Ollama](https://ollama.com) running locally with an embedding model pulled: + +```sh +# Install Ollama: https://ollama.com +ollama pull nomic-embed-text +export MEMORYD_EMBEDDING_PROVIDER=ollama +memoryd serve +``` + +The default model is `nomic-embed-text` (768 dimensions). You can override with: + +```sh +export MEMORYD_EMBEDDING_MODEL= +export MEMORYD_EMBEDDING_URL=http://localhost:11434 # default +export MEMORYD_EMBEDDING_DIMENSIONS=768 # default +``` + +### `openai` — cloud-based + +Requires an OpenAI API key: + +```sh +export MEMORYD_EMBEDDING_PROVIDER=openai +export OPENAI_API_KEY=sk-... +memoryd serve +``` + +The default model is `text-embedding-3-small` (1536 dimensions). You can override with: + +```sh +export MEMORYD_EMBEDDING_MODEL=text-embedding-3-large +export MEMORYD_EMBEDDING_DIMENSIONS=3072 +``` + ## Configuration ```jsonc diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts index 6c429b7..90b8fe7 100644 --- a/src/cli/commands/init.ts +++ b/src/cli/commands/init.ts @@ -3,6 +3,7 @@ import type { AgentContext } from '../agent.js'; import { bootstrapSidecar } from '../agent.js'; +import { resolveConfig } from '../../config.js'; export async function initCommand(ctx: AgentContext, _args: string[]): Promise { console.log('Initializing memoryd...'); @@ -46,4 +47,11 @@ export async function initCommand(ctx: AgentContext, _args: string[]): Promise { @@ -42,11 +43,23 @@ export async function serveCommand(ctx: AgentContext, args: string[]): Promise { expect(lines.some(l => l.includes('memoryd is ready'))).toBe(true); expect(lines.some(l => l.includes('DID:'))).toBe(true); }); + + it('prints noop embedding warning when provider is noop', async () => { + const savedProvider = process.env.MEMORYD_EMBEDDING_PROVIDER; + delete process.env.MEMORYD_EMBEDDING_PROVIDER; + try { + const { initCommand } = await import('../../src/cli/commands/init.js'); + const lines = await captureLog(() => initCommand(ctx, [])); + expect(lines.some(l => l.includes('noop embedding provider'))).toBe(true); + expect(lines.some(l => l.includes('MEMORYD_EMBEDDING_PROVIDER'))).toBe(true); + } finally { + if (savedProvider !== undefined) { + process.env.MEMORYD_EMBEDDING_PROVIDER = savedProvider; + } else { + delete process.env.MEMORYD_EMBEDDING_PROVIDER; + } + } + }); }); // ------------------------------------------------------------------------- diff --git a/tests/config.spec.ts b/tests/config.spec.ts index efa082e..0632300 100644 --- a/tests/config.spec.ts +++ b/tests/config.spec.ts @@ -42,6 +42,12 @@ describe('resolveConfig', () => { expect(config.port).toBe(3200); }); + it('defaults to noop embedding provider for warning detection', () => { + const config = resolveConfig(); + expect(config.embedding.provider).toBe('noop'); + // This is the value checked by CLI commands to print the noop warning. + }); + it('reads sidecar path from env', () => { process.env.MEMORYD_SIDECAR_PATH = '/tmp/test.db'; const config = resolveConfig();