Any LLM provider
StateJar as the memory layer in front of any model. It stores what each message says, and you put the memory a question needs in front of Claude, OpenAI, Gemini, a model on your own machine or any other provider, as its system message. StateJar never calls your model and never sees its key.
What it does
StateJar does not call your model. It sits before your own model call:
recall(question)returnsmemory_context, the memory the question needs, and the handle (shm_…) it came from.- Your code calls the model with
memory_contextas the system message and the user's message after it. Nothing from earlier turns is sent. ingest(message)stores the facts the message states as a new version, never the message itself.
So it works with any provider and any model: the code that calls the model stays yours, and so does the model's key. While your memory is small, all of it is sent. Once it passes about 400 tokens, only the fields the question needs are.
Install
pip install statejar
Then your provider's own package, if it has one: pip install anthropic, pip install openai or pip install google-genai. A model on your own machine needs nothing more than an HTTP call. Sign in once with statejar login, or set STATEJAR_API_KEY (getting started).
Set it up
Each example stores a message, recalls the memory for a question and puts it in front of the model. Each provider reads its own key from its own environment variable; StateJar never sees it.
Claude
import anthropic
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.")
memory = sj.recall("What is my budget?") # only the fields the question needs
client = anthropic.Anthropic() # ANTHROPIC_API_KEY
reply = client.messages.create(
model="claude-haiku-5-5", max_tokens=200,
system=memory["memory_context"], # the model sees this, never a transcript
messages=[{"role": "user", "content": "What is my budget?"}],
)
print(reply.content[0].text)
OpenAI
from openai import OpenAI
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.")
memory = sj.recall("What is my budget?")
client = OpenAI() # OPENAI_API_KEY
reply = client.chat.completions.create(
model="gpt-6-luna",
messages=[
{"role": "system", "content": memory["memory_context"]},
{"role": "user", "content": "What is my budget?"},
],
)
print(reply.choices[0].message.content)
Gemini
from google import genai
from google.genai import types
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.")
memory = sj.recall("What is my budget?")
client = genai.Client() # GEMINI_API_KEY
reply = client.models.generate_content(
model="gemini-3.8-flash",
config=types.GenerateContentConfig(system_instruction=memory["memory_context"]),
contents="What is my budget?",
)
print(reply.text)
A model on your own machine
import httpx
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.")
memory = sj.recall("What is my budget?")
# any local model: here Ollama on your own machine (`ollama run qwen3:4b`)
reply = httpx.post("http://localhost:11434/api/chat", timeout=120, json={
"model": "qwen3:4b", "stream": False,
"messages": [
{"role": "system", "content": memory["memory_context"]},
{"role": "user", "content": "What is my budget?"},
],
}).json()
print(reply["message"]["content"])
Any other provider
Any API that takes a system message works the same way: memory_context first, then the user's message. If the API has no system message, put memory_context before the user's message in the same text.
Check it works
- Run the example for your provider. The reply gives the budget (₹3,500), which only the memory held.
- The audit log lists the recall as a
querydisclosure, with the handle and the fields it sent.
What the model receives is memory_context, as the Python SDK prints it here, run against a local StateJar server:
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)Each example on this page is run against StateJar's staging server by our docs check: the Claude, OpenAI and Gemini ones when we hold a key for that provider, and the local one when an Ollama server answers.
What it sends
- To StateJar: the messages you
ingestand the questions yourecallwith. Never your model key, your model's replies, or the conversation as a whole. How StateJar reads a message, and who else may see it while it does, is in the privacy policy. - To your model's provider: what your code sends it:
memory_contextand the user's message. That provider's own terms apply to them. - What StateJar keeps: typed facts, never a message. Each recall is a row in your audit log with the handle and the fields it disclosed.
Troubleshooting
- The model does not use the memory: check that
memory_contextgoes in the system slot (system=for Claude, asystemmessage for OpenAI and Ollama,system_instructionfor Gemini), not after the user's message. - The model is missing a fact: once your memory passes about 400 tokens,
recallsends only the fields the question needs. Recall with the question the user asked, not a summary of it. - The provider refuses the request: the error is the provider's (a key, a quota, a model name). StateJar's part ends when
recallreturns. StateJarConnectionErroror401: see the questions on getting started.
This page as Markdown: /docs/any-llm.md