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:

SurfaceCommand
Bare interactive launch (TUI)praisonai -c · praisonai --session <id> · praisonai --fork --session <id> — added in PR #4912
Prompt / YAML runpraisonai run --continue "…" · praisonai run --session <id> "…" · praisonai run --fork --session <id>
Chat TUIpraisonai 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.

Diagram

Yes

No

Yes

No

🆔 You have a session id

📋 Listed by
session list?

✅ Use any sub-command
show / delete / export / resume
share / unshare

🕰️ Legacy id
pre-2026-07-17?

♻️ Best-effort
SessionManager fallback

🚫 not-found

Quick Start

bash
# List sessions for current project
praisonai session list

# List sessions across all projects
praisonai session list --all
List conversation sessions example
bash
# Start a new session
praisonai session start my-project

Commands

Start a Session

bash
praisonai session start my-project

Expected 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-project

List Sessions

bash
praisonai session list

Expected 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 UTC

The 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:

bash
praisonai session list --json
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 bare Agent(memory={"session_id": "..."}) (global default store) ✅ new
  • Sub-agent / forked child sessions still appear here (with their parent shown in the Parent column), but --continue skips 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):

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:

bash
praisonai session list --all

List sessions for a specific project:

bash
praisonai session list --project a1b2c3d4
FlagDescription
_(default — no flag)_Merges the current project’s store with the global default store, deduped by session id
--allShow sessions from all projects
--project <id>Show sessions for a specific project ID — that project’s store only
--jsonOutput 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.

bash
praisonai session rename 4f9a8c72-... "fix-auth-bug"

Expected Output:

Renamed session 4f9a8c72-... to: fix-auth-bug
ArgumentTypeRequiredDescription
session_idstrYesThe session’s opaque id (as shown in session list).
titlestrYesNew 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:

bash
praisonai session rename 4f9a8c72-... ""

Practical example — before and after:

bash
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-abc123

An 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 sessions

A 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:

bash
praisonai session rename 4f9a8c72-... "fix-auth-bug" --json
json
{"renamed": true, "session_id": "4f9a8c72-...", "title": "fix-auth-bug"}

session list displays a title using this order (_session_title()):

  1. The explicit stored metadata["title"] — set manually via session rename / --title or auto-generated after the first exchange
  2. The agent name
  3. A message snippet
  4. 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.

bash
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 / FlagTypeRequiredDescription
session_idstrYesSession 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 NintNoFork from this 0-based message index. Valid range is 0..count-1; out-of-range exits 1.
--title "..."strNoOptional 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 sessions

An 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:

bash
praisonai session fork sess_abc12345 --at-message 8 --title "cookie-path" --json
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.

bash
praisonai session resume my-project

Expected 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

bash
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)

bash
praisonai session resume my-project --transcript

Use --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.

Diagram
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 run

Search 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.

bash
praisonai session search "billing migration"
praisonai session search "billing migration" --limit 10 --window 8

It 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.

Diagram

Per-Session Usage Accounting

💬 praisonai run

🤖 LLM Call

📊 Token Collector

➕ accumulate_session_usage

💾 Session Store

🧾 1,240 in / 3,980 out · $0.0140

📋 session list: Tokens / Cost

↩️ resume rehydrates totals

Quick Start

  1. Run with a session
    bash
    praisonai run "Summarise the Q3 report" --session q3-review

    After the answer, the CLI prints a one-line footer:

    1,240 in / 3,980 out · $0.0140
  2. Check cumulative usage
    bash
    praisonai session list
    ID          Name        Status   Events   Tokens    Cost      Updated
    q3-review   q3-review   active   1        12,440    $0.0710   10:14
  3. Continue — totals keep accumulating
    bash
    praisonai run "Drill into the revenue line" --session q3-review

    Totals continue from where you left off — they do not reset.

What gets persisted

Each session stores a usage object in its metadata:

FieldTypeDefaultDescription
input_tokensint0Cumulative prompt tokens across the session.
output_tokensint0Cumulative completion tokens across the session.
cached_tokensint0Cumulative cached tokens (provider-reported).
total_tokensint0input + output total.
costfloat0.0Cumulative dollar cost rounded to 6 decimal places.
requestsint0Cumulative number of LLM interactions.

The persisted total_tokens and cost fields are exactly what praisonai usage aggregates across sessions by day, model, or project.

json
{
  "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.0140

Format: "{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

Diagram
project session storecost_tracker.get_pricingtoken_collectorpraisonai runUserproject session storecost_tracker.get_pricingtoken_collectorpraisonai runUser“Summarise the Q3 report” –session q3-reviewper-call usage (input/output/cached)price by modeldelta costmerge deltas into metadata.usageupdated cumulative totalsreset() (avoid double-counting)answer + footer “1,240 in / 3,980 out · $0.0140”

Reading usage programmatically

python
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.

bash
praisonai session show my-project

Expected 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.

bash
praisonai session show <session-id> --recap

The 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:

bash
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.

bash
praisonai session handoff <session-id>
praisonai session handoff <session-id> --json
praisonai session handoff <session-id> --copy

The 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:

bash
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

bash
praisonai session export my-project --format md
praisonai session export my-project --format json

session 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.

bash
praisonai session delete my-project

Expected Output:

Delete session my-project? [y/N]: y
✅ Deleted session: my-project

Skip the confirmation prompt with --yes / -y:

bash
praisonai session delete my-project --yes

An I/O failure is now surfaced instead of a fake success — the command exits non-zero:

❌ Failed to delete session: my-project

Export a Session

session export writes a resolved session as markdown (default) or JSON.

bash
# 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.md

Expected 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
FlagDefaultDescription
--format / -fmdExport 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-levelstandardRedaction 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.

Diagram

Sanitised Export

📝 Raw transcript

🛡️ redact_transcript
standard · strict

[redacted:secret:n]

[redacted:path:n]

✅ Safe to share

bash
# 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 strict

Every 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.

LevelAlso masksWhen 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----- blocksWide 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:

python
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):

python
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.

Diagram

Share Flow

💾 Session

🔎 session_resolver

📝 export_session
format=md, redact=True

🛡️ redact_transcript
standard · strict

📄 Self-contained HTML
~/.praisonai/shares/<hash>.html

🔗 file:// link

bash
# 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-abc123

share 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.

ArgumentTypeRequiredDescription
session_idstrYesSession ID to publish. Same id space as session list / resume / export.
FlagTypeDefaultDescription
--redact-levelstr (standard / strict)strictRedaction 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.html

JSON output — pass --json for a machine-readable link and on-disk path:

json
{
  "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 sessions

The .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]>.html

The 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.

bash
praisonai session unshare sess-abc123

unshare 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.

ArgumentTypeRequiredDescription
session_idstrYesSame id passed to share.

Expected Output (a file was removed):

✅ Unshared session: sess-abc123

Expected Output (nothing was published):

ℹ No shared transcript found for: sess-abc123

JSON output:

json
{"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. Same standard vs strict table.
  • 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.

bash
praisonai session import my-project.json

Expected Output:

✅ Imported session: my-project

Help

bash
praisonai session help

Expected 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 context

Token and Cost Tracking

Every session accumulates cumulative token usage and cost across all prompts, visible in session list and the resume panel.

Diagram

Session usage accounting

📝 praisonai run

🧠 token_collector

💾 accumulate_session_usage

📦 .praisonai/sessions/<id>.json

⚡ 1,240 in / 3,980 out · $0.0140

📋 praisonai session list

🔁 –continue rehydrates totals

After each run, a single-line footer is printed:

bash
$ praisonai run "Summarise yesterday's PRs"
... (agent output) ...
1,240 in / 3,980 out · $0.0140

The 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:

bash
$ praisonai run --continue
Session: research-bot
Model:   gpt-4o-mini
Messages restored: 12
Usage:   12,345 in / 38,902 out · $0.0140

Working 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 surfaceRestores history?Saves new messages?
Prompt mode (praisonai run "...")YesYes (unless --no-save)
YAML / file mode (praisonai run agents.yaml)YesYes (unless --no-save)
Actions mode (--output actions)YesYes (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.

bash
praisonai run --continue "Add tests for the new endpoint"

See Run for complete session continuity documentation.

Using Sessions with Prompts

Continue a Conversation

bash
# 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 learning

Expected 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

bash
# 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 --planning

Use Cases

Project-Based Conversations

bash
# 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-redesign

Learning Sessions

bash
# 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-rust

Code Review Sessions

bash
# 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-123

Auto-Save Sessions

Automatically save sessions after each agent run using the --auto-save flag:

bash
# 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-project

Python API

python
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 automatically

History in Context

Load conversation history from previous sessions into the current context:

bash
# Load history from last 5 sessions
praisonai "Continue our discussion" --history 5

Python API

python
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:

python
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.json

Project-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.

Diagram

Project Detection

Found

Not Found

📁 Current Directory

🔍 Git Root?

📂 Git Repository Root

📂 Use Current Directory

🔑 SHA256 Hash

🆔 Project ID
abc12345

💾 Session Directory
~/.praisonai/sessions/projects/abc12345/

Project identification:

  • Project ID: First 8 characters of SHA256 hash of project root path
  • Git detection: Uses git rev-parse --show-toplevel with 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.json

Session Storage

Sessions are stored in a project-scoped layout when using the default behavior:

~/.praisonai/sessions/projects/{project_id}/
└── {session_id}.json

With 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.json

Storage Backend Options

Store sessions in different backends for production deployments:

bash
# 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
BackendBest For
fileDevelopment, debugging
sqliteProduction, concurrent access
redis://urlDistributed 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.

bash
# 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:

FieldMerge strategy
messagesUnion, deduped by (role, content, timestamp); on-disk order preserved, new messages appended
metadataDict merge, incoming wins on key conflict
total_input_tokens / total_output_tokens / total_cost / request_countmax(on_disk, incoming)
current_modelIncoming if set, else on-disk
updated_atmax(on_disk, incoming)

Lost-Update Prevention

Diagram
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 file

praisonai 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.

Diagram
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-commandStore path
list / resume / --continueproject-scoped + global DefaultSessionStore
searchsame canonical stores via canonical_cli_stores(), indexed with SqliteSessionStore (FTS5/bm25), deduped by session_id
show / delete / exportsame project-scoped + global DefaultSessionStore (via session_resolver)
share / unsharesame 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 idbest-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.

PlatformFile lockingNotes
Linux / macOSfcntl.flock()Exclusive on write, shared on read
WindowsWhole-file lock (max(file_size, 1) bytes) — matches Unix fcntl.flock semanticsBlocking exclusive on write, shared (LK_RLCK) on read
Other (Pyodide, minimal embedded CPython)None — logs a one-time warningSingle-process is safe; concurrent writers may corrupt session files
Diagram
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 file

On 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:

bash
# Windows (PowerShell)
praisonai session start my-project
praisonai "Analyze this file" --session my-project

# Same commands work identically on macOS / Linux

Best Practices

Related