# How Session Replay Works with JSONL Transcript Imports in AgentMemory

> Learn how AgentMemory replays Claude Code sessions using JSONL transcript imports. Discover how it reconstructs timelines, extracts lessons, and compresses observations for a seamless replay experience.

- Repository: [Rohit Ghumare/agentmemory](https://github.com/rohitg00/agentmemory)
- Tags: how-to-guide
- Published: 2026-05-10

---

**AgentMemory reconstructs interactive Claude Code sessions from JSONL transcripts by importing, parsing, and compressing observations into replayable timelines with auto-extracted lessons and crystals.**

The `agentmemory` open-source project enables developers to import Claude Code conversation transcripts and replay them as interactive sessions. This functionality transforms static JSONL log files into dynamic, queryable memory structures that preserve the complete context of AI-assisted development workflows.

## Security Guard-Rails for Safe Imports

Before processing begins, the import pipeline validates file paths to prevent accidental exposure of sensitive data. The `isSensitive` function in [`src/functions/replay.ts#L24-L38`](https://github.com/rohitg00/agentmemory/blob/main/src/functions/replay.ts#L24-L38) rejects paths matching patterns for secrets, credentials, or environment files.

```typescript
const SENSITIVE_PATH_PATTERNS: RegExp[] = [
  /(^|[\\/_.-])secret([\\/_.-]|s?$)/i,
  /(^|[\\/_.-])credentials?([\\/_.-]|$)/i,
  // ... additional patterns
];

export function isSensitive(path: string): boolean {
  return SENSITIVE_PATH_PATTERNS.some((re) => re.test(path));
}

```

This validation runs against both explicitly provided paths and discovered files within directories, ensuring that tokens and private keys never enter the replay system.

## The Import Pipeline

### Registering the Import Function

The core entry point `mem::replay::import-jsonl` is registered in [`src/functions/replay.ts#L88-L102`](https://github.com/rohitg00/agentmemory/blob/main/src/functions/replay.ts#L88-L102). This function accepts an optional `path` parameter defaulting to `~/.claude/projects` and respects a `maxFiles` limit to prevent runaway directory traversals.

```typescript
sdk.registerFunction(
  "mem::replay::import-jsonl",
  async (data = {}): Promise<…> => {
    const defaultRoot = join(homedir(), ".claude", "projects");
    const rawPath = data.path || defaultRoot;
    // ...
  }
);

```

### File Discovery and Parsing

When the supplied path is a directory, the `findJsonlFiles` utility recursively locates JSONL files up to the specified limit. For each discovered file, the importer reads the content and passes it to `parseJsonlText` in [[`src/replay/jsonl-parser.ts`](https://github.com/rohitg00/agentmemory/blob/main/src/replay/jsonl-parser.ts)](https://github.com/rohitg00/agentmemory/blob/main/src/replay/jsonl-parser.ts), which converts each line into a `RawObservation` object.

```typescript
const parsed = parseJsonlText(text, generateId("sess"));
if (parsed.observations.length === 0) continue;

```

The parser handles malformed lines gracefully, discarding empty or invalid entries while preserving valid observations across user prompts, tool calls, and assistant replies.

## Data Transformation and Storage

### Session Creation and Updates

The importer maintains idempotency by checking for existing sessions before creating new records. As implemented in [`src/functions/replay.ts#L94-L122`](https://github.com/rohitg00/agentmemory/blob/main/src/functions/replay.ts#L94-L122), the logic either increments the observation count on existing sessions or initializes fresh `Session` objects in the KV store.

```typescript
if (existing) {
  existing.observationCount += parsed.observations.length;
  // ...
} else {
  const session: Session = {
    id: parsed.sessionId,
    project: parsed.project,
    // ...
  };
  await kv.set(KV.sessions, session.id, session);
}

```

### Observation Compression and Indexing

Each `RawObservation` undergoes synthetic compression via `buildSyntheticCompression` before storage. The system simultaneously updates the search index, making observations queryable immediately after import. This dual-write operation occurs in [`src/functions/replay.ts#L124-L132`](https://github.com/rohitg00/agentmemory/blob/main/src/functions/replay.ts#L124-L132).

### Auto-Extraction of Crystals and Lessons

The `deriveCrystalAndLessons` function analyzes imported observations to generate two key artifacts:

- **Crystals**: Content-addressed snapshots (`fingerprintId`) capturing tools invoked (`keyOutcomes`) and files modified (`filesAffected`)
- **Lessons**: Heuristic snippets matching predefined `LESSON_PATTERNS`, tagged as `auto-import` for immediate reuse in future sessions

Both artifacts use content-addressing to prevent duplication during re-imports. The crystal generation logic resides in [`src/functions/replay.ts#L61-L71`](https://github.com/rohitg00/agentmemory/blob/main/src/functions/replay.ts#L61-L71), while lesson extraction follows in the subsequent lines.

### Audit Logging

Every import concludes with an audit entry recording the source, path, file count, and observation total. The `safeAudit` call in [`src/functions/replay.ts#L48-L55`](https://github.com/rohitg00/agentmemory/blob/main/src/functions/replay.ts#L48-L55) ensures compliance and traceability.

## Loading and Timeline Projection

### Session Retrieval

To replay a session, clients invoke `mem::replay::load` as defined in [`src/functions/replay.ts#L62-L74`](https://github.com/rohitg00/agentmemory/blob/main/src/functions/replay.ts#L62-L74). This function retrieves the session metadata and associated observations from the KV store.

```typescript
sdk.registerFunction(
  "mem::replay::load",
  async (data) => {
    const session = await kv.get<Session>(KV.sessions, data.sessionId);
    const observations = await loadObservations(kv, data.sessionId);
    const timeline = projectTimeline(observations);
    return { success: true, timeline, session };
  }
);

```

### Timeline Construction

The `projectTimeline` function in [[`src/replay/timeline.ts`](https://github.com/rohitg00/agentmemory/blob/main/src/replay/timeline.ts)](https://github.com/rohitg00/agentmemory/blob/main/src/replay/timeline.ts) orders observations chronologically, calculates relative offsets (`offsetMs`), and aggregates events into a `Timeline` structure. This timeline drives the playback controls in the viewer interface, enabling step-through navigation of the original Claude session.

## Implementation Examples

### CLI Import

Import transcripts using the command-line interface:

```bash

# Import all JSONL transcripts under the default Claude projects folder

agentmemory import-jsonl

# Import a specific file or directory

agentmemory import-jsonl /path/to/my-transcript.jsonl

```

### REST API Import

Import programmatically via the REST endpoint:

```http
POST /agentmemory/replay/import-jsonl
Content-Type: application/json

{
  "path": "/home/user/.claude/projects",
  "maxFiles": 150
}

```

**Response:**

```json
{
  "success": true,
  "imported": 3,
  "sessionIds": ["sess_abc123", "sess_def456", "sess_ghi789"],
  "observations": 452,
  "discovered": 3,
  "truncated": false,
  "traversalCapped": false,
  "maxFiles": 150,
  "maxFilesUpperBound": 1000
}

```

### Loading a Session for Replay

Retrieve a replayable timeline via API:

```http
POST /agentmemory/replay/load
Content-Type: application/json

{ "sessionId": "sess_abc123" }

```

**Response:**

```json
{
  "success": true,
  "timeline": {
    "events": [ … ],
    "eventCount": 78,
    "totalDurationMs": 41200
  },
  "session": { "id": "sess_abc123", "project": "my-project", … }
}

```

### Browser-Based Usage

Trigger imports and load sessions from the viewer interface:

```javascript
// Trigger import from the Replay tab
document.querySelector('[data-action="replay-import"]').click();

// Select the newly created session
state.replay.sessionSelect.value = "sess_abc123";
await loadReplay(); // invokes mem::replay::load internally

```

The viewer UI hooks are located in [`src/viewer/index.html#L3520-L3570`](https://github.com/rohitg00/agentmemory/blob/main/src/viewer/index.html#L3520-L3570).

## Summary

- **Security-first imports**: Path validation prevents sensitive files from entering the replay pipeline via regex patterns in `isSensitive`.
- **Two-phase architecture**: The `mem::replay::import-jsonl` function ingests and compresses data, while `mem::replay::load` projects observations into navigable timelines.
- **Content-addressed storage**: Crystals and lessons use fingerprint IDs to avoid duplication during re-imports of the same transcript.
- **Complete audit trail**: Every import generates an audit entry recording source, volume, and session identifiers.
- **Multiple interfaces**: Support for CLI (`agentmemory import-jsonl`), REST API (`POST /agentmemory/replay/import-jsonl`), and browser-based interactions.

## Frequently Asked Questions

### What file format does AgentMemory require for session replay imports?

AgentMemory accepts **Claude Code JSONL transcripts**—newline-delimited JSON files where each line represents a discrete interaction (user prompt, tool call, or assistant response). The `parseJsonlText` function in [`src/replay/jsonl-parser.ts`](https://github.com/rohitg00/agentmemory/blob/main/src/replay/jsonl-parser.ts) handles the parsing, converting valid lines into `RawObservation` objects while discarding malformed entries.

### How does AgentMemory prevent importing sensitive credentials?

The system implements path-based guard-rails through the `isSensitive` function in `src/functions/replay.ts#L24-L38`. This utility tests file paths against regular expressions matching keywords like "secret", "credentials", ".env", and common token filenames. Any matching path is rejected before file contents are read, ensuring passwords and API keys remain outside the observation store.

### What is the difference between a Crystal and a Lesson in the replay system?

A **Crystal** is a content-addressed snapshot summarizing a session's key outcomes (tools used, files affected) and serves as a permanent reference point. A **Lesson** is an auto-extracted heuristic or pattern identified during import (matching `LESSON_PATTERNS`) that represents reusable knowledge. While crystals capture *what happened*, lessons capture *what was learned*, both tagged with `fingerprintId` to prevent duplication.

### Can I replay a session without persisting it to the KV store?

No. The replay architecture requires persistent storage because the `mem::replay::load` function reads observations from the KV store and projects them through [`src/replay/timeline.ts`](https://github.com/rohitg00/agentmemory/blob/main/src/replay/timeline.ts). However, the system supports temporary or ephemeral sessions—once imported, you can delete the session record via the API if long-term persistence is not required.