Best Practices - Production-Ready Patterns

August 18, 2026 · View on GitHub

This guide shares best practices learned from running OpenContext in production. Follow these patterns to build reliable, performant applications.

Core Principles

1. Use messageId for Idempotency

Always provide a unique, stable messageId for each fact:

// ❌ BAD: No messageId, can't safely re-ingest
await messages.storeMessages([{
  content: "User prefers dark mode",
  userId: "user-123",
  // ... no messageId
}]);

// ✅ GOOD: Stable, unique messageId
await messages.storeMessages([{
  messageId: `${userId}-${platform}-${externalId}`,
  content: "User prefers dark mode",
  userId: "user-123",
  platform: "slack",
  timestamp: Date.now(),
  createdAt: Date.now(),
}]);

Why: Re-ingesting the same message is safe and won't create duplicates.

2. Handle Warnings Gracefully

OpenContext degrades gracefully - never ignore warnings:

const results = await store.search({
  userId: "user-123",
  query: "preferences",
  limit: 10,
});

// ✅ GOOD: Handle warnings appropriately
for (const warning of results.warnings) {
  switch (warning.code) {
    case "embed_query_not_configured":
      // Fall back to keyword search
      logger.warn("Embeddings unavailable, using keyword search");
      break;
    case "raw_message_storage_unavailable":
      // Alert ops but continue
      alertOps("Memory storage unavailable");
      break;
  }
}

// ❌ BAD: Silently ignore warnings
const results = await store.search({ ... });
// No warning handling

3. Choose the Right Backend

Use CaseRecommended Backend
Desktop appSQLite-vec
Single-user serverSQLite-vec
Multi-user serverPostgres + pgvector
Cloud-nativeChroma
BrowserIndexedDB
// ✅ GOOD: Match backend to use case
const store = await createMemoryStore({
  dbPath: isDesktop() ? "./memory.db" : undefined,
  db: isDesktop() ? undefined : { getDb: () => postgresDb },
});

// ❌ BAD: Use Postgres for desktop app
const store = await createMemoryStore({
  db: { getDb: () => postgresDb },  // Overkill for desktop
});

Performance Best Practices

4. Batch Writes

Write in batches, not one-by-one:

// ❌ BAD: One request per message
for (const msg of messages) {
  await messages.storeMessages([msg]);  // N network calls
}

// ✅ GOOD: Batch all writes
await messages.storeMessages(messages);  // 1 network call

5. Cache Embeddings

Embeddings are expensive - cache them:

import { LRUCache } from "lru-cache";

const embeddingCache = new LRUCache<string, number[]>({
  max: 1000,
  ttl: 1000 * 60 * 60,  // 1 hour
});

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

  const embedding = await embedder.embed(text);
  embeddingCache.set(text, embedding);
  return embedding;
}

6. Use Appropriate Limits

// ✅ GOOD: Reasonable limits for UI
const results = await store.search({
  userId: "user-123",
  query: "recent messages",
  limit: 10,  // UI typically shows 5-20 results
  threshold: 0.7,  // Filter low-quality matches
});

// ❌ BAD: Excessive limits
const results = await store.search({
  limit: 10000,  // Too many results, slow
  threshold: 0.1,  // Too permissive, poor quality
});

Security Best Practices

7. Encrypt Secrets at Rest

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

// ✅ GOOD: Encrypt API keys before storage
// TokenEncryption reads ENCRYPTION_KEY from the environment.
const encryptor = new TokenEncryption();
const encrypted = encryptor.encryptToken(apiKey);
await database.save({ userId, encryptedToken: encrypted });

// ❌ BAD: Store secrets in plain text
await database.save({ userId, apiToken });  // Dangerous!

8. Validate URLs (SSRF Protection)

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

// ✅ GOOD: Validate URLs before calling
// The default mode enforces HTTPS and blocks private/loopback targets.
// Use { strictWhitelist: false } to skip the known-storage-provider whitelist.
try {
  const safeUrl = await validateUrlForSSRF(userProvidedUrl, { strictWhitelist: false });
  await fetch(safeUrl.toString());
} catch (error) {
  console.error("Blocked potentially unsafe URL:", error);
}

// ❌ BAD: Call user-provided URLs directly
await fetch(userProvidedUrl);  // SSRF vulnerability!

9. Use Least-Privilege Policies

// ✅ GOOD: Restrict who can write to memory graph
process.env.MEMORY_GRAPH_WRITE_POLICY = "agent-only";

// ❌ BAD: Allow anyone to write
process.env.MEMORY_GRAPH_WRITE_POLICY = "allow-all";

Data Modeling Best Practices

10. Store Facts, Not Chunks

// ❌ BAD: Store arbitrary chunks
await messages.storeMessages([{
  messageId: "chunk-1",
  content: "... partial sentence fragment ...",
}]);

// ✅ GOOD: Store complete facts
await messages.storeMessages([{
  messageId: "fact-1",
  content: "The user prefers dark mode across all applications",
  metadata: {
    source: "user_profile",
    confidence: 0.95,
  },
}]);

11. Use Metadata Effectively

await messages.storeMessages([{
  messageId: "msg-1",
  content: "Meeting decision: Use TypeScript for new project",
  metadata: {
    type: "decision",
    project: "new-project",
    meetingId: "meeting-123",
    participants: ["alice", "bob"],
    importance: "high",
  },
  // ...
}]);

12. Structure User IDs Consistently

// ✅ GOOD: Include source in userId
const userId = `slack|user|${slackUserId}`;  // "slack|user|U123456"
const userId = `gmail|user|${emailAddress}`;  // "gmail|user|example@gmail.com"

// ❌ BAD: Inconsistent formats
const userId = slackUserId;  // Just the ID, no source
const userId = email;  // Sometimes email, sometimes ID

Testing Best Practices

13. Use In-Memory Databases for Tests

// ✅ GOOD: In-memory SQLite for tests
process.env.MEMORY_STORE_DB_PATH = ":memory:";
const store = await createMemoryStore();

// ❌ BAD: Real database in tests
process.env.MEMORY_STORE_DB_PATH = "./production.db";
const store = await createMemoryStore();

14. Test Warning Scenarios

it("handles missing embedder gracefully", async () => {
  const store = await createMemoryStore({
    unified: {
      // No embedQuery - should produce warning
    },
  });

  const results = await store.search({
    userId: "test-user",
    query: "test",
    limit: 10,
  });

  expect(results.warnings).toContainEqual(
    expect.objectContaining({
      code: "embed_query_not_configured",
    })
  );
  // But results should still be returned
  expect(results.results).toBeDefined();
});

15. Mock External Services

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

// ✅ GOOD: Mock integrations in tests
const mockIntegrations = {
  isConnected: vi.fn().mockResolvedValue(true),
  fetchMessages: vi.fn().mockResolvedValue([]),
};

// ❌ BAD: Real API calls in tests
const context = await createMinimalContext({
  /* real credentials */
});  // Real calls!

Monitoring Best Practices

16. Use the Doctor Command

# ✅ GOOD: Regular health checks
npx @melandlabs/opencontext doctor --json | jq '.ok'  # CI-friendly

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

17. Log Important Events

import { logFileRead, logCommandExec } from "@melandlabs/opencontext";

// ✅ GOOD: Log critical operations
logFileRead("/sensitive/config.json");
logCommandExec("deploy", ["--env", "production"]);

// ❌ BAD: No audit trail
await improve({ userId, messageId, correction });
// No logging!

18. Track Metrics

// ✅ GOOD: Track key metrics
const metrics = {
  writes: 0,
  reads: 0,
  corrections: 0,
  errors: 0,
};

async function trackedRemember(...args) {
  metrics.writes++;
  try {
    return await rememberFact(...args);
  } catch (err) {
    metrics.errors++;
    throw err;
  }
}

// Report metrics periodically
setInterval(() => {
  console.log("[metrics]", metrics);
}, 60000);

Deployment Best Practices

19. Use Environment Variables for Config

# ✅ GOOD: Environment-based config
MEMORY_STORE_DB_PATH=/data/memory.db
EMBEDDING_PROVIDER=openrouter
OPENROUTER_API_KEY=sk-or-...

# ❌ BAD: Hard-coded config
# Config in source code

20. Run Health Checks in CI

# .github/workflows/test.yml
- name: Run OpenContext doctor
  run: npx @melandlabs/opencontext doctor --json | jq -e '.ok == true'

21. Use Restart Policies

# docker-compose.yml
services:
  opencontext:
    restart: unless-stopped  # ✅ GOOD
    # restart: "no"  # ❌ BAD

Common Pitfalls

❌ Don't: Ignore the Temporal Nature

// ❌ BAD: Overwrite facts
await messages.storeMessages([{
  messageId: "same-id",
  content: "Updated fact",
  // ... assumes overwrite behavior
}]);
// ✅ GOOD: Use corrections for updates
await improve({
  messageId: "original-id",
  correction: {
    type: "supersedes",
    newValue: "Updated fact",
    reason: "User updated preference",
  },
});

❌ Don't: Mix User IDs Across Sources

// ❌ BAD: Same user ID, different sources
await rememberFact("user-123", "Prefers dark mode", "slack");
await rememberFact("user-123", "Prefers light mode", "gmail");
// Conflict! Which is true?

// ✅ GOOD: Source-specific user IDs
await rememberFact("slack|user|123", "Prefers dark mode", "slack");
await rememberFact("gmail|user|123@example.com", "Prefers light mode", "gmail");

❌ Don't: Store Binary Data in Content

// ❌ BAD: Large blobs in content
await messages.storeMessages([{
  content: entirePDFString,  // Can be MB!
}]);

// ✅ GOOD: Store reference to blob
await messages.storeMessages([{
  content: "User uploaded document 'report.pdf'",
  metadata: {
    blobUrl: "s3://...",
    mimeType: "application/pdf",
    size: 1024000,
  },
}]);

Checklist for Production

Before deploying to production:

  • All messages have stable messageIds
  • Warnings are handled, not ignored
  • Backend matches use case (SQLite for desktop, Postgres for server)
  • Secrets are encrypted at rest
  • URLs are validated before calling
  • Tests use in-memory databases
  • Doctor command runs successfully
  • Health checks are in CI/CD
  • Metrics are being collected
  • Restart policy is configured
  • Environment variables are documented
  • Audit logging is enabled

Next Steps


Sources: