# How OpenCode's Session Summary Generation System Tracks Code Changes

> Discover how OpenCode's session summary generation system tracks all code changes in real-time comparing snapshot markers to effectively monitor additions deletions and modifications.

- Repository: [Anomaly/opencode](https://github.com/anomalyco/opencode)
- Tags: internals
- Published: 2026-02-16

---

**OpenCode automatically generates session summaries by comparing snapshot markers from the start and end of assistant operations, tracking additions, deletions, and file changes in real-time.**

OpenCode (anomalyco/opencode) implements a sophisticated **session summary generation system** that automatically records every code modification during AI-assisted development sessions. This system captures detailed metrics including line additions, deletions, and file-level changes by analyzing snapshot markers embedded in assistant message parts. The implementation resides primarily in [`packages/opencode/src/session/summary.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/summary.ts) and integrates with SQLite storage and an internal event bus for real-time UI updates.

## What Is the Session Summary Generation System?

The **session summary generation system** is a three-stage pipeline that executes whenever the assistant completes an operation. It produces a structured record containing four key metrics: **additions** (new lines introduced), **deletions** (lines removed), **files** (count of modified files), and optional **diffs** (detailed per-file change objects).

The pipeline operates as follows:

1. **Collect all messages** for the active session.
2. **Compute a diff** between the earliest `step-start` snapshot and the latest `step-finish` snapshot.
3. **Persist the diff** to storage and update the `Session` row in the SQLite database.

Changes are simultaneously broadcast on the internal event bus, enabling UI components like the session-diff view to refresh instantly without polling.

## How the System Tracks Changes in OpenCode

### Triggering the Summarization Process

The process begins when the `summarize` function in [`packages/opencode/src/session/summary.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/summary.ts) (lines 69-80) receives a session ID and the ID of the most recent user message:

```typescript
export const summarize = fn(
  z.object({ sessionID: z.string(), messageID: z.string() }),
  async (input) => {
    const all = await Session.messages({ sessionID: input.sessionID })
    await Promise.all([
      summarizeSession({ sessionID: input.sessionID, messages: all }),
      summarizeMessage({ messageID: input.messageID, messages: all }),
    ])
  },
)

```

This function gathers all session messages and executes two parallel operations: summarizing the entire session and attaching specific diffs to the triggering message.

### Computing Session-Level Diffs

The `summarizeSession` function (lines 83-98) aggregates statistics across all file changes:

```typescript
async function summarizeSession(input: { sessionID: string; messages: MessageV2.WithParts[] }) {
  const diffs = await computeDiff({ messages: input.messages })
  await Session.setSummary({
    sessionID: input.sessionID,
    summary: {
      additions: diffs.reduce((sum, x) => sum + x.additions, 0),
      deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
      files: diffs.length,
    },
  })
  await Storage.write(["session_diff", input.sessionID], diffs)
  Bus.publish(Session.Event.Diff, { sessionID: input.sessionID, diff: diffs })
}

```

The system stores the detailed diff array under the key `["session_diff", sessionID]` using the generic `Storage` layer backed by SQLite. It then publishes a `Session.Event.Diff` event to notify subscribers.

### Attaching Diffs to Specific Messages

The `summarizeMessage` function (lines 100-112) filters messages to find the specific user message and its associated assistant responses:

```typescript
async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) {
  const messages = input.messages.filter(
    (m) => m.info.id === input.messageID ||
           (m.info.role === "assistant" && m.info.parentID === input.messageID),
  )
  const msgWithParts = messages.find((m) => m.info.id === input.messageID)!
  const userMsg = msgWithParts.info as MessageV2.User
  const diffs = await computeDiff({ messages })
  userMsg.summary = { ...userMsg.summary, diffs }
  await Session.updateMessage(userMsg)
}

```

This allows the UI to display exactly which changes resulted from a specific user prompt.

### Analyzing Snapshot Markers

The core diff computation occurs in `computeDiff` (lines 35-60), which scans assistant message parts for snapshot markers:

```typescript
export async function computeDiff(input: { messages: MessageV2.WithParts[] }) {
  let from: string | undefined
  let to: string | undefined

  for (const item of input.messages) {
    if (!from) {
      for (const part of item.parts) {
        if (part.type === "step-start" && part.snapshot) {
          from = part.snapshot
          break
        }
      }
    }

    for (const part of item.parts) {
      if (part.type === "step-finish" && part.snapshot) {
        to = part.snapshot
      }
    }
  }

  if (from && to) return Snapshot.diffFull(from, to)
  return []
}

```

The function identifies the earliest `step-start` snapshot (initial state) and the latest `step-finish` snapshot (final state), then delegates to `Snapshot.diffFull` to generate the actual file-level differences.

### Normalizing File Paths for the UI

Stored diffs may contain escaped Git path strings (e.g., `"src\\/main.ts"`). The public `diff` API in [`packages/opencode/src/session/summary.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/summary.ts) (lines 14-33) cleans these paths:

```typescript
export const diff = fn(
  z.object({
    sessionID: Identifier.schema("session"),
    messageID: Identifier.schema("message").optional(),
  }),
  async (input) => {
    const diffs = await Storage.read<Snapshot.FileDiff[]>(["session_diff", input.sessionID]).catch(() => [])
    const next = diffs.map((item) => {
      const file = unquoteGitPath(item.file)
      if (file === item.file) return item
      return { ...item, file }
    })
    const changed = next.some((item, i) => item.file !== diffs[i]?.file)
    if (changed) Storage.write(["session_diff", input.sessionID], next).catch(() => {})
    return next
  },
)

```

The `unquoteGitPath` utility decodes escaped octal sequences and quoted strings, ensuring the UI displays clean, readable file paths.

## Database Schema and API Exposure

### SQLite Storage Structure

The database schema defined in [`packages/opencode/src/session/session.sql.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/session.sql.ts) includes dedicated columns for summary metrics:

- `summary_additions` – Total lines added
- `summary_deletions` – Total lines removed  
- `summary_files` – Count of modified files
- `summary_diffs` – Optional serialized diff objects

### Retrieving Summary Data

The `Session.getInfo` method (exposed via the `SessionInfo` Zod schema in [`packages/opencode/src/session/index.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/index.ts)) returns the summary block to callers:

```typescript
const info = await Session.getInfo("s_12345")
console.log(info.summary)
// → { additions: 42, deletions: 7, files: 3, diffs: [...] }

```

### Real-Time Updates via Event Bus

After each summarization, the system publishes `Session.Event.Diff` (defined in [`packages/opencode/src/session/index.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/index.ts), lines 76-82) to the internal event bus:

```typescript
Bus.publish(Session.Event.Diff, { sessionID: input.sessionID, diff: diffs })

```

This allows UI components to subscribe to change notifications and refresh the *Changes* tab instantly without polling the database.

## Practical Implementation Examples

### Summarize a Session Programmatically

```typescript
import { SessionSummary } from "@opencode/session/summary"

await SessionSummary.summarize({
  sessionID: "s_12345",
  messageID: "m_67890",
})

```

### Retrieve the Diff for UI Display

```typescript
import { SessionSummary } from "@opencode/session/summary"

const diff = await SessionSummary.diff({
  sessionID: "s_12345",
})
// Returns: Array<{ file: string, additions: number, deletions: number, ... }>

```

### Access High-Level Summary Statistics

```typescript
import { Session } from "@opencode/session"

const info = await Session.getInfo("s_12345")
console.log(info.summary)
// → { additions: 42, deletions: 7, files: 3, diffs: [...] }

```

### React to Changes in a React Frontend

```tsx
import { useBus } from "@opencode/bus"
import { Session } from "@opencode/session"
import { useState, useEffect } from "react"

function SessionDiffView({ sessionID }: { sessionID: string }) {
  const [diff, setDiff] = useState([])

  useEffect(() => {
    const unsub = Bus.subscribe(Session.Event.Diff, ({ diff }) => {
      if (diff.sessionID === sessionID) setDiff(diff.diff)
    })
    return () => unsub()
  }, [sessionID])

  return <DiffTable rows={diff} />
}

```

## Key Source Files

| File | Role |
|------|------|
| [[`packages/opencode/src/session/summary.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/summary.ts)](https://github.com/anomalyco/opencode/blob/dev/dev/packages/opencode/src/session/summary.ts) | Core summarizer implementing `summarize`, `summarizeSession`, `summarizeMessage`, `computeDiff`, and `diff` |
| [[`packages/opencode/src/session/index.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/index.ts)](https://github.com/anomalyco/opencode/blob/dev/dev/packages/opencode/src/session/index.ts) | Session model, database mapping, `SessionInfo` schema, and `Session.Event.Diff` definition |
| [[`packages/opencode/src/session/session.sql.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/session.sql.ts)](https://github.com/anomalyco/opencode/blob/dev/dev/packages/opencode/src/session/session.sql.ts) | SQLite table schema with `summary_additions`, `summary_deletions`, `summary_files`, and `summary_diffs` columns |
| [[`packages/opencode/src/snapshot/index.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/snapshot/index.ts)](https://github.com/anomalyco/opencode/blob/dev/dev/packages/opencode/src/snapshot/index.ts) | `Snapshot.diffFull` implementation that computes actual file-level differences between snapshots |
| [[`packages/opencode/src/storage/storage.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/storage/storage.ts)](https://github.com/anomalyco/opencode/blob/dev/dev/packages/opencode/src/storage/storage.ts) | Generic key/value storage layer used to persist diff arrays under `["session_diff", sessionID]` keys |

## Summary

- OpenCode's **session summary generation system** automatically records code changes by comparing `step-start` and `step-finish` snapshots stored in assistant message parts.
- The system calculates **additions**, **deletions**, and **file counts** by delegating to `Snapshot.diffFull` in [`packages/opencode/src/snapshot/index.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/snapshot/index.ts).
- Detailed diffs persist in SQLite via the `Storage` layer under the key `["session_diff", sessionID]`, while high-level statistics write to `summary_additions`, `summary_deletions`, and `summary_files` columns.
- Real-time UI updates occur through `Session.Event.Diff` events published on the internal bus, enabling live refresh of the Changes tab without polling.

## Frequently Asked Questions

### How does OpenCode detect which files changed during a session?

OpenCode detects file changes by scanning assistant message parts for `step-start` and `step-finish` snapshot markers in the `computeDiff` function within [`packages/opencode/src/session/summary.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/summary.ts). The system identifies the earliest `step-start` snapshot (representing the initial state) and the latest `step-finish` snapshot (representing the final state), then delegates to `Snapshot.diffFull` to generate an array of `FileDiff` objects containing the specific files modified, lines added, and lines deleted.

### Where are session summaries stored in OpenCode?

Session summaries are stored in two locations within the SQLite database managed by the `Storage` layer. High-level statistics (additions, deletions, file count) are stored in the `Session` table columns defined in [`packages/opencode/src/session/session.sql.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/session.sql.ts): `summary_additions`, `summary_deletions`, `summary_files`, and `summary_diffs`. Detailed per-file diff objects are stored as JSON in the generic key-value store under the composite key `["session_diff", sessionID]` via `Storage.write` in [`packages/opencode/src/storage/storage.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/storage/storage.ts).

### Can I access historical session diffs programmatically?

Yes, historical session diffs are accessible through the public `SessionSummary.diff` API exported from [`packages/opencode/src/session/summary.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/summary.ts). You can retrieve the complete diff array for any session by calling `SessionSummary.diff({ sessionID: "s_12345" })`, which returns an array of `FileDiff` objects containing file paths, addition counts, and deletion counts. Additionally, `Session.getInfo(sessionID)` returns high-level summary statistics suitable for displaying change counts in dashboards or CLI outputs.

### How does the session summary generation system handle file renames?

The session summary generation system handles file renames through the `unquoteGitPath` utility function invoked within the `diff` function in [`packages/opencode/src/session/summary.ts`](https://github.com/anomalyco/opencode/blob/main/packages/opencode/src/session/summary.ts). When diffs are retrieved from storage, file paths may contain escaped Git path strings (such as `"src\\/main.ts"` with escaped slashes or octal sequences). The `unquoteGitPath` function decodes these escaped sequences and quoted strings, returning clean file paths for UI display. If any paths are normalized during this process, the corrected diff array is written back to storage to ensure consistency.