@persistmemory/sdk
TypeScript
ESM, no runtime dependencies, and a fetch you can inject, which is what makes a test of your own code incapable of opening a socket by accident.
npm install @persistmemory/sdk
import { PersistMemory } from "@persistmemory/sdk";
const client = new PersistMemory({
apiKey: process.env.PERSISTMEMORY_API_KEY!
});
await client.memories.remember({
text: "We chose Postgres for the ledger, not DynamoDB."
});
const found = await client.search.query({
query: "what did we decide about the ledger"
});
console.log(found.results.map((one) => one.memory.title));
Assembling context for a model, and filing what came back
import { PersistMemory, NotFoundError, RateLimitError } from "@persistmemory/sdk";
const client = new PersistMemory({
apiKey: process.env.PERSISTMEMORY_API_KEY!,
timeoutMs: 15_000,
maxAttempts: 4
});
async function answer(question: string, conversationId: string): Promise<string> {
// Bounded by TOKENS, not by row count. Ten long memories overflow a
// window that fifty short ones fit inside.
const context = await client.search.context({
query: question,
tokenBudget: 1_500,
scope: "combined",
spaceIds: ["sp_work"]
});
if (context.truncated) {
console.warn("Something relevant was left out for budget.");
}
const reply = await callYourModel(context.context, question);
// Both turns in one call: a claim is often split across an exchange,
// and extracting each turn in isolation finds neither half.
const appended = await client.conversations.append(
conversationId,
{
messages: [
{ role: "user", content: question },
{ role: "assistant", content: reply }
]
},
{ idempotencyKey: `${conversationId}:${question.slice(0, 40)}` }
);
if (!appended.extracting) {
// Said out loud rather than assumed. With no queue configured the
// turns are stored and never become memory.
console.warn(appended.note);
}
return reply;
}
Paging
// One page, for a UI that renders one page.
const first = await client.memories
.list({ type: "decision", limit: 50 })
.first();
// Or the whole walk. `all` needs a bound: an
// unbounded collect on a large account is
// minutes of requests and a heap full of results.
const decisions = await client.memories
.list({ type: "decision" })
.all(500);
// Or item by item, stopping when you like.
for await (const memory of client.memories.list({ scope: "combined" })) {
if (memory.confidence < 0.5) break;
}
Errors
try {
await client.spaces.get("sp_missing");
} catch (error) {
if (error instanceof NotFoundError) return null;
if (error instanceof RateLimitError) {
// Already retried, and still refused.
// retryAfterSeconds is what the server said.
console.warn(error.retryAfterSeconds ?? 60);
}
throw error;
}