Session Event Log Architecture and Durable Conversation State in Claude Managed Agents
The session event log architecture stores every interaction as immutable server-side events accessed via client.beta.sessions.events, while durable conversation state is reconstructed client-side from this log using Streamlit's session_state, enabling conversations to survive browser refreshes and tab switches.
The anthropics/cwc-workshops repository hosts the Ship-Your-First-Managed-Agent workshop, which demonstrates how to build a resilient incident-response chat UI on top of Claude Managed Agents. Understanding the session event log architecture and durable conversation state is essential for building applications that maintain persistent context across page loads, browser restarts, and tab switches.
What is the Session Event Log Architecture?
The session event log architecture is a server-side persistence layer where Claude Managed Agents store every interaction as discrete, immutable events. These events include user messages, agent messages, tool calls, tool results, and status updates.
You access this log through the client.beta.sessions.events API interface, which exposes both synchronous listing and real-time streaming endpoints. When you create a new session using start_session, the API returns a unique session_id that serves as the immutable pointer to this event stream.
To replay a complete conversation history, iterate over client.beta.sessions.events.list(session_id, order="asc", limit=500).data. For live interactions, consume events through client.beta.sessions.events.stream(session_id), which returns a streaming iterator that yields events in real time as they are generated.
How Durable Conversation State Works
Durable conversation state relies on a dual-layer persistence model. The server-side event log guarantees durability, while Streamlit's st.session_state provides a lightweight, in-memory cache for the current UI view.
The UI stores two critical pieces of state in st.session_state:
sid: The currently selected session IDhist: The reconstructed conversation history as a list of(role, text)tuples
When the page reloads, the application checks for the existence of sid in session_state. If absent, it calls _list_sessions(agent_id) to retrieve recent sessions, selects the newest one, and invokes _load_history(sid) to rebuild hist from the server-side event log. Because the canonical data lives in the cloud, conversations survive browser restarts, tab refreshes, and even switching between different browsers.
Key Implementation Files
The workshop code is organized into four primary files that demonstrate the architecture:
provided.py: Contains UI helpers, session pickers, and history reconstruction logic that ties the event log tost.session_state.agent.py: A stub file that learners implement, containing the thin wrapper functions around the Claude Managed Agents API.agent_complete.py: Reference implementation showing working versions ofstart_session,stream_reply, anddelete_session.e2e.py: Command-line example demonstrating end-to-end session creation, event streaming, and deletion.
Reconstructing History from the Event Log
The _load_history(session_id) function in provided.py demonstrates how to reconstruct conversation state from raw events. It iterates over the event log and builds a display-friendly history list.
def _load_history(session_id: str):
"""Re-assemble the chat from the persisted event log."""
import agent
hist = []
for ev in agent.client.beta.sessions.events.list(
session_id, order="asc", limit=500).data:
if ev.type == "user.message":
hist.append(("user", _text(ev.content)))
elif ev.type == "agent.message":
txt = _text(ev.content)
if hist and hist[-1][0] == "assistant":
hist[-1] = ("assistant", hist[-1][1] + txt)
else:
hist.append(("assistant", txt))
# …handle tool_use / tool_result similarly…
return hist
This function handles event compaction by concatenating consecutive assistant messages, ensuring the UI presents a clean chat history rather than fragmented event entries.
Streaming Real-Time Events
For live chat interactions, the stream_reply function establishes a bidirectional streaming connection. This pattern, shown in agent_complete.py, sends user messages and yields agent responses as they arrive.
def stream_reply(session_id: str, user_text: str):
with client.beta.sessions.events.stream(session_id) as stream:
# Send the user message
client.beta.sessions.events.send(
session_id,
{"type": "user.message", "content": [{"type": "text", "text": user_text}]},
)
# Yield every event as it arrives
for ev in stream:
yield ev
if ev.type == "session.status_idle" and ev.stop_reason.type == "end_turn":
break
The streaming context manager ensures events flow in real-time, while the check for session.status_idle with stop_reason.type == "end_turn" signals completion of the agent's turn.
Creating and Managing Sessions
New sessions are instantiated through the start_session function, which binds an agent to specific resources like log files.
def start_session(agent_id: str, env_id: str, log_file_id: str) -> str:
session = client.beta.sessions.create(
agent=agent_id,
environment_id=env_id,
resources=[{"type": "file", "id": log_file_id, "mount_path": "/mnt/session/uploads/app.log"}],
)
return session.id
Notably, the Managed Agent service handles persistence automatically. Once the streaming loop ends and the session enters session.status_idle, the conversation state requires no explicit write-back to remain durable.
Summary
- Session event log architecture stores immutable interaction events server-side via
client.beta.sessions.events, accessible through both list and stream endpoints. - Durable conversation state combines the server-side event log with Streamlit's
st.session_stateto survive browser refreshes and tab switches. - History reconstruction relies on
_load_historyinprovided.pyto iterate overclient.beta.sessions.events.listand compile(role, text)tuples. - Real-time streaming uses
client.beta.sessions.events.streamwithin a context manager to yield events as they occur, checkingsession.status_idleto detect turn completion. - Automatic persistence means no explicit write-back is required; sessions remain stored in the cloud once created via
client.beta.sessions.create.
Frequently Asked Questions
Where is conversation history actually stored?
Conversation history is stored server-side in the Claude Managed Agents service as an immutable event log. The Streamlit UI only caches the session ID and reconstructed history in st.session_state, which means if the browser cache is cleared, the UI can rebuild the conversation by fetching events from the cloud using client.beta.sessions.events.list.
How does the workshop handle page refreshes?
When the page reloads, the code checks st.session_state for an existing sid. If none exists, it calls _list_sessions to retrieve recent sessions, selects the newest one, and invokes _load_history to repopulate the chat history from the server-side event log. This ensures the conversation survives browser refreshes and tab switches.
What is the difference between the list and stream endpoints?
The client.beta.sessions.events.list endpoint returns a paginated array of historical events, used for reconstructing past conversations. The client.beta.sessions.events.stream endpoint returns a real-time iterator that yields events as they are generated, used for live chat interfaces where you need to display agent responses immediately.
Do I need to manually save conversation state?
No. The Managed Agent service automatically persists every event to the server-side log. Once you send a message via client.beta.sessions.events.send and the session reaches session.status_idle, the conversation state is durable without requiring explicit write-back from your application code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →