This release is a pre-release and may not be stable for production use.
statejar (Python)
The Python client for the hosted StateJar memory API.
StateJar keeps conversational memory as structured, versioned state on the
StateJar service. This package sends a message to the service, gets back the
fields a question needs as memory_context, and hands that to your own
model call. It never calls a language model and never sees your provider key.
What this package includes — and what it does not
| Included | Not included |
|---|---|
StateJar, an HTTP client for /memory/ingest and /memory/query |
Any memory engine: extraction, canonicalization, handles and versioning all run on the StateJar service |
Typed exceptions (StateJarError, StateJarAuthError, StateJarConnectionError) |
Any LLM client or provider integration |
py.typed type information |
The StateJar backend, frontend or database code |
The only runtime dependency is httpx. Until 0.4.1 this distribution also
carried an embedded engine (statejar.LocalMemory); it was removed in 0.5.0
— see CHANGELOG.md.
Licence
Free to use with the StateJar service, under the StateJar SDK License in the LICENSE file included with this package. It is a proprietary licence, not an open-source one: you may install and use the unmodified package to access the service and redistribute unmodified copies; you may not distribute modified versions, use the package to build a competing service, or reverse engineer the service. Use of the service itself is governed by the terms at https://statejar.com/terms.
Install
pip install --pre statejar # 0.5.0rc1, the current pre-release
Requires Python 3.10+. --pre is needed until 0.5.0 itself is published;
without it pip finds no installable release.
Status and limits (0.5.0rc1, pre-release)
- Tested: the client's unit tests (HTTP mocked with respx), a clean
install on CPython 3.11 / Linux, and an end-to-end run of the installed
wheel against a StateJar server run locally (MariaDB 10.4). The hosted API
(
api.statejar.com) was not exercised by an automated test for this release. - What the service extracts is the service's behaviour, not this client's. Extraction on the service is a deterministic rules tier that can misread a sentence; no accuracy figure is claimed in this package.
Quickstart
Get a key on the API Keys page of the console
(keys look like sj_live_…). Then:
from statejar import StateJar
sj = StateJar(api_key="sj_live_...", session_tag="my-app")
turn = sj.turn("My name is Meera Nair and I prefer email")
turn["memory_context"] # prompt-ready system message for YOUR model
memory_context goes into your own LLM call as the system message, with
the user's text as the user message. With OpenAI, for example:
from openai import OpenAI
client = OpenAI() # reads OPENAI_API_KEY — never sent to StateJar
reply = client.chat.completions.create(
model="gpt-4o-mini", # the example defaults to this via OPENAI_MODEL
messages=[
{"role": "system", "content": turn["memory_context"]},
{"role": "user", "content": "Book my delivery"},
],
)
print(reply.choices[0].message.content)
That is the whole integration.
Statements without questions
For a message that needs no answer, skip retrieval entirely:
turn = sj.turn("Budget is under 25000 and the deadline is 20 September", ask=False)
# one request made; turn["memory_context"] is None
Working with the turn payload
turn["handle"] # the state handle this turn produced
turn["declined"] # values extraction refused, with reasons (e.g. a
# quantity offered to a money field) — never guessed
turn["subset_keys"] # exactly which fields were disclosed, e.g. ["facts.name"]
turn["retrieval_mode"] # field_match | intent_map | full_state | …
turn["audit_id"] # the disclosure is audited; replay it from the console
Lower-level access
stored = sj.ingest("I'm from Pune") # full ingest response
got = sj.recall("Where am I from?", audit=False) # subset + metadata;
# audit=True pins the
# disclosure to the trail
recall(audit=True) writes the disclosure to the server-side audit trail, so
you can prove later what was sent — the same trail the console's Audit page
shows. turn() audits by default; turn(text, audit=False) skips it.
API
| Method | Endpoint | Returns |
|---|---|---|
ingest(text) |
POST /memory/ingest |
handle, parent_handle, stored, state, conflicts, extraction_* |
recall(query, audit=False) |
POST /memory/query |
memory_context, subset, handle_used, metadata, audit_id |
turn(text, ask=True, audit=True) |
both, ingest first | all of the above, plus declined, subset_keys, retrieval_mode |
handle and handle_used are None while a session has no stored facts.
Auth is sent as X-API-Key; use one session_tag per conversation thread.
The client holds a connection pool — close it, or use it as a context manager:
with StateJar(api_key="sj_live_...", base_url="http://localhost:8000/api/v1") as sj:
sj.turn("I prefer email")
How it works
Every turn is two API calls, in this order, then yours:
- Ingest —
POST /memory/ingestcommits the message's facts: extracted, canonicalized, sealed into SHA-256 content-addressed state. - Recall —
POST /memory/queryreturns the stored fields the question needs, formatted asmemory_context. While a session's state is small (under the service's threshold) the whole state is returned instead —metadata.retrieval_modesays which. - Your LLM call —
memory_contextas the system message. Your provider, your key, your code; StateJar is not in it.
Two guarantees live in that ordering:
- BYOK — your key stays yours. The provider key is used inside your process, in step 3, and is never sent to StateJar. No transcript is stored either: the server sees one message at a time and keeps the facts it extracts, not the message.
- Ingest commits before recall runs, so the context you send already
contains the turn that asked the question. Reversing the order retrieves
state from before that message existed — and the failure is invisible,
because you still get a fluent answer, just one built on stale memory.
turn()gets the order right by construction; callingest()andrecall()yourself only when you have a reason to.
Errors
Everything raised is a StateJarError:
from statejar import (StateJar, StateJarError,
StateJarAuthError, StateJarConnectionError)
try:
turn = sj.turn("hi")
except StateJarAuthError: # HTTP 401 — bad/revoked key
...
except StateJarConnectionError: # unreachable host or timeout — nothing was
... # stored, so retrying ingest is safe
except StateJarError as e: # anything else; e.status_code, e.body
print(e.status_code, e.body) # (decoded JSON or text) and e.response_text
A missing api_key raises at construction as a StateJarAuthError that is
also a ValueError, so code written against either earlier client still
catches it.
Pointing at a local instance
sj = StateJar(api_key="sj_live_...", base_url="http://localhost:8000/api/v1")
Development
cd sdk && pip install -e ".[dev]" && pytest # ".[test]" is the same set
The tests mock HTTP with respx at the httpx client seam — no network, no server needed.
Copyright (c) 2026 StateJar. All rights reserved. The StateJar service implements the method described in Indian patent application No. 202621017626 (filed and published, not granted); see section 3 of LICENSE for the patent terms.
Metadata
Release files for statejar 0.5.0rc1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| statejar-0.5.0rc1.tar.gz | 18.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| statejar-0.5.0rc1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.7 kB
Release files / statejar-0.5.0rc1.tar.gz
| Download URL | statejar-0.5.0rc1.tar.gz |
|---|---|
| Size | 18.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9f7fa2a9617c9b4b4d5c81aa13cd8d2b4464dca73f197b4be97505fd0f755409
|
|
BLAKE2b-256 checksum How to use checksums |
76f179b28d8d41be077f85212fa385d15dcc7f70fe969a0988286ccaebecaf48
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / statejar-0.5.0rc1-py3-none-any.whl
| Download URL | statejar-0.5.0rc1-py3-none-any.whl |
|---|---|
| Size | 15.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1d46ab29298f431bd453ecd9c25e4474cf2103c9515b94f6109ebab86b5f87bd
|
|
BLAKE2b-256 checksum How to use checksums |
17c966699c457bd898a7a257b6bc4b6f0c71b4c10dbef0217a4251cdcf49524d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log