Python SDK
The statejar package on PyPI, an HTTP client for StateJar's memory API. It stores what a message says, returns only the memory a question needs as the system message for your own model, and asks before it stores an unclear amount. Your model key never reaches it.
What it does
- Store.
ingest(text)sends one message. StateJar extracts typed facts from it, refuses a value that fails its type, and stores a new version named by its handle (shm_…, the SHA-256 of the canonical state). No transcript is stored. - Recall.
recall(query)returnsmemory_context, the system message for the model you already use: the whole state while it is small, only the fields the question needs once it is large. Withaudit=Truethe disclosure is written to your audit log as an audited turn. - One turn.
turn(text)stores the message, then recalls with it, in that order, and audits the disclosure. - Confirm before store. An amount with no claim attached ("i have 5000 rs") is not stored. It comes back in
confirmationswith a question, andconfirm(id, accept=True)stores it as a new version.
StateJar never calls a model for you. The package is an HTTP client and nothing else, its one dependency is httpx, and your model key stays in your code. It also carries the statejar command, which signs a terminal in.
Install
pip install statejar
You need Python 3.10 or later. PyPI serves version 0.6.0 (checked on 27 Oct 2026). With pip install "statejar[keyring]", the key statejar login stores goes to your system's keychain when it has one, instead of a file.
Set it up
Sign in once with statejar login (the statejar command). On a server or in CI, put a key from the API keys page in STATEJAR_API_KEY instead.
| Argument | Default | What it does |
|---|---|---|
api_key | STATEJAR_API_KEY, then the key statejar login stored | The developer key, sent as the X-API-Key header. |
session_tag | default | The conversation. A new tag's first message builds on your newest memory in any tag. |
base_url | STATEJAR_BASE_URL, then https://api.statejar.com/api/v1 | The API, for example staging or a server you run. |
timeout | 60 | Seconds a request may take. |
StateJar is also a context manager: with StateJar(session_tag="user-42") as sj: closes its connections at the end.
Check it works
The first-turn example from getting started:
from statejar import StateJar
# the key comes from `statejar login` or STATEJAR_API_KEY; one tag per conversation
sj = StateJar(session_tag="user-42")
sj.ingest("I'm Meera from Pune and I prefer email.") # typed facts, no transcript
sj.ingest("My budget is ₹3,500.")
hit = sj.recall("What is my budget?", audit=True) # what the question needs
print(hit["handle_used"]) # shm_… — a SHA-256 of the canonical state
print(hit["memory_context"]) # the system message for YOUR model, nothing else
It prints a handle, then the facts your model would get. The run below used the key statejar login stored:
statejar login stored, on statejar 0.7.0 as built from StateJar's repository (PyPI's 0.6.0 has the same calls): two messages in, one question out, then the handle and the memory_context your model gets, the whole state since it is small. The account already held these facts from the terminal, so nothing new is stored and the handle is the one statejar add printed. Recorded 27 Oct 2026 against a local StateJar server; the output is real, with pauses over 3 s cut to 3 s. Full sizeThe recording (.cast)The audit log then lists the disclosure, with the handle and the fields it sent.
Confirm before store
An amount with no claim attached is asked about, not stored. The question comes back in confirmations, and only your answer writes a version:
from statejar import StateJar
sj = StateJar(session_tag="user-42")
# money without a claim ("i have 5000 rs") is not stored: it is asked about
got = sj.ingest("go goa i have 5000rs ruppes")
for c in got["confirmations"]:
print(c["question"]) # Is ₹5,000 your budget?
sj.confirm(c["id"], accept=True) # or accept=False: nothing is stored
print(sj.recall("what is my budget")["subset"])
It prints the question StateJar asked, answers yes, and recalls: the budget is now stored, beside the destination the same message gave.
The audit trail
Every disclosure is in your audit log: which handle, which fields, which model, never the message. The same rows come back over the API:
import httpx, os
from statejar import StateJar
sj = StateJar(session_tag="user-42")
sj.ingest("I'm Meera from Pune, I prefer email, and my budget is ₹3,500.")
sj.recall("What is my budget?", audit=True) # audit=True writes the disclosure
# every disclosure: which handle, which fields, which model — never the text
base = os.environ.get("STATEJAR_BASE_URL", "https://api.statejar.com/api/v1").rstrip("/")
rows = httpx.get(f"{base}/audit?limit=3", headers={"X-API-Key": sj.api_key}).json()
for row in rows["entries"]:
print(row["kind"], row["handle_used"], row["subset_keys"])
The newest three disclosures, each with its kind, its handle and the fields it sent. The middle row is the confirm example's recall, which did not ask for an audit: a plain recall is logged too, as query.
What it sends
- To StateJar: the text of each message you
ingest, each question yourecallwith, and your answer to a confirmation. Your key goes in theX-API-Keyheader over HTTPS, with theUser-Agentstatejar-pythonand the package's version. - Never: your model key, your model's replies, or anything else from your program. The package does not call a model.
- On your machine: nothing, apart from the key
statejar loginstored. - What StateJar stores: typed facts, never the messages. Every recall is in your audit log (kind
query, orquery_auditedwithaudit=True), and deleting your account deletes all of it.
Troubleshooting
StateJarAuthErrorwith "api_key is required": there is no key. Runstatejar login, or setSTATEJAR_API_KEY.StateJarAuthError(401): the key is wrong, revoked or expired, or it belongs to another server (staging and production keys differ).statejar whoamisays which account a stored key belongs to;STATEJAR_API_KEYwins over the stored key.StateJarConnectionError: the request never reached StateJar: DNS, a proxy, orSTATEJAR_BASE_URLpointing somewhere that is not an API root ending in/api/v1. Nothing was stored, soingestis safe to repeat.StateJarErrorwithstatus_code429: a limit of your plan (what each limit counts).bodycarries the server's answer.storedisFalseandhandleisNone: not an error. The message held no fact to store, such as a greeting or a question.confirmationsis not empty: StateJar asked instead of storing. Callconfirm(id, accept=True), oraccept=False; nothing is written until you do.
This page as Markdown: /docs/python-sdk.md