Reference

SDK

One package with two halves: a typed client for the service, and the deterministic cognitive layer to run in-process. The shared primitives are why they cannot drift.

On this page

Install

sh
pnpm add cogmemory

~3kB gzipped, ESM-first, and its only hard dependency is ofetch. ai and zod are optional peers, needed solely for the model-backed arbiter on the cogmemory/arbiter entry point — so a consumer with no model provider installs neither.

Imports are arranged for tree-shaking: createClient alone does not pull the in-process engine in, and the package declares no side effects.

The client

ts
import { createClient } from "cogmemory"const memory = createClient({  apiKey: process.env.COGNITIVE_MEMORY_KEY!,  baseUrl: process.env.COGNITIVE_MEMORY_URL ?? "https://cogmem.astracollab.app"})

apiKey is the only required field. baseUrl defaults to COGNITIVE_MEMORY_URL, then to the current origin in a browser, then to the hosted service; timeout, retry, headers and debug are optional.

Debug logging is on by default, which is a deliberate choice for anyone who has been burned by a wrong base URL — which is everyone, exactly once. Turn it off when you have stopped being that person.

Two construction styles ship on purpose — new Cogmem(…) and createClient(…) — because there is no reason to make anyone rename, and the factory is the one that is easy to mock in a test.

The turn helper

runTurn is the recommended entry point, because the order is the part that is easy to get wrong: build context keyed on the message being answered, run the model, learn from the finished exchange, then record how the domain went.

ts
import { createClient, runTurn } from "cogmemory"const memory = createClient({  apiKey: process.env.COGNITIVE_MEMORY_KEY!,  baseUrl: process.env.COGNITIVE_MEMORY_URL ?? "https://cogmem.astracollab.app"})const { context, learning, learningSkipped } = await runTurn(  memory,  { userMessage, run: (block) => callYourModel(block, userMessage) },  { domain: "database", sessionId })

The last two steps never fail the turn. A memory outage cannot take down a model call that already succeeded, so the helper catches and reports instead of rethrowing.

learningSkipped is returned rather than left null, because a silent skip looks identical to a working one until the thing you taught it never comes back. Log it.

The helpers

Three functions that compose the resource calls into the sequences people get wrong when they write the loop themselves. Each takes the client explicitly rather than closing over one, so a second client — a test double, a second organisation — needs no module-level singleton.

HelperDoes
runTurnThe whole turn: build, run, learn, record.
recallOrExplainRecall, phrased for a prompt.
seedMemoriesStore a list of facts once.
ts
import { recallOrExplain, seedMemories } from "cogmemory"// Recall, already formatted to paste into a prompt.const brief = await recallOrExplain(memory, "where do we deploy", { limit: 5 })// Load what you already know. Safe to re-run: restatements come back merged.const { stored, merged, rejected } = await seedMemories(memory, {  facts: ["Deploys go out on Tuesdays and Thursdays only"]})

recallOrExplain returns a string rather than results because the caller’s next step is almost always “put this in the prompt”. Doing the formatting here is what makes every integration handle the empty case the same way — by saying so, rather than letting a model fill the gap with a guess. Override the wording with emptyMessage.

seedMemories exists because a migration should not have to know what the service already holds. Anything refused comes back under rejected with a reason, so a seed that stored nothing tells you why instead of looking like success.

Resources

Method names map one-to-one onto endpoints, so the client is never a second, divergent copy of the service. Resources are readonly, which stops a caller reassigning one half of the client and wondering why the other half stopped seeing the change.

MethodEndpointDoes
memory.context.build()POST /v1/contextThe prompt block, its entries, total tokens, truncation.
memory.recall.search()POST /v1/recallRanked results. empty: true when nothing matched.
memory.turns.learn()POST /v1/turnsLearn a completed turn.
memory.memories.list()GET /v1/memoriesWhat is held, with tier counts.
memory.memories.get(id)GET /v1/memories/:idOne memory in full.
memory.memories.create()POST /v1/memoriesStore facts outright. Restatements fold in.
memory.memories.promote()PATCH /v1/memories/:idMove between tiers.
memory.memories.remove()DELETE /v1/memories/:idForget one memory.
memory.tensions.list()GET /v1/tensionsContradictions, filterable by status.
memory.tensions.create()POST /v1/tensionsRecord a contradiction.
memory.tensions.resolve()POST /v1/tensions/:idResolve one, keeping the pattern.
memory.selfModel.get()GET /v1/self-modelReliability per domain.
memory.selfModel.record()POST /v1/self-model/outcomeRecord how a domain went.
memory.stats.get()GET /v1/statsCounts, weak domains, open contradictions.
memory.health()GET /api/v1/healthLiveness, limits, extractor mode. No key spent.

Config and errors

Every non-2xx response throws CognitiveMemoryError carrying the tag, the message, and requiredScope or issues where they apply. A wrong key and a wrong scope are therefore distinguishable in a catch block, without matching on message text.

ts
import { CognitiveMemoryError } from "cogmemory"try {  await memory.turns.learn({ userMessage, assistantResponse })} catch (error) {  if (!(error instanceof CognitiveMemoryError)) throw error  if (error.isScopeError()) console.error("widen the key:", error.requiredScope)  else if (error.isValidationError()) console.error("bad body:", error.issues)  else throw error}

Ask the question semantically rather than matching on a status number.status === 403 scatters a magic number through every integration and means something slightly different at each one;isScopeError() means the same thing everywhere.

PredicateTrue whenRetry
isAuthError()401No — rotate the key
isScopeError()403No — widen the key
isValidationError()400No — fix the body
isNotFoundError()404No
isRateLimitError()429Yes
isServerError()≥ 500Yes — the client already does

The client retries 429 and 5xx for you. Wrapping those in your own retry loop is how a memory outage turns into a request flood. The tags behind each status are in the API reference.

The in-process layer

The second thing in the package is the deterministic layer — tiering, ranking, reconciliation, the fast gate — and it runs with no database, no network, and no model in the retrieval path.

That is what makes the shared primitives matter: the same code decides what a memory is worth on the server and in your test, so the two cannot drift. A test can assert on a ranking without a fixture database, and a local tool can classify text without asking a provider.

The model-backed arbiter is a separate entry point — cogmemory/arbiter — precisely so that ai and zod stay optional. Importing the deterministic layer never pulls in a model dependency.

Other languages

There is no Python, Go, or Rust client, and that is a deliberate position rather than a gap in the roadmap: the API is a dozen JSON endpoints over bearer auth, so a generated client in each language would be a second surface to keep in step with the service for no gain.

The integration is two calls, and any HTTP client does it — see the API reference for both with curl.