FishMem

Configuration

Configure the FishMem TypeScript engine explicitly.

Create an engine with Memory.create(config). Providers are explicit; FishMem does not silently substitute a mock embedder or LLM.

Local deterministic configuration

import { Memory } from "fishmem";

const memory = await Memory.create({
  graphStore: { provider: "sqlite", config: { url: "file:fishmem.db" } },
  vectorStore: {
    provider: "sqlite",
    config: {
      url: "file:fishmem.db",
      indexIdentity: "mock-384-v1",
    },
  },
  embedder: { provider: "mock", config: { dimensions: 384 } },
  llm: { provider: "mock" },
  derivation: { enabled: false },
});

The mock embedder is deterministic lexical hashing. It is useful for local development and tests, not a substitute for a semantic embedding model.

Providers

SectionSupported configuration
graphStorememory, sqlite, postgres, d1, or a custom GraphStore
vectorStorememory, sqlite, pgvector, qdrant, vectorize, or a custom VectorStore
embeddermock, openai, or a custom Embedder
llmmock, openai, anthropic, or a custom LLM

The graph store is the canonical memory and document-descriptor store. The vector store holds rebuildable keyword and semantic projections. Exact document originals remain inline by default; the Cloudflare application wires its R2 binding through documentOriginalStore.

Document original storage

Memory.create() accepts an optional documentOriginalStore adapter. It is an internal storage seam behind the existing documents interface:

interface DocumentOriginalStore {
  put(source: DocumentSource): Promise<void>;
  get(source: Omit<DocumentSource, "content">): Promise<string>;
  delete(namespaceId: string, documentIds: string[]): Promise<void>;
  deleteNamespace(namespaceId: string): Promise<void>;
  clear(): Promise<void>;
}

Leave it unset for Desktop and library-only Node deployments; direct UTF-8 originals then live inline with their descriptors. The Node control plane uses FISHMEM_ASSET_DIR for raw file assets and extraction artifacts. apps/web configures R2 automatically on Cloudflare. Implementations must be idempotent and private: callers never receive an internal object key or bypass document namespace checks.

OpenAI example

const memory = await Memory.create({
  graphStore: { provider: "sqlite", config: { url: "file:fishmem.db" } },
  vectorStore: {
    provider: "sqlite",
    config: {
      url: "file:fishmem.db",
      indexIdentity: "text-embedding-3-small",
    },
  },
  embedder: {
    provider: "openai",
    config: { model: "text-embedding-3-small" },
  },
  llm: {
    provider: "openai",
    config: { model: "gpt-4o-mini" },
  },
});

When OPENAI_API_KEY is present, OpenAI providers can be resolved by default, but production code should prefer explicit configuration so runtime behavior is auditable.

Canonical inference and derived views

const memory = await Memory.create({
  // providers...
  derivation: {
    enabled: true,
    schedule: "deferred",
    hook: (task) => executionContext.waitUntil(task),
  },
});

infer is an add-level canonical storage choice, not the global derivation switch:

  • infer: true (default) requires the configured LLM, makes one extraction call, and stores only refined records;
  • infer: false makes zero LLM calls and stores content, or each non-empty message, as a verbatim record.

The derivation configuration controls rebuildable state/profile projection work over those canonical records. It does not create a second writer.

Governed belief projection (opt-in)

Use the belief projection only for inferred preferences or learned rules that should accumulate independent evidence before becoming trusted. Explicit state continues to supersede immediately; ordinary memory search is unchanged.

import { createSqliteBeliefReconciler, Memory } from "fishmem";

const beliefReconciler = await createSqliteBeliefReconciler({
  url: "file:fishmem.db",
});

const memory = await Memory.create({
  // providers...
  derivation: {
    enabled: true,
    beliefs: {
      enabled: true,
      reconciler: beliefReconciler,
      memoryTypes: ["preference"],
      namespaceAllowlist: ["workspace_123"],
      killSwitch: () => process.env.BELIEF_PROJECTION_DISABLED === "1",
    },
  },
});

The default policy requires three independent evidence keys from three contexts, at least 0.6 weighted support, and a lead of 1. Evidence is stored as relational rows with canonical source IDs and can be rebuilt without an LLM. Public REST writes cannot set evidence keys, weights, or applicability.

The bundled web runtime remains off by default. Both variables are required for a bounded rollout; an empty or missing allowlist enables no namespace:

FISHMEM_BELIEF_RECONCILIATION_ENABLED=1
FISHMEM_BELIEF_RECONCILIATION_ALLOWLIST=workspace_123,workspace_456

Set FISHMEM_BELIEF_RECONCILIATION_DISABLED=1 for the dynamic kill switch. Keep the projection shadow-only until paired evaluation passes, then inspect it through GET /v1/beliefs.

Deferred vector projection

const memory = await Memory.create({
  // providers...
  vectorProjection: {
    schedule: "deferred",
    hook: (task) => executionContext.waitUntil(task),
  },
});

Canonical and lexical storage commit first. Semantic projection can then run in the background. Use flushVectorProjections() in scripts and tests that require a fully indexed state before continuing.

Embedding identity

Changing model or dimensions requires rebuilding the vector projection. Set a stable indexIdentity on stores that support it so a mismatch is detected instead of mixing incomparable vectors.

Warnings

Provide onWarning to observe recoverable projection or optional-retrieval failures:

const memory = await Memory.create({
  // providers...
  onWarning(warning) {
    logger.warn(warning);
  },
});

FishMem reports degraded optional work; it does not silently hide it behind a fallback.

On this page