session_id on your agent and conversation history is saved and restored automatically — no database setup required.
session_id; prior turns reload automatically from disk.
Upgrade if you rely on multi-process saves, gateway resume, or idempotent
save_state() — fixes landed in PRs #1709, #1897, #1972, and #2102.Quick Start
Agent Session Persistence
Deterministic Resume
As of PR #2277,praisonai session resume is a first-class restore — not a transcript display.
What is restored:
- Chat history — full conversation messages
- Model — the LLM used in the session (read from
metadata["model"], falls back tometadata["llm"]) - Agent name — the name of the agent that ran the session
praisonai session resume <id>— restores state and shows a “Session Resumed” panelpraisonai session resume <id> "<prompt>"— restores state and continues with a new promptpraisonai session resume <id> --transcript— opt-in to the old transcript-only view (panel title: “Session Transcript”)
praisonai run --continue or via the gateway/TUI are all reachable.
For the full CLI reference, see Session Command.
When context compaction is enabled, resume gets cheaper still: the compaction summary is persisted to the session and replayed on the next run, so --continue / session resume reconstructs a compacted working history (summary + retained tail) instead of the full raw transcript.
With
execution=ExecutionConfig(context_compaction=True) and a bound session_id, resume automatically uses the compacted working history. Sessions without a checkpoint resume from raw messages exactly as before. See Compacted Session Resume.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
Session expiry / cleanup. This page covers the low-level
session_id + DefaultSessionStore path used by Agent(memory={"session_id": ...}). If you instead use the high-level Session(...) wrapper, it supports session_ttl, is_expired(), time_to_expiry(), and close() — see Sessions & Remote Agents → Session Expiry & Cleanup.| 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:
Idempotent saves
Session._save_agent_chat_histories() uses set_chat_history(session_id, messages) to atomically replace the persisted history rather than appending. This means:
- Repeated
session.save_state()calls do not duplicate messages. - The per-turn
_persist_message()path and the_auto_save_session()flush share_auto_save_last_index, so each message is written exactly once.
set_chat_history fall back to add_message() with a logged warning — those stores may produce duplicates on repeated save_state() calls until they add set_chat_history.
Multi-Process Safety
The session store is safe under concurrent multi-process and multi-instance use on both reads and writes:- Atomic writes — every mutator (
add_message,set_agent_info,set_gateway_info,clear_session,update_session_metadata) reloads the session from disk insideFileLock, mutates, then atomically writes (temp file +os.replace). Concurrent writers cannot drop each other’s messages. - 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.
The
praisonai session CLI has its own session store (praisonai.cli.session.UnifiedSessionStore, separate from praisonaiagents.session.DefaultSessionStore documented above). Both stores use the same cross-platform locking strategy as of PR #1837. UnifiedSessionStore now reloads and merges under exclusive lock — shared message-prefix merge + delta-based stats merge — as of PR #1885. Updated in PR #1892: UnifiedSessionStore.save() reloads under lock and merges concurrent writes (previously it overwrote with the in-process cache, dropping messages from a second process that wrote between load and save). UnifiedSessionStore.load() always reads from disk. The Windows code path locks the entire file (max(file_size, 1) bytes via msvcrt.locking) instead of only the first byte, matching Unix fcntl.flock semantics. Concurrent writers from a TUI + --interactive session, or from two terminals sharing ~/.praisonai/sessions/, no longer drop each other’s messages. See CLI Sessions for the CLI-side details.Multiple processes (a gateway worker and a bot worker, several uvicorn workers, a CLI alongside a server) 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.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 use the same session store as agents. Each user gets a persistent session that survives bot restarts. Configure viabot.yaml:
max_history Resolution
When a bot starts, it resolvesmax_history using this precedence ladder:
Bot Session Configuration
Configure these settings in yourbot.yaml under each channel:
| Setting | YAML key | Type | Default | Description |
|---|---|---|---|---|
| Per-channel history limit | max_history | int | 100 | Highest precedence. Caps messages kept per user. |
| Session-scoped history limit | session.max_history | int | 100 | Used when max_history is not set. |
| Session reset mode | session.reset.mode | string | "none" | none, idle, daily, or both. |
| Idle reset threshold | session.reset.idle_minutes | int | 60 | Minutes of inactivity before reset (when mode includes idle). |
| Daily reset hour | session.reset.at_hour | int | — | Hour (0–23) for daily reset (required when mode includes daily). |
| Compaction enabled | session.compaction.enabled | bool | false | Master switch for summarising older turns. |
| Compaction strategy | session.compaction.strategy | string | "summarize" | truncate, sliding, summarize, smart, prune, llm_summarize. |
| Compaction message threshold | session.compaction.max_messages | int | 100 | Approximate threshold (converted to a token budget). |
| Compaction token budget | session.compaction.max_tokens | int | — | Optional explicit budget. Overrides max_messages. |
| Recent tail kept verbatim | session.compaction.keep_recent | int | 10 | Most-recent messages kept un-summarised. |
When compaction is enabled,
max_history becomes a hard upper bound (max_history × 4) instead of the primary trim mechanism. See Bot Session Compaction for the full flow.max_history at the channel level takes precedence over session.max_history. Use session.max_history for new configs — it is the preferred form.Session Reset Policies
| Mode | Behavior |
|---|---|
none | Sessions never auto-reset (default). |
idle | Resets after idle_minutes of inactivity. |
daily | Resets once per day at at_hour (0–23). |
both | Resets on idle or on daily schedule, whichever comes first. |
Without a
store parameter, BotSessionManager falls back to in-memory-only mode for backward compatibility.Bounded lock caches
Long-running bots keep concurrency safe without unbounded memory growth:| Lock type | Scope | Cleanup |
|---|---|---|
| Agent locks | One lock per agent instance | Tied to agent lifetime — auto-removed when the agent is garbage-collected |
| Per-user locks | Debounce, session, and run-control paths | Bounded LRU cache (max 10,000 entries, 1 hour TTL) — idle entries evict automatically |
id(agent) keys across unrelated users.
Session Store Protocol
All session stores implementSessionStoreProtocol — a lightweight interface that enables swapping backends:
Retention Policies
Long conversations stay safe — older turns are summarised and archived, not silently dropped.archived_messages field.
The Three Policies
| Policy | Behaviour |
|---|---|
"compact" (default) | Summarise the oldest overflow turns into one synthetic summary message and archive the raw turns in archived_messages — nothing is silently dropped. |
"truncate" | Legacy behaviour — hard-slice to the tail; older turns are permanently dropped. |
"keep_all" | Never trim the active window (unbounded history). |
store.py: DEFAULT_RETENTION = "compact" and DEFAULT_MAX_MESSAGES = 100.
What Non-Destructive Means
Overflow becomes one summary message plus a raw archive — the active window stays small while the full record survives.Choosing a Policy
Pick the policy that matches what you care about most.Configuration
Configure retention three ways — from zero-code env vars up to the constructor. Level 1 — env vars (zero code):Environment Variables
Both env vars are read only when the global default store is first constructed (lazy singleton). Setting them mid-process has no effect.| Env var | Type | Values | Effect |
|---|---|---|---|
PRAISONAI_SESSION_RETENTION | string | compact | truncate | keep_all | Overrides the global default store’s retention policy. Invalid values are logged and the default (compact) is used. |
PRAISONAI_SESSION_ACTIVE_WINDOW | int | any positive int | Overrides the number of recent turns kept live in messages. Non-int values are logged and ignored. |
Constructor Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
retention | str | None → compact (or truncate when a non-default max_messages is passed) | Overflow policy: compact, truncate, or keep_all. Invalid values raise ValueError. |
active_window | int | None → max_messages | Number of recent turns kept live in messages. Tune separately from max_messages. |
The archived_messages Field
Raw archived turns are preserved on disk in SessionData.archived_messages and loaded back into SessionData / ExtendedSessionData on subsequent reads — old session JSON without the field loads cleanly as an empty list. Once the archive crosses ARCHIVE_WARN_THRESHOLD (10_000 entries) the store logs a one-off warning (archived_messages has grown to N entries) so operators can spot runaway sessions.
Reads Return Full History
get_chat_history() no longer silently re-caps reads at the legacy 100-message tail — full stored history returns unless you pass max_messages explicitly.
Retention in Practice
A compact session summarises overflow on write and hands back summary + recent turns on resume.Retention Best Practices
Keep the default (compact) unless you have a specific reason to drop history
Keep the default (compact) unless you have a specific reason to drop history
Non-destructive rollup is the right default for
--continue, resume, and Agent(session_id=...) flows — the earliest turns survive as a summary plus a raw archive.Use keep_all only for short-lived sessions or when you compact externally
Use keep_all only for short-lived sessions or when you compact externally
keep_all never trims — archived_messages and the active window grow unbounded otherwise.Tune active_window separately from max_messages
Tune active_window separately from max_messages
A larger
active_window keeps more live context per turn but raises token cost. It defaults to max_messages when unset.Watch for the archived_messages warning in logs
Watch for the archived_messages warning in logs
The store logs once when the archive crosses
ARCHIVE_WARN_THRESHOLD (10_000) so operators can spot runaway sessions.Best Practices
Use meaningful session IDs
Use meaningful session IDs
Include user or context in the id:
f"user-{user_id}-{conversation_type}".Respect the default message limit
Respect the default message limit
Default
max_messages is 100. Overflow is summarised and archived by default (retention="compact") — see Retention Policies.Clean up unused sessions
Clean up unused sessions
Call
store.delete_session() to remove stale sessions and purge DB rows when using a DB adapter.Enable prompt caching for Anthropic
Enable prompt caching for Anthropic
Set
caching=True on the agent to reduce token cost on repeated conversations.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 (disk-fresh on every call) |
get_session(session_id) | Get full session data (disk-fresh on every call) |
set_agent_info(session_id, agent_name, user_id) | Attach agent name / user id (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 fields. Safe concurrently across processes. Skips None values. |
get_by_gateway_session(gateway_session_id) | Look up a session by its gateway session id |
get_sessions_by_agent(agent_name, limit) | List sessions belonging to an agent (each loaded disk-fresh) |
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 |
invalidate_cache(session_id) | Drop the in-memory cache entry (no-op for reads; reads always reload) |
Related
Session Protocol
Build custom session backends
Bot Session Compaction
Summarise older bot turns instead of dropping them

