Advanced Usage - Production Patterns

August 25, 2026 · View on GitHub

This guide covers advanced patterns for running OpenContext in production: multi-source search, temporal queries, platform integrations, and the Loop engine.

OpenContext can search across multiple data sources simultaneously. The default setup searches raw memory with a lexical fallback; to enable semantic search you wire an embedQuery function. You can also plug in optional searchInsights and searchKnowledge providers to search extracted facts and uploaded documents in the same call.

// multi-source-search-example.ts
// Run with: npx tsx multi-source-search-example.ts
// Note: semantic search needs @melandlabs/ai-rag installed.
import { createMemoryStore, LocalTransformersEmbeddingProvider } from "@melandlabs/opencontext";

async function main() {
  const embeddingProvider = new LocalTransformersEmbeddingProvider({
    modelName: "Xenova/all-MiniLM-L6-v2",
  });

  const store = await createMemoryStore({
    unified: {
      embedQuery: async ({ query }) => {
        return await embeddingProvider.embedQuery(query);
      },

      // Optional: search extracted insights (provide your own index).
      searchInsights: async ({ userId, query, limit, threshold }) => {
        // Replace with your insights index, e.g. vector DB or graph search.
        console.log("Searching insights for:", { userId, query, limit, threshold });
        return [];
      },

      // Optional: search uploaded documents (provide your own RAG index).
      searchKnowledge: async ({ userId, query, options }) => {
        // Replace with your knowledge-base index.
        console.log("Searching knowledge for:", { userId, query, options });
        return [];
      },
    },
  });

  const results = await store.search({
    userId: "user-123",
    query: "What did we decide about the architecture?",
    sources: ["memory", "insights", "knowledge"],
    limit: 10,
    threshold: 0.7,
    botIds: ["architect-bot"],  // Optional: filter by bot
    documentIds: ["doc-456"],   // Optional: filter by document
  });

  console.log(`Searched ${results.sources.length} source(s)`);
  for (const warning of results.warnings) {
    console.warn(`[${warning.source}] ${warning.code}: ${warning.message}`);
  }
  for (const hit of results.results) {
    console.log(`[${hit.type}] ${hit.content} (${hit.similarity})`);
  }

  await store.raw.close();
}

main().catch((error) => {
  console.error("Multi-source search failed:", error);
  process.exit(1);
});

Reflection and Write-Back

The four-verb memory API (remember, recall, forget, improve) covers the day-to-day operations of an agent: write a fact, recall it, archive it, supersede it. This section covers the fifth operation — reflect — which gathers evidence and either synthesises an answer or, in write-back mode, asks the LLM to vet a consolidation plan and persists the result.

There are two flavours, sharing the same evidence pipeline:

MethodWrites?Purpose
store.search({ ...input, synthesize: true })NoRead-only LLM synthesis over unified evidence
store.consolidate(input)YesAgentic gather → plan → vet → persist loop

Both methods are also exposed over HTTP and MCP:

TransportRead-onlyWrite-back
SDKstore.search({ ...input, synthesize: true })store.consolidate(input)
HTTPPOST /v1/search (set synthesize: true)POST /v1/consolidate:apply
MCPmemory.search (set synthesize: true)memory.consolidate

Read-only search({ synthesize: true })

store.search({ ...input, synthesize: true }) fans a single query out across four tiers:

  1. Raw messagessearchRawMessagesAnn (semantic) + searchRawMessagesLexical (BM25)
  2. SummariessearchSummaries (L1/L2/L3)
  3. InsightssearchInsights
  4. KnowledgesearchKnowledge (uploaded RAG docs)

…then asks the configured LLM (unified.reasoning.complete) to produce a single synthesised answer with bracket-cited evidence. No writes.

import { createMemoryStore } from "@melandlabs/opencontext";

const store = await createMemoryStore({
	unified: {
		embedQuery: async ({ query }) => myEmbedder.embed(query),
		searchSummaries: mySummariesBackend,
		searchInsights: myInsightsBackend,
		searchKnowledge: myKnowledgeBackend,
		reasoning: {
			complete: async (prompt) => myLlm.complete(prompt),
		},
	},
});

const out = await store.search({
	userId: "u-42",
	query: "what does the user like to do on weekends?",
	tiers: ["summary", "raw", "insight", "knowledge"],
	limit: 20,
	threshold: 0.7,
	synthesize: true,
});

console.log(out.answer); // LLM synthesis, bracket-cites [1], [2], …
console.log(out.evidence); // the underlying evidence items
console.log(out.warnings); // structured warnings, never throws

Behaviour under failure:

  • No LLM configured → returns the gathered evidence with a reflect_llm_not_configured warning instead of throwing.
  • LLM throws → evidence is preserved, a reflect_llm_failed warning is added, out.answer falls back to the raw evidence text.
  • Tier provider absent → the tier is skipped silently.
  • Empty query → short-circuits with { evidence: [], warnings: [] }.

Agentic write-back consolidate()

consolidate() is the additive write counterpart. The loop is:

    ┌──────────────┐    ┌─────────────────┐    ┌────────────┐
    │ gather (4tier)│ →  │ build plan (rule)│ →  │ vet w/ LLM │ (optional)
    └──────────────┘    └─────────────────┘    └────────────┘

                            ┌─────────────────┐    ┌──────────────┐
                            │ deprecateRecords │ ←  │ persistPlan  │ (graph store)
                            │  storage adapter │    └──────────────┘
                            └─────────────────┘
  1. Gather — same evidence pipeline as search({ synthesize: true }).
  2. Build planbuildMemoryConsolidationPlan(records, thresholds) produces a MemoryConsolidationPlan with preserve / decay / deprecate entries. Rule-based; no LLM invention.
  3. Vet (optional) — when reasoning.complete is configured, the LLM is asked to approve or veto each entry. It may only mark entries as approve / veto with a reason; it cannot add new operations.
  4. PersistgraphStore.persistPlan(translate(plan)) writes the graph updates; storage.deprecateRecords(...) soft-deprecates the records replaced by deprecate entries.
import { createMemoryStore, attachMemoryGraphStore } from "@melandlabs/opencontext";

// 1. Wire the graph store (opt-in).
attachMemoryGraphStore(store, {
	storage: myIndexedDbStorage,
	ownerScope: { userId: "u-42" },
});

// 2. Inspect the plan first (dry-run).
const dry = await store.consolidate({
	userId: "u-42",
	query: "summarise the last week",
	ownerScope: { userId: "u-42" },
	tiers: ["raw", "summary"],
	dryRun: true,
});
console.log(dry.plan); // MemoryConsolidationPlan
console.log(dry.applied); // false

// 3. Apply for real.
const result = await store.consolidate({
	userId: "u-42",
	query: "summarise the last week",
	ownerScope: { userId: "u-42" },
	tiers: ["raw", "summary"],
	dryRun: false,
});
console.log(result.applied); // true
console.log(result.persistenceResult); // { applied, skipped, conflicts }
console.log(result.deprecationCounts); // [{ supersededBySummaryId, count }]

Failure modes

Each failure mode emits a typed warning and falls back deterministically:

CodeTriggered whenBehaviour
reflect_apply_llm_skippedNo reasoning.complete configuredThe rule-based plan runs without vet.
reflect_apply_llm_vet_failedLLM threw or returned invalid JSONAll entries marked approve (no-op plan).
reflect_apply_graph_store_not_configuredNo graph store attacheddeprecateRecords still runs against the storage adapter.
reflect_apply_dry_rundryRun: trueNothing is written; applied: false.
reflect_apply_no_writesPlan has no actionable entriesapplied: false, returns the plan for inspection.

Replay / A/B testing

The plan field accepts a pre-built MemoryConsolidationPlan. Skips the plan-builder step so the same input can be replayed against different LLM configurations:

const dry1 = await store.consolidate({
	/* … */
	dryRun: true,
});
// dry1.plan is a MemoryConsolidationPlan

const replayed = await store.consolidate({
	/* same inputs */
	plan: dry1.plan,
});

FactType classification

Atomic facts are classified as one of three kinds, declared in @melandlabs/ai/memory/contracts:

type FactType = "world" | "experience" | "mental_model";
KindMeaning
worldFacts about the world (e.g. "water boils at 100 °C").
experienceFirst-person events ("I went hiking last weekend").
mental_modelGeneralised patterns ("the user prefers outdoors on weekends").

The classifier runs at LLM extraction time; the result rides along the RawMessage.factType? field and surfaces on MemoryRecord.factType. The read-side filter MemorySearchQuery.factTypes lets a caller narrow a search to one or more kinds:

await store.search({
	userId: "u-42",
	query: "what did I do last weekend?",
	sources: ["memory"], // restrict to memory source — insights/knowledge don't carry factType
	factTypes: ["experience"],
});

Schema migration: IndexedDB DB_VERSION 3 → 4, SQLite RAW_MESSAGES_SCHEMA_VERSION 3 → 4. Both are idempotent and tolerate v3 rows whose factType is undefined.

End-to-end example

The same surface is also covered by the demos at examples/src/simple/15-http-server.ts (HTTP) and examples/src/simple/16-mcp-server.ts (MCP). A dedicated runnable companion for the reflect / write-back loop is on the roadmap and will be re-added once @melandlabs/memory-store@0.4.0 ships to npm.

Reasoning-Backed Memory Retrieval

Dense retrieval works best when the query matches the language of the stored memories. Chat logs are usually written in the first person ("I told you I prefer dark mode"), but agents often ask questions in the third person ("What does the user prefer?"). OpenContext can plug in small LLM-powered reasoning providers to close that gap.

Two strategies are available:

  • rewrite: rephrases the assistant's question into a first-person memory-check question before running semantic search.
  • iterative: runs a small ReAct-style planner that searches, notes evidence, and searches again — useful for multi-hop or temporally constrained questions.

Configure them through unified.reasoning:

// reasoning-memory-example.ts
// Run with: node --env-file=../.env --experimental-strip-types src/tutorials/10-reasoning-memory-example.ts
import {
  createMemoryReasoningProviders,
  createMemoryStore,
  getRawMessageManager,
  LocalTransformersEmbeddingProvider,
} from "@melandlabs/opencontext";

async function main() {
  const embeddingProvider = new LocalTransformersEmbeddingProvider({
    modelName: "Xenova/all-MiniLM-L6-v2",
  });

  // Reads OPENCONTEXT_LLM_API_KEY / BASE_URL / MODEL from the environment.
  const reasoning = createMemoryReasoningProviders({});

  const store = await createMemoryStore({
    dbPath: "./tutorials-reasoning.db",
    unified: {
      embedQuery: async ({ query }) => embeddingProvider.embedQuery(query),
      reasoning: {
        queryRewriter: reasoning.queryRewriter,
        iterativePlanner: reasoning.iterativePlanner,
      },
    },
  });

  // ...store messages, then search with a reasoning strategy...
  const results = await store.search({
    userId: "user-42",
    query: "What does the user enjoy doing on weekends?",
    reasoningStrategy: "rewrite", // or "iterative"
    limit: 5,
    threshold: 0.0,
  });

  console.log(`Found ${results.count} result(s)`);
  console.log("Reasoning metadata:", results.reasoning);
  for (const hit of results.results) {
    console.log(`- ${hit.content}`);
  }

  await store.raw.close();
}

main().catch((error) => {
  console.error("Reasoning search failed:", error);
  process.exit(1);
});

Required environment variables:

OPENCONTEXT_LLM_API_KEY=your-key
OPENCONTEXT_LLM_BASE_URL=https://api.deepseek.com/v1   # or any OpenAI-compatible endpoint
OPENCONTEXT_LLM_MODEL=deepseek-chat                    # or e.g. openai/gpt-4o-mini

Optional tuning variables for the iterative planner:

OPENCONTEXT_LLM_REASONING_MAX_ITERATIONS=4   # maximum planner actions per search
OPENCONTEXT_LLM_REASONING_SEARCH_TOP_K=5     # results exposed to the planner per internal search

You can also pass these values explicitly when constructing providers:

const reasoning = createMemoryReasoningProviders({}, {
  planner: { maxIterations: 6, searchTopK: 10 },
});

The reasoning field on the result tells you which strategy ran and includes diagnostic details such as rewritten queries or iteration counts. If no reasoning providers are configured, setting reasoningStrategy emits a warning and falls back to the default search path.

Runnable example: examples/src/tutorials/10-reasoning-memory-example.ts

Server-wide Default

Callers usually want one global default — every search on this store should use the planner unless the caller overrides it. Set unified.reasoning.defaultStrategy when constructing the store:

const store = await createMemoryStore({
  dbPath: "./tutorials-reasoning.db",
  unified: {
    embedQuery: async ({ query }) => embeddingProvider.embedQuery(query),
    reasoning: {
      queryRewriter: reasoning.queryRewriter,
      iterativePlanner: reasoning.iterativePlanner,
      // No per-call reasoningStrategy? Use this as the default.
      defaultStrategy: "iterative",
    },
  },
});

// Inherits "iterative" from the store config.
const results = await store.search({ userId: "u-1", query: "..." });

// Per-call value still wins.
const adHoc = await store.search({
  userId: "u-1",
  query: "...",
  reasoningStrategy: "rewrite",
});

Resolution order at lookup time is: per-call reasoningStrategy → store-level unified.reasoning.defaultStrategy"none". Set defaultStrategy: "none" explicitly if you want to opt out of a default that another module turned on.

Exposing Reasoning Over HTTP and MCP

The same reasoning layer is reachable from the @melandlabs/memory-store CLI daemons — no host-side wiring required. Add --reasoning and the bin reads OPENCONTEXT_LLM_API_KEY / OPENCONTEXT_LLM_BASE_URL / OPENCONTEXT_LLM_MODEL from your environment and wires queryRewriter + iterativePlanner into the unified deps.

Reasoning is off by default — the flag is opt-in. If OPENCONTEXT_LLM_API_KEY is missing, the daemon refuses to start with a clear remediation message rather than starting in a degraded state.

# HTTP daemon (port 7421 by default)
opencontext-memory-http \
  --reasoning \
  --embedding-provider local \
  --memory-backend sqlite-vec

# MCP daemon (stdio transport)
opencontext-memory-mcp \
  --reasoning \
  --embedding-provider local \
  --memory-backend sqlite-vec

stdio wire format: NDJSON. Each JSON-RPC object is one line followed by a newline. This is set by @modelcontextprotocol/sdk@^1.25.3's StdioServerTransport. If you build a custom client, serialize with JSON.stringify(obj) + '\n' and split on \n on the receive side.

Once the daemon is running, the existing endpoints / tools accept a reasoningStrategy field that selects which strategy to run per call:

# HTTP — POST /v1/search
curl -X POST http://127.0.0.1:7421/v1/search \
  -H 'content-type: application/json' \
  -d '{
    "userId": "user-42",
    "query": "What outdoor activities has the user mentioned?",
    "reasoningStrategy": "iterative",
    "limit": 5
  }'
// MCP — memory.search tool
{
  "userId": "user-42",
  "query": "What does the user enjoy doing on weekends?",
  "reasoningStrategy": "rewrite", // or "iterative"
  "limit": 5
}

The response carries a reasoning block so callers can observe what ran:

{
  "query": "What outdoor activities has the user mentioned?",
  "count": 2,
  "results": [/* … */],
  "reasoning": {
    "strategy": "iterative",
    "iterations": 4,
    "evidenceCount": 6,
    "degraded": false
  },
  "warnings": []
}

If the daemon was started without --reasoning, calls with reasoningStrategy: "rewrite" | "iterative" still succeed — the response includes a memory_*_not_configured warning and falls back to the default hybrid search. Opting out is the safe default for low-latency / no-LLM deployments; opt in per daemon when you want reasoning available.

Additional flags and env vars for fine-grained control:

Flag / envDefaultPurpose
--reasoning / REASONING=1offEnable the LLM reasoning layer.
--no-reasoningForce-disable even when REASONING=1 is set.
--reasoning-base-url / OPENCONTEXT_LLM_BASE_URLhttps://openrouter.ai/api/v1OpenAI-compatible base URL.
--reasoning-model / OPENCONTEXT_LLM_MODELopenai/gpt-4o-miniReasoning LLM model identifier.
--reasoning-timeout-ms / OPENCONTEXT_LLM_TIMEOUT_MS30000Per-request timeout.
OPENCONTEXT_LLM_API_KEYrequired when --reasoningBearer token for the reasoning LLM.

See opencontext-memory-http --help / opencontext-memory-mcp --help for the full flag surface.

Date-Range Filtering

In addition to the single-point asOf snapshot, you can pass an inclusive dateFrom / dateTo range to restrict the memory source to a calendar window. The iterative planner receives the bounds and may emit narrower ranges in its own search actions; the default one-shot path simply filters candidates by their timestamp metadata.

Note: dateFrom / dateTo only filter the memory source. insights and knowledge results are not affected by this range, and memory candidates without a recognised timestamp are retained.

const results = await store.search({
  userId: "user-42",
  query: "What outdoor activities did I mention last summer?",
  reasoningStrategy: "iterative",
  dateFrom: "2024-06-01",
  dateTo: "2024-08-31",
  limit: 5,
  threshold: 0.0,
});

console.log("Reasoning metadata:", results.reasoning);
// -> { strategy: "iterative", dateRange: { from: "2024-06-01", to: "2024-08-31" }, ... }

asOf and dateFrom/dateTo are intentionally different:

  • asOf asks "what was true at this exact instant?" — a temporal snapshot over facts with validity windows.
  • dateFrom / dateTo ask "which memories were recorded inside this calendar window?" — an interval filter over message timestamps.

Vector Symbolic Architecture (VSA) Recall

store.search() returns the top-K nearest neighbours by cosine similarity. That's the right tool when the answer is "what's close to X?". Sometimes the right tool is "given a closed vocabulary, which entry matches X?". That's what VSA is for: it stores (role, filler) bindings as holographic reduced representations (HRR), and at recall time it superposes every stored binding, unbinds by the requested role, and picks the best-match vocabulary entry by cosine cleanup.

The two surfaces are intentionally separate — VSA recall is a single best-match, not a ranked list. Folding both into one result shape would require a confusing union type and a fake similarity field that doesn't mean the same thing across sources.

When to use VSA

VSA shines when:

  • You have a closed, labelled answer space (a vocabulary). E.g. mood ∈ { "happy", "neutral", "tired" }.
  • The question can be phrased as a role lookup ("what is the user's mood?").
  • You want millisecond-scale recall over a few thousand facts.
  • The role→filler relation is dense — you have many examples of "for this user, on a question like this, the answer is Y".

Reach for semantic search instead when:

  • The answer space is open-ended (long-form text, document retrieval).
  • You need multiple candidates ranked by similarity.
  • The vocabulary is too large for cleanup to discriminate (rule of thumb: D ≥ √N where N is the vocabulary size).

The four verbs

createMemoryStore() always returns a store.vsa facade backed by the same SQLite database as the raw-message store. The four verbs are:

VerbPurpose
vsaStoreFactPersist a (role, filler) binding as a HRR pair. Idempotent on factId.
vsaRecallRe-superpose all stored facts for the user/scope, unbind by the requested role, cleanup against a vocabulary, return the best-match label.
vsaListFactsRead-side projection of stored facts (no vectors). Useful for diagnostics and audit.
vsaForgetSoft-delete by id. Idempotent.

End-to-end example

import { createMemoryStore } from "@melandlabs/memory-store";
import { randomHRRVector } from "@melandlabs/vsa";

const DIM = 128;

function toVector(seed: number): number[] {
  const v = randomHRRVector(DIM, seed);
  return Array.from(v.data);
}

async function main() {
  const store = await createMemoryStore();

  const vocabulary = [
    { label: "happy",   vector: toVector(1) },
    { label: "neutral", vector: toVector(2) },
    { label: "tired",   vector: toVector(3) },
  ];

  const roleVector = toVector(100);
  const fillerVector = vocabulary[0].vector;

  // Persist three (role, filler) bindings. Each call returns a unique
  // factId; the superposed memory vector grows by one term per fact.
  for (let i = 0; i < 3; i += 1) {
    await store.vsa.storeFact({
      userId: "user-123",
      scopeTag: "demo",
      roleLabel: "user:mood",
      roleVector,
      fillerLabel: "happy",
      fillerVector,
      dim: DIM,
    });
  }

  // Recall — given the same role vector, the superposed memory vector
  // should decode to the most-bound filler.
  const recall = await store.vsa.recall({
    userId: "user-123",
    scopeTag: "demo",
    roleLabel: "user:mood",
    roleVector,
    vocabulary,
  });

  console.log(recall.fillerLabel); // → "happy"
  console.log(recall.allScores);  // → [{ label: "happy", score: 0.7 }, ...]
}

HTTP / MCP exposure

The same verbs are exposed over HTTP and MCP for any host that doesn't want to wire createMemoryStore directly:

  • POST /v1/vsa/storevsaStoreFact
  • POST /v1/vsa/recallvsaRecall
  • POST /v1/vsa/listvsaListFacts
  • POST /v1/vsa/forgetvsaForget

And the MCP tool names are memory.vsaStore, memory.vsaRecall, memory.vsaList, memory.vsaForget.

Capacity notes

HRR superpositions tolerate noise gracefully but degrade as you approach √D stored facts (rule of thumb from Plate, 1995). For D = 128 that's roughly 11 facts before crosstalk dominates a single best-match cleanup. For dense recall (thousands of facts) use D = 512 or D = 1024. The facade accepts whatever dimension you store; mismatches between facts with different dim values are surfaced as vsa_dim_mismatch warnings and dropped before superposition.

Runnable example: examples/src/simple/19-vsa.ts

Temporal (Time-Travel) Queries

Every fact has valid_from and valid_until, enabling queries as of a specific time. Pass an ISO-8601 string to asOf:

// temporal-query-example.ts
// Run with: npx tsx temporal-query-example.ts
import { createMemoryStore } from "@melandlabs/opencontext";

async function main() {
  const store = await createMemoryStore();

  // What did we believe about the project last month?
  const lastMonth = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();

  const results = await store.search({
    userId: "user-123",
    query: "project status and timeline",
    asOf: lastMonth,  // Query as of this ISO-8601 timestamp
    limit: 10,
  });

  console.log(`Found ${results.count} fact(s) that were true last month`);
  for (const hit of results.results) {
    console.log(`- ${hit.content}`);
  }

  await store.raw.close();
}

main().catch((error) => {
  console.error("Temporal query failed:", error);
  process.exit(1);
});

Use cases:

  • Audit: "What was the strategy on April 1st?"
  • Debugging: "Why did we make that decision last week?"
  • Compliance: "What information did we have then?"

Runnable example: examples/src/tutorials/05-time-travel-example.ts

Working with the Temporal Graph

For more advanced temporal queries, inspect the raw-message store directly. Every message records when it was created, archived, or deprecated, so you can reconstruct the history of a fact without a separate graph API:

// temporal-graph-example.ts
// Run with: npx tsx temporal-graph-example.ts
import { getRawMessageManager } from "@melandlabs/opencontext";

async function main() {
  const manager = await getRawMessageManager();

  // Query active facts for a user.
  const active = await manager.queryMessages({
    userId: "user-123",
    keywords: ["project status"],
    includeArchived: false,
  });

  console.log(`Active facts: ${active.length}`);
  for (const msg of active) {
    console.log(`- ${msg.content}`);
  }

  // Query deprecated / corrected facts to see what changed over time.
  const deprecated = await manager.queryMessages({
    userId: "user-123",
    includeArchived: true,
  });

  console.log("\nDeprecated or archived facts:");
  for (const msg of deprecated) {
    if (msg.deprecatedAt || msg.archivedAt) {
      console.log(
        `- ${msg.content}\n  deprecatedAt: ${msg.deprecatedAt ?? "n/a"}\n  archivedAt: ${msg.archivedAt ?? "n/a"}\n  reason: ${msg.deprecationReason ?? "n/a"}`,
      );
    }
  }
}

main().catch((error) => {
  console.error("Temporal graph query failed:", error);
  process.exit(1);
});

See also examples/src/tutorials/17-memory-service.ts for a reusable service wrapper around the raw-message store.

Platform Integrations

OpenContext supports multiple platforms. Integration IDs are exported from the contracts package, and each platform has a dedicated adapter under @melandlabs/integrations/*.

Available Platforms

// list-integrations-example.ts
// Run with: npx tsx list-integrations-example.ts
import { INTEGRATION_IDS } from "@melandlabs/opencontext";

console.log("Supported integrations:");
for (const id of INTEGRATION_IDS) {
  console.log(`- ${id}`);
}

Ingesting Platform Messages

There is no single IntegrationManager facade. Each platform adapter (e.g. @melandlabs/integrations/gmail, @melandlabs/integrations/slack) returns messages in its own shape. A typical ingestion loop normalizes those messages into RawMessage records and stores them:

// ingest-messages-example.ts
// Run with: npx tsx ingest-messages-example.ts
import { getRawMessageManager } from "@melandlabs/opencontext";
import type { RawMessage } from "@melandlabs/opencontext";

interface PlatformMessage {
  id: string;
  userId: string;
  content: string;
  platform: string;
  timestamp: number;
}

async function ingestMessages(messages: PlatformMessage[]) {
  const manager = await getRawMessageManager();

  const rawMessages: RawMessage[] = messages.map((msg) => ({
    messageId: msg.id,
    userId: msg.userId,
    content: msg.content,
    platform: msg.platform,
    botId: "ingest-bot",
    timestamp: msg.timestamp,
    createdAt: Date.now(),
  }));

  const ids = await manager.storeMessages(rawMessages);
  console.log(`Ingested ${ids.length} message(s)`);
}

async function main() {
  // Replace this with a real adapter call, e.g. fetchGmailMessages(userId).
  const exampleMessages: PlatformMessage[] = [
    {
      id: `msg-${Date.now()}`,
      userId: "user-123",
      content: "Meeting moved to 3pm",
      platform: "gmail",
      timestamp: Date.now(),
    },
  ];

  await ingestMessages(exampleMessages);
}

main().catch((error) => {
  console.error("Ingestion failed:", error);
  process.exit(1);
});

See the individual @melandlabs/integrations-* packages for platform-specific authentication, fetching, and sending APIs.

Platform Adapter Examples

  • examples/src/tutorials/12-integration-ids-example.ts — list every supported integration ID.
  • examples/src/tutorials/38-channels-example.ts — build and round-trip platform adapter error envelopes.
  • examples/src/tutorials/39-integrations-runtime-example.ts — platform display info, connectability checks, and task-integration inference.
  • examples/src/tutorials/40-contracts-example.ts — validate user types and integration IDs from the contracts package.

The Loop Engine

The Loop engine is a deterministic scheduler that wakes your agent on a schedule. It stores its config in ~/.opencontext/loop/config.json.

// loop-engine-example.ts
// Run with: npx tsx loop-engine-example.ts
import {
  LOOP_PATHS,
  ensureDirs,
  readPreferences,
  writePreferences,
} from "@melandlabs/opencontext";

async function main() {
  // Ensure Loop directories exist
  ensureDirs();

  // Read current preferences (or get defaults)
  const prefs = readPreferences();
  console.log("Current tick interval:", prefs.intervalSec, "seconds");
  console.log("Loop enabled:", prefs.enabled);

  // Update preferences. Only the fields you pass are patched.
  const updated = writePreferences({
    enabled: true,
    intervalSec: 300,          // Tick every 5 minutes
    narrative: true,           // Generate narrative brief/wrap
    briefTime: "09:00",        // Morning brief at 9 AM
    wrapTime: "21:00",         // Evening wrap at 9 PM
  });

  console.log("Updated preferences:", updated);
  console.log("Config file:", LOOP_PATHS.config);
}

main().catch((error) => {
  console.error("Loop engine example failed:", error);
  process.exit(1);
});

Loop Configuration File

After running the example, ~/.opencontext/loop/config.json looks similar to:

{
  "enabled": true,
  "intervalSec": 300,
  "narrative": true,
  "briefTime": "09:00",
  "wrapTime": "21:00",
  "noReplySkip": true,
  "promotionSkip": true
}

Runnable example: examples/src/tutorials/11-loop-example.ts

Scheduled Tasks

Use the @melandlabs/cron package to validate cron expressions and compute the next run time:

// scheduled-tasks-example.ts
// Run with: npx tsx scheduled-tasks-example.ts
import { computeNextRun, validateCronExpression } from "@melandlabs/opencontext";

async function main() {
  // Validate a cron expression
  const isValid = validateCronExpression("0 9 * * *");  // Daily at 9 AM
  console.log("Cron valid:", isValid);

  // Compute next run time. computeNextRun takes a ScheduleConfig object.
  const nextRun = computeNextRun(
    { type: "cron", expression: "0 9 * * *" },
    new Date(),
  );

  if (nextRun) {
    console.log("Next run:", nextRun.toISOString());
  } else {
    console.log("No next run scheduled");
  }
}

main().catch((error) => {
  console.error("Scheduled task example failed:", error);
  process.exit(1);
});

Runnable examples:

  • examples/src/tutorials/21-scheduled-tasks-example.ts — compute next cron run times.
  • examples/src/tutorials/29-cron-example.ts — validate expressions, compute next runs, and check isJobDue.

Encryption and Security

These utilities are re-exported from @melandlabs/opencontext for convenience.

Encrypting Secrets

TokenEncryption reads the key from the ENCRYPTION_KEY environment variable and expects a 32-byte value (or a password from which a 32-byte key is derived).

// token-encryption-example.ts
// Run with: ENCRYPTION_KEY=your-32-byte-key-here!!!! npx tsx token-encryption-example.ts
import { TokenEncryption } from "@melandlabs/opencontext";

async function main() {
  const encryptor = new TokenEncryption();

  const original = "sk-1234567890abcdef";

  // encryptToken / decryptToken are synchronous
  const encrypted = encryptor.encryptToken(original);
  console.log("Encrypted:", encrypted);

  const decrypted = encryptor.decryptToken(encrypted);
  console.log("Decrypted:", decrypted);
}

main().catch((error) => {
  console.error("Token encryption failed:", error);
  process.exit(1);
});

URL Validation (SSRF Protection)

// url-validation-example.ts
// Run with: npx tsx url-validation-example.ts
import { isTrustedStorageUrl, validateUrlForSSRF } from "@melandlabs/opencontext";

async function main() {
  // validateUrlForSSRF rejects plain HTTP, loopback and private IPs by default.
  // Pass { strictWhitelist: false } to skip the known-storage-provider whitelist.
  try {
    const safe = await validateUrlForSSRF("https://api.example.com/data", {
      strictWhitelist: false,
    });
    console.log("Safe URL:", safe.toString());
  } catch (error) {
    console.error("Unsafe URL:", error);
  }

  // Check if a storage URL is trusted
  const trusted = isTrustedStorageUrl("https://s3.amazonaws.com/my-bucket/file.txt");
  console.log("Trusted storage URL:", trusted);
}

main().catch((error) => {
  console.error("URL validation failed:", error);
  process.exit(1);
});

Runnable examples:

  • examples/src/tutorials/22-token-encryption-example.ts — encrypt and decrypt a token with TokenEncryption.
  • examples/src/tutorials/23-url-validation-example.ts — validate URLs and check trusted storage URLs.

Voice Capabilities

Voice plugins are re-exported from @melandlabs/opencontext for convenience. They are browser-oriented; the TTS plugin uses HTMLAudioElement and the STT plugin expects web Blob inputs, so these snippets are not runnable in a plain Node.js/CLI script.

Text-to-Speech (Kokoro)

// Browser-only example
import { KokoroPlugin } from "@melandlabs/opencontext";

const tts = new KokoroPlugin({ enabled: true, voice: "af_bella" });

// Speaks the text in the browser; returns a Promise that resolves when playback starts.
await tts.speak("Hello, world!");

Speech-to-Text (Whisper)

WhisperPlugin transcribes audio using the OpenAI Whisper API (or a compatible endpoint).

// whisper-example.ts
// Browser-only; run in an environment with Blob/File support.
import { WhisperPlugin } from "@melandlabs/opencontext";

async function main() {
  const stt = new WhisperPlugin({
    model: "whisper-1",
    apiKey: process.env.OPENAI_API_KEY,
  });

  // Load an audio file into a Blob. In the browser you can pass a File directly.
  const audioBuffer = Buffer.from(/* WAV bytes */);
  const audioBlob = new Blob([audioBuffer], { type: "audio/wav" });

  const result = await stt.transcribe({
    file: audioBlob,
    filename: "voice-input.wav",
  });

  console.log("Transcript:", result.text);
}

main().catch((error) => {
  console.error("Whisper transcription failed:", error);
  process.exit(1);
});

Web Search Integration

Web search utilities are re-exported from @melandlabs/opencontext for convenience.

// web-search-example.ts
// Run with: BRAVE_SEARCH_API_KEY=your-key npx tsx web-search-example.ts
import { needsRealTimeInfo, search } from "@melandlabs/opencontext";

async function main() {
  // Classify if a query needs real-time info
  const needsLive = needsRealTimeInfo("What's the weather today?");
  console.log("Needs live data:", needsLive);  // true

  // Perform web search (Brave Search API)
  if (needsLive && process.env.BRAVE_SEARCH_API_KEY) {
    const results = await search("OpenContext AI memory runtime", "web", 5);

    for (const result of results) {
      console.log(`- ${result.title}: ${result.url}`);
      console.log(`  ${result.description}`);
    }
  } else {
    console.log("Skipping live search: no BRAVE_SEARCH_API_KEY set");
  }
}

main().catch((error) => {
  console.error("Web search failed:", error);
  process.exit(1);
});

Runnable examples:

  • examples/src/tutorials/24-web-search-example.ts — classify search intent and call Brave Search.
  • examples/src/tutorials/33-search-example.tsneedsRealTimeInfo classification with assertions.

Audit Logging

OpenContext writes structured audit logs to ~/.opencontext/logs/audit.jsonl. The audit helpers are re-exported from @melandlabs/opencontext.

// audit-logging-example.ts
// Run with: npx tsx audit-logging-example.ts
import { logCommandExec, logFileRead, readAuditLogs } from "@melandlabs/opencontext";

async function main() {
  logFileRead("/etc/passwd");
  logCommandExec("git", ["status"]);

  const { entries, total } = readAuditLogs({ type: "file_read", limit: 10 });
  console.log(`Total audit entries: ${total}`);
  for (const entry of entries) {
    console.log(`[${entry.type}] ${entry.detail}`);
  }
}

main().catch((error) => {
  console.error("Audit logging example failed:", error);
  process.exit(1);
});

Parse the audit log from the shell:

# View recent audit entries
tail -f ~/.opencontext/logs/audit.jsonl | jq

# Count file-read entries
cat ~/.opencontext/logs/audit.jsonl | jq -r 'select(.type=="file_read") | .detail' | sort | uniq -c

Runnable examples:

  • examples/src/tutorials/25-audit-logging-example.ts — write and read audit log entries.
  • examples/src/tutorials/28-audit-example.ts — structured audit-log surface checks with assertions.

Performance Optimization

Batch Operations

// batch-store-example.ts
// Run with: npx tsx batch-store-example.ts
import { getRawMessageManager } from "@melandlabs/opencontext";

async function main() {
  const messages = await getRawMessageManager();
  const now = Date.now();

  // Batch store is much faster than individual calls.
  const batch = Array.from({ length: 1000 }, (_, i) => ({
    messageId: `msg-${now}-${i}`,
    userId: "user-123",
    content: `Message ${i}`,
    platform: "test",
    botId: "test-bot",
    timestamp: now,
    createdAt: now,
  }));

  const start = Date.now();
  const ids = await messages.storeMessages(batch);
  console.log(`Stored ${ids.length} message(s) in ${Date.now() - start}ms`);
}

main().catch((error) => {
  console.error("Batch store failed:", error);
  process.exit(1);
});

Runnable example: examples/src/tutorials/13-batch-example.ts

Embedding Caching

Avoid recomputing embeddings for repeated text by caching them. A Map works for short-lived processes; for production, swap in lru-cache or a shared cache store.

// embedding-cache-example.ts
// Run with: npx tsx embedding-cache-example.ts
import { LocalTransformersEmbeddingProvider } from "@melandlabs/opencontext";

const embeddingCache = new Map<string, number[]>();

async function getCachedEmbedding(text: string) {
  const cached = embeddingCache.get(text);
  if (cached) return cached;

  const provider = new LocalTransformersEmbeddingProvider({
    model: "Xenova/all-MiniLM-L6-v2",
  });
  const embedding = await provider.embedQuery({ query: text });
  embeddingCache.set(text, embedding);
  return embedding;
}

async function main() {
  const text = "User prefers dark mode";

  const first = await getCachedEmbedding(text);
  console.log("First embedding dimensions:", first.length);

  const second = await getCachedEmbedding(text);
  console.log("Cache hit, same embedding:", first === second);
}

main().catch((error) => {
  console.error("Embedding cache example failed:", error);
  process.exit(1);
});

Runnable examples:

  • examples/src/tutorials/07-local-embeddings-example.ts — generate embeddings locally.
  • examples/src/tutorials/08-local-embeddings-full-setup.ts — full local embedding + vector store setup.

Connection Pooling (Postgres)

When using the Postgres backend for raw-message storage, configure a connection pool with postgres + drizzle-orm. These are not bundled with @melandlabs/opencontext, so install them separately.

// postgres-pool-example.ts
// Run with: DATABASE_URL=postgres://... npx tsx postgres-pool-example.ts
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";

async function main() {
  const databaseUrl = process.env.DATABASE_URL;
  if (!databaseUrl) {
    throw new Error("DATABASE_URL is not set");
  }

  // Connection pool for Postgres
  const client = postgres(databaseUrl, {
    max: 10,            // Max connections
    idle_timeout: 20,
    connect_timeout: 10,
  });

  const db = drizzle(client, { logger: true });

  // Example: run a lightweight query to verify connectivity.
  const result = await db.execute("SELECT 1 as ok");
  console.log("Connected:", result);

  await client.end();
}

main().catch((error) => {
  console.error("Postgres pool example failed:", error);
  process.exit(1);
});

Runnable examples:

  • examples/src/tutorials/35-db-example.tsbatchInsert, password hashing, and dummy-password generation.
  • examples/src/tutorials/36-sqlite-example.ts — SQLite raw-message storage and BM25 lexical search.

Agent Runtimes

OpenContext exposes provider-agnostic agent primitives. You can drive Claude Code or OpenAI Codex CLI through the same IAgent lifecycle:

  • examples/src/tutorials/26-claude-agent-example.ts — run and plan with ClaudeAgent.
  • examples/src/tutorials/27-codex-agent-example.ts — run and plan with CodexAgent in a read-only sandbox.

Memory Consolidation

For LLM-free memory planning, use the pure utilities in @melandlabs/memory-consolidation to cluster evidence, discover relation candidates, and build a consolidation plan:

  • examples/src/tutorials/37-memory-consolidation-example.ts

Generic HTTP Client

The @melandlabs/api package provides typed get/post helpers and ApiError:

  • examples/src/tutorials/34-api-example.ts

Environment Mode Detection

Detect Tauri vs server mode and read canonical defaults:

  • examples/src/tutorials/30-env-config-example.ts

Monitoring

Health Checks

# Check all subsystems
npx @melandlabs/opencontext doctor

# Check specific subsystems
npx @melandlabs/opencontext doctor --section memory-store
npx @melandlabs/opencontext doctor --section embedding
npx @melandlabs/opencontext doctor --section integrations

# JSON output for monitoring
npx @melandlabs/opencontext doctor --json | jq '.ok'

Metrics

Track OpenContext usage in your own counters:

// metrics-example.ts
// Run with: npx tsx metrics-example.ts
import { createMemoryStore, getRawMessageManager } from "@melandlabs/opencontext";

const metrics = {
  memoryWrites: 0,
  memoryRecalls: 0,
};

async function trackedRemember(userId: string, content: string) {
  const manager = await getRawMessageManager();
  metrics.memoryWrites++;
  await manager.storeMessages([{
    messageId: `msg-${Date.now()}`,
    userId,
    content,
    platform: "tracked",
    botId: "metrics-bot",
    timestamp: Date.now(),
    createdAt: Date.now(),
  }]);
}

async function trackedRecall(userId: string, query: string) {
  const store = await createMemoryStore();
  metrics.memoryRecalls++;
  const results = await store.search({ userId, query, limit: 5 });
  await store.raw.close();
  return results;
}

async function main() {
  await trackedRemember("user-123", "User prefers TypeScript");
  const results = await trackedRecall("user-123", "What does the user prefer?");

  console.log("Metrics:", metrics);
  console.log(`Recalled ${results.count} result(s)`);

  // In a long-running process you might log metrics periodically:
  // setInterval(() => console.log("Metrics:", metrics), 60000);
}

main().catch((error) => {
  console.error("Metrics example failed:", error);
  process.exit(1);
});

DeepSeek Harness Plugin

The dsh-opencontext plugin gives DeepSeek Harness (DSH) agents persistent memory and retrieval-augmented generation capabilities by integrating with OpenContext.

Prerequisites

Installation

# Install directly from npm
dsh plugin --profile web add dsh-opencontext

# Start DSH Web
dsh web

From source (for development)

# Clone the repo and navigate to the plugin directory
cd plugins/dsh-opencontext

# Build the plugin
pnpm install
pnpm build

# Register with your DSH profile
dsh plugin --profile web add /path/to/opencontext/plugins/dsh-opencontext

# Start DSH Web
dsh web
# Visit http://127.0.0.1:3080/plugins to confirm the plugin is enabled

Available Tools (16)

Core Memory Tools (8)

ToolPurpose
oc_searchSearch long-term memory (unified across memory, insights, knowledge)
oc_rememberPersist one memory entry
oc_memory_listList recent memory entries in current scope
oc_memory_getRead one or more entries by ID
oc_memory_reviseSoft-deprecate an entry and store a successor
oc_memory_retireSoft-deprecate an entry
oc_prepare_contextManually build a bounded context block
oc_capture_sourceCapture an arbitrary content source

Summary & Outcome Tools (3)

ToolPurpose
oc_session_summaryGenerate and store a session summary
oc_task_outcomeRecord task outcomes, decisions, achievements
oc_recent_summariesList recent session summaries and task outcomes

Insights Tools (2)

ToolPurpose
oc_insights_searchSearch structured insights (decisions, preferences, outcomes)
oc_insight_captureCapture a structured insight from conversation

Knowledge/RAG Tools (3)

ToolPurpose
oc_knowledge_searchRAG search over uploaded documents
oc_document_uploadUpload documents to knowledge base
oc_document_listList all documents in knowledge base

Automatic Features

Recall Waterfall: Before each turn, automatically searches relevant historical memories and injects them as context.

Auto-Capture: Each user message is automatically written to the memory store.

Session Summarization: Optional automatic summarization at turn boundaries.

Doctor Command

/oc doctor

Returns plugin status, database path, memory count, and enabled features.

Configuration Options

FieldDefaultEnvironment Variable
capturePromptstrueOPENCONTEXT_DSH_CAPTURE_PROMPTS
maxRecallItems8OPENCONTEXT_DSH_MAX_RECALL_ITEMS
autoSummarizefalseOPENCONTEXT_DSH_AUTO_SUMMARIZE
captureToolResultsfalseOPENCONTEXT_DSH_CAPTURE_TOOL_RESULTS
enableInsightstrueOPENCONTEXT_DSH_ENABLE_INSIGHTS
enableKnowledgetrueOPENCONTEXT_DSH_ENABLE_KNOWLEDGE

Usage Examples

User: I prefer TypeScript, remember that Agent: Got it, I'll remember that.

(Next conversation) User: Write me a function Agent: I remember you prefer TypeScript. Here's a TS version...

User: Upload my API docs Agent: (uses oc_document_upload) Done. User: How do I call this API? Agent: (uses oc_knowledge_search) According to your docs, the API is called like...

Trust Model

Recalled memories are appended as untrusted historical evidence. If they contradict the user's current statement, the user always takes precedence.

Extract, Derive, and Per-Hit Signals

Two new first-class primitives turn raw messages into structured knowledge, and a parallel change lifts the lid on per-channel scoring.

Why extract and derive

OpenContext stores raw messages verbatim. That gives the read pipeline enough to do semantic + lexical recall; it does not, by itself, give you entity links or synthesized summaries. Two new primitives close that gap:

  • distill — reads one raw message, returns EntityEdge[] (label / kind / relation / source). Opt-in: host provides entityExtractor; without it, the call returns an empty list plus a distill_extractor_not_configured warning.
  • derive — reads a window of candidate fact texts, returns DerivedFact[] (text / kind / sources / optional window). Opt-in: host provides deriver; without it, the call returns an empty list plus a derive_deriver_not_configured warning.

The shared shape ({ edges | facts, warnings }) mirrors consolidate()'s degraded-mode contract: when the LLM isn't wired in, the SDK never throws — it surfaces a warning code and returns an empty list. The host decides what to do with the result.

A third change rides along: every store.search() hit gets a signals field with per-channel scores (semantic, lexical, entity, rrf). This makes the per-channel contributions visible without flattening them into a single number — useful when you want to threshold or re-rank downstream.

The two verbs

distill

import { distillRawMessage, type EntityEdge } from "@melandlabs/opencontext";

const entityExtractor = async (input: {
  userId: string;
  messageId: string;
  content: string;
}): Promise<EntityEdge[]> => {
  // Replace with your real extractor — local NER, hosted LLM, etc.
  return [
    {
      label: "Luna",
      kind: "person",
      relation: "mentions",
      sourceMessageId: input.messageId,
      extractedAt: Date.now(),
      confidence: 0.92,
    },
  ];
};

const unified = { embedQuery: async () => new Array(4).fill(0.1), entityExtractor };

const result = await distillRawMessage(unified, {
  userId: "u1",
  messageId: "m1",
  content: "I adopted a cat named Luna yesterday.",
  persist: async (edges) => console.log("storing", edges.length, "edges"),
});

console.log(result.edges);     // [{ label: "luna", ... }]
console.log(result.warnings);  // [] when the extractor is wired in

Without entityExtractor configured, you get:

{ "edges": [], "warnings": [{ "code": "distill_extractor_not_configured", "message": "..." }] }

derive

import { deriveFacts, type DerivedFact } from "@melandlabs/opencontext";

const deriver = async (input: {
  userId: string;
  userScope: { userId: string; botIds?: string[]; dateFrom?: string; dateTo?: string };
  recentFactTexts: string[];
  window?: { from: number; to: number };
}): Promise<DerivedFact[]> => {
  return [
    {
      text: "User has mentioned cats 4 times this month",
      kind: "frequency",
      sources: ["m1", "m2", "m3", "m4"],
      window: input.window,
      confidence: 0.8,
      derivedAt: Date.now(),
    },
  ];
};

const unified = {
  embedQuery: async () => new Array(4).fill(0.1),
  searchRawMessagesLexical: async () => [
    { id: "m1", content: "I love my cat Luna", similarity: 0.7, metadata: {} },
    { id: "m2", content: "Luna is a tabby", similarity: 0.6, metadata: {} },
  ],
  deriver,
};

const result = await deriveFacts(unified, {
  userId: "u1",
  candidateLimit: 50,
  persist: async (facts) => console.log("storing", facts.length, "facts"),
});

When candidateTexts is omitted, the SDK pulls candidates from searchRawMessagesLexical (or the SQLite FTS5 fallback) using keywords derived from the optional query field. Pass a topical query from the Loop-engine schedule (e.g. "cat preferences") — without it, the SDK falls back to userId + botIds which is rarely useful. When no candidates are available, you get derive_no_candidates.

Reading the signals

Every store.search() hit now carries:

{
  "type": "memory",
  "id": "m1",
  "content": "...",
  "similarity": 0.81,
  "metadata": { "rrfScore": 0.0325 },
  "signals": {
    "channels": ["semantic", "lexical"],
    "semantic": 0.91,
    "lexical": 0.45,
    "rrf": 0.0325
  }
}

The channels array lists which retrieval channels the hit appeared in; semantic / lexical / entity carry the per-channel score for the highest-ranked appearance; rrf is the fused RRF score when mergeStrategy: "rrf" is active.

Without entitySearch wired in, the entity channel is simply absent — no signals.entity and no entity in the channels array. If you asked for RRF and the entity dep is missing, you'll also get a memory_entity_search_not_configured warning so you can surface degraded-mode to the user.

When to call them

  • distill — call per-message, right after the message lands. Cheap enough to run synchronously in the write path when the extractor is local (rule-based); defer to a queue when the extractor is a hosted LLM.
  • derive — schedule from the Loop engine (e.g. nightly, weekly). The windowed candidate fetch + LLM call makes it too expensive for a write-path hook.

Both are best-effort: errors land in warnings, not thrown exceptions. A Loop-engine scheduler that retries on distill_extractor_failed or derive_deriver_failed is fine — the SDK does not require success.

Walkthrough

examples/src/tutorials/42-extract-derive.ts runs all three steps end-to-end:

  1. distill with a rule-based extractor and an in-memory entityStore mock.
  2. derive with a trivial summary deriver and an explicit candidateTexts list.
  3. search with mergeStrategy: "rrf" and an entitySearch dep that returns a hit for one of the messages — confirm the result carries signals.entity and signals.channels = ["semantic", "lexical", "entity"].

Run it via the examples runner:

cd examples
pnpm test

Complete Advanced Example Index

All runnable examples referenced in this guide:

ExampleTopic
examples/src/tutorials/00-hello-memory-example.tsStore and search a first memory
examples/src/tutorials/01-remember-example.tsStore a fact with metadata
examples/src/tutorials/02-recall-example.tsSearch unified memory across sources
examples/src/tutorials/03-forget-example.tsArchive a message
examples/src/tutorials/04-improve-example.tsDeprecate and supersede a fact
examples/src/tutorials/05-time-travel-example.tsTime-travel / asOf queries
examples/src/tutorials/06-minimal-config-example.tsMinimal SQLite-vec backend setup
examples/src/tutorials/07-local-embeddings-example.tsLocal embedding generation
examples/src/tutorials/08-local-embeddings-full-setup.tsFull local embedding + vector-store setup
examples/src/tutorials/09-http-client-example.tsHTTP client for the memory HTTP server
examples/src/tutorials/10-reasoning-memory-example.tsReasoning-backed retrieval + date-range filtering
examples/src/tutorials/11-loop-example.tsLoop engine preferences
examples/src/tutorials/12-integration-ids-example.tsList supported integration IDs
examples/src/tutorials/13-batch-example.tsBatch memory writes
examples/src/tutorials/17-memory-service.tsReusable memory service wrapper
examples/src/tutorials/18-remember-everything-example.tsIngest incoming platform messages
examples/src/tutorials/19-warning-handling-example.tsInspect search warnings
examples/src/tutorials/20-metadata-example.tsStore structured metadata with a fact
examples/src/tutorials/21-scheduled-tasks-example.tsCron next-run computation
examples/src/tutorials/22-token-encryption-example.tsToken encryption / decryption
examples/src/tutorials/23-url-validation-example.tsSSRF URL validation
examples/src/tutorials/24-web-search-example.tsWeb search with Brave
examples/src/tutorials/25-audit-logging-example.tsStructured audit logging
examples/src/tutorials/26-claude-agent-example.tsClaudeAgent run/plan/execute
examples/src/tutorials/27-codex-agent-example.tsCodexAgent run/plan
examples/src/tutorials/28-audit-example.tsAudit log surface checks
examples/src/tutorials/29-cron-example.tsCron validation, next-run, and due checks
examples/src/tutorials/30-env-config-example.tsTauri / server mode detection
examples/src/tutorials/31-storage-example.tsStorage provider operations
examples/src/tutorials/32-insights-example.tsInsight filtering and EventRank scoring
examples/src/tutorials/33-search-example.tsSearch-intent classification
examples/src/tutorials/34-api-example.tsTyped HTTP client helpers
examples/src/tutorials/35-db-example.tsbatchInsert, password hashing
examples/src/tutorials/36-sqlite-example.tsSQLite raw-message storage + BM25 search
examples/src/tutorials/37-memory-consolidation-example.tsEvidence clustering and consolidation planning
examples/src/tutorials/38-channels-example.tsPlatform adapter error envelopes
examples/src/tutorials/39-integrations-runtime-example.tsIntegration runtime helpers
examples/src/tutorials/40-contracts-example.tsUser-type and integration-id guards
examples/src/tutorials/41-peer-profile-example.tscreatePeerProfile + peer relationships
examples/src/tutorials/42-extract-derive.tsdistill + derive + per‑channel signals field on hits

For end-to-end use cases, see the examples linked from Personal Memory Assistant, Customer Support Agent, and Research Knowledge Tracker.

Next Steps


Sources: