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

# AgentOS Chat Session Isolation

> How AgentOS /chat clones a per-request agent so concurrent callers never share chat_history

`AgentOS` clones a fresh agent for each `POST /api/chat`, so concurrent callers never share `chat_history` and the agent you passed into `AgentOS(agents=[…])` is never mutated by request traffic.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    Client[🖥️ Client] --> Chat[🌐 POST /api/chat]
    Chat --> Check{🔍 Supports<br/>session isolation?}
    Check -->|Yes| Clone[⚙️ clone template]
    Clone --> Bind[🔑 bind session_id]
    Bind --> Run[🤖 agent.achat]
    Check -->|No: mock / plain callable| Shared[🤖 shared template]
    Shared --> Run
    Run --> Response[✅ response echoes session_id]

    classDef client fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef process fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef decision fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef config fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef result fill:#10B981,stroke:#7C90A0,color:#fff

    class Client client
    class Chat,Clone,Run process
    class Check decision
    class Bind config
    class Shared client
    class Response result
```

## Quick Start

**1. Serve an Agent**

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

app = AgentOS(agents=[
    Agent(name="assistant", instructions="Be helpful"),
])
app.serve(port=8000)
```

**2. Chat With a Session**

Supply a `session_id` to continue the same conversation across calls. Omit it to make each call a fresh session.

```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
# First turn — mint a session by supplying an id
curl -s -X POST http://127.0.0.1:8000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Hi, my name is Alice.", "session_id": "alice", "agent_name": "assistant"}'

# Follow-up on the same session — the agent remembers "Alice" for this session id
curl -s -X POST http://127.0.0.1:8000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "What is my name?", "session_id": "alice", "agent_name": "assistant"}'
```

Two concurrent clients using different `session_id`s never see each other's transcripts.

***

## How It Works

Each request resolves a per-request agent, resets its transcript, binds the session, and runs one turn.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
sequenceDiagram
    participant Client
    participant AgentOS as AgentOS /chat
    participant Helper as bind_session
    participant Agent as Cloned Agent

    Client->>AgentOS: POST {message, session_id, agent_name?}
    AgentOS->>AgentOS: resolve template (name lookup or agents[0])
    AgentOS->>AgentOS: _supports_session_isolation(template)?
    alt Isolate
        AgentOS->>Helper: bind_session(clone, session_id)
        Helper->>Agent: chat_history=[]  (must not raise)
        Helper->>Agent: _session_id, _history_session_id
        Helper-->>AgentOS: bound clone
        AgentOS->>Agent: achat(message)
        Agent-->>AgentOS: reply
    else Fall back
        AgentOS->>Agent: template.achat(message)
        Agent-->>AgentOS: reply
    end
    AgentOS-->>Client: {response, agent_name, session_id}
```

The `/chat` handler and the `/invoke` router share one session-isolation contract: both call `_supports_session_isolation` → `_clone_agent` → `bind_session(agent, session_id)` from `praisonai.api.agent_invoke`. Any behaviour change to session binding lands in `bind_session()` and both surfaces pick it up together.

***

## Session Contract

| Request | Behaviour |
| - | - |
| `session_id` supplied | Bound to `agent._session_id` (and `_history_session_id` when the attribute exists). Follow-up calls with the same id continue that conversation. |
| `session_id` omitted / falsy | Both cleared to `None` on the clone — each such call is a fresh session, not a sticky default. |
| Clone reset raises | HTTP 500 — the request is refused rather than delivering a clone that still holds the template's transcript. |
| Any request | The response echoes the supplied `session_id` back verbatim. |

The response model is unchanged: `{response, agent_name, session_id}`. Only the internal binding of `session_id` and the concurrency semantics changed.

> [!NOTE]
>
> Agents configured with `handoffs=[…]` are cloned per request too. Each `handoffs=[...]` tool closure is tagged with `_praison_handoff_source`, and `clone_for_channel()` rebinds the clone's delegation tool to the clone — so the per-request transcript, the session binding, and the delegation graph all stay correct. There is no longer a special case here.

> [!NOTE]
>
> If cloning fails, `/api/chat` returns HTTP `500` with detail `"Failed to isolate agent for session: {reason}"`. It is retry-safe and typically indicates a non-copyable custom object on the agent (e.g. a live socket).

> [!WARNING]
>
> `bind_session()` fails loudly. If the clone's `chat_history` setter raises (e.g. a custom `chat_history` backed by a locked store), the whole request returns HTTP `500` with detail `"Failed to isolate agent for session: Cannot reset chat_history for session isolation: ..."`. This is deliberate: silently continuing would leave the clone holding the template's previous transcript and leak it to whoever the incoming `session_id` belongs to. If you see this in production, replace the offending `chat_history` descriptor with a plain list assignment path.

> [!NOTE]
>
> Session isolation is unchanged by [Bot Session Learning](https://praison.ai/docs/features/bot-session-learning.md): `LearnManager` writes are scoped by the same `user_id` the session already carries, so per-user learn stores stay isolated even with the default-on posture.

***

## When Cloning Applies

Only real `Agent` instances that expose the per-session machinery are cloned. Plain mocks and lightweight callables fall back to the shared template.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Q{What did you pass to AgentOS?} 
    Q -->|Agent with no handoffs| Clone["✅ Per-request clone<br/>session_id bound"]
    Q -->|Agent with handoffs| Clone
    Q -->|Mock / plain callable| Fallback["↩️ Shared template<br/>backward compatible"]

    classDef question fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef safe fill:#10B981,stroke:#7C90A0,color:#fff
    classDef option fill:#189AB4,stroke:#7C90A0,color:#fff

    class Q question
    class Clone safe
    class Fallback option
```

***

## Best Practices

> [accordion] Send a session\_id to continue a conversation
>
> Supplying the same `session_id` on each turn is how a client continues one conversation. Omitting it starts a fresh session on a fresh clone every time.

> [accordion] Handoff graphs survive the per-request clone
>
> `clone_for_channel()` rebinds every `handoffs=[...]` tool closure to the clone, so `chat_history` stays isolated per request and the clone can still delegate to its handoff peers.

> [accordion] Retry on a 500 isolation error
>
> A `500 — Failed to isolate agent for session` means the clone step raised, usually from a non-copyable object on the agent. It is safe to retry after removing the offending object.

> [accordion] Isolation is in-process only
>
> This clone model isolates concurrent in-process requests. It does not persist sessions across process restarts — use a session store backend for durable history.

> [accordion] Don't swap in a chat\_history whose setter can raise
>
> `bind_session()` treats a raised setter as a fatal cross-tenant hazard and refuses the request. If you attach a custom `chat_history` descriptor (e.g. one that locks a backing store on assignment), make its setter always succeed — either accept `[]` unconditionally or replace it with a plain attribute.

***

## Related

> [card] [Serve Agents](https://praison.ai/docs/features/serve-agents.md)
>
> `serve_agents([...])` — per-session isolation over MCP, useful for comparison

> [card] [PraisonAI Call](https://praison.ai/docs/call.md)
>
> The call server that hosts the n8n agent-invoke router


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.