# Routa Session Lifecycle and Reconnection Flow: Managing ACP Sessions Across Network Interruptions

> Explore the Routa session lifecycle and reconnection flow managing ACP sessions across network interruptions. Built with a seven-phase lifecycle and auto-reconnecting Server-Sent Events.

- Repository: [Fengda Huang/routa](https://github.com/phodal/routa)
- Tags: internals
- Published: 2026-05-26

---

**Routa implements a seven-phase session lifecycle that persists Agent Communication Protocol (ACP) state from in-memory execution through permanent storage, while Server-Sent Events automatically reconnect and preserve message continuity without losing user input.**

The phodal/routa repository treats the *session* as the fundamental unit of work for AI interactions, maintaining state both in-memory via the ACP manager and in persistent storage. Understanding the complete session lifecycle and reconnection flow helps developers build resilient applications that survive network interruptions while maintaining consistent transcript history and tool-call boundaries.

## Session Lifecycle Phases

Routa manages sessions through a structured lifecycle that spans creation, execution, persistence, and eventual deletion. Each phase transitions through specific REST endpoints while the SSE stream provides real-time updates.

### Create

A new session begins when the provider-specific run API instantiates an ACP session through the manager. The system generates a UUID for the session ID and inserts a row into `acp_session_store`. On the frontend, the UI issues a `POST` request via `desktopAwareFetch` to an endpoint such as `/api/acp`, receives a `{sessionId}` payload, and stores it in the client state.

### Run and Update

While the session executes, the ACP manager streams notification events containing user messages, assistant messages, and tool calls. The manager simultaneously records a trace file under the workspace for audit purposes. The client opens an SSE channel using `new EventSource(resolveApiPath(\`/api/acp?sessionId=${id}\`))`, where each `message` event contains a JSON record merged into the local transcript through `history_to_transcript_messages` and `traces_to_transcript_messages`. The backend serves history via `GET /api/sessions/{sessionId}/history`, checking in-memory state first before falling back to the database.

### Persist

When the session stops or the user disconnects, the manager flushes in-memory history to the database. Calling `POST /api/sessions/{sessionId}/disconnect` triggers the backend to first execute `get_session_history`, write it using `acp_session_store.save_history`, and then terminate the process via `acp_manager.kill_session`. This ensures no conversation state is lost when the connection closes.

### Fork and Clone

Running or completed sessions can spawn child sessions through the fork operation. The `POST /api/sessions/{sessionId}/fork` endpoint creates a fresh session that inherits the provider, workspace, and current working directory from its parent. The UI posts the request, receives a new `sessionId`, and navigates to the new session page while maintaining the hierarchical relationship.

### Rename

Users can update session metadata without interrupting execution. The `PATCH /api/sessions/{sessionId}` endpoint updates the name in both locations: the manager updates the in-memory map through `acp_manager.rename_session`, while the database layer executes `acp_session_store.rename` to persist the change.

### Delete

Complete removal requires terminating the in-memory entry and database record. The `DELETE /api/sessions/{sessionId}` endpoint invokes `acp_manager.delete_session` for the running state and `acp_session_store.delete` for the persistent row, freeing all associated resources.

### Retrieve

Clients reconstruct the session view through several read endpoints. `GET /api/sessions/{sessionId}` returns metadata, `/api/sessions/{sessionId}/transcript` provides the canonical transcript combining history and trace data, and `/api/sessions/{sessionId}/context` exposes the hierarchical structure including parents, children, and siblings. The UI fetches these using `desktopAwareFetch` and renders them through components like `session-panel` and `session-context-panel`.

## Reconnection Flow and SSE Resilience

Routa's frontend handles network instability through a combination of native EventSource capabilities and explicit cache invalidation logic implemented in hooks like `useChatMessages` and `useKanbanEvents`.

### Opening the Stream

The client initializes the live connection by creating an `EventSource` pointing to `resolveApiPath('/api/acp?sessionId=${id}')`. This establishes a persistent HTTP connection that the server uses to push notification events as the ACP manager generates them.

### Automatic Retry Mechanism

The native `EventSource` implementation automatically attempts to reconnect following any network interruption. This behavior requires no custom retry logic, as the browser manages the exponential backoff and connection attempts according to the SSE specification.

### Client-Side Cache Invalidation

Upon the `onopen` event firing after a reconnection, the hook invalidates the React Query cache via `queryClient.invalidateQueries(['session', sessionId])`. This pattern is verified in [`src/client/hooks/__tests__/use-kanban-events.test.tsx`](https://github.com/phodal/routa/blob/main/src/client/hooks/__tests__/use-kanban-events.test.tsx) through the test case "invalidates when the SSE connection reconnects after the first connect", ensuring the UI fetches fresh state rather than displaying stale data cached before the interruption.

### Message Continuity Across Reconnects

The server maintains session state in the ACP manager during brief disconnections. Pending prompts that were not yet transmitted are re-queued upon reconnection, as indicated by the implementation comment `// Re-store pending text so the pending-prompt effect can re-send after reconnect`. This guarantees no user-typed input is lost. Additionally, the stream logic ensures tool-call chunks are never merged across a reconnect boundary, preventing stale chunks from attaching to new tool invocations—a behavior validated in [`use-chat-messages.test.tsx`](https://github.com/phodal/routa/blob/main/use-chat-messages.test.tsx).

## Implementation Details and Code Examples

The following patterns demonstrate how to interact with the session lifecycle and SSE reconnection flow:

```tsx
// Start a new session (client side)
const { data } = await desktopAwareFetch(
  resolveApiPath('/api/acp'),
  { method: 'POST', body: JSON.stringify(payload) }
);
const sessionId = data.sessionId;

// Subscribe to live updates with reconnection handling
const source = new EventSource(
  resolveApiPath(`/api/acp?sessionId=${sessionId}`)
);
source.onmessage = ev => {
  const notif = JSON.parse(ev.data);
  // Merge into transcript via history_to_transcript_messages()
};
source.onopen = () => queryClient.invalidateQueries(['session', sessionId]);

// Disconnect and persist history
await desktopAwareFetch(
  resolveApiPath(`/api/sessions/${sessionId}/disconnect`),
  { method: 'POST' }
);

// Fork an existing session
const forkRes = await desktopAwareFetch(
  resolveApiPath(`/api/sessions/${sessionId}/fork`),
  { method: 'POST' }
);
const newSessionId = forkRes.sessionId;

```

Key implementation files include:
- [`crates/routa-server/src/api/sessions.rs`](https://github.com/phodal/routa/blob/main/crates/routa-server/src/api/sessions.rs) – Defines HTTP endpoints for session CRUD, disconnect, fork, and retrieval operations
- [`crates/routa-server/src/application/sessions.rs`](https://github.com/phodal/routa/blob/main/crates/routa-server/src/application/sessions.rs) – Contains business logic for merging in-memory and database state, building transcripts, and handling session forks
- [`src/client/hooks/__tests__/use-kanban-events.test.tsx`](https://github.com/phodal/routa/blob/main/src/client/hooks/__tests__/use-kanban-events.test.tsx) – Validates reconnection detection and cache invalidation behavior
- [`src/client/components/session-panel.tsx`](https://github.com/phodal/routa/blob/main/src/client/components/session-panel.tsx) – Renders session metadata and transcript data
- [`docs/use-routa/sessions.md`](https://github.com/phodal/routa/blob/main/docs/use-routa/sessions.md) – Provides high-level architectural documentation for session concepts

## Summary

- **Routa sessions** exist both in the ACP manager's memory and in persistent storage, with UUID-based identification.
- The **seven-phase lifecycle** covers creation, execution, persistence, forking, renaming, deletion, and retrieval through specific REST endpoints.
- **SSE streams** provide real-time updates via `EventSource`, with automatic browser-managed reconnection.
- **Cache invalidation** on reconnect ensures the UI displays current server state rather than stale data.
- **Message continuity** is preserved through re-queuing pending prompts and protecting tool-call boundaries across reconnections.

## Frequently Asked Questions

### What happens to unsent user messages during a network reconnection?

The Routa frontend maintains pending text in a re-queue buffer. When the SSE connection reopens after a network interruption, the pending-prompt effect automatically re-sends any unsent input, ensuring no user messages are lost during transient disconnections.

### How does Routa persist session history when a user clicks disconnect?

The disconnect flow in [`crates/routa-server/src/application/sessions.rs`](https://github.com/phodal/routa/blob/main/crates/routa-server/src/application/sessions.rs) first retrieves the complete in-memory history via `get_session_history`, persists it to the database using `acp_session_store.save_history`, and then terminates the process with `acp_manager.kill_session`, guaranteeing all conversation data is written before resources are released.

### What is the difference between forking and renaming a session?

**Forking** creates a new child session via `POST /api/sessions/{sessionId}/fork` that inherits the parent's configuration but receives a new UUID and starts fresh execution. **Renaming** uses `PATCH /api/sessions/{sessionId}` to update only the display name through `acp_manager.rename_session` without affecting execution state or creating a new session entity.

### How does Routa prevent corrupted tool calls across SSE reconnections?

The stream processing logic in hooks like `useChatMessages` ensures that partial tool-call chunks are never merged across reconnection boundaries. This prevents a stale chunk from the previous connection from being incorrectly attached to a new tool invocation after the stream reconnects, maintaining the integrity of the assistant's tool-use boundaries.