> ## 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{🔍 Isolatable<br/>and no handoffs?}
    Check -->|Yes| Clone[⚙️ clone template]
    Clone --> Bind[🔑 bind session_id]
    Bind --> Run[🤖 agent.achat]
    Check -->|Has handoffs| 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

<Steps>
  <Step title="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)
    ```
  </Step>

  <Step title="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"}'
    ```
  </Step>
</Steps>

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) and no handoffs?
    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.

<Warning>
  An agent configured with `handoffs=[…]` is not cloned per request. It stays on the shared template, so `chat_history` is shared and `session_id` is not bound. This is deliberate — cloning would drop the handoffs. Use a session-aware backend (e.g. `praisonai_mcp.serve_agents`) if you need both handoffs and session isolation.
</Warning>

<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).
</Note>

<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.
</Warning>

<Note>
  Session isolation is unchanged by [Bot Session Learning](/docs/features/bot-session-learning): `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.
</Note>

***

## 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| Shared["⚠️ Shared template<br/>session_id not bound"]
    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 warn fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef option fill:#189AB4,stroke:#7C90A0,color:#fff

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

***

## Best Practices

<AccordionGroup>
  <Accordion title="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>

  <Accordion title="Don't rely on handoffs for isolation over /chat">
    Agents with `handoffs` stay on the shared template and are not session-isolated over `/chat`. Reach for `serve_agents([...])` when you need handoffs and per-session isolation together.
  </Accordion>

  <Accordion title="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>

  <Accordion title="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>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Serve Agents" icon="server" href="/docs/features/serve-agents">
    `serve_agents([...])` — per-session isolation with handoff support, useful for comparison
  </Card>

  <Card title="PraisonAI Call" icon="phone" href="/docs/call">
    The call server that hosts the n8n agent-invoke router
  </Card>
</CardGroup>


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