# 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:

1. `recall(question)` returns `memory_context`, the memory the question needs, and the handle (`shm_…`) it came from.
2. Your code calls the model with `memory_context` as the system message and the user's message after it. Nothing from earlier turns is sent.
3. `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

```bash
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](/docs/getting-started.html)).

## 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

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

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

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

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

1. Run the example for your provider. The reply gives the budget (₹3,500), which only the memory held.
2. The [audit log](/audit) lists the recall as a `query` disclosure, 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:

![The first-turn example run with python: the handle, then memory_context with the name, city, contact preference and budget](/media/casts/python-first-turn.svg)

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 `ingest` and the questions you `recall` with. 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](/privacy).
- **To your model's provider:** what your code sends it: `memory_context` and 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_context` goes in the system slot (`system=` for Claude, a `system` message for OpenAI and Ollama, `system_instruction` for Gemini), not after the user's message.
- **The model is missing a fact:** once your memory passes about 400 tokens, `recall` sends 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 `recall` returns.
- **`StateJarConnectionError` or `401`:** see the questions on [getting started](/docs/getting-started.html#questions).
