Logo

Memory

How to use the Quotient SDK to read and write Memory programmatically

Overview

Quotient exposes its memories via API, making it easy to integrate with other systems and agents. For example, you can fetch Quotient's marketing-related memories inside of a coding agent to help it write better copy for your website. Or you can go in the opposite direction — you can have your AI personal assistant create memories directly in Quotient, or you can set up an integration to write to Quotient's memory from your company knowledge store.

The Memory API models your content as a path-aware filesystem. Content is stored internally as Quotient Rich Text but the SDK uses markdown as the wire format for simplicity.

All paths start with /. The SDK provides separate ls and cat methods with unambiguous return types — no need to check what you got back.

EndpointAPI KeyServer SDKClient SDK
List folderprivate onlymemory.ls()
Read documentprivate onlymemory.cat()
Write documentprivate onlymemory.write()
Create folderprivate onlymemory.mkdir()
Deleteprivate onlymemory.rm()
Searchprivate onlymemory.search()
import { QuotientServer } from "@quotientjs/server";

const quotient = new QuotientServer({
  privateKey: process.env.QUOTIENT_PRIVATE_KEY!,
});

List Folder Contents

memory.ls(options)

GET /api/v0/memory/{path}

Auth: private key only · scope: MEMORY_READ

ParamTypeRequiredDescription
pathPathYesFolder path (must start with /)
deepbooleanNoIf true, returns recursive tree with nested children
// List direct children of root
const root = await quotient.memory.ls({ path: "/" });
// root.items → [{ type: "folder", name: "projects", path: "/projects/", ... }, ...]

// List direct children of a specific folder
const projects = await quotient.memory.ls({ path: "/projects" });

// Get full recursive tree
const tree = await quotient.memory.ls({ path: "/", deep: true });

Returns: { items: MemoryFolderItem[] }

Returns 400 if path is a document. Returns 404 if path doesn't exist.

Read a Document

memory.cat(options)

GET /api/v0/memory/{path}

Auth: private key only · scope: MEMORY_READ

ParamTypeRequiredDescription
pathPathYesDocument path (must start with /)
const doc = await quotient.memory.cat({ path: "/projects/budget" });
// doc.name → "budget"
// doc.content → "# Q1 Budget\n\nTotal: $50,000\n..."
// doc.tags → ["brand"]
// doc.pinned → false
// doc.updatedAt → "2024-01-15T10:30:00.000Z"

Returns:

FieldTypeDescription
namestringDocument name (last path segment)
pathstringFull document path
contentstringMarkdown content
tagsstring[]Assigned tags
pinnedbooleanWhether the document is pinned
updatedAtstringISO timestamp
createdAtstringISO timestamp

Returns 400 if path is a folder. Returns 404 if path doesn't exist.

Write a Document

memory.write(options)

POST /api/v0/memory/{path}

Auth: private key only · scope: MEMORY_WRITE

Creates or updates a document. Parent folders are auto-created.

ParamTypeRequiredDescription
pathPathYesDocument path
titlestringNoDocument title (derived from path if omitted)
contentstringNoMarkdown content. Omit for metadata-only updates
tagsstring[]NoReplaces all tags. Pass [] to clear
pinnedbooleanNoPin or unpin the document
// Upsert — creates if missing, updates if exists
await quotient.memory.write({
  path: "/projects/budget",
  content: "# Q1 Budget\n\nTotal: $50,000",
});

// Write with tags and pinning
await quotient.memory.write({
  path: "/brand/voice",
  content: "# Brand Voice\n\nWarm, confident, and direct.",
  tags: ["brand", "tone"],
  pinned: true,
});

// Metadata-only update (document must exist)
await quotient.memory.write({
  path: "/projects/budget",
  tags: ["brand"],
});

Returns:

FieldTypeDescription
namestringDocument name
pathstringFull document path
tagsstring[]?New tags (included when tags were set)
pinnedboolean?New pinned state (included when pinned was set)

When content is omitted, the document must already exist — returns 404 if the path is not a document.

Create a Folder

memory.mkdir(options)

POST /api/v0/memory/{path}

Auth: private key only · scope: MEMORY_WRITE

ParamTypeRequiredDescription
pathPathYesFolder path
namestringNoFolder name (derived from path if omitted)
const folder = await quotient.memory.mkdir({
  path: "/projects",
  name: "Projects",
});

mkdir is idempotent — calling it on an existing folder returns the existing folder instead of an error.

Delete Memory

memory.rm(options)

DELETE /api/v0/memory/{path}

Auth: private key only · scope: MEMORY_WRITE

ParamTypeRequiredDescription
pathPathYesPath to archive (auto-detects document vs folder)
// Archive a document
await quotient.memory.rm({ path: "/projects/budget" });

// Archive a folder and all its contents
await quotient.memory.rm({ path: "/projects" });

Returns: { success: boolean }

When called on a document, archives the document. When called on a folder, archives the folder and all its descendants (cascading delete).

memory.search(options)

POST /api/v0/memory/search

Auth: private key only · scope: MEMORY_READ

Runs a vector similarity search over all memory chunk embeddings.

ParamTypeRequiredDescription
querystringYesNatural-language search query
tagsstring[]NoFilter to documents with at least one of these tags
const results = await quotient.memory.search({
  query: "What is our brand tone?",
});

for (const chunk of results.results) {
  console.log(chunk.memoryPath); // e.g. "/brand/voice"
  console.log(chunk.content);    // the matching chunk text
  console.log(chunk.similarity); // cosine similarity score (0–1)
}

// Tag-filtered search
const brandResults = await quotient.memory.search({
  query: "brand guidelines",
  tags: ["brand", "tone"],
});

Returns: { results: SearchMemoryResult[] }

FieldTypeDescription
idstringChunk ID
memoryIdstringParent document ID
memoryPathstringParent document path
contentstringChunk text
headingPathstring[]Heading breadcrumb for the chunk's location
positionnumberChunk index within the document
similaritynumberCosine similarity score (0–1, higher is more similar)

Results are ordered by descending similarity. The default limit is 10 results.

Tags

Tags are flat string labels you attach to memory documents. They serve two purposes: skill scoping (agents load only memories matching their tags) and filtering (search, UI).

Platform tags

A fixed set of well-known labels:

TagCategoryDescription
emailChannelEmail campaign content
blogChannelBlog and long-form writing
socialChannelSocial media content
toneDomainVoice and tone guidelines
audienceDomainAudience personas and segments
brandDomainBrand identity and values
competitorsDomainCompetitive intelligence
productsDomainProduct information

Referential tags

Link a memory to a specific entity using the <prefix>:<id> format:

FormatExampleDescription
user:<cuid>user:clx1a2b3c4d5e6f7g8h9Tie memory to a specific user
await quotient.memory.write({
  path: "/users/alice/preferences",
  content: "Prefers formal tone. Focuses on ROI.",
  tags: ["user:clx1a2b3c4d5e6f7g8h9", "audience"],
});

Tags are always replaced wholesale — there is no "add one tag" operation. To add a tag to an existing set, read the current tags, append, and write back.

const doc = await quotient.memory.cat({ path: "/brand/voice" });
await quotient.memory.write({
  path: "/brand/voice",
  tags: [...doc.tags, "tone"],
});

Pinning

Pinned documents are always loaded into agent context, regardless of what the agent is doing. Use pinning for critical, always-relevant content — brand voice guidelines, compliance rules, or high-priority personas.

await quotient.memory.write({ path: "/brand/voice", pinned: true });
await quotient.memory.write({ path: "/brand/voice", pinned: false });

Use pinning sparingly — every pinned document consumes context tokens on every agent invocation. Tag-based loading is more efficient for content that is only relevant in certain contexts.

Indexing and Chunking

When you write a document, Quotient automatically:

  1. Chunks the content by heading structure and paragraph boundaries
  2. Embeds each chunk using a Voyage embedding model
  3. Stores the embeddings for fast vector similarity queries

This happens asynchronously after the write completes — the document is immediately readable via cat, but chunks may not be searchable for a few seconds after a write. Re-indexing also triggers automatically whenever content changes.

Next Steps