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.
Multi-Source Unified Search
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:
| Method | Writes? | Purpose |
|---|---|---|
store.search({ ...input, synthesize: true }) | No | Read-only LLM synthesis over unified evidence |
store.consolidate(input) | Yes | Agentic gather → plan → vet → persist loop |
Both methods are also exposed over HTTP and MCP:
| Transport | Read-only | Write-back |
|---|---|---|
| SDK | store.search({ ...input, synthesize: true }) | store.consolidate(input) |
| HTTP | POST /v1/search (set synthesize: true) | POST /v1/consolidate:apply |
| MCP | memory.search (set synthesize: true) | memory.consolidate |
Read-only search({ synthesize: true })
store.search({ ...input, synthesize: true }) fans a single query out across four tiers:
- Raw messages —
searchRawMessagesAnn(semantic) +searchRawMessagesLexical(BM25) - Summaries —
searchSummaries(L1/L2/L3) - Insights —
searchInsights - Knowledge —
searchKnowledge(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_configuredwarning instead of throwing. - LLM throws → evidence is preserved, a
reflect_llm_failedwarning is added,out.answerfalls 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 │ └──────────────┘
└─────────────────┘
- Gather — same evidence pipeline as
search({ synthesize: true }). - Build plan —
buildMemoryConsolidationPlan(records, thresholds)produces aMemoryConsolidationPlanwithpreserve/decay/deprecateentries. Rule-based; no LLM invention. - Vet (optional) — when
reasoning.completeis configured, the LLM is asked to approve or veto each entry. It may only mark entries asapprove/vetowith a reason; it cannot add new operations. - Persist —
graphStore.persistPlan(translate(plan))writes the graph updates;storage.deprecateRecords(...)soft-deprecates the records replaced bydeprecateentries.
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:
| Code | Triggered when | Behaviour |
|---|---|---|
reflect_apply_llm_skipped | No reasoning.complete configured | The rule-based plan runs without vet. |
reflect_apply_llm_vet_failed | LLM threw or returned invalid JSON | All entries marked approve (no-op plan). |
reflect_apply_graph_store_not_configured | No graph store attached | deprecateRecords still runs against the storage adapter. |
reflect_apply_dry_run | dryRun: true | Nothing is written; applied: false. |
reflect_apply_no_writes | Plan has no actionable entries | applied: 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";
| Kind | Meaning |
|---|---|
world | Facts about the world (e.g. "water boils at 100 °C"). |
experience | First-person events ("I went hiking last weekend"). |
mental_model | Generalised 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'sStdioServerTransport. If you build a custom client, serialize withJSON.stringify(obj) + '\n'and split on\non 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 / env | Default | Purpose |
|---|---|---|
--reasoning / REASONING=1 | off | Enable the LLM reasoning layer. |
--no-reasoning | — | Force-disable even when REASONING=1 is set. |
--reasoning-base-url / OPENCONTEXT_LLM_BASE_URL | https://openrouter.ai/api/v1 | OpenAI-compatible base URL. |
--reasoning-model / OPENCONTEXT_LLM_MODEL | openai/gpt-4o-mini | Reasoning LLM model identifier. |
--reasoning-timeout-ms / OPENCONTEXT_LLM_TIMEOUT_MS | 30000 | Per-request timeout. |
OPENCONTEXT_LLM_API_KEY | required when --reasoning | Bearer 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/dateToonly filter thememorysource.insightsandknowledgeresults 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:
asOfasks "what was true at this exact instant?" — a temporal snapshot over facts with validity windows.dateFrom/dateToask "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:
| Verb | Purpose |
|---|---|
vsaStoreFact | Persist a (role, filler) binding as a HRR pair. Idempotent on factId. |
vsaRecall | Re-superpose all stored facts for the user/scope, unbind by the requested role, cleanup against a vocabulary, return the best-match label. |
vsaListFacts | Read-side projection of stored facts (no vectors). Useful for diagnostics and audit. |
vsaForget | Soft-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/store—vsaStoreFactPOST /v1/vsa/recall—vsaRecallPOST /v1/vsa/list—vsaListFactsPOST /v1/vsa/forget—vsaForget
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.tsfor 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 checkisJobDue.
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 withTokenEncryption.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.ts—needsRealTimeInfoclassification 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.ts—batchInsert, 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 withClaudeAgent.examples/src/tutorials/27-codex-agent-example.ts— run and plan withCodexAgentin 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
- DeepSeek Harness installed
- Node.js >= 22.19.0 or >= 24.0.0
Installation
From npm (recommended)
# 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)
| Tool | Purpose |
|---|---|
oc_search | Search long-term memory (unified across memory, insights, knowledge) |
oc_remember | Persist one memory entry |
oc_memory_list | List recent memory entries in current scope |
oc_memory_get | Read one or more entries by ID |
oc_memory_revise | Soft-deprecate an entry and store a successor |
oc_memory_retire | Soft-deprecate an entry |
oc_prepare_context | Manually build a bounded context block |
oc_capture_source | Capture an arbitrary content source |
Summary & Outcome Tools (3)
| Tool | Purpose |
|---|---|
oc_session_summary | Generate and store a session summary |
oc_task_outcome | Record task outcomes, decisions, achievements |
oc_recent_summaries | List recent session summaries and task outcomes |
Insights Tools (2)
| Tool | Purpose |
|---|---|
oc_insights_search | Search structured insights (decisions, preferences, outcomes) |
oc_insight_capture | Capture a structured insight from conversation |
Knowledge/RAG Tools (3)
| Tool | Purpose |
|---|---|
oc_knowledge_search | RAG search over uploaded documents |
oc_document_upload | Upload documents to knowledge base |
oc_document_list | List 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
| Field | Default | Environment Variable |
|---|---|---|
capturePrompts | true | OPENCONTEXT_DSH_CAPTURE_PROMPTS |
maxRecallItems | 8 | OPENCONTEXT_DSH_MAX_RECALL_ITEMS |
autoSummarize | false | OPENCONTEXT_DSH_AUTO_SUMMARIZE |
captureToolResults | false | OPENCONTEXT_DSH_CAPTURE_TOOL_RESULTS |
enableInsights | true | OPENCONTEXT_DSH_ENABLE_INSIGHTS |
enableKnowledge | true | OPENCONTEXT_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, returnsEntityEdge[](label / kind / relation / source). Opt-in: host providesentityExtractor; without it, the call returns an empty list plus adistill_extractor_not_configuredwarning.derive— reads a window of candidate fact texts, returnsDerivedFact[](text / kind / sources / optional window). Opt-in: host providesderiver; without it, the call returns an empty list plus aderive_deriver_not_configuredwarning.
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:
distillwith a rule-based extractor and an in-memoryentityStoremock.derivewith a trivial summary deriver and an explicitcandidateTextslist.searchwithmergeStrategy: "rrf"and anentitySearchdep that returns a hit for one of the messages — confirm the result carriessignals.entityandsignals.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:
| Example | Topic |
|---|---|
examples/src/tutorials/00-hello-memory-example.ts | Store and search a first memory |
examples/src/tutorials/01-remember-example.ts | Store a fact with metadata |
examples/src/tutorials/02-recall-example.ts | Search unified memory across sources |
examples/src/tutorials/03-forget-example.ts | Archive a message |
examples/src/tutorials/04-improve-example.ts | Deprecate and supersede a fact |
examples/src/tutorials/05-time-travel-example.ts | Time-travel / asOf queries |
examples/src/tutorials/06-minimal-config-example.ts | Minimal SQLite-vec backend setup |
examples/src/tutorials/07-local-embeddings-example.ts | Local embedding generation |
examples/src/tutorials/08-local-embeddings-full-setup.ts | Full local embedding + vector-store setup |
examples/src/tutorials/09-http-client-example.ts | HTTP client for the memory HTTP server |
examples/src/tutorials/10-reasoning-memory-example.ts | Reasoning-backed retrieval + date-range filtering |
examples/src/tutorials/11-loop-example.ts | Loop engine preferences |
examples/src/tutorials/12-integration-ids-example.ts | List supported integration IDs |
examples/src/tutorials/13-batch-example.ts | Batch memory writes |
examples/src/tutorials/17-memory-service.ts | Reusable memory service wrapper |
examples/src/tutorials/18-remember-everything-example.ts | Ingest incoming platform messages |
examples/src/tutorials/19-warning-handling-example.ts | Inspect search warnings |
examples/src/tutorials/20-metadata-example.ts | Store structured metadata with a fact |
examples/src/tutorials/21-scheduled-tasks-example.ts | Cron next-run computation |
examples/src/tutorials/22-token-encryption-example.ts | Token encryption / decryption |
examples/src/tutorials/23-url-validation-example.ts | SSRF URL validation |
examples/src/tutorials/24-web-search-example.ts | Web search with Brave |
examples/src/tutorials/25-audit-logging-example.ts | Structured audit logging |
examples/src/tutorials/26-claude-agent-example.ts | ClaudeAgent run/plan/execute |
examples/src/tutorials/27-codex-agent-example.ts | CodexAgent run/plan |
examples/src/tutorials/28-audit-example.ts | Audit log surface checks |
examples/src/tutorials/29-cron-example.ts | Cron validation, next-run, and due checks |
examples/src/tutorials/30-env-config-example.ts | Tauri / server mode detection |
examples/src/tutorials/31-storage-example.ts | Storage provider operations |
examples/src/tutorials/32-insights-example.ts | Insight filtering and EventRank scoring |
examples/src/tutorials/33-search-example.ts | Search-intent classification |
examples/src/tutorials/34-api-example.ts | Typed HTTP client helpers |
examples/src/tutorials/35-db-example.ts | batchInsert, password hashing |
examples/src/tutorials/36-sqlite-example.ts | SQLite raw-message storage + BM25 search |
examples/src/tutorials/37-memory-consolidation-example.ts | Evidence clustering and consolidation planning |
examples/src/tutorials/38-channels-example.ts | Platform adapter error envelopes |
examples/src/tutorials/39-integrations-runtime-example.ts | Integration runtime helpers |
examples/src/tutorials/40-contracts-example.ts | User-type and integration-id guards |
examples/src/tutorials/41-peer-profile-example.ts | createPeerProfile + peer relationships |
examples/src/tutorials/42-extract-derive.ts | distill + 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
- 📖 Getting Started - Quick start
- 👤 User Guide - Core concepts
- 🔧 Developer Guide - Integration
- 📚 Best Practices - Optimization
Sources: