Best Practices
Recommendations for building effective memory-powered applications
Best Practices
Follow these best practices to build effective and efficient applications with Memokit.
Memory Content
Write Clear, Descriptive Content
Memory content should be clear and self-contained for better semantic search:
// Good: Clear and descriptive
await createMemory({
content: "User John prefers dark mode and uses VS Code as his primary IDE",
userId: "user_123"
});
// Avoid: Vague or context-dependent
await createMemory({
content: "He likes dark mode",
userId: "user_123"
});Include Relevant Context
Add context that helps with retrieval:
await createMemory({
content: "During the onboarding call on January 15, the user mentioned they need integration with Slack and GitHub for their development workflow",
userId: "user_123",
tags: ["onboarding", "integrations"]
});Use Tags for Organization
Tags help filter and organize memories:
await createMemory({
content: "User's favorite programming language is Python",
userId: "user_123",
tags: ["preference", "programming", "profile"]
});
// Later, filter by tags
await searchMemories({
query: "programming preferences",
userId: "user_123",
tags: ["preference"] // Only search preference-tagged memories
});Search Optimization
Write Natural Queries
Memokit uses semantic search, so natural language queries work best:
// Good: Natural language
await searchMemories({
query: "What are the user's communication preferences?"
});
// Less effective: Keyword-style
await searchMemories({
query: "communication preferences user"
});Set Appropriate Thresholds
Use the threshold parameter to filter out low-relevance results:
// High threshold for precise matches
await searchMemories({
query: "What is the user's email?",
threshold: 0.8, // Only highly relevant results
limit: 3
});
// Lower threshold for broader search
await searchMemories({
query: "Tell me about the user's background",
threshold: 0.5, // Include somewhat related results
limit: 10
});Scope Searches Appropriately
Always scope searches to the relevant user/agent:
// Good: Scoped to specific user
await searchMemories({
query: "project deadlines",
userId: "user_123"
});
// Good: Scoped to specific agent
await searchMemories({
query: "conversation context",
agentId: "support_bot"
});Entity & Relation Management
Extract Meaningful Entities
Create entities for important, reusable concepts:
// Good: Important entity worth tracking
await createEntity({
name: "Acme Corporation",
type: "ORGANIZATION",
description: "User's employer, a Fortune 500 tech company"
});
// Avoid: Transient or unimportant entities
await createEntity({
name: "today",
type: "OTHER" // Too generic
});Use Consistent Relationship Types
Establish a vocabulary of relationship types for your application:
// Define your relationship vocabulary
const RELATIONSHIP_TYPES = {
WORKS_FOR: 'WORKS_FOR',
MANAGES: 'MANAGES',
KNOWS: 'KNOWS',
INTERESTED_IN: 'INTERESTED_IN',
LOCATED_IN: 'LOCATED_IN'
};
// Use consistently
await createRelation({
sourceId: personEntity.id,
targetId: companyEntity.id,
type: RELATIONSHIP_TYPES.WORKS_FOR
});Error Handling
Handle Errors Gracefully
Always implement proper error handling:
async function searchUserMemories(userId, query) {
try {
const results = await searchMemories({ userId, query });
return results;
} catch (error) {
if (error.code === 'QUOTA_EXCEEDED') {
// Handle quota exceeded
logWarning('Quota exceeded, using cached results');
return getCachedResults(userId, query);
}
if (error.code === 'RATE_LIMIT_EXCEEDED') {
// Implement retry with backoff
await sleep(error.retryAfter * 1000);
return searchUserMemories(userId, query);
}
// Log and rethrow unknown errors
logError('Memory search failed', error);
throw error;
}
}Validate Input Before Sending
Validate data before making API calls:
function validateMemoryContent(content) {
if (!content || typeof content !== 'string') {
throw new Error('Content must be a non-empty string');
}
if (content.length > 10000) {
throw new Error('Content exceeds maximum length of 10,000 characters');
}
return content.trim();
}
async function createMemorySafe(data) {
const content = validateMemoryContent(data.content);
return createMemory({ ...data, content });
}Performance
Implement Caching
Cache frequently accessed data:
class MemoryCache {
constructor(ttlMs = 5 * 60 * 1000) {
this.cache = new Map();
this.ttlMs = ttlMs;
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
return null;
}
return entry.value;
}
set(key, value) {
this.cache.set(key, {
value,
expiresAt: Date.now() + this.ttlMs
});
}
}
const memoryCache = new MemoryCache();
async function getMemory(id) {
const cached = memoryCache.get(id);
if (cached) return cached;
const memory = await fetchMemory(id);
memoryCache.set(id, memory);
return memory;
}Use Pagination
When listing memories, use pagination to avoid loading too much data:
async function* getAllMemories(userId) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await listMemories({
userId,
page,
limit: 100
});
for (const memory of response.items) {
yield memory;
}
hasMore = page < response.pagination.totalPages;
page++;
}
}
// Usage
for await (const memory of getAllMemories('user_123')) {
processMemory(memory);
}Security
Never Expose API Keys
Keep API keys server-side:
// Good: Server-side API call
// server.js
app.post('/api/memories/search', async (req, res) => {
const results = await memokitClient.searchMemories({
query: req.body.query,
userId: req.user.id
});
res.json(results);
});
// Bad: Client-side API key exposure
// client.js (DON'T DO THIS)
fetch('https://api.memokit.dev/v1/memories/search', {
headers: {
'Authorization': 'Bearer mk_exposed_key' // NEVER DO THIS
}
});Validate User Access
Always verify users can only access their own data:
app.get('/api/memories/:id', async (req, res) => {
const memory = await memokitClient.getMemory(req.params.id);
// Verify ownership
if (memory.userId !== req.user.id) {
return res.status(403).json({ error: 'Access denied' });
}
res.json(memory);
});