Skip to content

Latest commit

 

History

History
442 lines (338 loc) · 15.1 KB

File metadata and controls

442 lines (338 loc) · 15.1 KB

Session File Format

Sessions are stored as JSONL (JSON Lines) files. Each line is a JSON object with a type field. Session entries form a tree structure via id/parentId fields, enabling in-place branching without creating new files.

File Location

~/.dreb/agent/sessions/--<path>--/<timestamp>_<uuid>.jsonl

Where <path> is the working directory with / replaced by -.

Deleting Sessions

Sessions can be removed by deleting their .jsonl files under ~/.dreb/agent/sessions/.

dreb also supports deleting sessions interactively from /resume (select a session and press Ctrl+D, then confirm). When available, dreb uses the trash CLI to avoid permanent deletion.

Session Version

Sessions have a version field in the header:

  • Version 1: Linear entry sequence (legacy, auto-migrated on load)
  • Version 2: Tree structure with id/parentId linking
  • Version 3: Renamed hookMessage role to custom (extensions unification)

Existing sessions are automatically migrated to the current version (v3) when loaded.

Source Files

Source on GitHub (dreb):

For TypeScript definitions in your project, inspect node_modules/@dreb/coding-agent/dist/ and node_modules/@dreb/ai/dist/.

Message Types

Session entries contain AgentMessage objects. Understanding these types is essential for parsing sessions and writing extensions.

Content Blocks

Messages contain arrays of typed content blocks:

interface TextContent {
  type: "text";
  text: string;
}

interface ImageContent {
  type: "image";
  data: string;      // base64 encoded
  mimeType: string;  // e.g., "image/jpeg", "image/png"
}

interface ThinkingContent {
  type: "thinking";
  thinking: string;
}

interface ToolCall {
  type: "toolCall";
  id: string;
  name: string;
  arguments: Record<string, any>;
}

Base Message Types (from dreb-ai)

interface UserMessage {
  role: "user";
  content: string | (TextContent | ImageContent)[];
  timestamp: number;  // Unix ms
}

interface AssistantMessage {
  role: "assistant";
  content: (TextContent | ThinkingContent | ToolCall)[];
  api: string;
  provider: string;
  model: string;
  usage: Usage;
  stopReason: "stop" | "length" | "toolUse" | "error" | "aborted";
  errorMessage?: string;
  timestamp: number;
}

interface ToolResultMessage {
  role: "toolResult";
  toolCallId: string;
  toolName: string;
  content: (TextContent | ImageContent)[];
  details?: any;      // Tool-specific metadata
  isError: boolean;
  timestamp: number;
}

interface Usage {
  input: number;
  output: number;
  cacheRead: number;
  cacheWrite: number;
  totalTokens: number;
  cost: {
    input: number;
    output: number;
    cacheRead: number;
    cacheWrite: number;
    total: number;
  };
}

Extended Message Types (from dreb-coding-agent)

interface BashExecutionMessage {
  role: "bashExecution";
  command: string;
  output: string;
  exitCode: number | undefined;
  cancelled: boolean;
  truncated: boolean;
  fullOutputPath?: string;
  excludeFromContext?: boolean;  // true for !! prefix commands
  timestamp: number;
}

interface CustomMessage {
  role: "custom";
  customType: string;            // Extension identifier
  content: string | (TextContent | ImageContent)[];
  display: boolean;              // Show in TUI
  details?: any;                 // Extension-specific metadata
  timestamp: number;
}

interface BranchSummaryMessage {
  role: "branchSummary";
  summary: string;
  fromId: string;                // Entry we branched from
  timestamp: number;
}

interface CompactionSummaryMessage {
  role: "compactionSummary";
  summary: string;
  tokensBefore: number;
  timestamp: number;
}

AgentMessage Union

type AgentMessage =
  | UserMessage
  | AssistantMessage
  | ToolResultMessage
  | BashExecutionMessage
  | CustomMessage
  | BranchSummaryMessage
  | CompactionSummaryMessage;

Entry Base

All entries (except SessionHeader) extend SessionEntryBase:

interface SessionEntryBase {
  type: string;
  id: string;           // 8-char hex ID
  parentId: string | null;  // Parent entry ID (null for first entry)
  timestamp: string;    // ISO timestamp
}

Entry Types

SessionHeader

First line of the file. Metadata only, not part of the tree (no id/parentId).

{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}

For sessions with a parent (created via /fork or newSession({ parentSession })):

{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","parentSession":"/path/to/original/session.jsonl"}

For subagent child sessions (set via --agent-type):

{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project","agentType":"feature-dev"}

SessionMessageEntry

A message in the conversation. The message field contains an AgentMessage.

{"type":"message","id":"a1b2c3d4","parentId":"prev1234","timestamp":"2024-12-03T14:00:01.000Z","message":{"role":"user","content":"Hello"}}
{"type":"message","id":"b2c3d4e5","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:00:02.000Z","message":{"role":"assistant","content":[{"type":"text","text":"Hi!"}],"provider":"anthropic","model":"claude-sonnet-4-5","usage":{...},"stopReason":"stop"}}
{"type":"message","id":"c3d4e5f6","parentId":"b2c3d4e5","timestamp":"2024-12-03T14:00:03.000Z","message":{"role":"toolResult","toolCallId":"call_123","toolName":"bash","content":[{"type":"text","text":"output"}],"isError":false}}

ModelChangeEntry

Emitted when the user switches models mid-session.

{"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-4o"}

ThinkingLevelChangeEntry

Emitted when the user changes the thinking/reasoning level.

{"type":"thinking_level_change","id":"e5f6g7h8","parentId":"d4e5f6g7","timestamp":"2024-12-03T14:06:00.000Z","thinkingLevel":"high"}

CompactionEntry

Created when context is compacted. Stores a summary of earlier messages.

{"type":"compaction","id":"f6g7h8i9","parentId":"e5f6g7h8","timestamp":"2024-12-03T14:10:00.000Z","summary":"User discussed X, Y, Z...","firstKeptEntryId":"c3d4e5f6","tokensBefore":50000}

Optional fields:

  • details: Implementation-specific data (e.g., { readFiles: string[], modifiedFiles: string[] } for default, or custom data for extensions)
  • fromHook: true if generated by an extension, false/undefined if dreb-generated (legacy field name)

BranchSummaryEntry

Created when switching branches via /tree with an LLM generated summary of the left branch up to the common ancestor. Captures context from the abandoned path.

{"type":"branch_summary","id":"g7h8i9j0","parentId":"a1b2c3d4","timestamp":"2024-12-03T14:15:00.000Z","fromId":"f6g7h8i9","summary":"Branch explored approach A..."}

Optional fields:

  • details: File tracking data ({ readFiles: string[], modifiedFiles: string[] }) for default, or custom data for extensions
  • fromHook: true if generated by an extension, false/undefined if dreb-generated (legacy field name)

CustomEntry

Extension state persistence. Does NOT participate in LLM context.

{"type":"custom","id":"h8i9j0k1","parentId":"g7h8i9j0","timestamp":"2024-12-03T14:20:00.000Z","customType":"my-extension","data":{"count":42}}

Use customType to identify your extension's entries on reload.

CustomMessageEntry

Extension-injected messages that DO participate in LLM context.

{"type":"custom_message","id":"i9j0k1l2","parentId":"h8i9j0k1","timestamp":"2024-12-03T14:25:00.000Z","customType":"my-extension","content":"Injected context...","display":true}

Fields:

  • content: String or (TextContent | ImageContent)[] (same as UserMessage)
  • display: true = show in TUI with distinct styling, false = hidden
  • details: Optional extension-specific metadata (not sent to LLM)

LabelEntry

User-defined bookmark/marker on an entry.

{"type":"label","id":"j0k1l2m3","parentId":"i9j0k1l2","timestamp":"2024-12-03T14:30:00.000Z","targetId":"a1b2c3d4","label":"checkpoint-1"}

Set label to undefined to clear a label.

SessionInfoEntry

Session metadata (e.g., user-defined display name). Set via /name command or dreb.setSessionName() in extensions.

{"type":"session_info","id":"k1l2m3n4","parentId":"j0k1l2m3","timestamp":"2024-12-03T14:35:00.000Z","name":"Refactor auth module"}

The session name is displayed in the session selector (/resume) instead of the first message when set.

Tree Structure

Entries form a tree:

  • First entry has parentId: null
  • Each subsequent entry points to its parent via parentId
  • Branching creates new children from an earlier entry
  • The "leaf" is the current position in the tree
[user msg] ─── [assistant] ─── [user msg] ─── [assistant] ─┬─ [user msg] ← current leaf
                                                            │
                                                            └─ [branch_summary] ─── [user msg] ← alternate branch

Context Building

buildSessionContext() walks from the current leaf to the root, producing the message list for the LLM:

  1. Collects all entries on the path
  2. Extracts current model and thinking level settings
  3. If a CompactionEntry is on the path:
    • Emits the summary first
    • Then messages from firstKeptEntryId to compaction
    • Then messages after compaction
  4. Converts BranchSummaryEntry and CustomMessageEntry to appropriate message formats

Parsing Example

import { readFileSync } from "fs";

const lines = readFileSync("session.jsonl", "utf8").trim().split("\n");

for (const line of lines) {
  const entry = JSON.parse(line);

  switch (entry.type) {
    case "session":
      console.log(`Session v${entry.version ?? 1}: ${entry.id}`);
      break;
    case "message":
      console.log(`[${entry.id}] ${entry.message.role}: ${JSON.stringify(entry.message.content)}`);
      break;
    case "compaction":
      console.log(`[${entry.id}] Compaction: ${entry.tokensBefore} tokens summarized`);
      break;
    case "branch_summary":
      console.log(`[${entry.id}] Branch from ${entry.fromId}`);
      break;
    case "custom":
      console.log(`[${entry.id}] Custom (${entry.customType}): ${JSON.stringify(entry.data)}`);
      break;
    case "custom_message":
      console.log(`[${entry.id}] Extension message (${entry.customType}): ${entry.content}`);
      break;
    case "label":
      console.log(`[${entry.id}] Label "${entry.label}" on ${entry.targetId}`);
      break;
    case "model_change":
      console.log(`[${entry.id}] Model: ${entry.provider}/${entry.modelId}`);
      break;
    case "thinking_level_change":
      console.log(`[${entry.id}] Thinking: ${entry.thinkingLevel}`);
      break;
  }
}

SessionManager API

Key methods for working with sessions programmatically.

Static Creation Methods

  • SessionManager.create(cwd, sessionDir?) - New session
  • SessionManager.open(path, sessionDir?) - Open existing session file
  • SessionManager.continueRecent(cwd, sessionDir?) - Continue most recent or create new
  • SessionManager.inMemory(cwd?) - No file persistence
  • SessionManager.forkFrom(sourcePath, targetCwd, sessionDir?) - Fork session from another project

Static Listing Methods

  • SessionManager.list(cwd, sessionDir?, onProgress?) - List sessions for a directory
  • SessionManager.listAll(onProgress?) - List all sessions across all projects

Instance Methods - Session Management

  • newSession(options?) - Start a new session (options: { parentSession?: string })
  • setSessionFile(path) - Switch to a different session file
  • createBranchedSession(leafId) - Extract branch to new session file

Instance Methods - Appending (all return entry ID)

  • appendMessage(message) - Add message
  • appendThinkingLevelChange(level) - Record thinking change
  • appendModelChange(provider, modelId) - Record model change
  • appendCompaction(summary, firstKeptEntryId, tokensBefore, details?, fromHook?) - Add compaction
  • appendCustomEntry(customType, data?) - Extension state (not in context)
  • appendSessionInfo(name) - Set session display name
  • appendCustomMessageEntry(customType, content, display, details?) - Extension message (in context)
  • appendLabelChange(targetId, label) - Set/clear label

Instance Methods - Tree Navigation

  • getLeafId() - Current position
  • getLeafEntry() - Get current leaf entry
  • getEntry(id) - Get entry by ID
  • getBranch(fromId?) - Walk from entry to root
  • getTree() - Get full tree structure
  • getChildren(parentId) - Get direct children
  • getLabel(id) - Get label for entry
  • branch(entryId) - Move leaf to earlier entry
  • resetLeaf() - Reset leaf to null (before any entries)
  • branchWithSummary(entryId, summary, details?, fromHook?) - Branch with context summary

Instance Methods - Context & Info

  • buildSessionContext() - Get messages, thinkingLevel, and model for LLM
  • getEntries() - All entries (excluding header)
  • getHeader() - Session header metadata
  • getSessionName() - Get display name from latest session_info entry
  • getCwd() - Working directory
  • getSessionDir() - Session storage directory
  • getSessionId() - Session UUID
  • getSessionFile() - Session file path (undefined for in-memory)
  • isPersisted() - Whether session is saved to disk

Performance Log

Assistant response performance is recorded to a JSONL file for rolling TPS statistics:

~/.dreb/agent/performance.jsonl

Each line is a JSON object with the following fields:

Field Type Description
timestamp ISO string When the response completed
sessionId string Session that produced the response
provider string Model provider (e.g. anthropic)
modelId string Model identifier (e.g. claude-sonnet-4)
outputTokens number Output tokens from the response
durationMs number Response duration in milliseconds
tps number Computed tokens per second (outputTokens * 1000 / durationMs)

Entries are written atomically with file locking for safe concurrent access. The log is pruned daily, removing entries older than 30 days. Responses with stopReason of error or aborted, zero output tokens, or durations under 10ms are not recorded.