Memory adapters
InMemoryMemory, SqliteMemory, HonchoMemory, and VectorMemory — when to use each Memory implementation.
Memory adapters
Every adapter implements the Memory interface from @maniac-ai/agents/schemas:
interface Memory {
save(record: MemoryRecord, scope: MemoryScope): Promise<string>;
load(id: string, scope: MemoryScope): Promise<MemoryRecord | null>;
search(query: MemoryQuery, scope: MemoryScope): Promise<MemoryRecord[]>;
delete(id: string, scope: MemoryScope): Promise<boolean>;
}Records carry a namespace, JSON content, and metadata. Namespaces follow predictable paths such as agent:<agentId>/threads/<threadId>/messages.
InMemoryMemory
The default for tests and quickstarts. Stores records in an in-process Map with monotonic ordering and implements MemorySequencer for turn indices.
import { InMemoryMemory, Maniac } from "@maniac-ai/agents";
const app = new Maniac({
memory: new InMemoryMemory(),
agents: [myAgent]
});search applies metadata filters and case-insensitive substring matching over JSON.stringify(content). There is no vector index — use VectorMemory when you need semantic recall.
SqliteMemory
Durable, single-process storage backed by Node's built-in node:sqlite. Zero additional npm dependencies and no native build step.
When to use it
- You want durable conversation, observation, and checkpoint storage on one host
- You can tolerate SQLite's single-writer concurrency model (one process writes; readers proceed concurrently under WAL)
Reach for Postgres when you need multi-host write concurrency or rich JSON containment queries. The SQLite schema is intentionally compatible with the Python PostgresMemory adapter for migration.
Requirements
- Node ≥ 22.5.0 (
node:sqliteis experimental on Node 22.x, unflagged from 23.5+) - SQLite ≥ 3.35 (bundled with Node ≥ 22.5) for
RETURNINGandON CONFLICT … DO UPDATE
Construction
import { SqliteMemory } from "@maniac-ai/agents/memory/sqlite";
// Adapter opens and owns the file
const memory = new SqliteMemory({ filename: "./agent.db" });
// Bring-your-own DatabaseSync — adapter never closes borrowed handles
const memory = new SqliteMemory({ database: existingHandle });| Option | Default | Purpose |
|---|---|---|
filename | — | File path or :memory: |
database | — | Existing DatabaseSync handle (mutually exclusive with filename) |
tablePrefix | "maniac_memory" | Table name prefix for multi-tenant databases |
applyPragmas | true | Apply WAL, foreign_keys, and recommended sync settings in setup() |
Schema
setup() creates:
CREATE TABLE maniac_memory (
id TEXT PRIMARY KEY,
namespace TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX maniac_memory_namespace_idx ON maniac_memory(namespace);
CREATE INDEX maniac_memory_created_at_idx ON maniac_memory(created_at);
CREATE TABLE maniac_memory_sequences (
namespace TEXT NOT NULL,
key TEXT NOT NULL,
value INTEGER NOT NULL,
PRIMARY KEY (namespace, key)
);content and metadata are JSON text; filter clauses use SQLite's json_extract.
Lifecycle
const memory = new SqliteMemory({ filename: "./agent.db" });
await memory.setup();
try {
// use stores and Maniac app
} finally {
await memory.close(); // no-op for borrowed database handles
}SqliteMemory drops into every store: ConversationStore, ObservationStore, WorkingMemoryStore, RunCheckpointStore, and BackgroundTaskStore.
HonchoMemory
Wraps a base Memory adapter (default InMemoryMemory; production setups typically use SqliteMemory) and exposes the Honcho SDK for session context and Dialectic peer chat.
import { HonchoMemory } from "@maniac-ai/agents/memory/honcho";
import { SqliteMemory } from "@maniac-ai/agents/memory/sqlite";
const base = new SqliteMemory({ filename: "./agent.db" });
await base.setup();
const memory = new HonchoMemory({
base,
workspaceId: process.env.HONCHO_WORKSPACE_ID,
environment: "production"
});When Maniac detects HonchoMemory, it automatically:
- Swaps
ConversationStore→HonchoConversationStore(loads viasession.context, mirrors writes) - Swaps
WorkingMemoryStore→HonchoWorkingMemoryStore - Injects the
ask_about_userbuilt-in tool
@honcho-ai/sdk is an optional peer dependency. Pairing HonchoMemory with observationalMemory logs a warning — both paths compress history and can double-compact.
VectorMemory
A decorator over any base Memory. Canonical records stay in the base adapter; embeddings live in a sidecar VectorIndex.
import { VectorMemory, SqliteVectorIndex } from "@maniac-ai/agents/memory";
import { SqliteMemory } from "@maniac-ai/agents/memory/sqlite";
const sqlite = new SqliteMemory({ filename: "./agent.db" });
await sqlite.setup();
const memory = new VectorMemory({
base: sqlite,
embedder: myEmbedder, // async (text: string) => number[]
index: new SqliteVectorIndex({ database: sqlite.database })
});On save, text is embedded and upserted into the index. On search with MemoryQuery.text, the adapter runs k-nearest-neighbor lookup, then loads matching records from the base. If the embedder throws, search falls back to the base adapter's substring matching.
Shipped index implementations:
InMemoryVectorIndex— cosine similarity, suitable for tests and small workloadsSqliteVectorIndex— shares theSqliteMemorydatabase handle
ObservationStore.searchObservations and searchReflections benefit automatically when the underlying Memory is wrapped.
Choosing an adapter
flowchart TD
Start["Need persistence?"] -->|No| IM["InMemoryMemory"]
Start -->|Yes| SQL["SqliteMemory"]
SQL --> Honcho{"Honcho cloud?"}
Honcho -->|Yes| HON["HonchoMemory wraps SqliteMemory"]
Honcho -->|No| Vec{"Semantic search?"}
Vec -->|Yes| VEC["VectorMemory wraps SqliteMemory"]
Vec -->|No| SQL