> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversations, Projects & Search

> Organize chats into projects, search everything, fork, and delete turns

Every conversation is an append-only transcript you can group into projects, search full-text, fork from any turn, or trim a message at a time.

```python theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
from praisonaiagents import Agent

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
)
# Each Desktop conversation is one transcript this agent appends to.
agent.start("Start a new conversation")
```

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Chats[💬 Conversations] --> Projects[📁 Projects]
    Chats --> Search[🔍 ⌘K Search]
    Chats --> Fork[🌿 Fork]

    classDef chats fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef proj fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef search fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef fork fill:#10B981,stroke:#7C90A0,color:#fff

    class Chats chats
    class Projects proj
    class Search search
    class Fork fork
```

## Quick Start

<Steps>
  <Step title="Group into a project">
    Right-click a conversation and choose **Move to project**. An in-app text prompt appears — type a project name (or leave it blank to remove the chat from its current project) and press **OK**. Press **Esc** or click outside to cancel without changing anything. If the prompt never appears on an older build, see [Troubleshooting](/docs/features/desktop/troubleshooting).
  </Step>

  <Step title="Search everything">
    Press `⌘K` and type — the app searches every conversation's text and jumps to the first match.
  </Step>

  <Step title="Fork from a turn">
    Fork any turn to branch a new conversation without changing the original.
  </Step>
</Steps>

The **Move to project** prompt routes on what you type:

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Right[🖱️ Right-click chat] --> Menu[📋 Move to project]
    Menu --> Ask[📝 In-app text prompt]
    Ask -->|Name entered| Post[📨 POST /project/{cid}]
    Ask -->|Blank| Remove[🧹 Remove from project]
    Ask -->|Cancel / Esc| None[⛔ No request]

    classDef action fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef ui fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef ok fill:#10B981,stroke:#7C90A0,color:#fff
    classDef warn fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Right,Menu action
    class Ask ui
    class Post,Remove ok
    class None warn
```

***

## How It Works

Transcripts are append-only JSON on disk, written with a write-then-rename pattern so a crash mid-write can never truncate an existing chat.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Turn[💬 Completed turn] --> Tmp[(chat.tmp)]
    Tmp --> Rename[rename → chat.json]
    Rename --> Store[(📁 chats/)]

    classDef turn fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef tmp fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef store fill:#10B981,stroke:#7C90A0,color:#fff

    class Turn turn
    class Tmp,Rename tmp
    class Store store
```

Transcripts live at `~/Library/Application Support/PraisonAI/chats/` as one JSON file per conversation.

<Note>
  A transcript is written only after a run completes — never per token — so a chat app never burns through an SSD's write endurance.
</Note>

***

## Reopening a chat restores history

The transcript you see on screen and the history the model reads are two different things — reopening a chat now aligns them.

* The transcript on disk is what the sidebar renders.
* The agent's in-memory `chat_history` is what the model actually sees.
* Reopen a chat after a restart, a settings change, or toggling tools, and the transcript is replayed into the fresh agent so the model has the same context you can read on screen.
* Blank or whitespace-only assistant messages (left behind by failed turns) are skipped, so the model is never taught that silence is a valid answer.
* A mid-session agent already holds those turns and is not replayed over — nothing is duplicated.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Disk[(💾 Transcript)] --> Reopen[🔁 Reopen chat]
    Reopen --> Agent[🤖 Fresh Agent]
    Agent --> Model[🧠 Model sees history]

    classDef store fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef action fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef agent fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class Disk store
    class Reopen action
    class Agent agent
    class Model result
```

<Note>
  Replay happens only when the cached agent has no history — reopening mid-session does not replay, so nothing is duplicated.
</Note>

***

## Actions

| Action          | Endpoint                       | Effect                                                                                  |
| --------------- | ------------------------------ | --------------------------------------------------------------------------------------- |
| Search          | `GET /search?q=`               | Full-text across every conversation                                                     |
| Fork            | `POST /fork/{cid}/{idx}`       | Copies the transcript up to message N into a new chat                                   |
| Delete message  | `DELETE /messages/{cid}/{idx}` | Removes one exchange (a user turn and its reply)                                        |
| Move to project | `POST /project/{cid}`          | Assigns the conversation to a project; a blank name removes it from its current project |
| Delete chat     | `DELETE /chats/{cid}`          | Removes the whole conversation                                                          |

<Warning>
  A corrupt transcript is surfaced in the sidebar as "(unreadable)" rather than hidden — a chat you can see is recoverable; one silently omitted looks like data loss. "Corrupt" now includes any file in `chats/` that is not a JSON object — for example, the JSON array the app's own **Export** produces. Such a file used to drop `/chats`, `/projects`, and `/search`; it now surfaces as an `(unreadable)` row like any other broken transcript. When a chat like this **can't be deleted** for the same on-disk reasons, see [Deleting a conversation](#deleting-a-conversation) below. See [Data & Privacy](/docs/features/desktop/data) for the on-disk shape and [Troubleshooting](/docs/features/desktop/troubleshooting) for recovery.
</Warning>

***

## Deleting a conversation

The app now tells the truth about a delete: the screen follows what the engine actually did on disk, not the click.

Answering `200 OK` for every case was how a chat closed on screen and reappeared on refresh — the sidebar delete and Clear-all now check the engine's answer first.

* **The sidebar delete follows the server's answer.** If the engine refuses the delete — a read-only data dir, a permission-changed folder, or a synced folder mid-conflict — the row stays, the open transcript stays, and a toast reads **"Could not delete that conversation."** On success, the transcript is cleared only if the deleted chat was the active one; the sidebar refreshes either way.
* **Clear-all tolerates partial failure.** Every conversation that couldn't be deleted is counted, and a toast reads **"Could not delete N conversation(s)."** The active transcript is blanked when its own delete succeeded, even if another one failed — the transcript on screen follows disk state, not the batch result.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Delete[🗑️ Delete chat] --> Engine{🧠 Engine deleted it?}
    Engine -->|Yes| Cleared[✅ Transcript cleared if active]
    Engine -->|No| Kept[⚠️ Row + transcript kept]
    Kept --> Toast[🔔 Could not delete toast]

    classDef action fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef engine fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef ok fill:#10B981,stroke:#7C90A0,color:#fff
    classDef warn fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Delete action
    class Engine engine
    class Cleared ok
    class Kept,Toast warn
```

The raw response codes behind these paths — `200`, `400`, `500` — are in [Engine API](/docs/features/desktop/api).

***

## Ranked `⌘K` search

`⌘K` search delegates to the library's `SqliteSessionStore` (FTS5/bm25) over your on-disk chats instead of a substring scan.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Query[💬 ⌘K query] --> Index[🗃️ In-memory index]
    Index --> Rank{🎯 FTS5 + bm25}
    Rank --> Results[📊 Ranked + snippets]

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

    class Query query
    class Index store
    class Rank rank
    class Results result
```

* **Ranked** by FTS5/bm25 relevance instead of directory order.
* Each result carries a **snippet** centred on the match.
* The index is **rebuilt in-memory per query**, so an edited chat is never served stale.
* Falls back to the old **substring scan** if `SqliteSessionStore` is unavailable, so search never breaks.

<Note>
  Desktop chats persist their id under `id`, not `session_id`, so the engine backfills each chat's id from its filename before indexing — every chat is searchable and addressable independently.
</Note>

The same engine powers the terminal — see [Session Search](/docs/cli/session-search).

***

## Best Practices

<AccordionGroup>
  <Accordion title="Fork instead of editing history">
    A fork copies the transcript up to a chosen turn into a new conversation and leaves the original untouched — branch an idea without losing the thread.
  </Accordion>

  <Accordion title="Group with projects">
    Move related conversations into a project so the sidebar stays legible as chats accumulate.
  </Accordion>

  <Accordion title="Trust auto-titling">
    With `auto_title` on, the first message becomes the chat title. Leave it on and rename only the few that need it.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Data & Privacy" icon="lock" href="/docs/features/desktop/data">
    Where transcripts live and how to export them
  </Card>

  <Card title="Chat & Streaming" icon="comments" href="/docs/features/desktop/chat">
    Per-turn actions like Fork and Delete
  </Card>
</CardGroup>
