Overview
This guide consolidates proven patterns and recommendations for building production-ready applications with Trix. These practices come from real-world deployments and will help you avoid common pitfalls while maximizing performance and reliability.
Writing Effective Memory Content
The quality of your memories directly impacts retrieval quality. Well-structured content retrieves better.
"dark mode, notifications off"
"likes coffee"
"Python"
"User prefers dark mode and has disabled notifications"
"User drinks coffee every morning, prefers medium roast"
"User is proficient in Python and prefers it for data analysis"
- Write complete sentences - Fragments lose context and match poorly
- Include the subject - "User prefers X" retrieves better than just "X"
- Be specific - Vague content matches too many queries
- Add context - Include why or when, not just what
Deduplicating Memories
Over time, similar memories accumulate. Deduplicate to keep your memory store clean and retrieval fast.
async function storeWithDedup(content: string, spaceId: string) {
// Check for existing similar memories
const existing = await client.memories.list({
q: content,
spaceId,
limit: 1
});
// If very similar memory exists, skip or update
if (existing.data.length > 0 && existing.data[0].score > 0.95) {
console.log('Similar memory exists, skipping');
return existing.data[0];
}
// No duplicate, create new memory
return client.memories.create({ content, spaceId });
}Run deduplication during off-peak hours. For large spaces, process in batches to avoid timeouts.
Tagging Strategy
Tags enable efficient filtering. Design a consistent tagging taxonomy upfront.
// Define your tag taxonomy
const TAG_TAXONOMY = {
category: ['preference', 'fact', 'interaction', 'feedback'],
source: ['conversation', 'import', 'system', 'user-provided'],
confidence: ['verified', 'inferred', 'uncertain'],
domain: ['work', 'personal', 'health', 'finance']
};
function buildTags(options: TagOptions): string[] {
const tags: string[] = [];
if (options.category && TAG_TAXONOMY.category.includes(options.category)) {
tags.push(`cat:${options.category}`);
}
if (options.source && TAG_TAXONOMY.source.includes(options.source)) {
tags.push(`src:${options.source}`);
}
if (options.confidence && TAG_TAXONOMY.confidence.includes(options.confidence)) {
tags.push(`conf:${options.confidence}`);
}
return tags;
}- Use prefixes -
cat:,src:help organize tags by type - Keep tags lowercase - Consistent casing prevents duplicates
- Limit tag count - 3-5 tags per memory is usually sufficient
- Document your taxonomy - Share with your team for consistency
Metadata Best Practices
Metadata stores structured information for filtering and auditing. Use it for data that doesn't belong in content.
await client.memories.create({
content: 'User completed the onboarding tutorial',
spaceId,
metadata: {
createdBy: 'system',
extractedFrom: 'session_abc123',
eventType: 'onboarding',
step: 5,
completed: true,
version: 1,
supersedes: null
}
});
const onboardingMemories = await client.memories.list({
spaceId,
metadata: { eventType: 'onboarding', completed: true }
});Don't store PII in metadata. Metadata is easier to accidentally expose in logs than content.
Search Optimization
Optimize queries for faster, more relevant results.
function optimizeQuery(rawQuery: string): string {
const fillers = ['the', 'a', 'an', 'is', 'are', 'was', 'were'];
let query = rawQuery.toLowerCase();
for (const filler of fillers) {
query = query.replace(new RegExp(`\\b${filler}\\b`, 'g'), '');
}
return query.trim().replace(/\s+/g, ' ');
}
async function smartSearch(query: string, spaceId: string, context: SearchContext) {
let limit = 5;
if (context.needsComprehensive) limit = 20;
else if (context.quickLookup) limit = 1;
return client.memories.list({ q: optimizeQuery(query), spaceId, limit });
}- Use specific queries - "user's coffee preference" beats "coffee"
- Request only needed fields - Reduce payload size when possible
- Set appropriate limits - Don't fetch 100 when you need 5
- Cache frequent queries - Reduce API calls for common lookups
Error Handling
Robust error handling keeps your application reliable when issues occur.
import { Trix, TrixError, RateLimitError, NotFoundError } from '@trixdb/client';
async function safeMemoryOperation<T>(
operation: () => Promise<T>,
fallback: T
): Promise<T> {
try {
return await operation();
} catch (error) {
if (error instanceof RateLimitError) {
const retryAfter = error.retryAfter || 1000;
await sleep(retryAfter);
return operation();
}
if (error instanceof NotFoundError) {
console.warn('Resource not found:', error.message);
return fallback;
}
if (error instanceof TrixError) {
console.error('Trix API error:', error.code, error.message);
return fallback;
}
throw error;
}
}Always have a fallback strategy. Your AI should still work, even if memory retrieval fails temporarily.
Async Processing
Store memories asynchronously to avoid adding latency to user interactions.
class AsyncMemoryService {
private queue: MemoryJob[] = [];
private processing = false;
enqueue(content: string, spaceId: string, metadata?: object) {
this.queue.push({ content, spaceId, metadata, timestamp: Date.now() });
this.processQueue();
}
private async processQueue() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
while (this.queue.length > 0) {
const batch = this.queue.splice(0, 10);
try {
await Promise.all(
batch.map(job =>
client.memories.create({
content: job.content,
spaceId: job.spaceId,
metadata: job.metadata
})
)
);
} catch (error) {
this.queue.unshift(...batch);
await sleep(1000);
}
}
this.processing = false;
}
}Security Practices
Protect your users' data with proper security measures.
- Never expose admin keys - Use scoped keys for client-side access
- Validate spaceId ownership - Ensure users can only access their spaces
- Sanitize content - Don't store raw user input without validation
- Audit access - Log who accessed what, when
- Rotate keys periodically - Especially after team member departures
Never store passwords, API keys, or other secrets as memory content. Memories are optimized for search, not secure storage.
Monitoring and Observability
Track performance and catch issues before they impact users.
- API latency (p50, p95, p99)
- Error rates by operation
- Results per search (are queries returning useful results?)
- Memory count per space (growth over time)
- Cache hit rates (if caching)
Common Pitfalls
Avoid these frequently encountered mistakes:
Storing Too Much
Don't store every message. Extract and store only meaningful facts. "User said hello" is rarely useful.
Ignoring Relevance Scores
Low-scoring results are noise. Set thresholds (0.6-0.8) to filter out irrelevant matches.
No Space Scoping
Always include spaceId in multi-user apps. Querying without it risks data leakage.
Sync Storage in Hot Path
Memory writes block response time. Store asynchronously after responding to users.
Performance Checklist
- Reuse the Trix client instance (don't recreate per request)
- Cache space IDs to avoid database lookups
- Use parallel requests when fetching from multiple spaces
- Set appropriate limits (don't over-fetch)
- Store memories asynchronously
- Batch operations when creating multiple memories
- Implement retry logic with exponential backoff
- Monitor latency percentiles, not just averages
Following these practices, most applications see sub-100ms retrieval times even with thousands of memories per space.
Summary
The most impactful practices to implement immediately:
- Write complete, contextual memory content - Better content means better retrieval
- Always scope by space - Essential for multi-user apps
- Store asynchronously - Don't slow down user responses
- Handle errors gracefully - AI should work even if memory fails
- Monitor performance - Track latency and error rates from day one