Session
Manage conversation sessions for multi-turn interactions
The session command manages conversation sessions, allowing you to save, resume, and organize multi-turn interactions.
Every sub-command (list, resume, search, show, delete, export, share, unshare) and every --continue / --session <id> flag resolves the same session by the same id through a single resolver.
Where --continue / --session <id> / --fork work
The same resume primitives are surfaced on four CLI entry points; each restores model + history + tool-call context from the same project-scoped / global session stores:
| Surface | Command |
|---|---|
| Bare interactive launch (TUI) | praisonai -c · praisonai --session <id> · praisonai --fork --session <id> — added in PR #4912 |
| Prompt / YAML run | praisonai run --continue "…" · praisonai run --session <id> "…" · praisonai run --fork --session <id> |
| Chat TUI | praisonai chat --continue · praisonai chat --session <id> |
| In-TUI slash command | /continue (most recent) · /continue <id> (specific) |
All four resolve the same session by the same id through the shared resolver, so a --session <id> on any surface picks up exactly where any other surface left off.
On a TTY, praisonai session resume (no id) and praisonai run --pick open an interactive picker of recent sessions instead of requiring you to paste an id.
Quick Start
# List sessions for current project
praisonai session list
# List sessions across all projects
praisonai session list --all
# Start a new session
praisonai session start my-projectCommands
Start a Session
praisonai session start my-projectExpected Output:
🆕 Starting new session: my-project
Session created successfully!
┌─────────────────────┬────────────────────────────┐
│ Property │ Value │
├─────────────────────┼────────────────────────────┤
│ Session ID │ my-project │
│ Created │ 2024-12-16 15:30:00 │
│ Status │ active │
│ Messages │ 0 │
└─────────────────────┴────────────────────────────┘
You can now run commands with this session context.
Use: praisonai "your prompt" --session my-projectList Sessions
praisonai session listExpected Output:
Project: my-app (ID: a1b2c3d4, identity: git-remote)
ID Name Status Events Tokens Cost Parent Updated
abc12345 research-bot active 12 12,345 $0.0140 - 2026-06-29 23:48 UTC
def67890 summariser paused 3 420 $0.0004 abc12345 2026-06-29 21:11 UTCThe header line now includes an identity: tag showing which resolver was used (see Project Sessions → How It Works).
Sessions that have not yet accumulated any usage show - in the Tokens and Cost columns.
JSON output — pass --json to get machine-readable output with usage data per session:
praisonai session list --json[
{
"id": "abc12345",
"name": "research-bot",
"status": "active",
"parent_id": null,
"events": 12,
"usage": {
"input_tokens": 10000,
"output_tokens": 2345,
"cached_tokens": 0,
"total_tokens": 12345,
"cost": 0.014,
"requests": 3
},
"total_tokens": 12345,
"cost": 0.014,
"updated": "2026-06-29T23:48:00Z"
}
]What shows up
Without --all or --project, the default listing surfaces:
- Sessions created by
praisonai run(project store) ✅ - Sessions created by
chat,code, gateway, TUI, API, or a bareAgent(memory={"session_id": "..."})(global default store) ✅ new - Sub-agent / forked child sessions still appear here (with their parent shown in the Parent column), but
--continueskips them in favour of the last root session.
Passing --project <id> stays project-scoped only; --all widens to every project.
Token / Cost columns
The Tokens and Cost columns show cumulative totals across all runs for each session. Totals are persisted in session-store metadata under usage and updated automatically by praisonai run / direct prompts whenever --session is set or a default project session is active. A - is rendered when no usage has been recorded yet.
JSON output (--json):
{
"sessions": [
{
"session_id": "session-abc123",
"total_tokens": 5220,
"cost": 0.014,
"usage": {
"input_tokens": 1240,
"output_tokens": 3980,
"cached_tokens": 0,
"total_tokens": 5220,
"cost": 0.014,
"requests": 2
}
}
]
}See Cost Tracking for how per-session totals accumulate across runs. For roll-ups across sessions see Usage.
List sessions across all projects:
praisonai session list --allList sessions for a specific project:
praisonai session list --project a1b2c3d4| Flag | Description |
|---|---|
| _(default — no flag)_ | Merges the current project’s store with the global default store, deduped by session id |
--all | Show sessions from all projects |
--project <id> | Show sessions for a specific project ID — that project’s store only |
--json | Output as JSON (includes usage, total_tokens, cost, and parent_id per session) |
Rename a Session
Most sessions are already auto-titled after their first exchange (see Session Auto-Title). Use session rename to override that name or to set one before the first turn lands.
session rename gives a session a human-readable title so session list reads like a menu of tasks instead of an agent name or message snippet. The title is display-only metadata — the session id never changes. See Session Rename for the full feature page.
praisonai session rename 4f9a8c72-... "fix-auth-bug"Expected Output:
Renamed session 4f9a8c72-... to: fix-auth-bug| Argument | Type | Required | Description |
|---|---|---|---|
session_id | str | Yes | The session’s opaque id (as shown in session list). |
title | str | Yes | New human-readable title. Pass an empty/whitespace-only string to clear a previously set title. |
Clear a previously set title by passing an empty string:
praisonai session rename 4f9a8c72-... ""Practical example — before and after:
praisonai session list
# session-abc123 (no title) 2 hours ago
# session-def456 "Auth Refactor" yesterday
praisonai session rename session-abc123 "Deploy runbook draft"
# ✅ Renamed session-abc123 → "Deploy runbook draft"
praisonai session rename session-abc123 ""
# ✅ Cleared title for session-abc123An id that resolves nowhere exits 1 with the same remediation text as the other session commands:
Session not found: 4f9a8c72-...
Use 'praisonai session list' to see available sessionsA failed metadata write prints Failed to rename session: <id> and also exits 1.
JSON output — pass the global --json flag for a machine-readable result instead of the human-readable line:
praisonai session rename 4f9a8c72-... "fix-auth-bug" --json{"renamed": true, "session_id": "4f9a8c72-...", "title": "fix-auth-bug"}session list displays a title using this order (_session_title()):
- The explicit stored
metadata["title"]— set manually viasession rename/--titleor auto-generated after the first exchange - The agent name
- A message snippet
- The session id
Fork a Session
session fork copies a saved session into a new child session, keeping both timelines resumable. It mirrors praisonai run --fork but works on any saved session without entering the REPL. See Session Forking for the full feature page, including the interactive /branch command.
praisonai session fork <session_id> [--at-message N] [--title "..."]Expected Output:
Forked session: sess_abc12345 -> sess_def67890
Resume the fork with: praisonai session resume sess_def67890| Argument / Flag | Type | Required | Description |
|---|---|---|---|
session_id | str | Yes | Session to fork. Resolved project store first, then global — each store is asked session_exists(session_id), so a colliding file name in another store never selects the wrong store. |
--at-message N | int | No | Fork from this 0-based message index. Valid range is 0..count-1; out-of-range exits 1. |
--title "..." | str | No | Optional title for the forked session. |
An unknown id exits 1 with the same remediation text as the other session commands:
Session not found: sess_missing
Use 'praisonai session list' to see available sessionsAn out-of-range --at-message is rejected up front (no silent slice wrap):
--at-message 99 is out of range (session has 14 messages, valid 0..13)JSON output — pass the global --json flag for a machine-readable result:
praisonai session fork sess_abc12345 --at-message 8 --title "cookie-path" --json{
"forked": true,
"parent_id": "sess_abc12345",
"session_id": "sess_def67890",
"from_message_index": 8,
"title": "cookie-path"
}from_message_index and title are null when not passed.
Resume a Session
session resume restores chat history, model, and agent name from a previous session.
History is preserved by default via compact retention — older turns are summarised and archived rather than dropped. See Session Persistence — Retention Policies.
praisonai session resume my-projectExpected Output:
🔄 Session Resumed
┌─────────────────────────────────────────────┐
│ Session: my-project │
│ Model: gpt-4o │
│ Messages restored: 12 │
│ Usage: 10,000 in / 2,345 out · $0.0140 │
└─────────────────────────────────────────────┘
--- Restored Conversation ---
[user] Can you explain the authentication flow?
[assistant] Based on the code...The Usage: line shows cumulative tokens and cost accumulated across all previous prompts in this session. If no usage has been recorded yet, the line is omitted.
Resume and continue with a prompt
praisonai session resume my-project "Now refactor the auth module"State is rehydrated, then the prompt runs through the shared praisonai run --session <id> path. The resume panel is suppressed when a prompt is provided — the run pipeline emits the only top-level output.
Show transcript only (legacy view)
praisonai session resume my-project --transcriptUse --transcript to inspect a session without restoring state. The panel title shows “Session Transcript”.
Cross-store lookup
session resume finds a session whether it was created via praisonai run --continue (project store) or via the gateway/TUI (global store). See Storage Backends.
sequenceDiagram
participant User
participant CLI as praisonai CLI
participant PS as Project Store
participant GS as Global Store
participant Run as run pipeline
User->>CLI: session resume <id> ["prompt"]
CLI->>PS: session_exists(<id>)?
alt found in project store
PS-->>CLI: RehydratedSession
else fall back
CLI->>GS: session_exists(<id>)?
GS-->>CLI: RehydratedSession or not found
end
alt prompt provided
CLI->>Run: _run_prompt(prompt, model, session=<id>)
Run-->>User: continuation output
else no prompt
CLI-->>User: "Session Resumed" panel + last 10 messages
end
classDef cli fill:#8B0000,stroke:#7C90A0,color:#fff
classDef store fill:#189AB4,stroke:#7C90A0,color:#fff
classDef run fill:#10B981,stroke:#7C90A0,color:#fff
class User,CLI cli
class PS,GS store
class Run runSearch across sessions
session search runs a ranked full-text search over every stored transcript — the same FTS5/bm25 engine as the session_search agent tool, reachable from the terminal.
praisonai session search "billing migration"
praisonai session search "billing migration" --limit 10 --window 8It iterates the same canonical stores as resume / show / delete / export, dedupes hits by session_id across stores, and prints a table ranked by score (or JSON with --json). See Session Search for the full page.
Cost & Token Tracking
PraisonAI accumulates input/output/cached tokens and dollar cost on every session run, so you can see exactly what a conversation has spent.
Quick Start
- Run with a session
bash praisonai run "Summarise the Q3 report" --session q3-reviewAfter the answer, the CLI prints a one-line footer:
1,240 in / 3,980 out · $0.0140 - Check cumulative usage
bash praisonai session listID Name Status Events Tokens Cost Updated q3-review q3-review active 1 12,440 $0.0710 10:14 - Continue — totals keep accumulating
bash praisonai run "Drill into the revenue line" --session q3-reviewTotals continue from where you left off — they do not reset.
What gets persisted
Each session stores a usage object in its metadata:
| Field | Type | Default | Description |
|---|---|---|---|
input_tokens | int | 0 | Cumulative prompt tokens across the session. |
output_tokens | int | 0 | Cumulative completion tokens across the session. |
cached_tokens | int | 0 | Cumulative cached tokens (provider-reported). |
total_tokens | int | 0 | input + output total. |
cost | float | 0.0 | Cumulative dollar cost rounded to 6 decimal places. |
requests | int | 0 | Cumulative number of LLM interactions. |
The persisted total_tokens and cost fields are exactly what praisonai usage aggregates across sessions by day, model, or project.
{
"session_id": "q3-review",
"metadata": {
"usage": {
"input_tokens": 1240,
"output_tokens": 3980,
"cached_tokens": 0,
"total_tokens": 5220,
"cost": 0.014,
"requests": 1
},
"total_tokens": 5220,
"cost": 0.014
}
}Footer format
After each prompt run with an active session, the CLI prints:
1,240 in / 3,980 out · $0.0140Format: "{input:,} in / {output:,} out · ${cost:.4f}" — locale-formatted integers, 4-decimal cost. The footer is suppressed in --json mode but usage is still persisted.
Resume behaviour
Totals are rehydrated on --continue / --session <id> and keep accumulating — they do not reset. The resume panel shows a usage summary line:
╭─ Session Resumed: q3-review ──────────────────────────╮
│ Session: q3-review │
│ Model: gpt-4o-mini │
│ Messages restored: 3 │
│ Usage: 12,440 tokens · $0.0710 (3 requests) │
╰───────────────────────────────────────────────────────╯How it works
Reading usage programmatically
from praisonai.cli.state.project_sessions import (
read_session_usage,
format_usage_footer,
)
usage = read_session_usage("q3-review")
print(format_usage_footer(usage))
# 1,240 in / 3,980 out · $0.0140
print(f"Cumulative requests: {usage['requests']}")
print(f"Cached tokens: {usage['cached_tokens']:,}")Notes
Best-effort — never breaks a run
Any failure (pricing lookup, persistence error, missing collector) leaves the session untouched. Usage accounting never breaks a run.
Multi-model aware
When a run uses more than one model, each model’s tokens are priced individually with get_pricing(model_name).
Cached tokens tracked separately
Provider-reported cached reads are accumulated in cached_tokens but excluded from cost — the provider already discounts them.
Footer only appears with an active session
One-shot praisonai run "..." without --session runs without footer output.
Show Session Details
session show resolves against the same stores as list / resume, so any id you can list or resume is also showable.
praisonai session show my-projectExpected Output:
📊 Session Details: my-project
┌─────────────────────┬────────────────────────────┐
│ Property │ Value │
├─────────────────────┼────────────────────────────┤
│ Session ID │ my-project │
│ Agent │ Researcher │
│ Model │ gpt-4o │
│ Created │ 2024-12-16 15:30:00 │
│ Updated │ 2024-12-16 15:45:00 │
│ Messages │ 12 │
└─────────────────────┴────────────────────────────┘
Recent Messages:
────────────────────────────────────────────────────
[User] Can you explain the authentication flow?
[Agent] Based on the code, the authentication...
────────────────────────────────────────────────────
[User] How do I add OAuth support?
[Agent] To add OAuth support, you would need to...
────────────────────────────────────────────────────session show now resolves through the same shared session resolver as list / resume, so its output includes the persisted agent, model, created / updated times, and message count for the resolved session.
Show a Read-Only Recap
Render a “where were we” block instead of raw session details. Non-destructive — never modifies the session or triggers compaction.
praisonai session show <session-id> --recapThe recap prints in a Session Recap panel:
╭─ Session Recap ──────────────────────────────────────╮
│ 📌 Recap — where we were: │
│ [Previous conversation summary] │
│ User asked about deploy runbook; we outlined 1–3. │
│ Recent: │
│ • user: what about step 4? │
│ • assistant: step 4 covers rollback… │
╰──────────────────────────────────────────────────────╯Combine with the global --json flag for structured output:
praisonai session show <session-id> --recap --json
# {"session_id": "...", "recap": "📌 Recap — where we were:\n..."}This is the CLI counterpart of the /recap bot chat command — same read-only recap, surfaced from the terminal.
Hand off a Session (Continuation Prompt)
Assemble a self-contained continuation prompt from durable state — recap describes where we were; handoff is a prompt a fresh context can act on.
praisonai session handoff <session-id>
praisonai session handoff <session-id> --json
praisonai session handoff <session-id> --copyThe prompt prints in a Session Handoff panel:
╭─ Session Handoff ────────────────────────────────────╮
│ You are resuming an interrupted run. Recover from │
│ durable state; do not trust memory of a previous │
│ conversation. │
│ │
│ GOAL: Ship the logging refactor │
│ DEFINITION OF DONE: All modules use structured logging│
│ PROGRESS: 3 of 5 workflow steps done; last completed: │
│ wire logger │
│ RECENT ACTIONS (recap-derived): │
│ Recap — where we were: refactored logging in main.py │
│ NEXT: continue toward the remaining work. First verify│
│ the current state on disk before acting. │
╰───────────────────────────────────────────────────────╯Combine with the global --json flag for structured output:
praisonai session handoff <session-id> --json
# {"session_id": "...", "handoff": "...", "has_goal": true, "has_checkpoint": true}Defaults to the most recent session when no id is given. See Session Handoff for the full feature, options, and Python API.
Export a Session
praisonai session export my-project --format md
praisonai session export my-project --format jsonsession export resolves the id through the same shared resolver, so an id from session list always exports the same session — with a legacy-store fallback for legacy-only ids.
Delete a Session
Delete targets the single canonical store that owns the id — a project-scoped session delete never removes an unrelated same-id session that lives only in the global store.
praisonai session delete my-projectExpected Output:
Delete session my-project? [y/N]: y
✅ Deleted session: my-projectSkip the confirmation prompt with --yes / -y:
praisonai session delete my-project --yesAn I/O failure is now surfaced instead of a fake success — the command exits non-zero:
❌ Failed to delete session: my-projectExport a Session
session export writes a resolved session as markdown (default) or JSON.
# Print as markdown (default)
praisonai session export my-project
# Or as JSON
praisonai session export my-project --format json
# Save to a file
praisonai session export my-project --format md --output my-project.mdExpected Output (markdown):
# Session: research-bot
- **Session ID**: my-project
- **Agent**: research-bot
- **Model**: gpt-4o
- **Created**: 2024-12-16 15:30:00
- **Updated**: 2024-12-16 15:45:00
- **Messages**: 12
## Conversation
### user
Can you explain the authentication flow?
### assistant
Based on the code, the authentication...When --output is set, the CLI writes the file and prints:
✅ Exported to: my-project.md| Flag | Default | Description |
|---|---|---|
--format / -f | md | Export format — md or json |
--output / -o | _(stdout)_ | Write to a file instead of printing |
--sanitise / --sanitize | _(off)_ | Redact secrets, absolute paths, the cwd, and embedded file contents with stable [redacted:<category>:<n>] placeholders. Opt-in; default export unchanged. |
--redact-level | standard | Redaction level when --sanitise is set: standard or strict. An unknown value is rejected with an actionable error. |
Legacy-only sessions fall back to the SessionManager exporter automatically — and legacy sessions also honour --sanitise now, so a raw transcript is never leaked through the fallback (see Sanitise before sharing).
Sanitise before sharing
praisonai session export <id> --sanitise (alias --sanitize) redacts secrets, absolute file paths, the working directory, and file contents embedded in tool I/O — replacing them with stable [redacted:<category>:<n>] placeholders — so a session is safe to paste into a bug report, share with a teammate, or attach to an audit trail. The default export (no flag) is byte-for-byte unchanged; sanitisation is strictly opt-in.
# Default — raw transcript, unchanged (may leak secrets)
praisonai session export my-project --format json
# Opt-in — secrets, paths, and cwd replaced with stable placeholders
praisonai session export my-project --format json --sanitise
# Same, US spelling
praisonai session export my-project --format md --sanitize
# Broader net (bearer tokens + PEM private-key blocks)
praisonai session export my-project --sanitise --redact-level strictEvery masked span becomes [redacted:<category>:<n>], where the same source value maps to the same placeholder within one export — so the reader still follows which value recurs where, without ever seeing it.
Before: read /home/alice/project/config.yaml — api_key=sk-ABCDEF0123456789ABCDEF
After: read [redacted:path:1] — api_key=[redacted:secret:1]The same path appearing three times becomes [redacted:path:1] three times, not :1, :2, :3.
Redaction levels.
| Level | Also masks | When to use |
|---|---|---|
standard (default for export) | Process-registered secrets; OpenAI-style sk-… and Stripe sk_live_… / sk_test_… keys; Slack / GitHub / AWS / Google / JWT token shapes; key = value / key: value pairs whose key contains any of api_key, secret, token, password, access_key — so AWS_SECRET_ACCESS_KEY=… and STRIPE_SECRET_KEY=… are caught, not only keys that end in one; absolute POSIX / Windows / UNC paths; and the current working directory. Detected secret values are masked as a whole — no half-masking when a value contains /. | Almost every case — a safe default that keeps the transcript readable. |
strict (default for share) | Everything standard masks, plus Authorization: Bearer … credentials and PEM -----BEGIN … PRIVATE KEY----- blocks | Wide sharing (public gist, open bug report, external audit) where over-redacting beats leaking. session share uses this by default. |
An unknown value is rejected up front (exit 1):
$ praisonai session export my-project --sanitise --redact-level loose
❌ Invalid --redact-level 'loose'. Choose one of: standard, strict.Python API. The same redaction is available on the resolver helper — handy when scripting an export from a notebook or CI job:
from praisonai_code.cli.state.session_resolver import export_session
# Default — raw
raw = export_session("my-project", format="json")
# Opt-in — sanitised
safe = export_session("my-project", format="json", redact=True, redact_level="standard")And directly against a resolved payload (dict → dict, input never mutated):
from praisonai_code.cli.state.redact import redact_transcript, REDACT_LEVELS
safe_payload = redact_transcript(
payload, # dict from resolved.to_dict()
level="standard", # or "strict"; must be in REDACT_LEVELS
extra_secrets=["HUNTER2SECRET"], # extra literal values to mask
)Share a Session
session share publishes a redacted, read-only HTML transcript to ~/.praisonai/shares/ and returns a file:// link — no external service or dependency.
# default: strict (safe for public sharing)
praisonai session share sess-abc123
# → ✅ Shared session: sess-abc123
# → ℹ Link: file:///Users/alice/.praisonai/shares/1a2b3c4d5e6f7a8b.html
# opt down to standard if the recipient is trusted
praisonai session share sess-abc123 --redact-level standard
# Revoke — deletes the shared HTML file
praisonai session unshare sess-abc123
# → ✅ Unshared session: sess-abc123share resolves session_id through the same shared resolver as list / resume / show / export (project store first, then global default store, then a best-effort legacy SessionManager fallback for pre-2026-07-17 ids), then calls export_session(session_id, format="md", redact=True, redact_level=<level>). Sharing is always redacted — there is intentionally no --no-redact escape hatch. The redacted Markdown is wrapped in a single self-contained HTML file (inline CSS, no external assets), with the transcript inserted via html.escape inside a <pre> block so no session text can be interpreted as markup.
| Argument | Type | Required | Description |
|---|---|---|---|
session_id | str | Yes | Session ID to publish. Same id space as session list / resume / export. |
| Flag | Type | Default | Description |
|---|---|---|---|
--redact-level | str (standard / strict) | strict | Redaction level applied before publish — same meaning as session export --sanitise. Defaults to strict because share publishes a transcript for wider distribution; pass --redact-level standard to opt down. An unknown value is rejected up front with exit 1. |
Expected Output:
✅ Shared session: sess-abc123
ℹ Link: file:///Users/alice/.praisonai/shares/1a2b3c4d5e6f7a8b.htmlJSON output — pass --json for a machine-readable link and on-disk path:
{
"session_id": "sess-abc123",
"shared": true,
"url": "file:///Users/alice/.praisonai/shares/1a2b3c4d5e6f7a8b.html",
"path": "/Users/alice/.praisonai/shares/1a2b3c4d5e6f7a8b.html"
}An id that resolves nowhere exits 1 with the same remediation text as the other session commands:
❌ Session not found: sess-abc123
Use 'praisonai session list' to see available sessionsThe .html file lands under the canonical data home alongside sessions/, so shares never split across a second home root:
~/.praisonai/
└── shares/ # NEW — created on first share
└── <sha256(id)[:16]>.htmlThe filename is sha256(id)[:16] — a stable per-session path. Re-sharing the same id overwrites the same file, and the write is atomic: the HTML is written to a sibling temp file, then os.replace() swaps it in, so a failed or interrupted write never truncates a previously published transcript. On an OSError the temp file is cleaned up and the command exits non-zero.
Unshare a Session
session unshare revokes a previously published transcript by deleting the shared HTML file.
praisonai session unshare sess-abc123unshare resolves the shared path deterministically via the same sha256(id)[:16] mapping — no session lookup — so it works even if the underlying session has since been deleted. A missing file is a clean no-op, not an error, so the command is idempotent and safe to script. A real OSError (permission denied, disk error) exits 1 with an actionable message.
| Argument | Type | Required | Description |
|---|---|---|---|
session_id | str | Yes | Same id passed to share. |
Expected Output (a file was removed):
✅ Unshared session: sess-abc123Expected Output (nothing was published):
ℹ No shared transcript found for: sess-abc123JSON output:
{"session_id": "sess-abc123", "revoked": true}revoked: false means nothing was there to revoke — not that the operation failed.
Related
- Sanitise before sharing — the underlying redactor is shared;
share=export --sanitise+ HTML wrap + atomic publish. Samestandardvsstricttable. - List sessions — how to find the id to share.
- Upstream: PraisonAI issue #3590, commits
9c27592+cdd41eb.
Import a Session
session import reads a previously-exported JSON file and prints the resulting id.
praisonai session import my-project.jsonExpected Output:
✅ Imported session: my-projectHelp
praisonai session helpExpected Output:
Session Commands:
praisonai session start <name> - Start a new session
praisonai session list - List all sessions
praisonai session resume <id> [prompt] - Resume a session, optionally with a prompt
--transcript - Show transcript only (legacy view)
praisonai session search <query> [--limit N] [--window N] - Ranked full-text search across sessions
praisonai session show <name> [--recap] - Show session details (or a read-only recap)
praisonai session handoff <id> [--json] [--copy] - Generate a continuation prompt from durable state
praisonai session rename <id> "<title>" - Give a session a human-readable title ("" clears it)
praisonai session fork <id> [--at-message N] [--title "..."] - Fork a session into a new child timeline
praisonai session delete <name> - Delete a session
praisonai session export <name> [--format md|json] [--output FILE]
[--sanitise|--sanitize] [--redact-level standard|strict] - Export a session
praisonai session share <id> [--redact-level standard|strict] - Publish a redacted HTML transcript (file:// link); defaults to strict
praisonai session unshare <id> - Revoke a shared transcript
praisonai session import <file> - Import a session (JSON)
praisonai session help - Show this help
Using Sessions with Prompts:
praisonai "prompt" --session <name> - Run with session contextToken and Cost Tracking
Every session accumulates cumulative token usage and cost across all prompts, visible in session list and the resume panel.
After each run, a single-line footer is printed:
$ praisonai run "Summarise yesterday's PRs"
... (agent output) ...
1,240 in / 3,980 out · $0.0140The middle dot is U+00B7 (·). The footer reflects cumulative totals since session start, not just the last prompt. It is silently suppressed in JSON mode (--json / --output json) — there is no --no-usage flag.
When you resume a session, the cumulative totals are rehydrated so subsequent prompts keep accumulating:
$ praisonai run --continue
Session: research-bot
Model: gpt-4o-mini
Messages restored: 12
Usage: 12,345 in / 38,902 out · $0.0140Working with praisonai run
The same project-scoped store powers --continue and --session on praisonai run. As of PR #1963, every surface restores history from and saves to this store:
praisonai run surface | Restores history? | Saves new messages? |
|---|---|---|
Prompt mode (praisonai run "...") | Yes | Yes (unless --no-save) |
YAML / file mode (praisonai run agents.yaml) | Yes | Yes (unless --no-save) |
Actions mode (--output actions) | Yes | Yes (unless --no-save) |
As of the fix for issue #2700, the actions-mode row is fully honoured for --auto-save, --session, --continue, and --fork. If you hit a TypeError about auto_save on an older praisonai-code build, upgrade and retry — see the run.mdx troubleshooting section.
As of PR #2277, --session <id> and --continue now persist model and agent_name into session metadata so a later session resume reproduces the same configuration deterministically. For advanced programmatic use, the rehydrate_session helper in praisonai.cli.session returns a RehydratedSession with session_id, chat_history, model, agent_name, metadata, and found fields — see the SDK reference for details.
praisonai run --continue "Add tests for the new endpoint"See Run for complete session continuity documentation.
Using Sessions with Prompts
Continue a Conversation
# First message
praisonai "What is Python?" --session learning
# Follow-up (context preserved)
praisonai "How do I install it?" --session learning
# Another follow-up
praisonai "Show me a hello world example" --session learningExpected Output (third message):
📂 Session: learning (3 messages)
╭────────────────────────────────── Response ──────────────────────────────────╮
│ Based on our conversation about Python, here's a hello world example: │
│ │
│ ```python │
│ print("Hello, World!") │
│ ``` │
│ │
│ After installing Python as we discussed, save this to a file called │
│ `hello.py` and run it with `python hello.py` │
╰──────────────────────────────────────────────────────────────────────────────╯Session with Other Features
# Session with memory
praisonai "Remember my preferences" --session project --memory
# Session with knowledge
praisonai "Search the docs" --session project --knowledge
# Session with planning
praisonai "Plan the implementation" --session project --planningUse Cases
Project-Based Conversations
# Start project session
praisonai session start website-redesign
# Multiple conversations over time
praisonai "What's the current design?" --session website-redesign
praisonai "Suggest improvements" --session website-redesign
praisonai "Create implementation plan" --session website-redesignLearning Sessions
# Create learning session
praisonai session start learn-rust
# Progressive learning
praisonai "Explain ownership in Rust" --session learn-rust
praisonai "Show me an example" --session learn-rust
praisonai "What about borrowing?" --session learn-rustCode Review Sessions
# Start review session
praisonai session start pr-review-123
# Review conversation
praisonai "Review this PR" --session pr-review-123 --fast-context ./src
praisonai "What about security concerns?" --session pr-review-123
praisonai "Summarize the review" --session pr-review-123Auto-Save Sessions
Automatically save sessions after each agent run using the --auto-save flag:
# Auto-save session with each interaction
praisonai "Analyze this code" --auto-save my-project
# Continue the conversation (auto-saved)
praisonai "Now refactor it" --auto-save my-projectPython API
from praisonaiagents import Agent
from praisonaiagents.config import MemoryConfig
agent = Agent(
name="Assistant",
memory=MemoryConfig(auto_save="my-project") # Auto-save session after each run
)
agent.start("Analyze this code") # Session saved automaticallyHistory in Context
Load conversation history from previous sessions into the current context:
# Load history from last 5 sessions
praisonai "Continue our discussion" --history 5Python API
from praisonaiagents import Agent
agent = Agent(
name="Assistant",
memory=True,
context=True, # Enable context management for history
)
# Agent now has context from previous sessions
agent.start("What did we discuss yesterday?")Workflow Checkpoints
Save and resume workflow execution at any step:
from praisonaiagents import AgentFlowManager
manager = WorkflowManager()
# Execute with checkpoints (saves after each step)
result = manager.execute(
"deploy-workflow",
checkpoint="deploy-v1"
)
# Resume from checkpoint if interrupted
result = manager.execute(
"deploy-workflow",
resume="deploy-v1"
)
# List all checkpoints
checkpoints = manager.list_checkpoints()
# Delete a checkpoint
manager.delete_checkpoint("deploy-v1")Checkpoint Storage
~/.praisonai/
└── checkpoints/
├── deploy-v1.json
└── build-v2.jsonProject-Scoped Sessions
Sessions are automatically scoped to your current project. PraisonAI detects your project by finding the git repository root, or uses the current working directory as a fallback.
Project identification:
- Project ID: First 8 characters of SHA256 hash of project root path
- Git detection: Uses
git rev-parse --show-toplevelwith 5-second timeout - Fallback: Current working directory if not in a git repository
Storage structure:
~/.praisonai/sessions/
├── projects/
│ ├── abc12345/ # Project sessions
│ │ ├── session-def789.json
│ │ └── session-ghi012.json
│ └── xyz98765/ # Another project
│ └── session-jkl345.json
└── global/ # Legacy global sessions
├── old-session.json
└── another.jsonSession Storage
Sessions are stored in a project-scoped layout when using the default behavior:
~/.praisonai/sessions/projects/{project_id}/
└── {session_id}.jsonWith project-scoped sessions, your sessions are organized by project automatically. Legacy sessions remain accessible via the --all flag:
~/.praisonai/
└── memory/
└── praison/
└── sessions/
├── my-project.json
├── research-task.json
└── code-review.jsonStorage Backend Options
Store sessions in different backends for production deployments:
# List sessions with SQLite backend
praisonai session list --storage-backend sqlite --storage-path ~/.praisonai/sessions.db
# List sessions with Redis backend (for distributed systems)
praisonai session list --storage-backend redis://localhost:6379
# List sessions with file backend (default)
praisonai session list --storage-backend file --storage-path ~/.praisonai/sessions| Backend | Best For |
|---|---|
file | Development, debugging |
sqlite | Production, concurrent access |
redis://url | Distributed systems, shared sessions |
See Storage Backends for more details.
Concurrent Sessions
Multiple praisonai processes can safely share the same session — the CLI store reloads, merges, and writes under an exclusive lock so no messages are lost when the TUI, --interactive mode, and praisonai "…" --session all touch the same file.
# Terminal 1: keep the TUI open on a session
praisonai tui launch --session my-project
# Terminal 2: same session, ad-hoc message via --interactive
praisonai "Add a one-line summary" --interactive --session my-project
# Both messages end up in ~/.praisonai/sessions/my-project.json
# in arrival order — no silent drops.Merge Strategy
When two writers race, the session store merges their changes:
| Field | Merge strategy |
|---|---|
messages | Union, deduped by (role, content, timestamp); on-disk order preserved, new messages appended |
metadata | Dict merge, incoming wins on key conflict |
total_input_tokens / total_output_tokens / total_cost / request_count | max(on_disk, incoming) |
current_model | Incoming if set, else on-disk |
updated_at | max(on_disk, incoming) |
Lost-Update Prevention
sequenceDiagram
participant A as Process A
participant B as Process B
participant File as Session File
A->>File: Load [m1..m10]
B->>File: Load [m1..m10]
B->>B: Append m11
B->>File: Save with lock: [m1..m11]
A->>A: Append m12
A->>File: Save with lock
Note over A,File: Reloads under lock, sees [m1..m11]
Note over A,File: Merges to [m1..m11,m12]
File-->>A: Merged result saved
classDef process fill:#8B0000,stroke:#7C90A0,color:#fff
classDef file fill:#189AB4,stroke:#7C90A0,color:#fff
classDef result fill:#10B981,stroke:#7C90A0,color:#fff
classDef wait fill:#F59E0B,stroke:#7C90A0,color:#fff
class A,B process
class File filepraisonai session show and praisonai session resume always reflect the latest on-disk state — the in-process cache is invalidated automatically when another process writes (mtime-based check).
This concurrent-save safety was added in PR #1854. For the equivalent feature in the SDK-level store, see Multi-Process Safety.
How Session IDs Resolve
Every session sub-command and every --continue / --session <id> flag resolves the same session by the same id through a single session_resolver.
Session Identity
show, delete, and export now read the same project-scoped + global DefaultSessionStore that list, resume, and --continue already use — so any id you can list or resume is also showable, deletable, and exportable by the same id.
sequenceDiagram
participant User
participant CLI as praisonai session
participant Resolver as session_resolver
participant Store as DefaultSessionStore
participant Legacy as SessionManager
User->>CLI: list / resume / show / delete / export <id>
CLI->>Resolver: resolve_session(<id>)
Resolver->>Store: project-scoped then global lookup
alt id in canonical store
Store-->>Resolver: ResolvedSession
Resolver-->>CLI: found
CLI-->>User: ✅ result
else legacy id (pre-2026-07-17)
Resolver->>Legacy: best-effort fallback
Legacy-->>Resolver: ResolvedSession or not-found
Resolver-->>CLI: found / not-found
CLI-->>User: result or 🚫 not-found
end
classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
classDef resolver fill:#189AB4,stroke:#7C90A0,color:#fff
classDef result fill:#10B981,stroke:#7C90A0,color:#fff
classDef legacy fill:#F59E0B,stroke:#7C90A0,color:#fff
class User,CLI agent
class Resolver,Store resolver
class Legacy legacy| Sub-command | Store path |
|---|---|
list / resume / --continue | project-scoped + global DefaultSessionStore |
search | same canonical stores via canonical_cli_stores(), indexed with SqliteSessionStore (FTS5/bm25), deduped by session_id |
show / delete / export | same project-scoped + global DefaultSessionStore (via session_resolver) |
share / unshare | same resolver + export_session path as export --sanitise (share); unshare deletes ~/.praisonai/shares/<sha256(id)[:16]>.html with no lookup |
| any of the above, pre-2026-07-17 legacy id | best-effort SessionManager fallback (deprecation window) |
Cross-Platform Support
The praisonai session commands work on Windows, macOS, and Linux — file locking is automatic and platform-appropriate.
| Platform | File locking | Notes |
|---|---|---|
| Linux / macOS | fcntl.flock() | Exclusive on write, shared on read |
| Windows | Whole-file lock (max(file_size, 1) bytes) — matches Unix fcntl.flock semantics | Blocking exclusive on write, shared (LK_RLCK) on read |
| Other (Pyodide, minimal embedded CPython) | None — logs a one-time warning | Single-process is safe; concurrent writers may corrupt session files |
sequenceDiagram
participant CLI1 as praisonai session start foo
participant Code as praisonai code
participant Session as session file
participant CLI2 as praisonai session show foo
CLI1->>Session: Acquire write lock
CLI1->>Session: Create session data
CLI1->>Session: Release lock
Code->>Session: Acquire write lock
Code->>Session: Append conversation turn
Code->>Session: Release lock
CLI2->>Session: Acquire read lock (waits if needed)
Session-->>CLI2: Return session data
CLI2->>Session: Release lock
classDef cli fill:#8B0000,stroke:#7C90A0,color:#fff
classDef file fill:#189AB4,stroke:#7C90A0,color:#fff
classDef result fill:#10B981,stroke:#7C90A0,color:#fff
class CLI1,CLI2,Code cli
class Session fileOn Windows, sessions are stored under %USERPROFILE%\.praisonai\sessions\{session_id}.json following the OS convention via Path.home(). (Legacy pre-canonical installs may still read from %USERPROFILE%\.praison\sessions\ as a fallback — see Storage Paths.) The same directory is shared by praisonai code, praisonai run, and the gateway/TUI, so a session written by one surface is readable by the others. Cross-platform locking was added in PR #1837. Concurrent multi-process writes (e.g. TUI + praisonai --interactive sharing the same session directory) are preserved without message loss as of PR #1885 and PR #1892. For the SDK-level session store with the same cross-platform guarantees, see Session Persistence.
Example usage across platforms:
# Windows (PowerShell)
praisonai session start my-project
praisonai "Analyze this file" --session my-project
# Same commands work identically on macOS / LinuxBest Practices
Use descriptive names like project-auth-feature
Create separate sessions for different projects
CleanupDelete old sessions to free up storage
ContextStart new sessions when changing topics significantly
Related
Session flags and usage footer for praisonai run
SDK-level session management
Session ForkingFork mid-session with /branch or session fork
Per-session persistence and /cost slash command
Persisted usage shape and project scoping
UsageAggregate token and cost reporting across all sessions
Gateway Session PortabilityBack up / migrate / restore with praisonai gateway sessions export / import