@recalio-client/sdk
v1.1.0
Published
Recalio SDK - Memory Layer for AI Agents
Downloads
235
Readme
Recalio Client SDK
Memory Layer SDK for AI Agents - TypeScript/JavaScript client library.
Installation
npm install @recalio/clientQuick Start
import { Recalio } from '@recalio/client'
const recalio = new Recalio({
apiKey: 'rec_sk_your_api_key_here',
baseUrl: 'https://api.recalio.dev' // Optional, defaults to production
})
// Add memory
await recalio.memory.add({
messages: [{ role: 'user', content: 'I work at Google as a Senior Engineer' }],
contextId: 'chat-123',
userId: 'alice'
})
// Search memory
const result = await recalio.memory.search('Where does Alice work?', {
userId: 'alice',
rerank: true,
includeSummary: true
})
console.log(result.summary) // "Alice works at Google as a Senior Engineer."
console.log(result.aiContext) // Structured context for AI agentsAPI Reference
memory.add(input)
Add conversations or data to memory. The ingestion layer automatically extracts entities (people, organizations, etc.) and facts (relationships) from your messages.
Parameters:
messages(any) - Messages to ingest. Accepts any format:- Array of chat messages:
[{ role: 'user', content: '...' }] - Plain string:
"Alice works at Google" - Array of strings:
["Fact 1", "Fact 2"] - Any object/structured data
- Array of chat messages:
contextId(string, required) - Unique identifier for this context. Examples:- Chat conversation:
'chat-123'or'thread-abc' - Phone call:
'call-456' - Session:
'session-789' - Page/document:
'page-xyz'
- Chat conversation:
userId(string, optional) - User ID for private data. Should be unique per user (e.g.,'alice_chen_123'or'user_${name}_${id}'). When set, data is only accessible to that user.contextType(string, optional) - Type of context (default:'conversation'). Examples:'conversation','meeting','email','call'source(string, optional) - Source system (default:'ingestion_api'). Examples:'web_app','slack','api'
Returns: ``typescript { success: boolean contextId: string status: 'processing' // Indicates async queue processing message: string // e.g., "Your data has been queued for processing. (2 jobs ahead)" jobId: string // Queue job ID for tracking queuePosition: number // Number of jobs ahead stats: { // All zeros until processing completes entitiesExtracted: 0 factsExtracted: 0 factsAdded: 0 factsUpdated: 0 factsIgnored: 0 factsEscalated: 0 } tokenUsage: { // All zeros until processing completes promptTokens: 0 completionTokens: 0 totalTokens: 0 } }
> **Note:** Recalio uses **queue-based processing** for reliable ingestion. Stats and token counts are zeros initially and will be logged after processing completes (typically 2-5 seconds). Use `memory.search()` to verify completion.
**Examples:**
```typescript
// Example 1: Add a conversation
const result = await recalio.memory.add({
messages: [
{ role: 'user', content: "I just met Alice Chen. She's a Senior Engineer at Google." },
{ role: 'assistant', content: "That's interesting! What does she work on?" },
{ role: 'user', content: "She works on AI for the Search team." }
],
contextId: 'chat-123',
userId: 'user_me_123'
})
// Returns: { success: true, contextId: 'chat-123', status: 'processing', message: '...', jobId: 'job_abc', queuePosition: 0, stats: {...}, tokenUsage: {...} }
// Example 2: Add plain text
await recalio.memory.add({
messages: "Alice Chen works at Google as a Senior Engineer on the Search team.",
contextId: 'note-456'
})
// Example 3: Add user-specific preferences
await recalio.memory.add({
messages: "I prefer email notifications and my favorite color is blue.",
contextId: 'preferences-alice',
userId: 'alice_chen_123', // Private to Alice
contextType: 'preferences'
})
// Example 4: Add meeting notes
await recalio.memory.add({
messages: [
"Meeting with Alice Chen and Bob Smith",
"Alice discussed her work on AI at Google",
"Bob manages the Cloud division"
],
contextId: 'meeting-789',
contextType: 'meeting',
source: 'calendar_integration'
})memory.search(query, options?)
Search memory and get AI-ready context. Returns structured facts, entities, and formatted context for your AI agent.
Parameters:
query(string) - Search query (natural language)options(SearchOptions, optional):userId(string) - Include user's private data in resultscontextId(string) - Filter to specific context onlylimit(number) - Maximum results (default: 100)onlyCurrent(boolean) - Only return current facts (exclude historical)rerank(boolean) - Enable LLM reranking for better relevance (default:false). Only applies if ≥10 facts.includeSummary(boolean) - Include natural language summary (default:true)expandDepth(number) - Graph expansion depth in hops (default:2). Higher values traverse more relationships but are slower.
Returns:
{
query: string
entities: Entity[] // Relevant entities found
facts: Fact[] // Relevant facts/relationships
aiContext: string // Structured context for prompt injection
summary?: string // Natural language summary (if includeSummary is true)
retrievalTimeMs: number // Time taken in milliseconds
}Entity Structure:
{
id: string
name: string // e.g., "Alice Chen"
label: string // e.g., "Person", "Organization"
properties?: Record<string, any>
}Fact Structure:
{
id: string
subject: { id: string; name: string; label: string }
predicate: string // e.g., "works_at", "reports_to"
object: { id: string; name: string; label: string }
factType?: string // e.g., "relationship", "attribute"
confidence?: number // 0.0 to 1.0
properties?: Record<string, any>
validFrom?: string // ISO date string
validTo?: string // ISO date string (if historical)
isCurrent?: boolean // true if currently valid
}Examples:
// Example 1: Simple search WITH error handling
// ⚠️ IMPORTANT: You MUST handle errors - search() can throw exceptions
let result
try {
result = await recalio.memory.search('Where does Alice work?')
// Check for credit exhaustion (doesn't throw, returns in error field)
if (result.error) {
console.warn('Memory unavailable:', result.error.message)
// result.entities = []
// result.facts = []
// result.aiContext = ''
}
} catch (error) {
// Handle other errors (UNAUTHORIZED, PAYLOAD_TOO_LARGE, etc.)
console.error('Search failed:', error.message)
// Return empty result or handle appropriately
result = {
query: 'Where does Alice work?',
entities: [],
facts: [],
aiContext: '',
retrievalTimeMs: 0
}
}
// Use result safely
const aiContext = result.aiContext || 'No memory available'
console.log(result.summary)
// "Alice Chen works at Google as a Senior Engineer on the Search team."
console.log(result.aiContext)
// FACTS and ENTITIES represent relevant context to the current conversation.
//
// <FACTS>
// ## CURRENT FACTS (valid now):
// ### Generals:
// - [CURRENT] Alice Chen works at Google. (since 2024-01-15 10:30:00)
// </FACTS>
//
// <ENTITIES>
// - Alice Chen: Alice Chen is a Senior Engineer at Google...
// - Google: Google is a technology company...
// </ENTITIES>
// Example 2: Search with reranking (better for complex queries)
const result = await recalio.memory.search('Who does Alice work with?', {
rerank: true, // Enable LLM reranking
includeSummary: true, // Get summary
expandDepth: 2, // Traverse 2 hops in graph
limit: 50 // Max 50 results
})
// Example 3: Search user-specific memory
const userResult = await recalio.memory.search('What are my preferences?', {
userId: 'alice_chen_123', // Only search Alice's private data
includeSummary: true
})
// Example 4: Search specific context
const contextResult = await recalio.memory.search('What was discussed?', {
contextId: 'meeting-789', // Only search this meeting
includeSummary: true
})
// Example 5: Search only current facts (exclude historical)
const currentResult = await recalio.memory.search('Where does Alice work now?', {
onlyCurrent: true, // Exclude historical facts
expandDepth: 1 // 1 hop only (faster)
})Using Memory in AI Agents
Best Practice: Memory Context at Bottom of Prompt
When using aiContext in your AI agent prompts, place it at the bottom of your system prompt, after instructions but before user messages:
// ✅ CORRECT: Memory context at bottom
const systemPrompt = `
You are a helpful assistant.
Instructions:
- Be concise and factual
- Use the provided context to answer questions
- If context is insufficient, say so
${searchResult.aiContext} // Memory context at bottom
`
// ❌ WRONG: Don't put memory at the top
const badPrompt = `
${searchResult.aiContext} // Don't do this
You are a helpful assistant...
`Complete Workflow Example
import { Recalio } from '@recalio/client'
import OpenAI from 'openai'
const recalio = new Recalio({ apiKey: 'rec_sk_xxx' })
const openai = new OpenAI({ apiKey: 'sk-xxx' })
// Step 1: Add conversation to memory
await recalio.memory.add({
messages: [
{ role: 'user', content: "I just met Alice Chen. She's a Senior Engineer at Google." },
{ role: 'assistant', content: "That's interesting! What does she work on?" },
{ role: 'user', content: "She works on AI for the Search team." }
],
contextId: 'chat-123',
userId: 'user_me_123'
})
// Step 2: When user asks a question, search memory
// IMPORTANT: Handle errors - search() can throw exceptions
let searchResult
try {
searchResult = await recalio.memory.search('Tell me about Alice Chen', {
userId: 'user_me_123',
rerank: true,
includeSummary: true,
expandDepth: 2
})
// Check for credit exhaustion (doesn't throw, returns in error field)
if (searchResult.error) {
console.warn('Memory unavailable:', searchResult.error.message)
// Continue without memory - AI agent can still work
}
} catch (error) {
// Handle other errors (UNAUTHORIZED, PAYLOAD_TOO_LARGE, etc.)
if (error instanceof RecalioError) {
console.error('Search failed:', error.message)
// Return empty result or handle appropriately
}
searchResult = {
query: 'Tell me about Alice Chen',
entities: [],
facts: [],
aiContext: '',
retrievalTimeMs: 0
}
}
// Step 3: Build prompt with memory at bottom
const messages = [
{
role: 'system',
content: `You are a helpful assistant.
Instructions:
- Use the provided context to answer questions accurately
- Be concise and factual
- If you don't know something, say so
${searchResult.aiContext}` // Memory context at bottom
},
{
role: 'user',
content: 'Tell me about Alice Chen'
}
]
// Step 4: Send to your LLM
const completion = await openai.chat.completions.create({
model: 'gpt-4',
messages: messages
})
console.log(completion.choices[0].message.content)Customer Support Example
async function handleCustomerQuery(customerId: string, query: string) {
// Search customer's history
const memory = await recalio.memory.search(query, {
userId: `customer_${customerId}`,
includeSummary: true,
rerank: true
})
// Build prompt with memory at bottom
const systemPrompt = `
You are a customer support agent.
Guidelines:
- Be friendly and helpful
- Use customer history to provide personalized responses
- Escalate if you cannot resolve the issue
${memory.aiContext} // Customer history at bottom
`
// Send to your LLM
return await yourLLM.generate(systemPrompt, query)
}Error Handling
Recalio SDK handles errors in two ways:
- Thrown Exceptions - For most errors (UNAUTHORIZED, PAYLOAD_TOO_LARGE, etc.)
- In-Response Errors - For credit exhaustion (returns empty data with error field)
Error Types
import { RecalioError, RecalioErrorCode } from '@recalio/client'
// Error codes
RecalioErrorCode.UNAUTHORIZED // Invalid API key
RecalioErrorCode.INSUFFICIENT_CREDITS // Credits exhausted (in response.error)
RecalioErrorCode.PAYLOAD_TOO_LARGE // Request too large
RecalioErrorCode.TOO_MANY_REQUESTS // Rate limit exceeded
RecalioErrorCode.NOT_FOUND // Resource not found
RecalioErrorCode.BAD_REQUEST // Invalid request
RecalioErrorCode.CONFLICT // Resource conflict
RecalioErrorCode.INTERNAL_SERVER_ERROR // Server errorHandling Credit Exhaustion (In-Response Error)
Credit exhaustion doesn't throw - it returns empty data with an error field:
import { Recalio, isCreditExhaustionError, extractCreditResetDate } from '@recalio/client'
const result = await recalio.memory.search('Where does Alice work?')
// Check for credit exhaustion (doesn't throw exception)
if (result.error) {
if (isCreditExhaustionError(result.error)) {
const resetDate = extractCreditResetDate(result.error)
console.warn(`Credits exhausted. Resets at: ${resetDate}`)
// Continue without memory - AI agent can still work
// result.entities = []
// result.facts = []
// result.aiContext = ''
}
}
// Use result normally - empty arrays if credits exhausted
const aiPrompt = result.aiContext || 'No memory available'Handling Thrown Exceptions
Most errors throw exceptions:
import { Recalio, RecalioError, RecalioErrorCode } from '@recalio/client'
try {
const result = await recalio.memory.add({
messages: 'Test message',
contextId: 'test-123'
})
} catch (error) {
if (error instanceof RecalioError) {
// Check error code
switch (error.code) {
case RecalioErrorCode.UNAUTHORIZED:
console.error('Invalid API key - regenerate your key')
break
case RecalioErrorCode.PAYLOAD_TOO_LARGE:
console.error('Data too large - split into smaller chunks')
// Split data and retry
break
case RecalioErrorCode.TOO_MANY_REQUESTS:
console.error('Rate limit exceeded - wait and retry')
// Implement exponential backoff
await new Promise(resolve => setTimeout(resolve, 1000))
// Retry request
break
case RecalioErrorCode.NOT_FOUND:
console.error('Resource not found')
break
case RecalioErrorCode.BAD_REQUEST:
console.error('Invalid request:', error.message)
break
default:
console.error('Error:', error.getUserMessage())
}
// Check if retryable
if (error.isRetryable()) {
// Implement retry logic
}
// Check if user action required
if (error.requiresUserAction()) {
// Show user-friendly message
console.error(error.getUserMessage())
}
} else {
console.error('Unknown error:', error)
}
}Complete Error Handling Example
import {
Recalio,
RecalioError,
RecalioErrorCode,
isCreditExhaustionError,
getErrorMessage
} from '@recalio/client'
async function searchWithMemory(query: string) {
try {
const result = await recalio.memory.search(query)
// Check for in-response errors (credit exhaustion)
if (result.error) {
if (isCreditExhaustionError(result.error)) {
console.warn('Memory unavailable: Credits exhausted')
// Continue without memory - don't break AI agent
return {
aiContext: '',
hasMemory: false,
error: result.error
}
}
}
// Normal response
return {
aiContext: result.aiContext,
summary: result.summary,
hasMemory: true,
entities: result.entities,
facts: result.facts
}
} catch (error) {
if (error instanceof RecalioError) {
// Handle thrown exceptions
if (error.code === RecalioErrorCode.UNAUTHORIZED) {
throw new Error('API key invalid - please regenerate')
}
// Log other errors but don't break
console.error('Memory search error:', getErrorMessage({
code: error.code,
message: error.message
}))
// Return empty result - AI agent can continue
return {
aiContext: '',
hasMemory: false,
error: {
code: error.code,
message: error.message
}
}
}
// Unknown error - still return empty to not break AI agent
return {
aiContext: '',
hasMemory: false,
error: {
code: 'UNKNOWN',
message: 'Unknown error occurred'
}
}
}
}
// Use in AI agent
const memory = await searchWithMemory('user query')
const prompt = memory.hasMemory
? `Use this context: ${memory.aiContext}`
: 'No memory available'Error Helper Functions
import {
isCreditExhaustionError, // Check if credit exhaustion
isRetryableError, // Check if error is retryable
requiresUserAction, // Check if user action needed
getErrorMessage, // Get user-friendly message
extractCreditResetDate // Extract credit reset date
} from '@recalio/client'
// Check error type
if (isCreditExhaustionError(result.error)) {
const resetDate = extractCreditResetDate(result.error)
console.log(`Credits reset at: ${resetDate}`)
}
// Get user-friendly message
const message = getErrorMessage({
code: RecalioErrorCode.INSUFFICIENT_CREDITS,
message: 'Credit limit exceeded...'
})Safe Methods (Never Throw)
For AI agents that should never crash, use the *Safe methods:
import { Recalio } from '@recalio/client'
const recalio = new Recalio({ apiKey: 'rec_xxx' })
// searchSafe - never throws, returns empty data on error
const result = await recalio.memory.searchSafe('query')
if (result.error) {
console.warn('Memory unavailable:', result.error.message)
// Continue without memory - AI agent still works
}
const aiContext = result.aiContext || 'No memory available'
// addSafe - never throws, returns error in result
const addResult = await recalio.memory.addSafe({
messages: 'Test',
contextId: 'chat-123'
})
if (addResult.error) {
console.warn('Failed to save memory:', addResult.error.message)
// Continue - memory not saved but agent still works
}When to use Safe methods:
- Production AI agents that must never crash
- When you want to gracefully degrade without memory
- When you prefer checking
result.errorover try/catch
When to use regular methods:
- When you want to handle errors explicitly
- When errors should stop execution
- When you need detailed error information
Best Practices
- Always check
errorfield in responses (for credit exhaustion) - Catch exceptions for all other errors
- Don't break AI agents - return empty data when memory unavailable
- Log errors for debugging but continue gracefully
- Use helper functions for error checking and messages
- Use
*Safemethods for production AI agents that must never crash
User Management
createUser(input)
Create a new user in your organization.
await recalio.createUser({
userId: 'alice_chen_123',
email: '[email protected]',
properties: { name: 'Alice Chen', role: 'engineer' }
})getUser(input)
Get user by ID or userId.
const { user } = await recalio.getUser({ userId: 'alice_chen_123' })updateUser(input)
Update user information.
await recalio.updateUser({
userId: 'alice_chen_123',
email: '[email protected]',
properties: { role: 'senior_engineer' }
})listUsers()
List all users in organization.
const { users } = await recalio.listUsers()deleteUser(userId)
Delete a user.
await recalio.deleteUser('alice_chen_123')License
MIT
