npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

memoryo-sdk

v0.1.2

Published

Zero-runtime-dependency TypeScript SDK for governed, traceable AI memory with MemoryOS.

Downloads

364

Readme

MemoryOS TypeScript SDK

npm install memoryo-sdk

Start simple: solo builders and small teams

For whom: solo developers, MVPs, small SaaS apps, and single-agent products. You do not need eventId, runId, service writers, or authority rules to start. MemoryOS creates safe internal source metadata automatically.

import { MemoryOS } from "memoryo-sdk";

const mem = new MemoryOS(process.env.MEMORYOS_API_KEY!);

const write = await mem.add(
  [
    { role: "user", content: "I prefer short answers when debugging." },
    { role: "assistant", content: "Got it. I will keep debugging replies concise." },
  ],
  "user_123",
);

if (!write.jobId) throw new Error(`Memory write was not queued: ${write.status}`);
const job = await mem.waitForJob(write.jobId);
if (!job.succeeded) throw new Error(`Memory write failed: ${job.errorSummary ?? job.status}`);

const context = await mem.get(
  "How should I answer this user?",
  "user_123",
);

const systemPrompt = context.hasContext
  ? `${BASE_SYSTEM_PROMPT}\n\n${context.systemPromptAddition}`
  : BASE_SYSTEM_PROMPT;

This is the recommended first integration. Add source metadata only when more than one backend service writes memories for the same users.

Opt-in scoped preferences (0.1.2 release candidate)

This source checkout exposes an application-declared contract, not automatic natural-language scope/uncertainty recognition. These fields are included in the 0.1.2 source/tarball, not the published 0.1.1 package. Preparing a tarball does not publish a release. The backend feature remains disabled by default; disabled requests fail with 409 / PREF_409 rather than falling back to unscoped writes.

const applicability = { kind: "context", namespace: "project", key: "release-check" } as const;
const quote = "For Release Check, use Python for examples.";
const write = await mem.add({
  externalUserId: "user_123",
  messages: [{ role: "user", content: quote }],
  preferenceClaims: [{
    propertyKey: "examples.language", value: "Python", applicability,
    turnIndex: 0, quote,
  }],
});
// Queued, not confirmed storage. Check status separately, not during streaming.
const context = await mem.get({
  externalUserId: "user_123", query: "Which language should examples use?",
  preferenceContext: { applicability, propertyKeys: ["examples.language"] },
});

Existing positional add() and get() calls remain supported. Omitted preference fields stay omitted. When declarations are supplied, the job processes only those declarations, not other facts in the transcript. Property/scope keys must be stable and application-owned; never guess unknown scope as global. Declarations stay client_asserted, authority capped at 20. Do not combine them with source or agentId, or explicit reads with asOf. Types are not runtime security checks; the backend validates evidence, limits, permissions and authority.

For intentional replacement, use commitment: "replacement", replacesMemoryId and the exact string at provenance.preference_version returned by the backend. Tentative uncertainty stays pending without withholding an existing default; review actions never activate candidate text. Existing job status preserves raw extractionMetadata and provides sourceReviewsCapacityRejected; completion alone does not mean storage. Immediate reads may precede job completion. There is no automatic polling, new model call, authority decision, or read-after-write guarantee in the SDK. See the full contract.

An explicitly unresolved current choice

The 0.1.2 types also accept choiceState: "current_choice_unresolved" with commitment: "uncertain", known applicability, and both existing-winner references. This is an application declaration, not automatic interpretation of the user's wording. Merely tentative interest is not a reason to withhold a default.

// currentId/currentVersion come from an owned backend management or prior
// retrieval result; never fabricate them from assistant text.
const undecidedQuote = "For Release Check, I am undecided between my current C++ default and Python.";
const held = await mem.add({
  externalUserId: "user_123", messages: [{ role: "user", content: undecidedQuote }],
  preferenceClaims: [{
    propertyKey: "examples.language", value: "Python", applicability,
    turnIndex: 0, quote: undecidedQuote, commitment: "uncertain",
    choiceState: "current_choice_unresolved",
    replacesMemoryId: currentId, replacesVersion: currentVersion,
  }],
});

The backend validates evidence, exact identity, ownership, currentness, authority and version. JavaScript can bypass TypeScript shapes; the SDK does not silently repair invalid declarations or strip fields after server rejection. A queued write is not confirmed withholding: raw job extractionMetadata.preference_choices records processing outcomes. Poll only when your workflow explicitly needs that confirmation, not automatically while generating an answer.

Tenant get() returns exported PreferenceReadDecision entries in context.preferenceDecisions (an empty array from old servers without the field) and preserves context.clarificationQuestion. The result type's field is optional for compatibility with existing consumers constructing result objects. Check it independently of context.hasContext and context.items: unresolved choice can be the only information, or accompany usable unrelated facts.

const reviewed = await mem.get({
  externalUserId: "user_123", query: "Which language should examples use?",
  preferenceContext: { applicability, propertyKeys: ["examples.language"] },
});
for (const decision of reviewed.preferenceDecisions ?? []) {
  // Pass this structured outcome to your existing chat workflow. The SDK
  // does not render UI, change an agent prompt, choose a value or resolve it.
  console.log(decision.propertyKey, decision.applicability, decision.state);
}

Review dismissal/expiry cannot restore a withheld default. A matching keep_current review can reaffirm an existing valid value; an exact version-bound replacement can resolve the choice. Neither raises authority or activates candidate text. This capability remains unpublished and disabled by default, and does not add automatic semantic admission.

Validate a release before publication

From sdk/typescript, after installing development dependencies:

npm run typecheck
npm run test:package

test:package builds via the real prepack lifecycle, packs the release, and installs that tarball into a disposable consumer offline. It reuses the existing contract tests through the public package name in ESM and CommonJS, and checks positive/negative preference type fixtures with Node16, NodeNext and Bundler resolution. It also rejects unexpected package files or runtime dependencies. The disposable consumer and tarball are removed after testing; nothing is published.

For a tarball to hand off, run npm pack after those checks. Packing rebuilds dist so an old local bundle cannot silently become the release. Publication also runs the typecheck and installed-package gate in prepublishOnly. Both lifecycles follow npm's documented script ordering.

These are client packaging/contract checks against controlled responses, not proof of live extraction accuracy or permission to enable backend features. Consumers must upgrade their dependency explicitly; publishing a release does not update a deployed assistant automatically.

Multi-service mode: support, billing, CRM, and product services

For whom: teams where multiple services can write facts about the same user. Use this when you need auditability, deduplication, conflict handling, or source-of-truth routing.

After registering the service writers in the Tenant Dashboard, teams can pass their registered source.service keys:

import { MemoryOS } from "memoryo-sdk";

const mem = new MemoryOS(process.env.MEMORYOS_API_KEY!);

await mem.add(
  [{ role: "assistant", content: "Customer is on the Growth plan." }],
  "cust_123", // externalUserId: your stable customer/user id
  "support-bot", // agentId: your app or AI agent id
  { ticketId: "TCK-8842", channel: "billing" }, // metadata: optional app context
  MemoryOS.source("billing-service", {
    eventId: "invoice_evt_8842",
    scope: { workspaceId: "ws_123" },
    evidence: [{ sourceType: "billing_record", reference: "invoice_evt_8842" }],
  }),
);

await mem.add(
  [{ role: "assistant", content: "Customer support previously saw the Starter plan." }],
  "cust_123", // same externalUserId, so MemoryOS can compare facts
  "support-bot",
  { ticketId: "SUP-2109", channel: "support" },
  MemoryOS.source("support-service"), // eventId and observedAt are generated
);

Production teams should also bind dedicated API keys to those writers. Then Billing, Support, CRM, and other services can have different authority rules without changing the basic SDK flow.

Webhook Signature Verification

import crypto from "node:crypto";

function verifyMemoryOSWebhook(
  body: string,
  signatureHeader: string,
  webhookSecret: string,
): boolean {
  const expected = crypto
    .createHmac("sha256", webhookSecret)
    .update(body, "utf8")
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "utf8"),
    Buffer.from(signatureHeader, "utf8"),
  );
}

const signature = request.headers["x-memoryos-signature"];
if (!signature || !verifyMemoryOSWebhook(rawBody, signature, WEBHOOK_SECRET)) {
  throw new Error("Invalid MemoryOS webhook signature");
}
import { MemoryOS } from "memoryo-sdk";

const client = new MemoryOS("mem_live_xxx", "https://api.memoryo.dev");

const result = await client.add(
  [{ role: "user", content: "I prefer Python and FastAPI" }],
  "student_44821",
);

const memories = await client.get(
  "programming preferences",
  "student_44821",
  3,
);

console.log(result.status, result.quotaMode);
console.log(memories.quotaMode);
console.log(memories.items[0]?.content ?? "No memories found");

memoryo-sdk is a strict, zero-runtime-dependency client for the MemoryOS API.

  • Native fetch only
  • Works in server-side Node.js 18+
  • Built-in retries for 429 and 5xx
  • Typed inputs, outputs, and errors
  • ESM and CJS bundles

Security: Keep tenant API keys on your server. Do not embed a MemoryOS API key in browser JavaScript or any NEXT_PUBLIC_* environment variable.

Quickstart

  1. Create an API key in MemoryOS.
  2. Install with npm install memoryo-sdk.
  3. Import MemoryOS.
  4. Construct the client with your API key.
  5. Override baseUrl only for local or self-hosted environments.
  6. Call add() with the tenant's externalUserId.
  7. Check result.status and result.quotaMode to handle blocked or passthrough cleanly.
  8. Call get() with the same externalUserId.
  9. Use retrieveResult.systemPromptAddition only when retrieveResult.quotaMode !== "PASSTHROUGH".
  10. Call list() to paginate stored memories.

add().wasQueued means extraction was queued, not that storage is complete. Use waitForJob() and job.createdMemoryIds when you need exact created records. wasStored remains as a deprecated compatibility alias. waitForJob() continues polling through the backend's retryable failed state and returns when the job completes or exhausts retries as dead.

When retrieval returns result.clarification, render its question in your existing chat and send the user's selected answer with client.answerClarification(...). MemoryOS—not the model—validates identity, expiry, and the conflict transition.

Source reviews (0.1.1)

With source-merge containment deployed, reviews can also represent a blocked automatic merge, not only uncertainty. The current memory remains active and the incoming candidate is pending. This backend behavior uses the same SDK methods; it does not require a new package version. A completed ingestion job can create zero memories. Read the job outcome and present the returned review in your existing chat; do not report the merge as stored.

When recognized uncertainty has no usable alternative, result.sourceReviews may contain a non-activating review. Present its question and allowed actions in your existing chat. Submit the actual user's selection, never a model decision:

const review = result.sourceReviews?.[0];
if (review) {
  // userAction is the user's choice from review.actions in your chat.
  const resolution = await client.answerSourceReview({
    reviewId: review.id,
    externalUserId: "user_1",
    version: review.version,
    action: userAction,
  });
}

keep_current and dismiss close the pending interpretation without changing stored memory or raising authority. restate leaves it pending and returns resolved: false, nextStep: "add_memory": obtain the user's actual new statement and submit it through normal add() ingestion. It is not completed storage. Reviews are bounded to three recent items, expire seven days after the server's last observation, and are absent for historical retrieval. This fallback does not fix semantic misclassification or suppress all pending-property context.

Other operations

  1. Call delete() to archive or hard-delete a memory.
  2. Call export() to download the user export bundle.
  3. All failures throw typed SDK errors.
  4. 401 maps to AuthError.
  5. 404 maps to NotFoundError.
  6. 429 maps to RateLimitError.
  7. See examples/ for integration patterns.

Handling Degraded Responses

const result = await client.get(
  "...",
  "...",
);

if (result.isPassthrough) {
  // MemoryOS quota exhausted or system degraded
  // Your AI still works — just without memory context
  systemPrompt = BASE_SYSTEM_PROMPT;
} else if (result.isDegraded) {
  // Serving from cache — fewer memories than usual
  // Still worth using
  systemPrompt = BASE_SYSTEM_PROMPT + result.systemPromptAddition;
} else {
  // Full memory context
  systemPrompt = BASE_SYSTEM_PROMPT + result.systemPromptAddition;
}

// Always call your LLM — never skip because of MemoryOS state
const response = await llm.complete(systemPrompt, userMessage);
const result = await client.add(messages, "...");

if (result.processingStatus === "delayed") {
  // Queue is busy — memory will be stored but takes longer
  // ETA in seconds:
  const eta = result.processingEtaSeconds;
  // Log it — do not surface to end user
}