AgentRAM Python SDK
Persistent memory for AI agents, in two API calls. This is the official Python 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 third-party dependencies (standard library only).
Install
pip install agentram-sdk
Installs as
agentram-sdkon PyPI, but you import it asagentramin code (the install name and import name differ, which is common for Python packages).
Get a key
Sign up at agentram.dev for an API key. New accounts start with 1,000 free credits, no card required.
Quickstart
from agentram import AgentRAM
ram = AgentRAM(api_key="agentram_...", agent_id="agent-01")
# Store something (1 credit)
ram.store("user_language", "French")
# Read it back later, even in a brand-new session (1 credit)
lang = ram.recall("user_language") # -> "French" (or None if missing/expired)
That's the whole idea: one call to remember, one to recall.
Everything you can do
# Personal memory (scoped to an agent_id)
ram.store("tone", "formal", ttl_days=30) # auto-expire after 30 days
ram.recall("tone") # -> "formal" | None
ram.delete("tone") # -> True | False
ram.list(limit=50) # -> [{"key","value","created_at","expires_at"}, ...]
ram.search("lang") # -> matching records (text search, no embeddings)
# Shared memory (several agents reading/writing one pool)
ns = ram.create_namespace("team-alpha") # -> {"namespace_key": "ns_...", "label": "team-alpha"}
ram.store_shared(ns["namespace_key"], "goal", "ship v1")
ram.recall_shared(ns["namespace_key"], "goal") # -> "ship v1" | None
ram.list_shared(ns["namespace_key"])
# Temporal memory: facts that change over time, with history
ram.update_fact("invoice_number", "1044") # replaces what's current (2 credits)
ram.current("invoice_number") # -> the assertion that is true now | None
ram.retire("invoice_number") # -> True | False (ends it, keeps the trail)
ram.list_facts() # -> everything currently true, one per key
ram.history("invoice_number") # -> every version, newest first
# Account
ram.credits() # -> current balance (free)
ram.credits_remaining # last known balance, updated after every call (no extra request)
You can override the agent per call: ram.store("k", "v", agent_id="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.
ram.update_fact("invoice_number", "1043", written_by="billing-agent")
ram.update_fact("invoice_number", "1044", written_by="billing-agent")
fact = ram.current("invoice_number")
fact["value"] # '1044'
fact["written_by"] # 'billing-agent'
fact["written_at"] # '2026-07-31T...'
for a in ram.history("invoice_number"):
print(a["written_at"], a["value"], a["state"]) # live / superseded / retired
update_fact() 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
list_facts() returns one entry per key, with the value and who wrote it:
for fact in ram.list_facts():
if fact["contested"]:
print(fact["key"], "needs resolving")
else:
print(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:
from agentram import ConflictError, LAST_WRITE_WINS
try:
fact = ram.current("invoice_number")
except ConflictError as conflict:
for a in conflict.assertions: # newest first
print(a["value"], "from", a["written_by"], "at", a["written_at"])
winner = conflict.assertions[0]
ram.assert_fact("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:
fact = 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 None 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 inherits from AgentRAMError, so one except catches all of it:
from agentram import AgentRAM, InsufficientCreditsError, RateLimitError, AgentRAMError
try:
ram.store("k", "v")
except InsufficientCreditsError:
... # balance hit zero - top up at agentram.dev/#pricing
except RateLimitError:
... # 60 requests/minute per key - back off and retry
except AgentRAMError as e:
print(e.status_code, e.message)
recall() and recall_shared() return None for a missing or expired memory
rather than raising, and delete() returns False - so the common "not there"
case stays out of your try/except. current() and retire() behave the same
way for assertions.
ConflictError is the one error that carries 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 automatically
retries
429and5xxresponses a couple of times with backoff. - Credits: writes and reads cost 1 credit;
update_fact()costs 2 (a read plus a write);create_namespace()andcredits()are free. Full pricing at agentram.dev. - TTL: pass
ttl_daysto expire a memory automatically.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agentram_sdk-0.2.0.tar.gz.
File metadata
- Download URL: agentram_sdk-0.2.0.tar.gz
- Upload date:
- Size: 15.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50e8c569b84e7ce4e26dd02e5826b83d9938aedae457455af0576bba1d8b7926
|
|
| MD5 |
b3d0de868d16a5d5edb3c404f18fccc6
|
|
| BLAKE2b-256 |
3a837a21911108f9aeef6e3b7b727bab390d2a411d40394b2e3f757fd8109c6b
|
File details
Details for the file agentram_sdk-0.2.0-py3-none-any.whl.
File metadata
- Download URL: agentram_sdk-0.2.0-py3-none-any.whl
- Upload date:
- Size: 14.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aaddfd1c8a5acda9081004745c65099d7c52be9a31b656e8fab385a794b4e29e
|
|
| MD5 |
b43167d84b08c744cdec873051ab3af0
|
|
| BLAKE2b-256 |
fa2fbe4aa63238be74b78394c1925149e72925a8716c8a8f6f8dc547fe88b8c6
|