Session Persistence
PraisonAI Agents provides automatic session persistence with zero configuration. Simply provide asession_id to your Agent and conversation history is automatically saved and restored.
Released in PR #1709 — Earlier versions could silently drop the latest messages when
update_session_metadata() ran concurrently with add_message() across processes or store instances. Upgrade to pick up the fix.Quick Start
How It Works
When you provide asession_id to an Agent:
- Automatic Persistence: Conversation history is automatically saved to disk after each message
- Automatic Restoration: When a new Agent is created with the same
session_id, history is restored - Zero Configuration: No database setup required - uses JSON files by default
Default Storage Location
Sessions are stored in:~/.praisonai/sessions/{session_id}.json
Behavior Matrix
| Scenario | Behavior |
|---|---|
session_id provided, no DB | JSON persistence (automatic) |
session_id provided, with DB | DB adapter used |
No session_id, same Agent instance | In-memory only |
No session_id, new Agent instance | No history |
In-Memory Memory (Default)
Even withoutsession_id, the same Agent instance remembers previous messages:
In-memory memory is lost when the Agent instance is garbage collected or the process ends.
Use
session_id for persistence across processes.Persistent Sessions
Basic Usage
Resuming Sessions
Session File Format
Sessions are stored as JSON files with automatic metadata tracking:Session Metadata Fields
The following metadata is automatically populated after each assistant turn:| Field | Type | Description |
|---|---|---|
model | string | LLM model used in the session |
total_tokens | int | Cumulative input+output tokens |
cost | float | Estimated USD cost |
agent_id | string | Gateway or registry agent id |
source | string | Origin: chat, gateway, cli, api |
agent_name | string | Human-readable agent name |
How metadata is populated
After every assistant turn,praisonaiagents/agent/memory_mixin.py::_persist_session_stats() calls store.update_session_metadata(session_id, model=..., total_tokens=..., cost=..., source=..., agent_id=...). You normally don’t call this directly — but you can call it to record custom metadata on a session:
Multi-Process Safety
The session store is safe under concurrent multi-process and multi-instance use:- File locking —
fcntl.flock()on Unix,msvcrt.locking()on Windows - Atomic writes — temp file +
os.replace()prevents partial-write corruption - Reload under lock — every mutator (
add_message,set_agent_info,set_gateway_info,clear_session,update_session_metadata) reloads the session from disk inside theFileLockbefore mutating, so two processes sharing the same session directory cannot drop each other’s messages.
The reload-under-lock guarantee was completed in PraisonAI PR #1709 (metadata) and PR #1724
(agent info, gateway info, clear). Earlier releases could silently drop messages if
set_agent_info / clear_session / set_gateway_info raced with add_message across
two DefaultSessionStore instances pointed at the same directory.- Atomic writes:
add_messageand friends reload underFileLock, mutate, then atomically write (temp file + rename) so concurrent writers cannot corrupt a session file. - Fresh reads:
get_chat_history,get_session, andget_sessions_by_agentreload from disk underFileLockon every call and refresh the in-process cache. Two store instances pointing at the samesession_dirwill always see each other’s writes. - Cross-platform locks:
fcntl.flockon Unix/macOS,msvcrt.lockingon Windows.
add_message(), add_user_message(), add_assistant_message(), clear_session(), update_session_metadata(), set_agent_info(), and delete_session() — acquire a cross-process file lock, reload the session from disk inside the lock, mutate it, and write atomically via a temp file + os.replace(). It is safe to share a session directory across multiple workers, containers, or store instances; concurrent metadata updates and message appends from different workers will both be preserved.
If the lock cannot be acquired within lock_timeout (default 5.0s), the operation raises IOError — previously this was silent. Increase lock_timeout for slow / network filesystems:
Multiple processes (e.g. a gateway worker and a bot worker, or multiple uvicorn workers) can safely share the same
session_dir. Each call to get_chat_history returns the latest committed state on disk — there is no stale-cache window.store.invalidate_cache(session_id) still exists for backwards compatibility, but since reads always reload from disk it is effectively a no-op on the read path. You no longer need to call it before get_chat_history / get_session.HierarchicalSessionStore, which adds extended-field preservation (parent_id, children_ids, snapshots, forked_from_message_id) across all mutators as of PR #1745.
Direct Session Store Access
For advanced use cases, you can access the session store directly:Custom Session Directory
Using with DB Adapter
When a DB adapter is provided, it takes precedence over JSON persistence. TheDbSessionAdapter now persists both messages and metadata to the conversation store, ensuring session metadata survives process restarts.
For DB-backed sessions,
clear_session() and delete_session() now purge persisted messages from the database via the conversation store’s delete_messages() method, ensuring that cleared history does not reappear after restarts.When using the built-in DbSessionAdapter (via praisonai.db), both messages and metadata are automatically persisted to your database. The set_metadata() and get_metadata() methods now round-trip through the conversation store, so metadata survives process restarts without additional configuration. For complete examples, see the HostedAgent persistence guide.Context Caching
For cost optimization with Anthropic models, usecaching=True:
Bot Session Persistence
Bots now use the same session store as agents. Each user gets a persistent session that survives bot restarts:Without a
store parameter, BotSessionManager falls back to in-memory-only mode for backward compatibility.Session Store Protocol
All session stores implementSessionStoreProtocol — a lightweight interface that enables swapping backends:
Learn more about building custom session stores
Best Practices
-
Use meaningful session IDs: Include user ID or context in the session ID
- Handle session limits: Default max messages is 100. Older messages are trimmed.
-
Clean up old sessions: Use
store.delete_session()to remove unused sessions, which also purges persisted DB rows. -
Use prompt caching: Enable
caching=Truefor Anthropic models to reduce costs.
API Reference
Agent Parameters
| Parameter | Type | Description |
|---|---|---|
session_id | str | Session identifier for persistence |
db | DbAdapter | Optional database adapter (overrides JSON) |
prompt_caching | bool | Enable Anthropic prompt caching |
DefaultSessionStore Methods
| Method | Description |
|---|---|
add_message(session_id, role, content, metadata) | Add a message (reload-under-lock) |
add_user_message(session_id, content) | Convenience wrapper for add_message(role="user", ...) |
add_assistant_message(session_id, content) | Convenience wrapper for add_message(role="assistant", ...) |
get_chat_history(session_id, max_messages) | Get chat history (reload-under-lock on every call) |
get_session(session_id) | Get full session data |
update_session_metadata(session_id, **fields) | Merge run stats / metadata fields. Safe concurrently across processes. Skips None values. |
set_agent_info(session_id, agent_name=None, user_id=None) | Set or update agent info (reload-under-lock). |
clear_session(session_id) | Clear all messages (reload-under-lock) |
delete_session(session_id) | Delete session completely |
list_sessions(limit) | List all sessions |
session_exists(session_id) | Check if session exists |
set_agent_info(session_id, agent_name, user_id) | Attach agent name / user id to a session (reload-under-lock) |
set_gateway_info(session_id, gateway_session_id, agent_id) | Link a session to a gateway session id and agent id |
update_session_metadata(session_id, **fields) | Merge run stats / metadata into a session; agent_id / agent_name / user_id keys also update the top-level fields |
get_by_gateway_session(gateway_session_id) | Look up a session by its gateway session id |
invalidate_cache(session_id) | Drop the in-memory cache entry; next read will hit disk |

