npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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/client

Quick 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 agents

API 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
  • 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'
  • 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 results
    • contextId (string) - Filter to specific context only
    • limit (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:

  1. Thrown Exceptions - For most errors (UNAUTHORIZED, PAYLOAD_TOO_LARGE, etc.)
  2. 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 error

Handling 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.error over 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

  1. Always check error field in responses (for credit exhaustion)
  2. Catch exceptions for all other errors
  3. Don't break AI agents - return empty data when memory unavailable
  4. Log errors for debugging but continue gracefully
  5. Use helper functions for error checking and messages
  6. Use *Safe methods 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