Skip to content

Repository files navigation

getmnemo

Store and retrieve long-term memory for AI agents with TypeScript or JavaScript.

Install

npm install getmnemo

Requires Node.js 18 or later. The package has no runtime dependencies and supports both ESM and CommonJS.

Quickstart

Create a client, add a memory, and search the same container:

import { Mnemo } from 'getmnemo'

const mnemo = new Mnemo({
  apiKey: process.env.MNEMO_API_KEY!,
})

await mnemo.add({
  containerTag: 'user:jane',
  content: 'Jane prefers Japanese short-grain rice for onigiri.',
  memoryType: 'preference',
})

const { results } = await mnemo.search({
  containerTag: 'user:jane',
  q: 'What kind of rice does Jane prefer?',
})

console.log(results)

Keep the API key on your server. Do not expose a full-access key in browser code.

Containers

A container keeps one user, customer, project, or agent separate from the others.

Use a readable containerTag:

await mnemo.add({
  containerTag: 'customer:acme',
  content: 'Acme renewed through December.',
})

Or use the equivalent structured scope:

await mnemo.add({
  scope: { type: 'customer', id: 'acme' },
  content: 'Acme renewed through December.',
})

Set a default when most calls use the same container:

const mnemo = new Mnemo({
  apiKey: process.env.MNEMO_API_KEY!,
  defaultContainerTag: 'user:jane',
})

await mnemo.add({ content: 'Jane likes onigiri.' })
await mnemo.search({ q: 'What food does Jane like?' })

The SDK stops the request if a required container is missing.

Add memories

Add one memory

const response = await mnemo.add({
  containerTag: 'user:jane',
  content: 'Jane avoids shellfish.',
  memoryType: 'preference',
  metadata: { source: 'onboarding' },
})

console.log(response.items[0].id)
console.log(response.receipt.status) // "searchable"

Add many memories

addMany() accepts up to 100 memories:

const response = await mnemo.addMany({
  scope: { type: 'customer', id: 'acme' },
  source: { provider: 'hubspot', importId: 'run_42' },
  items: [
    {
      content: 'Acme renewed through December.',
      idempotencyKey: 'hubspot:deal:123:v9',
      metadata: { objectType: 'deal' },
    },
    {
      content: 'The account owner is Priya.',
      idempotencyKey: 'hubspot:company:456:owner:v3',
    },
  ],
})

console.log(response.stats)
console.log(response.receipt.items)

For a governed import whose source system is authoritative, keep the submitted text and metadata unchanged while skipping derived LLM enrichment:

await mnemo.addMany({
  scope: { type: 'customer', id: 'acme' },
  enrichmentMode: 'skip',
  items: approvedRecords,
})

The API key determines the workspace. workspaceId remains accepted only as a deprecated compatibility option and is no longer sent as a caller-selected header. For audit or rebuild workflows, use createWorkspaceExport(), then poll getWorkspaceExport() until its download URL is available.

Use a stable idempotencyKey when an import may be retried. Repeating the same write will not create another copy.

Every successful add() and addMany() response includes a receipt:

{
  writeId: 'e5bf3f0f-16e2-4a5d-9c7f-98e437cf86c4',
  status: 'searchable',
  searchableAt: '2026-07-28T09:14:22.442Z',
  items: [
    {
      inputIndex: 0,
      memoryId: '8a8a4f8c-cf91-43e4-9a0e-7c2bb2c4d3f2',
      status: 'created',
    },
  ],
}

Receipt items follow input order. A deduplicated item reused an equivalent memory already present in the same container. Both outcomes are searchable when the API returns.

The SDK validates the complete receipt before resolving add() or addMany(). Missing, duplicated, or contradictory item outcomes fail the call instead of being reported as successful. Timed-out or disconnected memory batches are retried only when every item carries a stable idempotencyKey.

Search memories

const { results } = await mnemo.search({
  containerTag: 'user:jane',
  q: 'What changed after the dentist appointment?',
  limit: 10,
  includeSources: true,
})

for (const result of results) {
  console.log(result.score, result.content, result.sources?.provenance)
}

Use the following options only when your application needs more control:

Option Values Purpose
searchMode hybrid, memories, documents Select the content to search.
filters Record<string, unknown> Restrict results by metadata.
includeSources boolean Return provenance with each result.
strategies temporal, graph, rerank, agentic Add a retrieval strategy to the normal search.
excludeIds string[] Leave out results already in the agent's context.

For example:

const result = await mnemo.search({
  containerTag: 'user:jane',
  q: 'What changed after the dentist appointment?',
  strategies: ['temporal'],
  excludeIds: ['mem_123'],
})

console.log(result.strategiesRan)

temporal usually adds little latency. graph may add moderate latency. rerank and agentic may add model cost and take longer.

Ingest documents

Use documents for conversations, notes, transcripts, and other raw text. Mnemo processes documents asynchronously.

Ingest one document

const accepted = await mnemo.documents.create({
  containerTag: 'customer:acme',
  content: meetingTranscript,
  contentType: 'conversation',
  customId: 'meeting-2026-07-27',
})

const job = await mnemo.jobs.wait(accepted.jobId)
console.log(job.status)

Ingest a batch

createBatch() accepts up to 50 documents. Every result is reported separately, so one invalid document does not hide the others.

const batch = await mnemo.documents.createBatch({
  documents: [
    {
      scope: { type: 'customer', id: 'acme' },
      content: meetingTranscript,
      contentType: 'conversation',
      customId: 'meeting-2026-07-27',
    },
    {
      scope: { type: 'customer', id: 'acme' },
      content: accountNotes,
      contentType: 'note',
      customId: 'account-notes-2026-07-27',
    },
  ],
})

for (const item of batch.results) {
  if (item.status === 'accepted') {
    await mnemo.jobs.wait(item.jobId)
  } else {
    console.error(`Document ${item.index} failed: ${item.error}`)
  }
}

Ingest YouTube videos

Queue a public YouTube video as a timestamped transcript:

const estimate = await mnemo.youtube.estimate({
  url: 'https://www.youtube.com/watch?v=VIDEO_ID',
  mode: 'transcript',
})

if (estimate.quota.willFit) {
  const video = await mnemo.youtube.create({
    scope: { type: 'course', id: 'onboarding' },
    url: estimate.canonicalUrl,
    mode: 'transcript',
  })

  await mnemo.jobs.wait(video.jobId)
}

Use transcript_and_visuals when diagrams, interface changes, actions, or on-screen text matter. Mnemo stores selected timestamped observations with YouTube evidence links; it does not copy or retain the video itself.

Read and update memories

Addressing a memory by id needs a container, exactly like list. Pass containerTag or scope per call, or set defaultContainerTag on the client and omit it everywhere:

const memory = await mnemo.get('mem_123', { containerTag: 'user:jane' })

await mnemo.update(
  memory.id,
  {
    content: 'Jane now prefers brown rice.',
    metadata: { changedBy: 'user' },
  },
  { containerTag: 'user:jane' },
)

const page = await mnemo.list({
  containerTag: 'user:jane',
  limit: 20,
})

console.log(page.items, page.nextCursor)

A structured scope works too, and the client's defaultContainerTag fills in whenever a call passes no container of its own:

await mnemo.get('mem_123', { scope: { type: 'customer', id: 'acme' } })

const scoped = new Mnemo({
  apiKey: process.env.GETMNEMO_API_KEY!,
  defaultContainerTag: 'user:jane',
})

await scoped.get('mem_123') // containerTag=user:jane is sent for you

Delete and restore memories

Deletion is recoverable when recovery is enabled for the workspace:

const deleted = await mnemo.delete('mem_123', { containerTag: 'user:jane' })

console.log(deleted.receipt?.restorableUntil)

await mnemo.restore('mem_123')

Skip the recovery window only when permanent deletion is intentional:

await mnemo.delete('mem_123', { permanent: true, containerTag: 'user:jane' })

Protect important memories

Protected memories require explicitly privileged API-key scopes before they can be changed or deleted:

const result = await mnemo.add({
  containerTag: 'user:jane',
  content: 'Never disclose the account recovery phrase.',
  mutationPolicy: 'privileged',
})

await mnemo.protect(result.items[0].id)
await mnemo.unprotect(result.items[0].id)

Use a server-side key with memories:protect to create or change protection. Updating, deleting, and restoring protected memories also requires the matching memories:protected:update or memories:protected:delete scope.

Build prompt context

profile() returns prompt-ready facts, recent context, preferences, and hard constraints for one container:

const context = await mnemo.profile({
  containerTag: 'user:jane',
  staticLimit: 12,
  dynamicLimit: 10,
})

Search is off by default. To include search results, provide both includeSearch and q:

const context = await mnemo.profile({
  containerTag: 'user:jane',
  includeSearch: true,
  q: 'What should I know before replying?',
})

Personal memory

Version 0.6.0 adds the personal-memory surface: people, reminders, a timeline, a daily brief, meetings, inbound capture and memory merge. Each resource needs its own API-key scope (for example people:read, reminders:write, brief:read); existing keys do not gain them automatically.

People

One memory container per person, tagged person:<slug>:

const jane = await mnemo.people.create({
  displayName: 'Jane Doe',
  relationship: 'client',
  email: 'jane@example.com',
  importantDates: [{ label: 'Birthday', date: '1990-04-12', recurring: true }],
})

const page = await mnemo.people.list({ q: 'jane' })
const person = await mnemo.people.get(jane.slug)

await mnemo.people.update(jane.slug, { company: 'Acme', notes: null })

// Reader answer over the person's memories, plus open reminders.
const summary = await mnemo.people.summary(jane.slug, {
  q: 'What matters to Jane right now?',
})

// Archive (keeps the container); optionally soft-delete its memories.
await mnemo.people.archive(jane.slug, { deleteMemories: true })

Per-person memories are ordinary memories in that container: mnemo.list({ containerTag: jane.tag }) or mnemo.add({ containerTag: jane.tag, content: '...' }).

Reminders

A reminder is a memory with memoryType: "reminder" and a dueAt. File it under a person, any container, or the client's defaultContainerTag:

const reminder = await mnemo.reminders.create({
  content: 'Send Jane the revised deck',
  dueAt: '2026-09-10T09:00:00Z',
  personSlug: 'jane-doe',
  idempotencyKey: 'deck:v2',
})

// Open reminders, workspace-wide, ordered by due time.
const open = await mnemo.reminders.list({ status: 'open', days: 30 })

// Overdue / due today / upcoming in the caller's timezone.
const buckets = await mnemo.reminders.upcoming({
  days: 7,
  timezone: 'Asia/Karachi',
})

await mnemo.reminders.update(reminder.id, { dueAt: '2026-09-11T09:00:00Z' })
await mnemo.reminders.complete(reminder.id)
await mnemo.reminders.reopen(reminder.id, { dueAt: '2026-09-12T09:00:00Z' })

reminders.list and reminders.upcoming are workspace-wide unless you pass containerTag or containerType; the client's defaultContainerTag is not applied to them.

Timeline

A merged, newest-first stream of memories, reminders and documents for one container (event rows are opt-in):

const timeline = await mnemo.timeline.get({
  containerTag: 'person:jane-doe',
  from: '2026-09-01T00:00:00Z',
  types: ['memory', 'reminder', 'document'],
  limit: 50,
})

for (const item of timeline.items) {
  console.log(item.occurredAt, item.type, item.title, item.createdBy?.label)
}

Daily brief

Reminders due, important dates, recent captures, open follow-ups and today's meetings for one container:

const brief = await mnemo.brief.today({
  containerTag: 'user:me',
  timezone: 'Asia/Karachi',
  sections: ['core', 'followUps'],
})

console.log(brief.reminders?.dueToday, brief.followUps?.answer)

// A specific local day.
const yesterday = await mnemo.brief.get({
  containerTag: 'user:me',
  date: '2026-09-01',
})

Meetings

Upcoming calendar meetings (synced through a connected Google Calendar) with attendees matched to people, and a pre-meeting brief:

const { items, connections } = await mnemo.meetings.upcoming({ days: 3 })

const meeting = await mnemo.meetings.get(items[0].documentId)
const prep = await mnemo.meetings.brief(meeting.documentId, {
  q: 'What did we agree last time?',
})

console.log(prep.brief?.answer, prep.people, prep.previousMeetings)

Inbound capture (WhatsApp / SMS)

Link a phone number so its texts and voice notes become memories. The verification code is returned only on create and regenerateCode; the phone must text it once to activate the channel:

const channel = await mnemo.inbound.channels.create({
  phone: '+14155550100',
  containerTag: 'user:me', // defaults to phone:<E.164>
})

console.log(channel.verificationCode, channel.inboundNumber, channel.webhookUrl)

await mnemo.inbound.channels.list()
await mnemo.inbound.channels.update(channel.id, { status: 'disabled' })
await mnemo.inbound.channels.regenerateCode(channel.id)
await mnemo.inbound.channels.delete(channel.id)

Merge memories

Fold duplicates into one survivor. With into, that memory keeps its id and the others are soft-deleted (restorable); without it a new memory is created from content. The key needs both memories:write and memories:delete:

const merged = await mnemo.memories.merge({
  containerTag: 'user:jane',
  ids: ['mem_1', 'mem_2'],
  into: 'mem_1',
})

console.log(merged.memory.id, merged.deletedIds, merged.replayed)

Provenance and filters

Every Memory now reports who wrote it in createdBy (api_key, mcp, user, connector, inbound or system, with the key or client name as label) and its dueAt. list() can filter on both:

const recent = await mnemo.list({
  containerTag: 'user:jane',
  since: '2026-09-01T00:00:00Z',
  createdByKind: 'inbound',
  memoryType: 'note',
})

Memory types

memoryType is optional. Supported values include:

  • memory
  • preference
  • fact
  • observation
  • event
  • note
  • reminder
  • goal

The API stores an unknown value as memory instead of rejecting the write.

Error handling

The SDK throws:

  • MnemoHTTPError for an unsuccessful API response.
  • MnemoTimeoutError when a request reaches its timeout.
  • MnemoError as the base class for SDK errors.
import { MnemoHTTPError } from 'getmnemo'

try {
  await mnemo.search({
    containerTag: 'user:jane',
    q: 'rice preference',
  })
} catch (error) {
  if (error instanceof MnemoHTTPError) {
    console.error(error.status, error.body)
  } else {
    throw error
  }
}

The client retries transient network errors, 429 responses, and 5xx responses. Set maxRetries: 0 to disable retries.

Client configuration

Option Required Default Description
apiKey Yes - Mnemo API key. The key is bound to one workspace.
workspaceId No - Deprecated compatibility option; not sent to the API.
defaultContainerTag No - Container used when a call does not provide one.
baseUrl No https://api.mnemohq.com API base URL.
timeoutMs No 30000 Request timeout in milliseconds.
maxRetries No 3 Retries for transient failures.
fetch No globalThis.fetch Custom fetch implementation for tests or proxies.

API keys are full-access by default. You can create a key with only the read, write, delete, or billing scopes it needs. The personal-memory resources use explicit scopes (people:*, reminders:*, brief:read, meetings:read, timeline:read, inbound:*) that existing keys do not gain automatically. Use a read-only scoped key or a server proxy when a key may reach client code.

Method reference

Method What it does
add(input) Add one memory.
addMany(input) Add up to 100 memories.
createWorkspaceExport() Start a tenant-bound workspace export.
listWorkspaceExports() List export jobs for the current API-key workspace.
getWorkspaceExport(exportId) Read one export job and its download URL.
search(input) Search memories, documents, or both.
get(memoryId, options?) Get one memory in an explicit or default container.
list(input) List memories in one explicit or default scope, with since / until / createdByKind / memoryType filters.
update(memoryId, input, options?) Update one memory in an explicit or default container.
delete(memoryId, options?) Delete one memory in an explicit or default container.
restore(memoryId) Restore a recoverable deletion.
protect(memoryId) Require privileged mutation scopes for a memory.
unprotect(memoryId) Return a memory to the standard mutation policy.
setProtection(memoryId, policy) Set the mutation policy explicitly.
profile(input) Build context for a prompt.
documents.create(input) Start one document ingestion job.
documents.createBatch(input) Start up to 50 document ingestion jobs.
documents.get(documentId) Get one document.
documents.list(input) List documents in one explicit or default scope.
documents.update(documentId, input) Update and optionally reprocess a document.
documents.delete(documentId) Delete a document.
jobs.get(jobId, options?) Get one ingestion job.
jobs.list() List ingestion jobs.
jobs.wait(jobId, options?) Wait for an ingestion job to finish.
youtube.estimate(input) Estimate a YouTube ingestion against quota.
youtube.create(input) Start a YouTube ingestion.
youtube.get(ingestionId) Get one YouTube ingestion.
people.create(input) Create a person (person:<slug> container).
people.list(input?) List people, newest first.
people.get(slug) Get one person with memory and reminder counts.
people.update(slug, input) Update contact fields; null clears a field.
people.archive(slug, options?) Archive a person, optionally soft-deleting their memories.
people.summary(slug, options?) Reader summary plus open reminders and recent memories.
reminders.create(input) Create a reminder under a person or container.
reminders.list(input?) List reminders by status and due window.
reminders.upcoming(input?) Overdue / today / upcoming buckets and important dates.
reminders.update(memoryId, input) Reschedule or edit a reminder.
reminders.complete(memoryId) Complete a reminder.
reminders.reopen(memoryId, input) Reopen a completed reminder with a new due time.
timeline.get(input?) Merged memory / document / event stream for one container.
brief.today(input?) Daily brief for today.
brief.get(input?) Daily brief for a specific local day.
meetings.upcoming(input?) Upcoming calendar meetings and connections.
meetings.get(documentId) Get one meeting.
meetings.brief(documentId, options?) Pre-meeting brief with matched people.
inbound.channels.create(input) Link a phone number for WhatsApp / SMS capture.
inbound.channels.list(input?) List capture channels.
inbound.channels.get(id) Get one capture channel.
inbound.channels.update(id, input) Change a channel's container or disable it.
inbound.channels.delete(id) Remove a capture channel.
inbound.channels.regenerateCode(id) Issue a new verification code.
memories.merge(input) Merge 2..20 memories into one survivor.

All request and response types are exported from getmnemo.

Help

License

MIT

About

TypeScript / JavaScript SDK for Mnemo Memory

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages