Sessions Graph
Sessions Graph is the Context Graph component for session context and cross-session recall. It is the authority on (:Session) nodes in the Context Graph family. It stores free-form text assertions — called Memories — written explicitly by agents, and makes them searchable in future sessions.
Requires Memgraph ≥ 3.6 (text search is stable from that release).
Installation
pip install sessions-graph
To use with Agent Context Graph:
pip install sessions-graph[agent-context-graph]
To use session reconciliation (entity extraction from session content):
pip install sessions-graph[reconciliation]
Quick start
from sessions_graph import SessionsGraph
graph = SessionsGraph() # connects via MEMGRAPH_URL / MEMGRAPH_USER / MEMGRAPH_PASSWORD env vars
graph.setup() # creates constraints and the text index (run once)
# Write a memory
mem = graph.save_memory(
user_id="alice",
content="Prefers Python over TypeScript",
session_id="s-abc123", # optional — links memory to a session for provenance
)
# Retrieve all memories for a user
memories = graph.get_memories("alice")
# Search memories by content (full-text, powered by Tantivy)
results = graph.search_memories("alice", "Python")
# Update or delete
graph.update_memory(mem.memory_id, "Prefers Python, especially for data tooling")
graph.delete_memory(mem.memory_id)
Integration with Agent Context Graph
Wire the SessionsGraphConnector into an AgentLink to get automatic session provenance — the connector tracks the active session_id and user_id from SessionStartEvent so you can reference them when saving memories.
from sessions_graph import SessionsGraph
from sessions_graph.connector import SessionsGraphConnector
from agent_context_graph import AgentLink
from agent_context_graph.adapters.claude import ClaudeAdapter
graph = SessionsGraph()
graph.setup()
connector = SessionsGraphConnector(graph)
link = AgentLink()
link.add_connector(connector)
adapter = ClaudeAdapter(
link,
session_id="s-abc123",
session_kwargs={"user_id": "alice"},
)
# During the session, save memories via the Python API:
graph.save_memory(
user_id=connector.active_user_id,
content="User works primarily in the ai-toolkit repository",
session_id=connector.active_session_id,
)
Graph schema
(:User {user_id})
├─[:HAS_MEMORY]──▶ (:Memory {memory_id, user_id, content, created_at})
│ ▲ │
│ [:PRODUCED_MEMORY] [:HAS_CHUNK]
│ │ ▼
└─[:HAD_SESSION]─▶ (:Session {session_id, (:Chunk {hash, text})
reconciliation_status, ▲
reconciled_at}) [:HAS_CHUNK]
│ │
[:HAS_ACTION] │
▼ │
(:Action) ─────────────────────┘
│
(:Entity)-[:MENTIONED_IN]->(:Chunk)
(:User)-[:HAD_SESSION]->(:Session) is written by SessionsGraphConnector on
session start; it is the join key other Context Graph components hang off of.
(:Action) is owned by Actions Graph; (:Chunk) and the
extracted entity nodes are owned by
unstructured2graph. See Session
reconciliation below for how they get linked.
Text search
Sessions Graph uses Memgraph text search (powered by Tantivy) for search_memories. The text index is created on setup():
CREATE TEXT INDEX memory_content_index ON :Memory(content);
Searches run as:
CALL text_search.search_all('memory_content_index', 'Python')
YIELD node AS m, score
WHERE m.user_id = 'alice'
RETURN m.content, score
ORDER BY score DESC
LIMIT 10;
The query string follows Tantivy query syntax.
Session reconciliation
A session's Actions Graph content (Messages, ToolCalls, ToolResults) and
Memories are mostly opaque text today. Session reconciliation runs that content
through unstructured2graph's chunk + LightRAG
entity-extraction pipeline, turning it into queryable graph entities linked
back to the session that produced them — see
CONTEXT.md for the Session Reconciliation /
Reconcilable Content / Reconciliation Status terminology.
The same pass also writes the session's episodic memory: an
(:Episode {summary, summarized_at}) node linked via
(:Session)-[:HAS_EPISODE]->(:Episode) (at most one per session — re-running
reconciliation updates it rather than adding another), produced by a second,
dedicated LLM call over the same deduped session text — a "what happened in
this session" gist, not the structured entity graph. This is what a "what did
we do last time?" recall query actually reads.
This requires the sessions-graph[reconciliation] extra and an LLM API key
(OPENAI_API_KEY or ANTHROPIC_API_KEY) for LightRAG — see the
lightrag-memgraph README.
Reconciliation never runs inside the SESSION_END hook itself. LightRAG
entity extraction is LLM-backed and slow, and hook runtimes (Claude Code,
Codex) enforce a timeout on hook commands. Instead:
-
On
SESSION_END,SessionsGraphConnectorcheaply marks the sessionreconciliation_status = 'pending'— no LLM calls, safe inside the hook. -
The actual reconciliation run happens out-of-band, via the CLI:
# Reconcile one session sessions-graph reconcile --session s-abc123 # Sweep every session still marked 'pending' (e.g. from cron) sessions-graph reconcile --pending --limit 50 # Optional: override LightRAG's working dir (default ./lightrag_storage) sessions-graph reconcile --pending --working-dir ./lightrag_storage
The CLI runs with
enforce_ontology=True: extracted entities get real type labels (:Person,:Organization, …) gated by unstructured2graph's default ontology, and anything outside it is kept but flaggedontology_conformant = false. See entity typing. -
Or, if you want it triggered automatically without a manual/cron step, opt in to a best-effort detached background process spawned right after a session ends: pass
SessionsGraphConnector(graph, auto_reconcile=True), or setSESSIONS_GRAPH_AUTO_RECONCILE=1in the environment the connector is constructed in (this is what hook-based runtimes read, since they don't expose a constructor kwarg for it). This is fire-and-forget — if the process dies before finishing (machine sleep, crash), the session stayspendingandsessions-graph reconcile --pendingis the reliable backfill.
Programmatically:
from sessions_graph import SessionsGraph
from lightrag_memgraph import MemgraphLightRAGWrapper
graph = SessionsGraph()
graph.setup()
lightrag_wrapper = MemgraphLightRAGWrapper()
await lightrag_wrapper.initialize(working_dir="./lightrag_storage")
summary = await graph.reconcile_session(
"s-abc123",
lightrag_wrapper=lightrag_wrapper,
enforce_ontology=True, # match the CLI: promote entity_type to real labels
)
print(summary.status, summary.texts_considered, summary.texts_deduped, summary.summary_written)
Label promotion is opt-in and mirrors unstructured2graph's flags: the default
(enforce_ontology=False, promote_labels=False) leaves entities under the
LightRAG workspace label with an entity_type property only; enforce_ontology=True
restricts promotion to an ontology (pass ontology_path= for a custom one);
promote_labels=True promotes every entity_type with no vocabulary. See
unstructured2graph § entity typing.
Extracted entities land in the same LightRAG workspace as any documents
ingested via unstructured2graph by default, so a person or concept mentioned
both in a session and in an ingested document merges into one node. Pass
entity_workspace= explicitly to reconcile_session() to isolate them instead.
Content is deduplicated by hash before ever reaching the LLM, so re-running a
sweep over already-processed content never re-bills it. Each reconcilable unit
(a message, tool call, tool result, or memory) is truncated to
MAX_RECONCILABLE_CHARS (8000) before extraction, but a chatty session still
has many units, so the first run can be substantial. Consider this before
enabling auto_reconcile broadly.
API reference
| Method | Description |
|---|---|
setup() |
Create constraints, text index, and reconciliation indexes. Run once on first use. |
drop() |
Remove all Memory-related constraints and indexes. |
save_memory(user_id, content, *, session_id, memory_id) |
Persist a new Memory. Returns the stored Memory object. |
get_memories(user_id) |
Return all Memories for a user, newest first. |
get_memories_for_session(session_id) |
Return all Memories produced by a session, newest first. |
search_memories(user_id, query, *, limit=10) |
Full-text search over Memory content. |
update_memory(memory_id, content) |
Replace the content of an existing Memory. Returns None if not found. |
delete_memory(memory_id) |
Remove a Memory and all its relationships. |
async reconcile_session(session_id, *, lightrag_wrapper, actions_graph=None, entity_workspace=None, promote_labels=False, enforce_ontology=False, ontology_path=None) |
Run session reconciliation for one session. promote_labels/enforce_ontology/ontology_path control entity-type label promotion (see above). Returns a ReconciliationSummary. Requires the reconciliation extra. |
get_pending_reconciliation_sessions(*, limit=100) |
Return session IDs marked reconciliation_status = 'pending'. |
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sessions_graph-0.5.0.tar.gz.
File metadata
- Download URL: sessions_graph-0.5.0.tar.gz
- Upload date:
- Size: 24.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f07df388d846d5be32c3ed3929e87966a1ebce76ca433dd13714971ba6279860
|
|
| MD5 |
52be14fc2bce9c6b6e49167fac4a3120
|
|
| BLAKE2b-256 |
6e41c45f1ba2bd94f79924c45b0f96f70134a5fa7eaa31252424fa40b94e37ec
|
File details
Details for the file sessions_graph-0.5.0-py3-none-any.whl.
File metadata
- Download URL: sessions_graph-0.5.0-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bc6c34b179500d32e946894577998ca15a8eda1d4b0b8139d76a1c79ee80778
|
|
| MD5 |
6a711f6648d8b2570caf6712c48d1c7f
|
|
| BLAKE2b-256 |
b1de24b340c761184c3126263266c7caa49f2f2716525d9ca7df4cbcf7abf240
|