Metadata-Version: 2.4
Name: agentram-sdk
Version: 0.2.0
Summary: Persistent memory for AI agents in two API calls. The official Python SDK for AgentRAM.
Project-URL: Homepage, https://agentram.dev
Project-URL: Documentation, https://agentram.dev/docs.html
Project-URL: Source, https://github.com/seanmarkwei/agentram-python
Author-email: Sean Markwei <hello@agentram.dev>
License: MIT
License-File: LICENSE
Keywords: agentram,agents,ai,llm,memory,persistent-memory
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown

# AgentRAM Python SDK

Persistent memory for AI agents, in two API calls. This is the official Python
client for [AgentRAM](https://agentram.dev) - 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

```bash
pip install agentram-sdk
```

> Installs as **`agentram-sdk`** on PyPI, but you import it as **`agentram`** in code (the install name and import name differ, which is common for Python packages).

## Get a key

Sign up at [agentram.dev](https://agentram.dev) for an API key. New accounts
start with **1,000 free credits**, no card required.

## Quickstart

```python
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

```python
# 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.

```python
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:

```python
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:

```python
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:

```python
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:

```python
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](#temporal-memory-facts-that-change) above.

## Notes

- **Rate limit:** 60 requests/minute per API key. The client automatically
  retries `429` and `5xx` responses a couple of times with backoff.
- **Credits:** writes and reads cost 1 credit; `update_fact()` costs 2 (a read
  plus a write); `create_namespace()` and `credits()` are free. Full pricing at [agentram.dev](https://agentram.dev/#pricing).
- **TTL:** pass `ttl_days` to expire a memory automatically.

## License

MIT
