Skip to content

API Reference

Terminal window
npm install @youcraft/recall-structured better-sqlite3 zod

Peer dependencies:

  • better-sqlite3 — SQLite database
  • zod — Schema validation
  • openai — LLM provider (for extraction)
  • ai — AI SDK v6 (for multi-hop agent, optional)

Creates a structured memory client.

import { createStructuredMemory } from '@youcraft/recall-structured'
const memory = createStructuredMemory(config)
PropertyTypeRequiredDescription
dbstringYesSQLite database file path
llmLLMConfigYesLLM configuration
schemasSchemaMapYesSchema definitions
handlersHandlerMapNoEvent handlers
interface LLMConfig {
provider: 'openai'
apiKey: string
model?: string // Default: 'gpt-5-nano'
}
interface SchemaMap {
[name: string]: {
description: string
schema: z.ZodObject<any>
}
}
interface HandlerMap {
[schemaName: string]: {
onInsert?: (data: any, ctx: HandlerContext) => Promise<void>
onUpdate?: (id: string, data: any, ctx: HandlerContext) => Promise<void>
onDelete?: (id: string, ctx: HandlerContext) => Promise<void>
}
}
interface HandlerContext {
userId: string
schema: string
input: string
confidence: number
reason: string
}
const memory = createStructuredMemory({
db: 'memory.db',
llm: {
provider: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-5-nano',
},
schemas: {
payments: {
description: 'Financial transactions',
schema: z.object({
recipient: z.string(),
amount: z.number(),
}),
},
},
handlers: {
payments: {
onInsert: async (data, ctx) => {
console.log('New payment:', data)
},
},
},
})

The client returned by createStructuredMemory().

Process a user message for structured data operations.

const result = await memory.process(input, options)

Parameters:

NameTypeDescription
inputstringUser message to process
options.userIdstringUser ID for data scoping
options.datestringOptional date context (ISO string)

Returns: ProcessResult

// Insert result
{
matched: true,
schema: 'payments',
action: 'insert',
id: 'uuid',
data: { recipient: 'Jayden', amount: 150 },
confidence: 0.95,
handlerCalled: true,
}
// Query result
{
matched: true,
schema: 'payments',
action: 'query',
question: 'How much total?',
sql: 'SELECT SUM(amount)...',
result: 250,
explanation: 'Sum of all payments',
confidence: 0.92,
}
// Update result
{
matched: true,
schema: 'payments',
action: 'update',
id: 'uuid',
data: { amount: 200 },
confidence: 0.90,
handlerCalled: true,
}
// Delete result
{
matched: true,
schema: 'payments',
action: 'delete',
id: 'uuid',
confidence: 0.88,
handlerCalled: true,
}
// Not matched
{
matched: false,
reason: 'No schema matched the input',
}

Execute a natural language query.

const result = await memory.query(question, options)

Parameters:

NameTypeDescription
questionstringNatural language question
options.userIdstringUser ID for data scoping

Returns: QueryResult

{
sql: 'SELECT SUM(amount) FROM payments WHERE user_id = ?',
result: 250,
explanation: 'Sum of all payments for the user',
}

List records from a schema.

const records = await memory.list(schema, options)

Parameters:

NameTypeDescription
schemastringSchema name
options.userIdstringUser ID
options.limitnumberMax records (default: 100)
options.offsetnumberSkip records (default: 0)

Returns: Array of records with base fields (id, user_id, created_at, updated_at).

Get a single record by ID.

const record = await memory.get(schema, id)

Returns: Record or null.

Update a record directly (bypasses LLM).

const updated = await memory.update(schema, id, data)

Parameters:

NameTypeDescription
schemastringSchema name
idstringRecord ID
dataPartial<T>Fields to update

Returns: Updated record.

Throws: RecordNotFoundError if record doesn’t exist.

Delete a record directly (bypasses LLM).

await memory.delete(schema, id)

Throws: RecordNotFoundError if record doesn’t exist.

Get schema information.

const schemas = memory.getSchemas()
// → [{ name: 'payments', description: '...', columns: [...] }]

Close the database connection.

memory.close()

Creates a multi-hop agent for complex operations.

import { createStructuredMemoryAgent } from '@youcraft/recall-structured'
const agent = createStructuredMemoryAgent(config)
PropertyTypeRequiredDescription
dbstringYesSQLite database file path
schemasSchemaMapYesSchema definitions
const agent = createStructuredMemoryAgent({
db: 'memory.db',
schemas: {
payments: {
description: 'Financial transactions',
schema: z.object({
recipient: z.string(),
amount: z.number(),
}),
},
},
})

The agent returned by createStructuredMemoryAgent().

Process a message with multi-hop tool calls.

import { openai } from '@ai-sdk/openai'
const result = await agent.process(model, input, options)

Parameters:

NameTypeDescription
modelLanguageModelAI SDK v6 language model
inputstringUser message
options.userIdstringUser ID
options.maxStepsnumberMax tool call steps (default: 10)
options.extractedContextExtractedContextPre-extracted data from Phase 1

ExtractedContext:

interface ExtractedContext {
schema: string
intent: 'insert' | 'update' | 'delete' | 'query'
data?: Record<string, unknown>
confidence: number
}

Returns: AgentProcessResult

{
text: "Updated your payment to Jayden from $150 to $200",
steps: 2,
toolCalls: [
{ toolName: 'searchRecords', input: {...}, output: {...} },
{ toolName: 'updateRecord', input: {...}, output: {...} },
],
dataModified: true,
}

Get CRUD tools for a user.

const tools = agent.getTools(userId)

Returns tools compatible with AI SDK v6’s generateText() or streamText().

Get the agent’s system prompt (regenerated with current date/time).

const prompt = agent.getSystemPrompt()

Get tools and system prompt together.

const { tools, systemPrompt, schemaInfo } = agent.getAgentContext(userId)

Get schema information.

const schemas = agent.getSchemas()

Close the database connection.

agent.close()

Thrown when data fails Zod validation.

import { SchemaValidationError } from '@youcraft/recall-structured'
try {
await memory.update('payments', id, { amount: 'invalid' })
} catch (e) {
if (e instanceof SchemaValidationError) {
console.log(e.schema) // 'payments'
console.log(e.data) // { amount: 'invalid' }
console.log(e.error) // ZodError
}
}

Thrown when a query can’t be answered.

import { QueryGenerationError } from '@youcraft/recall-structured'
try {
await memory.query("What's the meaning of life?", { userId })
} catch (e) {
if (e instanceof QueryGenerationError) {
console.log(e.question) // "What's the meaning of life?"
console.log(e.explanation) // "This question cannot be answered..."
}
}

Thrown when a record doesn’t exist.

import { RecordNotFoundError } from '@youcraft/recall-structured'
try {
await memory.delete('payments', 'nonexistent-id')
} catch (e) {
if (e instanceof RecordNotFoundError) {
console.log(e.schema) // 'payments'
console.log(e.id) // 'nonexistent-id'
}
}

type ProcessResult =
| ProcessResultInsert
| ProcessResultUpdate
| ProcessResultDelete
| ProcessResultQuery
| ProcessResultNotMatched
interface ProcessResultInsert {
matched: true
schema: string
action: 'insert'
id: string
data: Record<string, unknown>
confidence: number
handlerCalled: boolean
}
interface ProcessResultUpdate {
matched: true
schema: string
action: 'update'
id: string
data: Record<string, unknown>
confidence: number
handlerCalled: boolean
}
interface ProcessResultDelete {
matched: true
schema: string
action: 'delete'
id: string
confidence: number
handlerCalled: boolean
}
interface ProcessResultQuery {
matched: true
schema: string
action: 'query'
question: string
sql: string
result: unknown
explanation: string
confidence: number
}
interface ProcessResultNotMatched {
matched: false
reason: string
}
interface QueryResult {
sql: string
result: unknown
explanation: string
}
interface AgentProcessResult {
text: string
steps: number
toolCalls: Array<{
toolName: string
input: unknown
output: unknown
}>
dataModified: boolean
}
interface SchemaInfo {
name: string
description: string
columns: ColumnInfo[]
}
interface ColumnInfo {
name: string
type: string
nullable: boolean
description?: string
}