# @elizaos/plugin-inmemorydb Pure in-memory, ephemeral `IDatabaseAdapter` for elizaOS — zero setup, zero persistence, everything gone on `close()`. ## Purpose / role Provides a complete `DatabaseAdapter` implementation backed by JavaScript `Map` structures and an in-memory HNSW vector index. No disk I/O, no migrations, no configuration required. Load it as a plugin so the runtime registers the adapter automatically, or construct `InMemoryDatabaseAdapter` directly (useful in tests). It is opt-in — the runtime leaves the existing adapter registered if one is already present. Supported platforms: Node.js and browser (separate build entries in `exports`). Loaded via the `init` hook in `index.ts`. ## Plugin surface This plugin registers no actions, providers, evaluators, or routes. Its sole contribution is a `DatabaseAdapter`. | What | Name | Where | |------|------|--------| | Plugin object | `plugin` / `default` | `index.ts` | | Adapter factory | `createDatabaseAdapter(agentId)` | `index.ts` | | Adapter class | `InMemoryDatabaseAdapter` | `adapter.ts` | | Storage backend | `MemoryStorage` | `storage-memory.ts` | | Vector index | `EphemeralHNSW` | `hnsw.ts` | `init` hook behavior: checks for an existing adapter on the runtime (`r.adapter`, `r.databaseAdapter`, or `r.hasDatabaseAdapter()`); if one is already registered it exits silently. Otherwise it constructs a `MemoryStorage` singleton (keyed by `Symbol.for("elizaos.plugin-inmemorydb.global-singletons")`) and registers a new `InMemoryDatabaseAdapter` for the given `agentId`. ## Layout ``` plugins/plugin-inmemorydb/ index.ts Plugin entry — init hook, createDatabaseAdapter(), re-exports index.browser.ts Browser entry — re-exports index.ts (different build target) adapter.ts InMemoryDatabaseAdapter — full IDatabaseAdapter implementation storage-memory.ts MemoryStorage — Map-of-Maps backing store (IStorage) hnsw.ts EphemeralHNSW — cosine-distance HNSW vector index (IVectorStorage) types.ts IStorage, IVectorStorage, VectorSearchResult, COLLECTIONS enum generated/specs/ Auto-generated specs (do not hand-edit) build.ts build script (Bun.build + tsc d.ts emit) vitest.config.ts Test config ``` ## Commands ```bash bun run --cwd plugins/plugin-inmemorydb build # compile to dist/ bun run --cwd plugins/plugin-inmemorydb dev # build --watch bun run --cwd plugins/plugin-inmemorydb test # vitest run bun run --cwd plugins/plugin-inmemorydb test:watch # vitest watch bun run --cwd plugins/plugin-inmemorydb typecheck # tsgo --noEmit bun run --cwd plugins/plugin-inmemorydb lint # biome check --write bun run --cwd plugins/plugin-inmemorydb format # biome format --write bun run --cwd plugins/plugin-inmemorydb clean # rm -rf dist .turbo .turbo-tsconfig.json *.tsbuildinfo ``` ## Config / env vars None. This plugin reads no environment variables and requires no configuration. The `init` hook receives `config: Record` but ignores it entirely. ## How to extend The plugin exposes `IStorage` (in `types.ts`) as a stable interface. To swap the backing store: 1. Implement `IStorage` (all methods are async). 2. Instantiate `new InMemoryDatabaseAdapter(yourStorage, agentId)` directly instead of going through the plugin's `init` hook. 3. Call `adapter.initialize()` before use. To add new collection types: add a key to `COLLECTIONS` in `types.ts`, then add CRUD methods to `InMemoryDatabaseAdapter` following the existing pattern (call `this.storage.set/get/getWhere/delete`). To replace the vector index: implement `IVectorStorage` (in `types.ts`) and pass an instance into the adapter — the adapter currently creates `EphemeralHNSW` in its constructor; a minimal refactor exposes it as a constructor parameter. ## Conventions / gotchas - **No persistence.** All data is lost when `close()` is called or the process exits. Do not use in production agents that need to remember past interactions. - **Global singleton storage.** `MemoryStorage` is shared across all adapter instances in the same process via `Symbol.for(...)`. This means multiple agent runtimes in one process share state unless you construct `MemoryStorage` independently. - **No transaction atomicity.** The `transaction()` method just invokes the callback with `this` — no rollback, no isolation. - **Default embedding dimension is 384.** Call `adapter.ensureEmbeddingDimension(n)` before writing memories with a different embedding size; it updates the dimension on the HNSW index. When changing dimensions inside a live process, call `clearEmbeddingsOutsideActiveDimension()` immediately after `ensureEmbeddingDimension(n)` so old-width vectors are stripped from memory rows and the HNSW index is rebuilt with only active-width vectors. - **Batch API only.** Single-item helpers from earlier revisions are removed. All call sites must use batch methods (`createEntities`, `getMemoriesByIds`, etc.). - **Browser build.** `index.browser.ts` re-exports `index.ts`. The build produces separate `dist/node/` and `dist/browser/` entries; the package `exports` map selects the right one automatically. - **`node:crypto` dependency.** `adapter.ts` imports `randomUUID` from `node:crypto`. The browser build polyfills this via the build config; do not replace with `Math.random()`. ## ⛔ NON-NEGOTIABLE — evidence, trajectories & real end-to-end tests > The binding, repo-wide standard is **[AGENTS.md](../../AGENTS.md)**. Read it. > Nothing in this package is *done* until it is *proven* done — a reviewer must confirm it > works **without reading the code**, from the artifacts you attach. This applies to **every** > feature, fix, refactor, and chore here. "Tests pass" is not proof; "CI is green" is not proof. - **Record AND read model trajectories.** Capture the *actual* inputs and outputs of the model from a **live** LLM — not the deterministic proxy, not a mock: the prompt, the providers/context, the raw model output, every tool/action call, and the result. Then **open the trajectory and review it by hand.** A captured-but-unread trajectory is not evidence (`packages/scenario-runner/bin/eliza-scenarios run --report `). - **Real, full-featured E2E — no larp.** Every feature ships detailed end-to-end tests that drive the *real* path end to end. Not the happy "front door" only: cover error paths, edge/empty/invalid input, concurrency, roles/permissions, and adversarial input. A test that asserts against a mock/stub/fixture standing in for the thing under test **does not count**. If the real model/device/chain/connector/account is hard to reach, **make it reachable — that is the work**, not an excuse to mock. If the existing tests here are shallow or mocked, fixing them is part of your change. - **Screenshots + logs at every phase**, plus a **complete walkthrough video/run-through** of the entire feature or view, start to finish (`bun run test:e2e:record`). - **Manually review every artifact the change touches** — never just the green check: client logs (console + network), server logs (`[ClassName] …`), the model trajectories in and out, before/after full-page screenshots, **and the domain artifacts listed below for this package.** - **No residuals. No shortcuts.** The goal is not "done" — it is *everything* done. Clear every blocker by the **hard path**: build the real architecture, stand up the real model/device/service, actually test it. Never leave a TODO, a stub, a stepping-stone, or a "follow-up." When unsure, research thoroughly, weigh the options, and ship the best, highest-effort, production-ready version. Keep going until every possibility is exhausted. Artifacts → attached inline in the PR (MP4 video, JPG screenshots, logs in `
`); attach each evidence type **or** explicitly mark it N/A with a reason — never leave it blank. If `develop` moved and changed behavior, **re-capture** evidence; stale proof is worse than none. **Capture & manually review for this package — storage / memory:** - The actual rows / embeddings / documents written **and read back**, with their shape inspected — not a mock asserting itself. - Query correctness: precision/recall on real data, ordering, pagination, and migration up/down. - GC/retention, concurrency, and large-payload paths. - A trajectory showing memory/knowledge actually recalled into a turn, where relevant.