# Session Event Log Architecture and Durable Conversation State in Claude Managed Agents

> Understand session event log architecture and durable conversation state in Claude managed agents. Learn how to reconstruct client-side state from immutable server-side events for resilient conversations.

- Repository: [Anthropic/cwc-workshops](https://github.com/anthropics/cwc-workshops)
- Tags: architecture
- Published: 2026-07-18

---

**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 ID
- `hist`: 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`](https://github.com/anthropics/cwc-workshops/blob/main/provided.py)**: Contains UI helpers, session pickers, and history reconstruction logic that ties the event log to `st.session_state`.
- **[`agent.py`](https://github.com/anthropics/cwc-workshops/blob/main/agent.py)**: A stub file that learners implement, containing the thin wrapper functions around the Claude Managed Agents API.
- **[`agent_complete.py`](https://github.com/anthropics/cwc-workshops/blob/main/agent_complete.py)**: Reference implementation showing working versions of `start_session`, `stream_reply`, and `delete_session`.
- **[`e2e.py`](https://github.com/anthropics/cwc-workshops/blob/main/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`](https://github.com/anthropics/cwc-workshops/blob/main/provided.py) demonstrates how to reconstruct conversation state from raw events. It iterates over the event log and builds a display-friendly history list.

```python
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`](https://github.com/anthropics/cwc-workshops/blob/main/agent_complete.py), sends user messages and yields agent responses as they arrive.

```python
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.

```python
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_state` to survive browser refreshes and tab switches.
- **History reconstruction** relies on `_load_history` in [`provided.py`](https://github.com/anthropics/cwc-workshops/blob/main/provided.py) to iterate over `client.beta.sessions.events.list` and compile `(role, text)` tuples.
- **Real-time streaming** uses `client.beta.sessions.events.stream` within a context manager to yield events as they occur, checking `session.status_idle` to 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.