Appearance
Sessions
Sessions let you persist an agent's conversation history and state across process restarts, serverless invocations, or separate user requests.
Each session manager is bound to a session_id (the conversation bucket). Each Agent has an agent_id (the agent within that session). For a single assistant per user, use the user's id as session_id and a fixed name like "assistant" as agent_id.
File-based sessions
Store sessions on the local filesystem:
python
from elsai import Agent
from elsai.agent import AgentConfig
from elsai.session import FileSessionManager
session_manager = FileSessionManager(
session_id="user-alice",
storage_dir="./sessions",
)
agent = Agent(
config=AgentConfig(
agent_id="assistant",
session_manager=session_manager,
),
)
agent("My name is Alice.")
# session saved to ./sessions/session_user-alice/agents/agent_assistant/
# Later (even in a new process):
session_manager = FileSessionManager(
session_id="user-alice",
storage_dir="./sessions",
)
agent2 = Agent(
config=AgentConfig(
agent_id="assistant",
session_manager=session_manager,
),
)
result = agent2("What is my name?")
print(result) # → "Your name is Alice."S3-based sessions
Store sessions in Amazon S3 for serverless or distributed deployments:
python
from elsai import Agent
from elsai.agent import AgentConfig
from elsai.session import S3SessionManager
session_manager = S3SessionManager(
session_id="user-bob",
bucket="my-agent-sessions",
prefix="sessions/",
region_name="us-west-2",
)
agent = Agent(
config=AgentConfig(
agent_id="assistant",
session_manager=session_manager,
),
)Repository-based sessions
For database-backed sessions, implement SessionRepository and pass it to RepositorySessionManager:
python
from elsai import Agent
from elsai.agent import AgentConfig
from elsai.session import RepositorySessionManager, SessionRepository
from elsai.session import Session, SessionAgent, SessionMessage
class PostgresSessionRepository(SessionRepository):
def create_session(self, session: Session, **kwargs) -> Session:
...
def read_session(self, session_id: str, **kwargs) -> Session | None:
...
def create_agent(self, session_id: str, session_agent: SessionAgent, **kwargs) -> None:
...
def read_agent(self, session_id: str, agent_id: str, **kwargs) -> SessionAgent | None:
...
def update_agent(self, session_id: str, session_agent: SessionAgent, **kwargs) -> None:
...
def create_message(
self, session_id: str, agent_id: str, session_message: SessionMessage, **kwargs
) -> None:
...
def read_message(
self, session_id: str, agent_id: str, message_id: int, **kwargs
) -> SessionMessage | None:
...
def update_message(
self, session_id: str, agent_id: str, session_message: SessionMessage, **kwargs
) -> None:
...
def list_messages(
self, session_id: str, agent_id: str, limit: int | None = None, offset: int = 0, **kwargs
) -> list[SessionMessage]:
...
manager = RepositorySessionManager(
session_id="user-123",
session_repository=PostgresSessionRepository(),
)
agent = Agent(config=AgentConfig(agent_id="assistant", session_manager=manager))File storage structure
When using FileSessionManager, sessions are stored in this directory layout:
<storage_dir>/
└── session_<session_id>/
├── session.json ← session metadata & timestamps
├── agents/
│ └── agent_<agent_id>/
│ ├── agent.json ← agent state & configuration
│ └── messages/
│ ├── message_<id>.json ← individual conversation messages
│ └── message_<id>.json
└── multi_agents/
└── multi_agent_<id>/
└── multi_agent.json ← orchestrator state & node historyEach message_<id>.json holds a single conversation turn:
json
{
"role": "user",
"content": [{ "text": "What is 2 + 2?" }],
"created_at": "2025-06-01T12:00:00Z"
}S3 storage structure
S3SessionManager mirrors the same hierarchy in S3:
<prefix>/
└── session_<session_id>/
├── session.json
├── agents/
│ └── agent_<agent_id>/
│ ├── agent.json
│ └── messages/
│ └── message_<id>.json
└── multi_agents/
└── multi_agent_<id>/
└── multi_agent.jsonRequired IAM permissions:
json
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::my-bucket/*", "arn:aws:s3:::my-bucket"]
}SessionManager interface
The SessionManager is a plugin — it registers hooks to persist agent state after each message and on initialization.
| Concept | Description |
|---|---|
session_id | Set on the session manager — identifies the conversation bucket |
agent_id | Set on the agent — identifies the agent within the session |
storage_dir / bucket | Backend location on FileSessionManager / S3SessionManager |
What gets persisted
By default sessions save:
- Conversation messages (
agent.messages) - Agent state (
agent.state) - Conversation manager state
Snapshots (in-memory)
Take a point-in-time snapshot without a session manager:
python
# Save state
snapshot = agent.take_snapshot(preset="session")
# Serialise if needed
import json
snapshot_dict = snapshot.model_dump()
# Restore later
from elsai.agent import Snapshot
agent2 = Agent(config=AgentConfig(agent_id="user-alice"))
agent2.load_snapshot(Snapshot(**snapshot_dict))Snapshot fields
| Field | Description |
|---|---|
messages | Full conversation history |
state | Agent state key-value pairs |
conversation_manager_state | Conversation manager internals |
interrupt_state | Interrupt/resume state |
system_prompt | System prompt content blocks |
python
# Selective snapshot
snapshot = agent.take_snapshot(include=["messages", "state"])
snapshot = agent.take_snapshot(exclude=["interrupt_state"])Custom data in snapshots
python
snapshot = agent.take_snapshot(
preset="session",
app_data={"user_tier": "premium", "feature_flags": ["beta_tools"]},
)
print(snapshot.app_data["user_tier"]) # "premium"Multi-user example
python
from elsai import Agent
from elsai.agent import AgentConfig
from elsai.session import FileSessionManager
def get_agent_for_user(user_id: str) -> Agent:
session_manager = FileSessionManager(
session_id=user_id,
storage_dir="./sessions",
)
return Agent(
system_prompt="You are a personal assistant for this user.",
config=AgentConfig(
agent_id="assistant",
session_manager=session_manager,
),
)
# Each user gets their own isolated conversation
alice_agent = get_agent_for_user("alice")
bob_agent = get_agent_for_user("bob")
alice_agent("My favourite colour is blue.")
bob_agent("I love Python programming.")
# Later:
alice_agent2 = get_agent_for_user("alice")
print(alice_agent2("What is my favourite colour?")) # blueA2A sessions
When you expose an agent via A2AServer, each incoming A2A request carries a context_id. Elsai treats that ID as the conversation bucket:
| Concept | A2A mapping |
|---|---|
session_id | Equals the A2A context_id |
agent_id | Your agent's configured agent_id (for example "assistant") |
| Session storage | One isolated session per context_id |
| Tool state | Per-context directories under $ELSAI_A2A_SESSION_DIR/<context_id>/ |
By default, A2AServer creates a FileSessionManager per context:
$ELSAI_A2A_SESSION_DIR/<context_id>/
├── session.json
├── agents/agent_<agent_id>/messages/…
└── repl/, shell/, browser/, … ← tool state when those tools are usedSet ELSAI_A2A_SESSION_DIR to a persistent volume in production (EBS, EFS, etc.). Conversation history and on-disk tool state survive process restarts and LRU eviction of in-memory agents.
File-backed A2A sessions
Override the default storage root with session_manager_factory:
python
from elsai.multiagent.a2a import A2AServer
from elsai.session import FileSessionManager
server = A2AServer(
agent=template_agent,
session_manager_factory=lambda ctx_id: FileSessionManager(
session_id=ctx_id,
storage_dir="/data/a2a-sessions",
),
)Each context_id gets its own subdirectory under /data/a2a-sessions/session_<context_id>/ following the file storage structure above.
S3-backed A2A sessions (production)
For distributed or serverless deployments on AWS, persist each context's conversation history in S3:
python
import os
from elsai.multiagent.a2a import A2AServer
from elsai.session import S3SessionManager
def s3_session_for_context(context_id: str) -> S3SessionManager:
return S3SessionManager(
session_id=context_id,
bucket=os.environ["A2A_SESSIONS_BUCKET"],
prefix=os.getenv("A2A_SESSIONS_PREFIX", "a2a/contexts/"),
region_name=os.getenv("AWS_REGION", "us-east-1"),
)
server = A2AServer(
agent=template_agent,
session_manager_factory=s3_session_for_context,
max_active_contexts=200,
strict_tool_policy=True,
)S3 keys follow the same hierarchy as S3 storage structure — one session_<context_id>/ prefix per A2A conversation. Tool state files (REPL, shell, browser profiles, etc.) remain on the server filesystem under $ELSAI_A2A_SESSION_DIR unless your tools use external stores.
See Agent-to-Agent (A2A) for agent factory mode, LRU pooling, stateful tool policy, the full production S3 example, and environment variables.