Project-Scoped Sessions

Sessions are remembered per project, with cumulative token and cost tracking across all runs

python
from praisonaiagents import Agent, MemoryConfig

agent = Agent(name="project-agent", instructions="Manage project-scoped agent sessions.")
agent.start("Load my marketing-campaign project session and continue where we left off.")

The user resumes work in the same project folder; prior turns and cumulative usage load automatically.

bash
cd ~/code/my-app && praisonai run "Build a TODO app"
praisonai run --continue "Add a delete endpoint"
Diagram

Project Session Flow

๐Ÿ“ Project A

๐Ÿ—‚๏ธ Sessions A

๐Ÿ“ Project B

๐Ÿ—‚๏ธ Sessions B

๐Ÿ”„ –continue

๐Ÿ”„ –continue

๐Ÿ“Š A history only

๐Ÿ“Š B history only

Quick Start

  1. Start in your project root
    bash
    cd ~/code/my-app && praisonai run "Build a TODO app"

    Project context comes from the git repository โ€” its remote or root commit โ€” falling back to a path hash only outside a repo.

  2. Continue later
    bash
    praisonai run --continue "Add a delete endpoint"

    Prior user and assistant messages replay before your new prompt โ€” no need to summarise what happened last time.

  3. List this project's sessions
    bash
    $ praisonai session list
    Project: my-app (ID: a1b2c3d4, identity: git-remote)
    ...

    The identity: label tells you which resolver won โ€” useful when explaining why two clones share sessions or why a fresh repo has none.

    Shows every session --continue could resume for this project โ€” the project’s own sessions plus any globally-stored sessions (from chat, gateway, TUI, API, or a bare Agent( memory=MemoryConfig( session_id=..., ))). Duplicates are collapsed; the freshest record wins.


How It Works

Diagram
sequenceDiagram
    participant User
    participant CLI
    participant PS as Project session store
    participant GS as Global default store
    participant Agent

    User->>CLI: praisonai run --continue "..."
    CLI->>PS: list_sessions(limit)
    CLI->>GS: list_sessions(limit)
    Note over CLI: merge + dedup by session_id, freshest wins, prefer root sessions
    CLI-->>CLI: last root session id
    CLI->>Agent: restore history, run prompt
    Agent->>PS: append new messages
    Agent-->>User: response

    classDef cli fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef store fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class User,CLI cli
    class PS,GS store
    class Agent result

find_last_session() merges the project-scoped store and the global default store so --continue resolves the most-recent session regardless of how it was created (run, chat, gateway, TUI, API, or a bare Agent( memory=MemoryConfig( session_id=..., ))). Sub-agent / forked children are skipped โ€” the last root session wins. It falls back to a child only if the project has none. Each store is scanned with a limit window (default 50).

The CLI now follows the repository, not the folder โ€” cloning, moving, or git worktree-ing a repo keeps the same session history.

PriorityWhere the id comes fromIdentity sourceWhen it applies
1Normalised git remote URL (origin first, then any configured remote)git-remoteAny repo with a remote
2Repository root commit SHA (oldest across all refs; pinned to .git/praisonai-root-commit on first use)root-commitRepo has commits but no remote
3Cached id persisted at .git/praisonai-projectcached-idGit repo with neither remote nor commits
4Absolute-path SHA-256 hash (legacy behaviour)pathNon-git directories only

All four sources collapse to the same 8-char sha256 short hash, so the on-disk session layout is unchanged.

Diagram

No

Yes

Yes

No

Yes

No

๐Ÿ“ Where are you running praisonai?

Inside a git repo?

๐Ÿ“„ path
legacy path-hash

Has a remote?

๐ŸŒ git-remote
Follows the remote

Has commits?

๐ŸŒฑ root-commit
Oldest root, pinned in
.git/praisonai-root-commit

๐Ÿ’พ cached-id
Stored in .git/praisonai-project

What survives

Session history follows the repository, not the folder.

ScenarioSession history follows you?Why
mv myrepo mynewrepo (rename directory)โœ… YesIdentity comes from remote / root commit, not the path
Clone the same repo to ~/work/a and ~/work/bโœ… Yes (shared history)Both resolve to the same normalised remote
git worktree add ../feature-xโœ… YesWorktrees share --git-common-dir, so the same identity resolves
git checkout --orphan feature-x + first commitโœ… YesOldest root wins over the newer one; the SHA is pinned so ties never flip
Two orphan branches created in the same second (scripted setup)โœ… YesThe pin locks the choice on first resolve โ€” no tie-break to flip
Rewritten history / shallow clone where the pinned SHA is no longer a rootโš ๏ธ Re-chosen and re-pinnedThe dead pin is not trusted; the fresh choice is persisted so it stays stable from then on
pip install a project into /venvs/foo/src/myrepoโœ… YesSame remote resolves the same id
Delete .git and git init again with no remoteโš ๏ธ New identity (root commit changes)Legacy sessions are auto-migrated on first --continue
Non-git directory renamedโŒ Old behaviour: new idPath hash changes

Upgrading from an earlier CLI? Your existing project sessions are stored under a directory named after a hash of the repo’s absolute path. On the first --continue / praisonai run after upgrading, sessions are copied (not moved) into the new identity-based directory automatically. No configuration, no data loss โ€” just re-run in the same repo and prior history is there.

After upgrading, the first resolve in each git repo also writes a small .git/praisonai-root-commit file next to the existing .git/praisonai-project. Both are internal to PraisonAI, shared across all worktrees of the repo, and safe to commit or leave untracked โ€” deleting either just causes the next run to recompute and re-pin.

History restore and save wiring landed in PR #1963. If no prior session exists, a warning appears and a new session starts.

Reading the identity source programmatically:

python
from praisonai_code.cli.utils.project import (
    get_project_id,
    get_project_identity_source,
    resolve_project_identity,
)

pid = get_project_id()                     # 8-char hash โ€” unchanged public API
source = get_project_identity_source()     # "git-remote" / "root-commit" / "cached-id" / "path"
pid, source = resolve_project_identity()   # both in one call

Persisted usage shape

Every session stores a usage blob in ~/.praisonai/sessions/projects/<project_id>/<session_id>.json. Config-driven consumers and scripts can read this directly.

KeyTypeNotes
input_tokensintCumulative prompt tokens
output_tokensintCumulative completion tokens
cached_tokensintCumulative cached / prefix-hit tokens (when provider reports them)
total_tokensintinput + output (cached not double-counted); mirrored to flat total_tokens on session metadata for back-compat with list_sessions
costfloatUSD, priced via get_pricing(model); mirrored to flat cost
requestsintCount of agent.start() runs that produced usage

The flat total_tokens and cost fields at the session root are mirrors kept for backwards compatibility with any code reading the old schema.

SDK helpers (praisonaiagents 1.6.85+):

python
from praisonai.cli.state.project_sessions import (
    read_session_usage,
    format_usage_footer,
    accumulate_session_usage,
)

usage = read_session_usage(session_id="abc12345")
print(usage)
# {"input_tokens": 10000, "output_tokens": 2345, "cached_tokens": 0,
#  "total_tokens": 12345, "cost": 0.014, "requests": 3}

footer = format_usage_footer(usage)
print(footer)
# "10,000 in / 2,345 out ยท $0.0140"

updated = accumulate_session_usage(session_id="abc12345", model="gpt-4o-mini")

Configuration Options

FlagDescription
--continueResume the most recent root session for this project โ€” searches both the project store and the global default store
--session <id>Resume a specific session
--fork --session <id>Branch a session to try alternatives
--no-saveOne-off prompt โ€” nothing persisted
session list --allList sessions across all projects

Works with YAML / team runs

The same four flags apply to praisonai run agents.yaml, so an AgentTeam resumes exactly like a single-agent prompt.

Diagram
sequenceDiagram
    participant User
    participant Adapter
    participant Team as AgentTeam
    participant Store as Session store

    User->>Adapter: praisonai run agents.yaml --continue
    Adapter->>Team: restore_session_state(id)
    Team->>Team: rehydrate per-agent chat_history (deduped)
    Adapter->>Team: astart()
    Adapter->>Team: capture chat_history into state
    Adapter->>Store: save_session_state(id)
    Adapter-->>User: response

    classDef actor fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class User,Adapter actor
    class Team process
    class Store result
CommandBehavior
praisonai run agents.yaml --continueResumes the most recent root session; prior team turns replay
praisonai run agents.yaml --session <id>Resumes a specific session โ€” merges saved team._state and re-injects each agent’s chat history
praisonai run agents.yaml --fork --session <id>Forks via the hierarchical store; history is copied into the fork (deduplicated)
praisonai run agents.yaml --no-saveOne-off team run โ€” nothing is persisted after kickoff

Per-agent chat_history is captured into team state on save (under the reserved key _cli_session_chat_history) and re-injected on restore, so a resumed YAML team continues the actual conversation โ€” not just team bookkeeping.


Best Practices

Run from the project root

Start praisonai run from the repo root so git detection stays consistent โ€” subdirectories still resolve to the same project.

Use –fork for risky experiments

Try alternatives without altering the main thread: praisonai run --fork --session abc123 "try Redis instead".

Use –no-save for throwaway prompts

Quick questions or PII-sensitive input: praisonai run --no-save "How do I hash passwords?".

Clean up with session list –all

Review and delete stale sessions across projects periodically.

Clones and moves keep their history

Two clones of the same repo โ€” for example, one on your laptop and one on a build box โ€” automatically share session history because the id comes from the git remote. Renaming or moving a checkout does not change the id either.

Orphan branches don't reassign the project id

Creating an orphan branch (git checkout --orphan โ€ฆ + a first commit) adds a new root commit but does not change the project id โ€” the oldest root wins and the choice is pinned to .git/praisonai-root-commit on first resolve, so even two roots created in the same second stay stable.


Related