Track and persist user interaction history across sessions — for AI chatbots, query tools, and any workflow requiring session continuity.
Project description
session-history-saver
Track and persist user interaction history across sessions — for AI chatbots, query tools, and any workflow requiring session continuity, auditing, or fine-tuning data collection.
Features
| Feature | Detail |
|---|---|
| Zero dependencies | Pure Python stdlib — no third-party packages required |
| 3 storage backends | In-memory · JSON files · SQLite |
| Pluggable | Bring your own backend by subclassing BaseStorage |
| Rich message model | role, content, tokens, model, latency, status, metadata |
| Flexible API | Direct calls · context manager · decorator |
| Export formats | JSON · JSONL · CSV · Markdown · plain text |
| Env-variable config | All settings overridable via SHS_* env vars |
| Thread-safe | All backends use locking where required |
| Pagination + search | Full-text search across message content |
Installation
# From source
pip install .
# For development (includes pytest)
pip install -e ".[dev]"
Quick Start
from session_history_saver import SessionTracker
# SQLite backend (persists across restarts)
tracker = SessionTracker(backend="sqlite", db_path="./history.db")
# Start a session
session = tracker.start_session(user_id="alice", project_id="my-chatbot")
# Log messages
tracker.add_user_message(session.session_id, "Hello!")
tracker.add_assistant_message(
session.session_id,
"Hi! How can I help?",
tokens=12, model="claude-3-sonnet", latency_ms=320.0
)
# End and persist
tracker.end_session(session.session_id)
Backends
In-Memory (testing / short-lived scripts)
tracker = SessionTracker(backend="memory")
Data lives only for the process lifetime. Ideal for unit tests.
JSON files
tracker = SessionTracker(backend="json", storage_dir="./sessions")
# Each session → ./sessions/<session_id>.json
Human-readable, portable, no database setup needed.
SQLite (recommended for production)
tracker = SessionTracker(backend="sqlite", db_path="./history.db")
Efficient, searchable, WAL-mode enabled for concurrent writes.
Custom backend
from session_history_saver import BaseStorage
class MyRedisStorage(BaseStorage):
def save_session(self, session): ...
def load_session(self, session_id): ...
def delete_session(self, session_id): ...
def list_sessions(self, **kwargs): ...
def session_exists(self, session_id): ...
tracker = SessionTracker(custom_storage=MyRedisStorage())
Usage Patterns
1 — Direct API
session = tracker.start_session(
user_id="alice",
project_id="support-bot",
title="Billing query",
tags=["billing", "priority"],
metadata={"channel": "web"},
)
tracker.add_message(session.session_id, role="user", content="I was double-charged.")
tracker.add_message(
session.session_id,
role="assistant",
content="I can see the duplicate charge. Let me fix that.",
tokens=18, model="claude-3-sonnet", latency_ms=410
)
tracker.end_session(session.session_id)
2 — Context manager
with tracker.track_session(user_id="bob", project_id="onboarding") as s:
tracker.add_user_message(s.session_id, "How do I get started?")
tracker.add_assistant_message(s.session_id, "Welcome! Let's begin with setup.")
# session is automatically ended on exit
3 — Decorator (wraps any function)
@tracker.track(project_id="search-service")
def run_query(question: str) -> str:
return llm.generate(question) # your LLM call here
answer = run_query("What is quantum entanglement?")
# First positional str arg → user message
# Return value → assistant message
Message Parameters
| Parameter | Type | Description |
|---|---|---|
role |
str |
user, assistant, system, tool |
content |
str |
Message body |
tokens |
int |
Token count (for cost tracking) |
model |
str |
Model name (e.g. claude-3-sonnet) |
latency_ms |
float |
Response time in milliseconds |
status |
str |
delivered, pending, error |
metadata |
dict |
Arbitrary key-value pairs |
message_id |
str |
Override auto-generated UUID |
timestamp |
datetime |
Override auto-generated UTC time |
Retrieval
# Get a single session
session = tracker.get_session(session_id)
# Get all messages
messages = tracker.get_messages(session_id)
# List with filters
sessions = tracker.list_sessions(
user_id="alice",
project_id="support-bot",
tags=["billing"],
limit=50,
offset=0,
)
# Full-text search across message content
results = tracker.search("double-charged", limit=20)
# Stats — global
print(tracker.stats())
# {'total_sessions': 42, 'total_messages': 310, 'total_tokens': 18200, 'active_sessions': 1}
# Stats — single session
print(tracker.stats(session_id))
# {'message_count': 4, 'total_tokens': 56, 'duration_seconds': 12.3, ...}
Export
from session_history_saver import to_json, to_csv, to_jsonl, to_markdown, to_plain_text, save_to_file
session = tracker.get_session(session_id)
# JSON string
json_str = to_json(session)
# JSONL (batch of sessions — great for fine-tuning datasets)
jsonl_str = to_jsonl(tracker.list_sessions(project_id="my-app"))
save_to_file(jsonl_str, "./finetune_data.jsonl")
# CSV
csv_str = to_csv([session])
save_to_file(csv_str, "./export.csv")
# Markdown (human-readable audit trail)
md = to_markdown(session, include_metadata=True)
save_to_file(md, "./session_report.md")
# Plain text (one line per turn)
print(to_plain_text(session))
Configuration
Via constructor
tracker = SessionTracker(
backend="sqlite", # memory | json | sqlite
db_path="./history.db", # SQLite path
storage_dir="./sessions", # JSON directory
auto_save=True, # persist on every add_message
default_user_id="system", # fallback user
default_project_id="prod", # fallback project
)
Via SessionHistoryConfig + environment variables
export SHS_BACKEND=sqlite
export SHS_DB_PATH=/var/data/history.db
export SHS_DEFAULT_PROJECT=my-app
export SHS_AUTO_SAVE=true
from session_history_saver import SessionHistoryConfig, SessionTracker
cfg = SessionHistoryConfig() # reads env vars automatically
tracker = SessionTracker(**cfg.to_tracker_kwargs())
Environment variable reference
| Variable | Default | Description |
|---|---|---|
SHS_BACKEND |
sqlite |
Storage backend |
SHS_DB_PATH |
./session_history.db |
SQLite DB path |
SHS_STORAGE_DIR |
./session_history |
JSON file directory |
SHS_AUTO_SAVE |
true |
Auto-persist on every message |
SHS_DEFAULT_USER |
— | Fallback user ID |
SHS_DEFAULT_PROJECT |
— | Fallback project ID |
Running Tests
pip install -e ".[dev]"
pytest tests/ -v --tb=short
Project Structure
session_history_saver/
├── session_history_saver/
│ ├── __init__.py # Public API
│ ├── models.py # Message, Session data models
│ ├── storage.py # InMemoryStorage, JSONStorage, SQLiteStorage
│ ├── tracker.py # SessionTracker — main interface
│ ├── exporters.py # to_json, to_csv, to_markdown, …
│ └── config.py # SessionHistoryConfig
├── tests/
│ └── test_session_history.py
├── examples/
│ └── usage.py
├── pyproject.toml
└── README.md
License
MIT
Project details
Release history Release notifications | RSS feed
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 session_history_saver-1.0.0.tar.gz.
File metadata
- Download URL: session_history_saver-1.0.0.tar.gz
- Upload date:
- Size: 22.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29ddcdb87c42c0cd3b75049a699546168fac0a4fc6fae9aff4a0b6e5de0cb546
|
|
| MD5 |
ef798bf62bc1b4a65da178af02c3531e
|
|
| BLAKE2b-256 |
bccc4b1563e0738fc0afec2f87ff096865059fa308e635e1a8c745d960a1f3ea
|
File details
Details for the file session_history_saver-1.0.0-py3-none-any.whl.
File metadata
- Download URL: session_history_saver-1.0.0-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a282a74d85723ddd73cbd7c572faef1d9de0c4e706172089fdc5c8412670ca7a
|
|
| MD5 |
f66c521acb4a5c7afd354460cd6339e7
|
|
| BLAKE2b-256 |
766ca05d6514d1e3cd6306756cb9d0e0ecfbfbf47e603528c4e3b92ce4ba9668
|