npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@achref_hasni/membridge

v0.1.1

Published

The vendor-neutral TypeScript interface and benchmark harness for AI agent memory. Write once, swap memory backends (Mem0, Zep, Letta) with a config change, and get honest latency, cost, and recall comparisons for your LLM agents.

Readme


Table of contents


What MemBridge is (and is not)

MemBridge is the missing abstraction layer for AI agent memory. The ecosystem is crowded with incompatible backends — Mem0, Zep, Letta (MemGPT) — each with its own SDK, storage model, and cost profile. MemBridge gives you one small, stable API over all of them, so your LLM agent, RAG pipeline, or chatbot is not welded to a single vendor.

| MemBridge is | MemBridge is not | |------------------|----------------------| | A vendor-neutral MemoryDriver interface | A vector database or embedding model | | A driver registry + adapter pattern | A hosted memory store | | A benchmark harness (M3–M4, planned) | A knowledge graph engine | | An escape hatch to raw vendor SDKs | A replacement for backend-specific features |

npm package name: @achref_hasni/membridge — the unscoped name membridge is blocked by npm (too similar to the existing mem-bridge package). The CLI command is still membridge.

M1 reality check: Only the Mem0 driver is implemented today. Zep and Letta are declared as optional peer dependencies and documented for M2 — swapping to them in config will fail until those drivers land.


Why MemBridge

  • One interface, any backend. add / search / get / delete over a normalized memory model. Switch backends by editing config — zero call-site changes (once the target driver exists).
  • Honest benchmarks (planned). Same dataset, same machine, same workload for every driver. Reports p50 and p95 (never just averages) and cost in dollars with the pricing assumption stated.
  • No lock-in, no hidden features. Every driver exposes .raw() — the real vendor client — so a backend's signature feature is always one call away.
  • Built for trust. Strict TypeScript, readable source, pinned deps, and recorded cassettes in tests so driver behavior is reproducible without a live vendor account.

Quickstart

1. Install

pnpm add @achref_hasni/membridge mem0ai
# mem0ai is the Mem0 SDK — install only the backend(s) you use

2. Set your API key

cp .env.example .env
# Edit .env and set MEM0_API_KEY=...

Or pass the key inline in config (see Configuration).

3. Use in code

import { createMemory } from '@achref_hasni/membridge';

const memory = await createMemory({
  drivers: [{ name: 'mem0', options: { apiKey: process.env.MEM0_API_KEY } }],
});

await memory.add({
  userId: 'alice',
  messages: [{ role: 'user', content: 'I always want a window seat' }],
});

const hits = await memory.search({ userId: 'alice', query: 'seating preference', limit: 5 });
console.log(hits[0]?.content); // "Prefers a window seat"  (score: 0.91)

4. Verify the CLI

pnpm build
node dist/cli/index.js list
# Registered drivers:
#   - mem0

Installation

Package managers

# pnpm (recommended — matches packageManager in package.json)
pnpm add @achref_hasni/membridge mem0ai

# npm
npm install @achref_hasni/membridge mem0ai

# yarn
yarn add @achref_hasni/membridge mem0ai

Peer dependencies (optional backends)

MemBridge declares backend SDKs as optional peer dependencies. You only install what you use:

| Driver | npm package | Status | |--------|-------------|--------| | mem0 | mem0ai (>=3) | ✅ M1 | | zep | @getzep/zep-cloud (>=3) | ⏳ M2 | | letta | @letta-ai/letta-client (>=1) | ⏳ M2 |

If you reference a driver name that is not registered, createMemory() throws a MemBridgeError with op: 'resolve'.

Requirements

  • Node.js >= 20
  • TypeScript projects: types ship with the package (dist/index.d.ts)

The contract

Every driver implements this interface. Keep call sites against MemoryDriver — never against vendor SDKs directly (unless you intentionally use the escape hatch).

interface MemoryDriver {
  readonly name: string;
  add(input: { userId: string; messages: { role: string; content: string }[] }): Promise<MemoryRecord[]>;
  search(input: { userId: string; query: string; limit?: number }): Promise<MemoryRecord[]>;
  get(input: { userId: string; limit?: number }): Promise<MemoryRecord[]>;
  delete(input: { userId: string; id?: string }): Promise<void>;
  raw(): unknown; // escape hatch — underlying vendor client
}

Normalized MemoryRecord

All drivers return the same shape:

interface MemoryRecord {
  id: string;
  userId: string;
  content: string;
  metadata?: Record<string, unknown>;
  createdAt: string;   // ISO-8601
  score?: number;      // populated by search(); higher = more relevant
}

Configuration

Programmatic config (supported today)

This is the only config path implemented in M1:

import { createMemory, defineConfig } from '@achref_hasni/membridge';

// Inline object — simplest path
const memory = await createMemory({
  drivers: [{ name: 'mem0', options: { apiKey: process.env.MEM0_API_KEY } }],
});

// With defaultDriver when multiple backends are configured (M2+)
const cfg = defineConfig({
  drivers: [
    { name: 'mem0', options: { apiKey: process.env.MEM0_API_KEY } },
    // { name: 'zep', options: { apiKey: process.env.ZEP_API_KEY } },  // M2
  ],
  defaultDriver: 'mem0',
});
const mem = await createMemory(cfg);

defineConfig() is a typed identity helper — it gives editors full type-checking but does not load files.

Config file loading

MemBridge discovers and loads membridge.config.json, .js, or .mjs from the current directory:

membridge validate              # auto-discover config in cwd
membridge validate -c ./my.json # explicit path

From code:

import { loadConfig, loadConfigFile } from '@achref_hasni/membridge';

const { config, configPath } = await loadConfig();
// or
const cfg = await loadConfigFile('./membridge.config.json');

TypeScript config files (membridge.config.ts) are not loaded at runtime — import them from your app and pass the object to createMemory().

Config schema

Validated with Zod. Invalid config throws ZodError at parse time.

| Field | Type | Required | Description | |-------|------|----------|-------------| | drivers | { name, options }[] | yes (min 1) | Registered driver names + driver-specific options | | defaultDriver | string | no | Which driver createMemory() picks when no override is passed. Must match a drivers[].name. | | bench | object | no | Benchmark settings (schema only in M1; no runner yet) | | bench.dataset | string | no | Path to benchmark dataset file | | bench.costPer1kTokens | number | no | USD per 1k tokens for cost reporting | | bench.replay | boolean | no | Default false. Run against cassettes instead of live backends (M3+) |

Mem0 driver options

| Option | Type | Default | Description | |--------|------|---------|-------------| | apiKey | string | process.env.MEM0_API_KEY | Mem0 platform API key | | host | string | process.env.MEM0_HOST | Custom host (self-hosted / proxy) | | organizationId | string | — | Mem0 organization ID | | projectId | string | — | Mem0 project ID | | client | unknown | — | Inject a fake/live client (tests & replay). Skips SDK construction. |

Swap the backend (when the driver exists)

  const memory = await createMemory({
-   drivers: [{ name: 'mem0', options: { apiKey: process.env.MEM0_API_KEY } }],
+   drivers: [{ name: 'zep',  options: { apiKey: process.env.ZEP_API_KEY } }],
  });
// Call sites using MemoryDriver stay identical

Environment variables

Copy .env.example to .env. Never commit real keys.

| Variable | Used by | Status | |----------|---------|--------| | MEM0_API_KEY | Mem0 driver | ✅ Read when options.apiKey is omitted | | MEM0_HOST | Mem0 driver | ✅ Read when options.host is omitted | | ZEP_API_KEY | Zep driver | ⏳ M2 | | LETTA_API_KEY | Letta driver | ⏳ M2 | | LETTA_BASE_URL | Letta driver | ⏳ M2 |

MemBridge does not load .env automatically. Use dotenv or your framework's env handling, or pass keys in config.


API reference

createMemory(config, driverName?)

Resolves a ready-to-use MemoryDriver from config.

import { createMemory } from '@achref_hasni/membridge';

const memory = await createMemory(config);
const memory2 = await createMemory(config, 'mem0'); // explicit override

Resolution order for driver name:

  1. driverName argument (if provided)
  2. config.defaultDriver
  3. First entry in config.drivers

Throws MemBridgeError (op: 'resolve') if the chosen name is not in config.drivers or not registered in the registry.

raw<T>(driver)

Escape hatch — returns the underlying vendor client with your type parameter:

import { raw } from '@achref_hasni/membridge';
import type MemoryClient from 'mem0ai';

const client = raw<MemoryClient>(memory);
await client.users(); // Mem0-specific call MemBridge does not wrap

parseConfig(input) / defineConfig(config)

Validate or author config objects. parseConfig throws ZodError on invalid input.

registry / DriverRegistry

The default registry is pre-loaded with built-in drivers (currently mem0). Register custom drivers:

import { DriverRegistry, type DriverFactory } from '@achref_hasni/membridge';

const custom = new DriverRegistry();
custom.register('my-backend', myFactory as DriverFactory);
const driver = await custom.resolve('my-backend', { /* options */ });

Importing @achref_hasni/membridge registers built-in drivers on the global registry as a side effect.

Errors — MemBridgeError

All driver operations wrap failures in a consistent shape:

class MemBridgeError extends Error {
  readonly driver: string;   // e.g. "mem0"
  readonly op: 'add' | 'search' | 'get' | 'delete' | 'init' | 'resolve';
  override readonly cause: unknown; // original backend error, preserved
}

Config validation throws ZodError, not MemBridgeError — a known inconsistency at the config seam.


CLI reference

After pnpm build, the membridge binary is at dist/cli/index.js (also linked via package.json bin when installed globally).

membridge list

Lists registered driver names.

membridge list
# Registered drivers:
#   - mem0

membridge validate

Validates a config file and checks that every named driver is registered.

membridge validate
membridge validate -c ./membridge.config.json

membridge bench

Prints the benchmark comparison table preview (placeholder metrics until M3–M4). Exits with code 1 until the runner ships.

membridge bench
┌──────────┬────────┬───────────┬───────────┬──────────────┬─────────────┐
│ driver   │ recall │ p50 (ms)  │ p95 (ms)  │ $ / 1k convo │ footprint   │
├──────────┼────────┼───────────┼───────────┼──────────────┼─────────────┤
│ mem0     │ —      │ —         │ —         │ —            │ —           │
└──────────┴────────┴───────────┴───────────┴──────────────┴─────────────┘

membridge parse

Validate an inline JSON config string (for scripting):

membridge parse '{"drivers":[{"name":"mem0"}]}'

Architecture

src/
├── index.ts           # Public entry: createMemory(), re-exports, driver registration
├── config.ts          # Zod schemas + defineConfig / parseConfig
├── core/
│   ├── types.ts       # MemoryDriver contract + normalized types
│   ├── registry.ts    # DriverRegistry (name → factory)
│   ├── errors.ts      # MemBridgeError + wrapOp()
│   └── escape-hatch.ts
├── drivers/
│   └── mem0/index.ts  # Mem0 adapter (normalization, lazy SDK import)
└── cli/index.ts       # Commander CLI

Request flow for memory.add():

createMemory() → parseConfig() → registry.resolve() → mem0Factory() → Mem0Driver.add() → wrapOp() → mem0ai client

See CONTEXT.md for domain glossary and milestone definitions.


Writing a driver

Each new driver (M2+) must:

  1. Implement MemoryDriver — all four CRUD methods plus raw()
  2. Register a DriverFactory under a stable name (e.g. 'zep')
  3. Validate driver-specific options with Zod
  4. Wrap backend errors with wrapOp()MemBridgeError
  5. Normalize responses to MemoryRecord
  6. Lazy-import the vendor SDK so unused backends stay out of the install tree
  7. Support options.client injection for recorded tests
  8. Include a cassette-based integration test

Minimal skeleton:

import { wrapOp } from '@achref_hasni/membridge';
import type { DriverFactory, MemoryDriver } from '@achref_hasni/membridge';

export const myFactory: DriverFactory = async (options) => {
  const client = options.client ?? await createLiveClient(options);
  return {
    name: 'my-backend',
    add: (input) => wrapOp('my-backend', 'add', () => /* ... */),
    search: (input) => wrapOp('my-backend', 'search', () => /* ... */),
    get: (input) => wrapOp('my-backend', 'get', () => /* ... */),
    delete: (input) => wrapOp('my-backend', 'delete', () => /* ... */),
    raw: () => client,
  };
};

Testing & replay cassettes

Tests use an injectable client instead of live API calls:

const memory = await createMemory({
  drivers: [{ name: 'mem0', options: { client: fakeMem0Client } }],
});

Recorded fixtures live in test/fixtures/*.cassette.json. See test/mem0.test.ts for the full pattern.

Run tests:

pnpm test          # single run
pnpm test:watch    # watch mode

Current coverage (14 tests):

  • Config validation (parseConfig)
  • Registry register / list / resolve / unknown driver
  • Escape hatch (raw())
  • Mem0 CRUD normalization, request forwarding, error wrapping, missing API key

Not yet tested: CLI commands, createMemory edge cases (missing driver in config), live SDK import path, bench/replay engine.


Benchmark (roadmap)

The launch artifact — one command, one reproducible comparison table — lands in M3–M4:

membridge bench   # not yet available

Planned output:

┌──────────┬────────┬───────────┬───────────┬──────────────┬─────────────┐
│ driver   │ recall │ p50 (ms)  │ p95 (ms)  │ $ / 1k convo │ footprint   │
├──────────┼────────┼───────────┼───────────┼──────────────┼─────────────┤
│ mem0     │  …     │   …       │   …       │   …          │   …         │
│ zep      │  …     │   …       │   …       │   …          │   …         │
│ letta    │  …     │   …       │   …       │   …          │   …         │
└──────────┴────────┴───────────┴───────────┴──────────────┴─────────────┘

Every report will record hardware, driver versions, and the pricing assumption. Replay mode (bench.replay: true) will re-run against cassettes for CI without live keys.


Milestone status

| Milestone | Scope | State | |-----------|-------|-------| | M1 | Core contract, driver registry, zod config, Mem0 driver + recorded test | ✅ done | | M2 | Zep and Letta drivers behind the same interface | ⏳ next | | M3 | Benchmark runner + metrics (latency percentiles, $ cost, recall) | ◻️ planned | | M4 | membridge bench → markdown + JSON comparison table | ◻️ planned | | M5 | Docs site + comparison table as README hero | ◻️ planned |


Development

Setup

git clone https://github.com/AchrefHASNI/membridge.git
cd membridge
pnpm install
cp .env.example .env   # optional — only needed for live Mem0 calls

Scripts

| Script | Command | Description | |--------|---------|-------------| | build | pnpm build | Bundle ESM + CJS + types via tsup | | dev | pnpm dev | Watch mode rebuild | | test | pnpm test | Vitest single run | | typecheck | pnpm typecheck | tsc --noEmit | | lint | pnpm lint | Biome check | | lint:fix | pnpm lint:fix | Biome auto-fix | | format | pnpm format | Biome format | | audit | pnpm audit | Dependency audit |

Quality gate (CI-equivalent)

pnpm typecheck && pnpm test && pnpm lint && pnpm build

Tech stack

TypeScript (strict) · Node ≥ 20 · pnpm · tsup · Vitest · Biome · Zod · Commander

Project layout

| Path | Purpose | |------|---------| | src/ | Library source | | test/ | Vitest tests + cassettes | | dist/ | Build output (published to npm) | | website/ | Marketing/docs Vite app (not published in npm files) | | CONTEXT.md | Domain glossary | | .env.example | Environment variable template |


Troubleshooting

unknown driver "zep" (or letta)

Only mem0 is registered in M1. Zep and Letta drivers arrive in M2.

init failed: the "mem0ai" package is not installed

pnpm add mem0ai

missing Mem0 API key

Set MEM0_API_KEY in your environment or pass options.apiKey in config. Empty string counts as missing.

Cannot find package 'commander' when running CLI locally

Run pnpm install first, then pnpm build. The CLI depends on runtime deps from node_modules.

Config throws ZodError

Check that drivers is a non-empty array and defaultDriver (if set) matches a driver name.

MEM0_HOST in .env has no effect

Ensure you load .env into process.env (MemBridge does not call dotenv). Then MEM0_HOST is used when options.host is omitted. Or pass host explicitly:

{ name: 'mem0', options: { apiKey: '...', host: 'https://your-mem0-host' } }

Contributing

Driver requests and PRs welcome. Each new driver needs:

  • The four contract methods + .raw()
  • Normalized errors (MemBridgeError)
  • A recorded integration test with cassette fixture
  • Lazy optional SDK import
  • Justification for any new dependency

Keep the dependency tree small. See Writing a driver.


Author

Built by Achref Hasni.

License

Apache-2.0 © 2026 Achref Hasni — chosen for the patent grant and maximum adoption.