session_id on your agent and conversation history is saved and restored automatically — no database setup required.
The SQLite transcript default introduced in PraisonAI PR #3409 is gateway-only. The single-user code paths on this page are unaffected — a plain
Agent script keeps using the default JSON store. See SQLite Transcript Store for the gateway change.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.Works for AgentTeam / YAML runs too.
praisonai run agents.yaml --continue / --session <id> resumes a whole team — per-agent chat history and shared team state replay before the new prompt. See YAML / Team Session Continuity.Quick Start
1
Start with a session_id
2
Resume from the CLI
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
Assistant tool-call turns and
role="tool" result turns are round-tripped by the default JSON store — a resumed model sees the same tool history it produced before. File-format compatibility is preserved: old text-only files load unchanged. See Session Resume for the full contract.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
This path honours
PRAISONAI_HOME and is resolved live — setting PRAISONAI_HOME before the first agent turn (even after importing praisonaiagents) redirects session storage to $PRAISONAI_HOME/sessions/. See Session Store → Where sessions are stored for the full resolution order.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.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:
These fields enable cost tracking and usage analytics across sessions.
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.Write-failure salvage
When a durable session write fails, the turn is salvaged to a spill file and re-folded on the next load — nothing is silently lost. On a durable-write failure the store spills just that turn to an atomic fallback file under~/.praisonai/state/session_spill/, then fires the observe-only SESSION_PERSIST_FAILED hook so operators can alert on the failure. On the next session load the store scans the spill directory for this session’s files and re-ingests any un-persisted turns.
Key guarantees, all from
store.py:
- Atomic, private writes — temp file +
os.replace+ best-effort dirfsync, and the final file is chmod’d to0o600. - No lost turns on rapid failure — the random 4-hex suffix means consecutive same-ms/PID failures never overwrite each other.
- No duplicates on recovery — re-ingest is de-duplicated on
(role, content, timestamp), so an already-persisted turn is never doubled. - Retention still applies — recovered turns run through
_enforce_windowbefore write-back, so recovery respectsmax_messages/active_window/retentionlike any ordinary write. - One bad file can’t block the rest — malformed spills (non-object root / non-list
messages/ non-object message) are skipped, not fatal. - Retry-safe — spill files are deleted only after a successful re-insert; if the re-ingest write itself fails, spills stay in place for the next load to retry.
Agent params, no new dependencies, and no behaviour change on the happy path — subscribe to the hook to observe the failure:
Corruption-quarantine on load
If a session.json is unreadable at load time (malformed JSON or invalid UTF-8), the store moves it aside to <file>.json.corrupt-<epoch_ms> before starting a fresh session — the raw bytes stay recoverable instead of being silently overwritten by the next write.
Before this fix, the next write clobbered the corrupt file with an empty session and the user’s entire conversation history was gone with only a log line. Now nothing is destroyed: a corrupt copy is always preserved, and the event is observable — not just logged.
What triggers it
Ajson.JSONDecodeError or a UnicodeDecodeError on the on-disk session file quarantines the bytes and starts fresh. The same contract applies to both DefaultSessionStore and the HierarchicalSessionStore (ExtendedSessionData) override — a corrupt binary file is quarantined in either store rather than propagating.
A transient
OSError on read (permission errors, NFS/network blips, antivirus locks, disk-full-during-read) is re-raised, not quarantined. The caller aborts the pending write, so a healthy on-disk copy is never destroyed by a flaky read. json.JSONDecodeError is a subclass of ValueError, not OSError, so genuine corruption never falls into this branch.Where the quarantine lands
The quarantined file sits next to the original in the same session directory (whatever store is configured — default~/.praisonai/sessions/…).
How to observe it
Subscribe toSESSION_PERSIST_FAILED; the corruption branch reuses the existing payload with distinguishing semantics.
The
"corrupt session file:" prefix on error distinguishes a corrupt read from the older write-failure branch, which uses different error text and carries a spill path in spilled.
How to recover manually
Inspect the.json.corrupt-<ts> file with any JSON tool, hex editor, or jq, hand-repair the malformed segment (or extract salvageable turns), then either drop the repaired content back into place at <file>.json or import it via the existing session-import path.
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
Prompt caching is opt-in viacaching=CachingConfig(prompt_caching=True):
cache_control breakpoints on the system block and stable history prefix. OpenAI and Gemini cache the prefix automatically when it is byte-stable, so no wire change is needed there.
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:
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
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:
Recreating agents per request is safe: locks no longer share stale
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
Built-in defaults from
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
The global default store is a lazy singleton keyed on the resolved session directory. ChangingPRAISONAI_HOME mid-process changes that directory, which rebuilds the singleton and re-reads these env vars as part of constructing the new store (PraisonAI PR #4125). Changing only the retention/active-window vars — with no directory change — has no effect on an already-built store; those values stay pinned to first construction. Set all three at process start for deterministic behaviour.
Constructor Parameters
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( memory=MemoryConfig( 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
DefaultSessionStore Methods
When you use
SqliteSessionStore instead of the default JSON store, get_by_gateway_session() and list_sessions_by_gateway_agent() become indexed lookups against the session_route table (Issue #2956) — routing latency stays flat as sessions accumulate. See Session Store → SqliteSessionStore.Related
Session Protocol
Build custom session backends
Bot Session Compaction
Summarise older bot turns instead of dropping them
Hook Events
Subscribe to
SESSION_PERSIST_FAILED to alert on write failures and corrupt reads
