How Session Replay Works with JSONL Transcript Imports in AgentMemory
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 rejects paths matching patterns for secrets, credentials, or environment files.
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. This function accepts an optional path parameter defaulting to ~/.claude/projects and respects a maxFiles limit to prevent runaway directory traversals.
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), which converts each line into a RawObservation object.
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, the logic either increments the observation count on existing sessions or initializes fresh Session objects in the KV store.
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.
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 asauto-importfor 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, 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 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. This function retrieves the session metadata and associated observations from the KV store.
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) 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:
# 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:
POST /agentmemory/replay/import-jsonl
Content-Type: application/json
{
"path": "/home/user/.claude/projects",
"maxFiles": 150
}
Response:
{
"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:
POST /agentmemory/replay/load
Content-Type: application/json
{ "sessionId": "sess_abc123" }
Response:
{
"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:
// 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.
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-jsonlfunction ingests and compresses data, whilemem::replay::loadprojects 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 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →