Core Client
MemoriesClient for use with any LLM framework.
@memories.sh/core provides a standalone client that works with any LLM SDK — Anthropic, OpenAI, or custom integrations. Zero runtime dependencies, edge-compatible.
Install
pnpm add @memories.sh/coreBasic Usage
import { MemoriesClient } from "@memories.sh/core"
const client = new MemoriesClient({
apiKey: "mem_xxx", // or uses MEMORIES_API_KEY env var
tenantId: "acme-prod",
})
const { rules, memories } = await client.context.get({
query: "auth patterns",
projectId: "github.com/acme/platform",
userId: "user_123",
mode: "all",
})
// Use with any LLM SDK
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5-20250929",
system: client.buildSystemPrompt({ rules, memories }),
messages: [{ role: "user", content: userMessage }],
})API Reference
Constructor
const client = new MemoriesClient({
apiKey: "mem_xxx", // API key (default: MEMORIES_API_KEY env var)
baseUrl: "https://memories.sh", // optional (default)
transport: "auto", // "auto" (default), "sdk_http", or "mcp"
tenantId: "acme-prod", // AI SDK Project (security/database boundary)
userId: "user_123", // End-user scope inside tenantId (optional)
projectId: "github.com/acme/platform", // Optional repo context filter (not auth boundary)
})Transport behavior:
auto(default): usessdk_httpfor normal base URLs (for examplehttps://memories.sh), and switches tomcponly whenbaseUrlends with/api/mcpsdk_http: uses/api/sdk/v1/*endpointsmcp: uses JSON-RPC/api/mcp
The SDK client never shells out to the CLI. CLI commands are a separate integration path for local/dev workflows.
For the canonical SDK HTTP contracts (envelopes, status codes, and route list), see SDK endpoint contract.
client.context.get(input?)
Fetch rules and relevant memories with explicit runtime scope controls.
const { rules, memories } = await client.context.get({
query: "deployment process",
projectId: "github.com/acme/platform",
userId: "user_123",
mode: "all",
})
// Rules + working only
const ctx = await client.context.get({
query: "auth",
mode: "working",
limit: 20,
includeRules: true, // default true
})mode controls tier selection:
all(default): rules + working + long-termworking: rules + working onlylong_term: rules + long-term onlyrules_only: rules only
Legacy signature remains supported:
await client.context.get("auth", { projectId: "github.com/acme/platform", limit: 20 })Session Lifecycle Pattern
MemoriesClient exposes session-aware retrieval fields on context.get(), but session lifecycle writes are currently HTTP endpoint calls (/api/sdk/v1/sessions/*).
import { MemoriesClient } from "@memories.sh/core"
const apiKey = process.env.MEMORIES_API_KEY!
const baseUrl = "https://memories.sh"
const scope = {
tenantId: "acme-prod",
userId: "user_123",
projectId: "github.com/acme/platform",
}
const client = new MemoriesClient({
apiKey,
tenantId: scope.tenantId,
userId: scope.userId,
})
async function sdkPost(path: string, body: Record<string, unknown>) {
const res = await fetch(`${baseUrl}${path}`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
if (!res.ok) throw new Error(`SDK request failed: ${res.status}`)
return res.json()
}
async function sdkGet(path: string) {
const res = await fetch(`${baseUrl}${path}`, {
headers: { Authorization: `Bearer ${apiKey}` },
})
if (!res.ok) throw new Error(`SDK request failed: ${res.status}`)
return res.json()
}
const started = await sdkPost("/api/sdk/v1/sessions/start", {
title: "Investigate checkout timeout",
client: "my-agent",
scope,
})
const sessionId = started.data.sessionId as string
const ctx = await client.context.get({
query: "checkout timeout investigation",
projectId: scope.projectId,
sessionId,
budgetTokens: 6000,
turnCount: 3,
turnBudget: 24,
lastActivityAt: new Date().toISOString(),
inactivityThresholdMinutes: 45,
})
await sdkPost("/api/sdk/v1/sessions/checkpoint", {
sessionId,
content: `Recalled ${ctx.memories.length} memories`,
kind: "summary",
scope,
})
await sdkPost("/api/sdk/v1/sessions/end", {
sessionId,
status: "closed",
scope,
})
// Optional: read latest snapshot if one was created by lifecycle hooks
const snapshot = await sdkGet(
`/api/sdk/v1/sessions/${sessionId}/snapshot?tenantId=${scope.tenantId}&userId=${scope.userId}&projectId=${encodeURIComponent(scope.projectId)}`
)If you require explicit snapshot creation in this flow, create it via local CLI/MCP (memories session snapshot or snapshot_session) and then read it through the SDK endpoint above.
Hybrid Retrieval Usage
Enable graph-augmented recall per request:
const ctx = await client.context.get({
query: "why did billing alerts spike?",
strategy: "hybrid", // or "lexical"
graphDepth: 1, // 0 | 1 | 2
graphLimit: 8, // max graph-expanded memories
userId: "user_123",
projectId: "github.com/acme/platform",
})
console.log(ctx.trace)
// {
// requestedStrategy: "hybrid",
// strategy: "baseline" | "hybrid_graph",
// rolloutMode: "off" | "shadow" | "canary",
// shadowExecuted: boolean,
// qualityGateStatus: "pass" | "warn" | "fail" | "insufficient_data" | "unavailable",
// qualityGateBlocked: boolean,
// qualityGateReasonCodes: string[],
// fallbackTriggered: boolean,
// fallbackReason: string | null
// }trace.strategy is the applied strategy after rollout guardrails. This lets you detect when a hybrid request safely falls back to baseline.
Legacy aliases baseline and hybrid_graph are still accepted by the client, but they are deprecated and emit a one-time warning. You can silence warnings with MEMORIES_SUPPRESS_DEPRECATION_WARNINGS=true.
With graph mapping enabled, hybrid retrieval can surface semantically related memories through similar_to edges even when lexical overlap is weak.
Workspace rollout and safety signals are available from SDK HTTP graph endpoints:
GET /api/sdk/v1/graph/rolloutfor mode + shadow metrics + quality gate snapshotGET /api/sdk/v1/graph/statusfor counts + alarms (HIGH_FALLBACK_RATE,GRAPH_EXPANSION_ERRORS,CANARY_QUALITY_GATE_BLOCKED)
For embedding model selection, retrieval strategy defaults, and migration notes, see SDK Embeddings Guide.
client.memories.add(input)
Create a new memory.
await client.memories.add({
content: "Use TypeScript with strict mode",
type: "rule",
tags: ["code-style"],
paths: ["src/**"],
})client.memories.search(query, options?)
Full-text search across memories.
const results = await client.memories.search("auth patterns", {
type: "rule",
limit: 10,
})client.memories.list(options?)
List memories with optional filters.
const memories = await client.memories.list({
type: "rule",
tags: ["code-style"],
limit: 50,
})client.memories.edit(id, updates)
Update an existing memory.
await client.memories.edit("mem_abc123", {
content: "Updated content",
tags: ["updated-tag"],
})client.memories.forget(id)
Soft-delete a memory.
await client.memories.forget("mem_abc123")client.memories.bulkForget(filters, options?)
Bulk soft-delete memories matching filters.
// Preview what would be deleted
const preview = await client.memories.bulkForget(
{ types: ["note", "fact"], olderThanDays: 90 },
{ dryRun: true }
)
console.log(preview.count, preview.memories) // preview list
// Actually delete
const result = await client.memories.bulkForget({
tags: ["temporary"],
pattern: "TODO*",
})
console.log(result.count, result.ids) // deleted IDsFilter options: types, tags, olderThanDays, pattern, projectId, all. Provide at least one filter, or use all: true (cannot combine with other filters).
client.memories.vacuum()
Permanently purge all soft-deleted memories to reclaim storage space.
const result = await client.memories.vacuum()
console.log(result.purged) // number of permanently removed recordsConsolidation Run (HTTP Endpoint)
Consolidation currently runs through the SDK endpoint (/api/sdk/v1/memories/consolidate).
const consolidate = await fetch("https://memories.sh/api/sdk/v1/memories/consolidate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.MEMORIES_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
types: ["rule", "decision", "fact"],
dryRun: true,
scope: {
tenantId: "acme-prod",
userId: "user_123",
projectId: "github.com/acme/platform",
},
}),
})
const consolidateJson = await consolidate.json()
console.log(consolidateJson.data.runId, consolidateJson.data.run)client.buildSystemPrompt(context)
Format rules and memories into a system prompt block.
const { rules, memories } = await client.context.get({ query: "auth" })
const systemPrompt = client.buildSystemPrompt({ rules, memories })
// Returns formatted text like:
// ## Rules (always follow)
// - Use TypeScript with strict mode
// - Prefer functional patterns
//
// ## Relevant Context (from memory)
// - [decision] Chose Supabase over Firebase for auth
// - [fact] Project uses pnpmScoping
Use all three scopes with clear roles:
tenantId= AI SDK Project (security/database boundary)userId= end-user scope insidetenantIdprojectId= optional repo context filter (not auth boundary)
If tenantId is omitted, requests route to the API key's default workspace database.
If tenantId is provided and no mapping exists yet, the runtime can auto-provision a project Turso database (when server provisioning credentials are configured). This lets SaaS apps avoid manual project DB setup during first use.
const client = new MemoriesClient({
apiKey: "mem_xxx",
tenantId: "acme-prod",
userId: "user_123",
})
await client.memories.add({
content: "User prefers concise updates in repository reviews",
type: "note",
projectId: "github.com/acme/platform",
})
// Per-call override is also supported:
const context = await client.context.get({
userId: "user_456",
projectId: "github.com/acme/platform",
mode: "working",
})Management APIs
When you manage API keys or AI SDK Projects (tenantId mappings) programmatically, prefer the SDK management endpoints:
/api/sdk/v1/management/keys/api/sdk/v1/management/tenant-overrides
The core client exposes these directly:
import { MemoriesClient } from "@memories.sh/core"
const client = new MemoriesClient({
apiKey: process.env.MEMORIES_API_KEY,
baseUrl: "https://memories.sh",
transport: "sdk_http",
})
const keyStatus = await client.management.keys.get()
const rotatedKey = await client.management.keys.create({
expiresAt: "2027-01-01T00:00:00.000Z",
})
const revoked = await client.management.keys.revoke()
const sdkProjects = await client.management.tenants.list()
const upsertedProject = await client.management.tenants.upsert({
tenantId: "acme-prod",
mode: "provision",
})
const disabledProject = await client.management.tenants.disable("acme-prod")
void [keyStatus, rotatedKey, revoked, sdkProjects, upsertedProject, disabledProject]Legacy key management route (/api/mcp/key) remains available for backward compatibility.
For single-user apps, tenantId can be your app environment or workspace id:
const client = new MemoriesClient({
apiKey: "mem_xxx",
tenantId: "my-app-prod",
userId: "user_123",
})
// All operations are now scoped to user_123
await client.memories.add({ content: "User prefers dark mode", type: "note" })
const { memories } = await client.context.get({ query: "user preferences" })
// Only returns memories for user_123Skill Files (Scoped)
Skill files are first-class scoped records in the SDK (tenantId + userId + projectId), separate from memory type: "skill" labels.
await client.skills.upsertFile({
path: ".agents/skills/review/SKILL.md",
content: "---\nname: review\n---\nUse strict checks.",
tenantId: "acme-prod",
userId: "user_123",
projectId: "github.com/acme/platform",
})
const skillFiles = await client.skills.listFiles({
tenantId: "acme-prod",
userId: "user_123",
projectId: "github.com/acme/platform",
})
await client.skills.deleteFile({
path: ".agents/skills/review/SKILL.md",
tenantId: "acme-prod",
userId: "user_123",
projectId: "github.com/acme/platform",
})
await client.skills.promoteFromSession({
sessionId: "sess_abc123",
snapshotId: "snap_987", // optional; latest snapshot is used when omitted
path: ".agents/skills/incident-response/SKILL.md",
title: "Incident response procedure",
procedureKey: "incident-response-v1",
maxSteps: 8,
tenantId: "acme-prod",
userId: "user_123",
projectId: "github.com/acme/platform",
})Edge Runtime
The core client is edge-compatible with zero dependencies:
import { MemoriesClient } from "@memories.sh/core"
export const runtime = "edge"
export async function POST(req: Request) {
const client = new MemoriesClient({ tenantId: "acme-prod" })
const { rules } = await client.context.get()
return Response.json({ rules: rules.map((r) => r.content) })
}