# agent conversation memory (using Osysharp.Agents)

> A conversation is stored as a `ChatSession` plus its `ChatMessage` turns, so an agent asked a follow-up can refer back to what was said earlier. Both ride `Osysharp.Agents` — the same capability an agent declaration already needs — so there is nothing extra to opt into. An agent stays stateless until a call names a conversation. A conversation belongs to the user who started it and is readable only by them.

<!-- id: agent-conversation-memory · area: agent · stability: preview · html: https://osysharp.com/reference/agent/conversation-memory/ -->

## Summary        {#summary}
An agent is **stateless by default**. Each turn is answered on its own: nothing from the previous exchange is in
scope unless a call names the conversation it belongs to. That is the right shape for an agent that classifies a
document or answers one question.

A conversation is stored as a **`ChatSession`** with its turns as **`ChatMessage`** rows, and an agent asked a
follow-up can see what came before. Both entities ride `Osysharp.Agents` — the capability an `agent` declaration
already requires — so a conversation needs no second import.

```osy syntax
using Osysharp.Agents;
```

## Signature      {#signature}

```osy syntax
using Osysharp.Agents;
```

Two entities enter the app's model:

| entity | what it holds |
|---|---|
| `ChatSession` | one conversation — which agent, its title, when it was last active, who it belongs to, and optionally the record it is about |
| `ChatMessage` | one turn — a user message, an assistant reply, a tool call or a tool result, in `Sequence` order |

## Description    {#description}

**Conversations are part of what the agent surface is.** They are not a second thing to import: a session and its
turns are what an agent that holds a conversation, parks for a human, or reports an outcome would otherwise
re-implement, so they ride `Osysharp.Agents` itself. Nothing about your agents changes to get them: the same agent
declaration, the same calls.

**Which conversation, if any, is decided per call.** A request that carries a `conversationId` continues that
conversation; one that does not starts a new one. An app whose agents only ever classify a document names no
conversation and its tables stay empty.

**A conversation belongs to one user.** It is stamped with the user who created it, listings return only that user's
conversations, and asking for someone else's by id answers `404` — the same answer as an id that does not exist, so
no one can probe for which conversations exist. Anonymous visitors own no conversations: an anonymous caller gets an
empty list, and creating one requires a signed-in user.

**A long conversation is summarised rather than truncated.** When the history outgrows the model's context window,
earlier turns are replaced by a summary turn, so the beginning of the conversation is still represented instead of
being dropped.

**Entity context is optional.** A conversation can record the record it is about — "ask the agent about *this*
order" — so it can be listed alongside that record rather than in one undifferentiated pile.

## Examples       {#examples}

An app whose agent holds a conversation:

```osy title="conversation-memory-optin" test app=conversation-memory-optin
using Osysharp.Agents;

[Principal] entity User {
  [MaxLength(255)] string Email;
}

entity Order {
  [Required, MaxLength(50)] string Reference;
}
```

A chat request that carries a `conversationId` continues that conversation; one that does not starts a new one.

## See also       {#see-also}
- [default LLM model (app.DefaultModel)](https://osysharp.com/reference/agent/default-model/) — the model an agent talks to.
