Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<model-name>
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
Expand Down
8 changes: 8 additions & 0 deletions src/cli/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
console.log('Initializing memoryd...');
Expand Down Expand Up @@ -46,4 +47,11 @@ export async function initCommand(ctx: AgentContext, _args: string[]): Promise<v
}

console.log('memoryd is ready.');

const cfg = resolveConfig();
if (cfg.embedding.provider === 'noop') {
console.log('');
console.log('Warning: Using noop embedding provider — semantic search is disabled.');
console.log(' Set MEMORYD_EMBEDDING_PROVIDER=ollama or =openai for full hybrid search.');
}
}
13 changes: 13 additions & 0 deletions src/cli/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import type { AgentContext } from '../agent.js';

import { resolveConfig } from '../../config.js';
import { flagValue, hasFlag, parsePort } from '../flags.js';

export async function serveCommand(ctx: AgentContext, args: string[]): Promise<void> {
Expand Down Expand Up @@ -42,11 +43,23 @@ export async function serveCommand(ctx: AgentContext, args: string[]): Promise<v

if (useStdio) {
await server.startStdio();
printNoopWarning();
// stdio transport blocks until the client disconnects.
} else {
const { port: actualPort } = await server.startHttp();
console.log(`memoryd MCP server listening on http://localhost:${actualPort}`);
console.log(`DID: ${ctx.did}`);
console.log('Press Ctrl+C to stop.');
printNoopWarning();
}
}

/** Print a warning when the noop embedding provider is active. */
function printNoopWarning(): void {
const cfg = resolveConfig();
if (cfg.embedding.provider === 'noop') {
console.log('');
console.log('Warning: Using noop embedding provider — semantic search is disabled.');
console.log(' Set MEMORYD_EMBEDDING_PROVIDER=ollama or =openai for full hybrid search.');
}
}
17 changes: 17 additions & 0 deletions tests/cli/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,23 @@ describe('CLI commands', () => {
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;
}
}
});
});

// -------------------------------------------------------------------------
Expand Down
6 changes: 6 additions & 0 deletions tests/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading