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.
Quick Start
- Serve an Agent
python from praisonai import AgentOS from praisonaiagents import Agent app = AgentOS(agents=[ Agent(name="assistant", instructions="Be helpful"), ]) app.serve(port=8000) - Chat With a Session
Supply a
session_idto continue the same conversation across calls. Omit it to make each call a fresh session.bash # 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_ids 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.
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.
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.
Best Practices
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.
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.
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.
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.
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.