@automatorswork/memory
v0.7.0
Published
Official SDK for memory.automators.work — sovereign memory as an API for AI agents. Types generated from the live OpenAPI 3.1 spec.
Maintainers
Readme
@automatorswork/memory
Official SDK for memory.automators.work — sovereign memory as an API for AI agents.
Three verbs (remember, recall, forget) with semantic search, MMR diversity, RRF hybrid retrieval, LLM-powered synthesis, mining, supersession, and idempotent writes. Zero dependencies.
Install
npm install @automatorswork/memoryGet an API key: memory.automators.work
Quick start
import { Memory } from "@automatorswork/memory";
const mem = new Memory({ apiKey: process.env.MEMORY_API_KEY });
// Store something
await mem.remember({
content: "User prefers concise answers in Spanish.",
type: "preference",
tags: ["lang:es"],
});
// Ask the memory a question (LLM-synthesized answer)
const r = await mem.recall("how does the user like responses?", {
synthesize: true,
topK: 3,
});
console.log(r.answer);
// → "The user prefers concise answers in Spanish."Features
| Capability | Method |
|------------|--------|
| Store an atomic memory | remember({ content, type?, tags?, supersedes? }) |
| Auto-classify via LLM | remember({ content, autoClassify: true }) |
| Mine atomic memories from a transcript | mineSession(transcript) |
| Semantic search | recall(query) |
| Hybrid search (vector + BM25) | recall(query, { rrf: true }) (default) |
| MMR diversity | recall(query, { mmr: true, lambda: 0.7 }) (default) |
| LLM-synthesized answer | recall(query, { synthesize: true }) |
| Supersede a previous entry | remember({ content, supersedes: "prev_id" }) |
| Walk supersession chain | chain(id) |
| Filter by type / tags / date | recall(q, { type, tags, since }) |
| Soft-delete (30d recoverable) | forget(id) |
| Bulk forget by ids or query | forgetBulk({ ids }) / forgetBulk({ query, confirm }) |
| Export everything (JSONL) | export() |
| Stats | stats() |
Cookbook
Idempotent writes
The server uses content-addressed hashing. Sending the same (content, type) twice returns the first entry with idempotent_hit: true — no duplicates, safe to retry.
const a = await mem.remember({ content: "User prefers Rust", type: "preference" });
const b = await mem.remember({ content: "User prefers Rust", type: "preference" });
console.log(a.id === b.id); // true
console.log(b.idempotent_hit); // trueSupersession chains
Model "this replaces that" without losing history.
const v1 = await mem.remember({
content: "User prefers Rust",
type: "preference",
});
// Later: a newer preference supersedes it
const v2 = await mem.remember({
content: "User now prefers Go",
type: "preference",
supersedes: v1.id,
});
// Recall by default sees only v2
const r = await mem.recall("what language?"); // → v2
// But the full chain is preserved
const history = await mem.chain(v2.id);
// history.chain = [v1 (superseded), v2 (current)]Mining from a conversation
Extract atomic memories from a transcript using the built-in LLM (Granite 4.0 H Micro).
const transcript = `
User: I prefer TypeScript.
User: Also, deploy only on Fridays.
User: Correction: deploy Wednesdays instead.
`;
const { extracted, entries } = await mem.mineSession(transcript);
// Returns classified atomic memories: preference, decision, correction.Hybrid retrieval (vector + BM25)
// Default: RRF fuses both channels. Great for exact-keyword queries
// (names, IDs, SKUs) that pure vector search might miss.
await mem.recall("Invoice QX-7731");
// Disable RRF if you only want semantic.
await mem.recall("how does the user feel?", { rrf: false });Answer-style recall
const r = await mem.recall("what's the deploy schedule?", {
synthesize: true,
topK: 5,
});
console.log(r.answer); // → "Deploys are on Wednesdays."
console.log(r.results); // the sources used
console.log(r.took_ms.synthesis); // added latency (~500 ms)Correction boost
Mark a memory as type: "correction" to have it surface above older entries in recall (2× score multiplier).
await mem.remember({
content: "User actually uses pnpm, not npm.",
type: "correction",
});Plans and limits
| Plan | Memories | Requests/hour | |------|----------|---------------| | Free | 1 | 100 | | Pro | 10 | 10,000 | | Enterprise | unlimited | 100,000 |
Each memory is an isolated container with its own API key. Manage them at memory.automators.work/dashboard.
Data sovereignty
Your data lives in a private git repository — not a black box. You can request a clone URL at any time. Nothing is locked in.
API reference
Full documentation at memory.automators.work/docs.
Webhooks
Every container client (Memory, KnowledgeBase, Collection) exposes the same 7-method webhook surface. Subscribe to mutations, verify the HMAC-SHA256 signature, and log deliveries for debugging.
import { Memory, verifyWebhookSignature } from "@automatorswork/memory";
const mem = new Memory({ apiKey: process.env.MEMORY_KEY });
// 1. Register a receiver — save the returned `secret`, it's shown once.
const wh = await mem.createWebhook({
url: "https://your-app.example.com/hooks",
events: ["memory.remember", "memory.forget", "image.upload"], // or ["*"]
});
console.log("save this:", wh.secret); // whsec_…
// 2. Fire a synthetic test.ping to verify reachability.
await mem.testWebhook(wh.id); // { ok: true, status: 200, duration_ms: ... }
// 3. In your receiver, verify before acting.
export async function onRequest(request) {
const rawBody = await request.text();
const ok = await verifyWebhookSignature({
secret: process.env.WEBHOOK_SECRET,
rawBody,
header: request.headers.get("x-memory-signature"),
});
if (!ok) return new Response("bad signature", { status: 401 });
const evt = JSON.parse(rawBody); // { id, type, timestamp, data, ... }
switch (evt.type) {
case "memory.remember": /* … */ break;
case "memory.forget": /* … */ break;
}
return new Response("ok");
}Retries, auto-disable, and delivery forensics are handled server-side:
await mem.listWebhookDeliveries(wh.id); // last 50 attempts, newest first
// → { deliveries: [{ event_type, attempt, ok, status, duration_ms, error, response_snippet, … }] }
await mem.updateWebhook(wh.id, { rotate_secret: true }); // returns a fresh whsec_… once
await mem.updateWebhook(wh.id, { active: false }); // pause without deleting
await mem.deleteWebhook(wh.id); // also wipes delivery logSee /docs#webhooks for event payload schemas, retry policy, and signing details.
Types generated from the live OpenAPI spec
All request/response types in index.d.ts are derived from the backend's
OpenAPI 3.1 spec (generated/openapi.d.ts), regenerated via
openapi-typescript. The
raw paths and components["schemas"] are re-exported, so TypeScript users
can drill down to any endpoint's exact shape:
import type { Schemas, paths } from "@automatorswork/memory";
type Entry = Schemas["Entry"];
type RememberBody = paths["/v1/remember"]["post"]["requestBody"]["content"]["application/json"];Staying in sync with the backend
If you're developing against the service (or running the SDK against a fork), regenerate the types and run the drift detector:
npm run sync # pulls https://memory.automators.work/openapi.json
npm run sync:local # reads ../ctx-git-service/site/openapi.json
npm run check # coverage-only, no type regenerationThe drift detector extracts every (METHOD, path) the SDK calls and
cross-references it against the spec. A METHOD path present in the SDK
but missing from the spec is a hard failure — either the spec is stale
or the SDK is calling a removed endpoint. Wired into prepublishOnly so
you cannot publish an SDK that calls endpoints the backend doesn't
advertise.
Error handling
import { Memory, MemoryError } from "@automatorswork/memory";
try {
await mem.remember({ content: "…" });
} catch (err) {
if (err instanceof MemoryError) {
console.error("status:", err.status);
console.error("message:", err.message);
console.error("data:", err.data);
}
}Rate limit headers are surfaced by the underlying fetch — pass a custom fetch to observe them:
const mem = new Memory({
apiKey: "...",
fetch: async (url, init) => {
const r = await fetch(url, init);
console.log("X-RateLimit-Remaining:", r.headers.get("X-RateLimit-Remaining"));
return r;
},
});Node / runtime support
- Node ≥ 18 for
0.3.x. Node 22 required from0.4.0— see POLICIES.md for the migration matrix and rationale. - Deno, Bun, Cloudflare Workers, any modern browser — all supported. Uses only Web-standard APIs (
fetch,AbortController,crypto.subtle,TextEncoder). - ESM only.
Stability
See POLICIES.md for what counts as public API, the deprecation cycle (1-minor minimum before removal), and the backend-version coupling. Short version:
- SemVer. Inside
0.x, minor bumps can remove already-@deprecatedsymbols. - Anything listed in
index.d.tswithout a leading underscore and without@deprecatedis public. - Deprecations land with IDE-visible
@deprecatedJSDoc + a documented migration; no noisy runtime warnings.
License
MIT
