# How the Two-Phase Coordinated Update Prevents Session Disruption in Prime Agent

> Learn how Prime Agent's two-phase coordinated update prevents session disruption by locking state, draining streams, applying atomic mutations, and committing safely.

- Repository: [Prime Intellect/prime-agent](https://github.com/PrimeIntellect-ai/prime-agent)
- Tags: internals
- Published: 2026-09-05

---

**Prime Agent's two-phase coordinated update prevents session disruption by locking the session state, draining pending tool-result streams, applying atomic mutations, and releasing the lock only after the commit completes, ensuring users never see partial updates or lost messages.**

In Prime Intellect's Prime Agent, the session state is shared between the daemon (which manages LLM inference) and the terminal UI front-end. When users trigger disruptive actions—such as switching conversation branches, editing prompts, or loading new tools—the system employs a **two-phase coordinated update** protocol to maintain session continuity without visible glitches or message loss.

## The Challenge: Shared State Modifications

The Prime Agent architecture splits responsibilities between the back-end daemon (`packages/coding-agent`) and the front-end TUI (`packages/tui`). Since both components access the same session tree concurrently, abrupt state changes risk leaving the UI in an inconsistent state or losing in-flight tool results. The two-phase protocol solves this by treating session updates as atomic transactions.

## Phase 1: Prepare (Lock-and-Drain)

The first phase establishes a safety perimeter around the session state, ensuring no new operations interfere while the update is being staged.

### Acquiring the Update Lock

When the UI initiates a change, it sends a `session_update_start` message to the daemon. The daemon responds by setting the **`SessionManager.updateLock`** flag to `true`. According to the source code in [[`packages/coding-agent/src/core/session-manager.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/session-manager.ts)](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/session-manager.ts) (lines 48-55), this boolean flag gates all new input, preventing concurrent writers from corrupting the session log during the update.

```typescript
// Simplified logic from SessionManager
private updateLock = false;

async handleMessage(msg: DaemonMessage) {
  if (msg.type === "session_update_start") {
    this.updateLock = true;
    await this.drainPendingStreams();
  }
}

```

### Draining Pending Streams

With the lock held, the daemon stops accepting new user messages and begins draining any pending tool-result streams. Workers are notified via `Worker.postMessage({ type: "prepareUpdate" })`, allowing them to finish outstanding tool calls before acknowledging. As implemented in [[`packages/coding-agent/src/worker/worker.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/worker/worker.ts)](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/worker/worker.ts) (lines 210-225), this ensures no partial responses remain hanging when the mutation occurs.

## Phase 2: Apply (Commit-and-Resume)

Once the session is quiescent and all workers have acknowledged the preparation phase, the system applies the actual mutation atomically.

### Atomic Session Mutation

The UI transmits the concrete payload—such as a new branch ID or edited prompt—via a `session_update` message. The daemon applies this change inside **`SessionManager.applyUpdate()`**, located at lines 102-118 of [[`packages/coding-agent/src/core/session-manager.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/session-manager.ts)](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/core/session-manager.ts). Because the update lock is still held during this operation, the session tree cannot be observed in an inconsistent intermediate state.

```typescript
case "session_update":
  if (!this.updateLock) throw new Error("Lock not held");
  this.applyUpdate(msg.payload); // Atomic tree mutation
  break;

```

### Resuming Normal Operations

After the mutation succeeds, the daemon releases the lock and emits `session_update_done`. This signal tells the UI that the session tree is in a consistent state and normal message flow can resume. The front-end only renders the updated session after receiving this confirmation, eliminating flicker or loss of messages.

## Implementation: Coordinating UI and Daemon

The complete handshake spans both the front-end TUI and the back-end daemon. Here is how a branch switch is implemented in [[`packages/tui/src/tui.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/tui/src/tui.ts)](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/tui/src/tui.ts):

```typescript
async function switchBranch(newBranch: string) {
  // Phase 1 – tell daemon we are about to change state
  await daemon.send({ type: "session_update_start" });

  // Phase 2 – deliver the actual mutation
  await daemon.send({
    type: "session_update",
    payload: { branchId: newBranch },
  });

  // Phase 3 – release the lock so normal traffic resumes
  await daemon.send({ type: "session_update_done" });
}

```

On the daemon side, the `SessionManager` implements the corresponding handler logic:

```typescript
class SessionManager {
  private updateLock = false;

  async handleMessage(msg: DaemonMessage) {
    switch (msg.type) {
      case "session_update_start":
        this.updateLock = true;
        await this.drainPendingStreams();
        break;
      case "session_update":
        if (!this.updateLock) throw new Error("Lock not held");
        this.applyUpdate(msg.payload);
        break;
      case "session_update_done":
        this.updateLock = false;
        this.resumeIncomingMessages();
        break;
    }
  }
}

```

## Summary

- **Lock-and-Drain**: The `session_update_start` message triggers `SessionManager.updateLock`, pausing new input and draining pending tool-result streams to prevent partial states.
- **Atomic Commit**: `SessionManager.applyUpdate()` executes the mutation while the lock is held, ensuring the session tree transitions directly from one valid state to another.
- **Coordinated Resume**: The `session_update_done` signal guarantees the UI only renders complete snapshots, eliminating flicker and message loss during branch switches or prompt edits.
- **Worker Coordination**: Background workers in [`packages/coding-agent/src/worker/worker.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/worker/worker.ts) finish in-flight operations before acknowledging the prepare phase, protecting in-progress tool calls.

## Frequently Asked Questions

### What happens if a tool call is still running when an update starts?

The daemon sends a `"prepareUpdate"` message to all workers via `Worker.postMessage()`. As implemented in [[`packages/coding-agent/src/worker/worker.ts`](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/worker/worker.ts)](https://github.com/PrimeIntellect-ai/prime-agent/blob/main/packages/coding-agent/src/worker/worker.ts) (lines 210-225), workers complete their outstanding tool calls and stream the final results back before acknowledging. The update only proceeds after all pending streams are drained, ensuring no tool results are lost.

### How does the UI know when it is safe to resume rendering?

The UI waits for the `session_update_done` message from the daemon. This signal confirms that `SessionManager.applyUpdate()` has completed and the `updateLock` has been released. Only then does the front-end resume normal message flow and render the updated session tree.

### Can the session tree be observed in an inconsistent state during updates?

No. The `updateLock` boolean flag guarantees that `SessionManager.applyUpdate()` runs exclusively while the lock is held. Since new input is paused and workers have drained their streams, there are no concurrent readers or writers during the atomic mutation. The UI only accesses the session after receiving the completion signal.

### Why is a two-phase protocol necessary instead of a single message?

A single message would risk applying changes while tool results are still streaming or while the UI is rendering, causing visible flicker or lost messages. The two-phase coordinated update separates the "prepare" (lock-and-drain) from the "commit" (apply-and-resume), ensuring the session remains valid and complete throughout the transition.