agentram-sdk
v0.2.0
Published
Persistent memory for AI agents in two API calls. The official TypeScript/JavaScript SDK for AgentRAM.
Maintainers
Readme
AgentRAM TypeScript SDK
Persistent memory for AI agents, in two API calls. The official TypeScript/JavaScript client for AgentRAM - a simple, credit-based HTTP API that gives your agents long-term memory. No vector database, no embeddings, no infrastructure to run.
Zero dependencies - uses the built-in fetch (Node 18+, browsers, and edge
runtimes). Ships with full TypeScript types.
Install
npm install agentram-sdkGet a key
Sign up at agentram.dev for an API key. New accounts start with 1,000 free credits, no card required.
Quickstart
import { AgentRAM } from "agentram-sdk";
const ram = new AgentRAM({ apiKey: "agentram_...", agentId: "agent-01" });
// Store something (1 credit)
await ram.store("user_language", "French");
// Read it back later, even in a brand-new session (1 credit)
const lang = await ram.recall("user_language"); // "French" | nullOne call to remember, one to recall.
Everything you can do
// Personal memory (scoped to an agentId)
await ram.store("tone", "formal", { ttlDays: 30 }); // auto-expire after 30 days
await ram.recall("tone"); // "formal" | null
await ram.delete("tone"); // true | false
await ram.list({ limit: 50 }); // MemoryRecord[]
await ram.search("lang"); // text search, no embeddings
// Shared memory (several agents reading/writing one pool)
const ns = await ram.createNamespace("team-alpha"); // { namespace_key, label }
await ram.storeShared(ns.namespace_key, "goal", "ship v1");
await ram.recallShared(ns.namespace_key, "goal"); // "ship v1" | null
await ram.listShared(ns.namespace_key);
// Temporal memory: facts that change over time, with history
await ram.updateFact("invoice_number", "1044"); // replaces what's current (2 credits)
await ram.current("invoice_number"); // the assertion true now | null
await ram.retire("invoice_number"); // true | false (ends it, keeps the trail)
await ram.listFacts(); // everything currently true, one per key
await ram.history("invoice_number"); // every version, newest first
// Account
await ram.credits(); // current balance (free)
ram.creditsRemaining; // last known balance, updated after every callOverride the agent per call: ram.store("k", "v", { agentId: "agent-02" }).
Temporal memory: facts that change
store() and recall() overwrite in place. That is the right shape for most
things, but some facts have a history that matters: the last invoice number, the
model an agent is currently using, the deploy target for a project. When one of
those changes you often want to know what it used to be, who changed it, and
when.
Assertions are an append-only log for exactly that. Each write records a value plus who wrote it and which earlier value it replaced.
await ram.updateFact("invoice_number", "1043", { writtenBy: "billing-agent" });
await ram.updateFact("invoice_number", "1044", { writtenBy: "billing-agent" });
const fact = await ram.current("invoice_number");
fact.value; // "1044"
fact.written_by; // "billing-agent"
fact.written_at; // "2026-07-31T..."
for (const a of await ram.history("invoice_number")) {
console.log(a.written_at, a.value, a.state); // live / superseded / retired
}updateFact() is the everyday call: it reads what is current and links the new
value to it, so the chain stays intact. It costs 2 credits because it is a read
plus a write.
Seeing everything at once
listFacts() returns one entry per key, with the value and who wrote it:
for (const fact of await ram.listFacts()) {
if (fact.contested) console.log(fact.key, "needs resolving");
else console.log(fact.key, "=", fact.value, "by", fact.written_by);
}A contested key comes back flagged and without a value, for the same reason
current() refuses one: guessing across a list is the same mistake as guessing
on a single read. Pass resolve: LAST_WRITE_WINS to fill those in with the
newest value. Retired and expired keys do not appear.
When two writers disagree
If two agents write the same key without either knowing about the other, the key is contested: there are two live values and neither replaced the other. The store will not pick one for you, because silently returning whichever came back first is the exact bug this is meant to prevent. It tells you instead:
import { ConflictError, LAST_WRITE_WINS } from "agentram-sdk";
try {
const fact = await ram.current("invoice_number");
} catch (e) {
if (e instanceof ConflictError) {
for (const a of e.assertions) { // newest first
console.log(a.value, "from", a.written_by, "at", a.written_at);
}
const winner = e.assertions[0];
await ram.assertFact("invoice_number", "1045", { supersedes: winner.assertion_id });
}
}Asserting a value that supersedes one of them resolves the conflict: the others stop being current too.
If you would rather never handle this and just take the newest value, ask for it explicitly:
const fact = await ram.current("invoice_number", { resolve: LAST_WRITE_WINS });That is safe to pass on every call, since it does nothing unless there is an actual conflict. It is spelled out in full on purpose. It is last-write-wins, with last-write-wins's failure mode, and that should be a decision you made rather than a default you inherited.
retire() is not delete()
delete() erases a flat memory and leaves nothing behind. retire() ends a
fact while keeping its history: current() returns null afterwards, but the
retirement is itself recorded, with who did it and when, so the trail survives.
A separate keyspace
Assertions and flat memories do not see each other. An assertion called
"invoice_number" and a memory called "invoice_number" are two unrelated
things. Use store()/recall() for facts you are happy to overwrite, and
assertions for facts whose history you care about.
Errors
Everything extends AgentRAMError, so one catch handles all of it:
import { AgentRAM, InsufficientCreditsError, RateLimitError, AgentRAMError } from "agentram-sdk";
try {
await ram.store("k", "v");
} catch (e) {
if (e instanceof InsufficientCreditsError) { /* top up */ }
else if (e instanceof RateLimitError) { /* back off */ }
else if (e instanceof AgentRAMError) { console.log(e.statusCode, e.message); }
}recall() and recallShared() return null for a missing/expired memory
instead of throwing, and delete() returns false - so the common "not there"
case stays out of your try/catch. current() and retire() behave the same
way for assertions.
ConflictError is the one error carrying extra data: .assertions holds every
competing value when a key is contested, which is what you need to resolve it.
See Temporal memory above.
Notes
- Rate limit: 60 requests/minute per API key. The client auto-retries
429and5xxa couple of times with backoff. - Credits: writes and reads cost 1 credit;
updateFact()costs 2 (a read plus a write);createNamespace()andcredits()are free. Pricing at agentram.dev. - Runtimes: anything with a global
fetch- Node 18+, Deno, Bun, browsers, Cloudflare Workers, Vercel Edge.
Build (for contributors)
npm install
npm run build # compiles src/ to dist/ with type declarationsLicense
MIT
