Agentic AI Atlasby a5c.ai
OverviewWikiGraphFor AgentsEdgesSearchWorkspace
/
GitHubDocsDiscord
iiRecord
Agentic AI Atlas · Session Manager and Session Data Access
page:docs-adapters-reference-07-session-managera5c.ai
Search record views/
Record · tabs

Available views

II.Record viewspp. 1 - 1
overviewarticlejsongraph
II.
Page JSON

page:docs-adapters-reference-07-session-manager

Structured · live

Session Manager and Session Data Access json

Inspect the normalized record payload exactly as the atlas UI reads it.

File · wiki/docs/adapters/reference/07-session-manager.mdCluster · wiki
Record JSON
{
  "id": "page:docs-adapters-reference-07-session-manager",
  "_kind": "Page",
  "_file": "wiki/docs/adapters/reference/07-session-manager.md",
  "_cluster": "wiki",
  "attributes": {
    "nodeKind": "Page",
    "sourcePath": "docs/adapters/reference/07-session-manager.md",
    "sourceKind": "repo-docs",
    "title": "Session Manager and Session Data Access",
    "displayName": "Session Manager and Session Data Access",
    "slug": "docs/adapters/reference/07-session-manager",
    "articlePath": "wiki/docs/adapters/reference/07-session-manager.md",
    "article": "\n# Session Manager and Session Data Access\n\n**Specification v1.0** | `@a5c-ai/adapters`\n\n> **SCOPE EXTENSION:** hermes-agent (`@NousResearch/hermes-agent`) is included as a 10th supported agent per explicit project requirements from the project owner. It extends the original scope document's 9 built-in agents. All hermes-specific content in this spec is marked with this same scope extension note.\n\n---\n\n## 1. Overview\n\nThe `SessionManager` provides read-only access to agent session data stored in each agent's native format and location. adapters does not own, replicate, or migrate session data -- it reads on demand, normalizes into unified types, and exposes a consistent query surface across all ten supported agents.\n\nThe session subsystem addresses four primary concerns:\n\n1. **Listing and retrieval.** Enumerate sessions for a given agent, filtered and sorted by various criteria, and retrieve full session content by ID.\n2. **Cross-agent search.** Full-text search across sessions from any combination of agents, with unified result ranking.\n3. **Cost aggregation.** Compute total spend across agents, models, time ranges, and tags.\n4. **Session interop.** Unified session IDs that map bidirectionally to each agent's native session identifiers, plus diff and export capabilities for cross-agent session comparison.\n\n### 1.1 Design Principles\n\n- **Read-only by contract.** `SessionManager` never writes to agent session files. Session creation and mutation happen exclusively through `RunHandle` (which spawns the agent subprocess) or the agent's own CLI. This avoids corruption of agent-native formats.\n- **Lazy parsing.** Session files are parsed on demand, not indexed eagerly. The `list()` method reads only metadata (file timestamps, lightweight header parsing) unless content is explicitly requested.\n- **Registry-mediated parsing.** Each persistent-session adapter registers how to find, list, and parse its native files. Existing agent adapter methods (`sessionDir()`, `listSessionFiles()`, and `parseSessionFile()`) remain supported compatibility shims, but the `SessionManager` routes through the shared session adapter contract.\n- **Unified ID scheme.** Every session gets a deterministic unified ID of the form `<agent>:<native-id>`. This enables cross-agent references without a central registry.\n- **Atlas-backed metadata.** Runtime session metadata comes from Atlas `SessionSemantics` and `PluginTarget` projections when available. Parser implementations still live with the target-specific codec because native file formats differ.\n\n### 1.2 Unified Persistent Session Adapter Contract\n\nThe persistent-session contract is the boundary between the SDK/gateway/CLI session surfaces and target-specific storage formats. A registered session adapter is read-only and provides:\n\n```typescript\ninterface PersistentSessionAdapter {\n  readonly agent: AgentName;\n  readonly metadata?: {\n    readonly sessionDirStrategy?: string;\n    readonly sessionPersistence?: string;\n    readonly pluginTargetId?: string;\n    readonly adapterName?: string;\n  };\n\n  sessionDir(cwd?: string): string;\n  listSessionFiles(cwd?: string): Promise<string[]>;\n  parseSessionFile(filePath: string): Promise<Session>;\n}\n```\n\nThe registry owns target lookup and ID mapping through `resolveUnifiedId(agent, nativeSessionId)` and `resolveNativeId(unifiedId)`. Target codecs own native parsing. This keeps the shared contract small enough for claude-code, codex, pi, gemini, opencode, and plugin-generated targets while preserving target-specific behavior such as JSONL message heuristics, tree linearization, and SQLite-backed sessions.\n\n#### Metadata Inputs\n\n- `SessionSemantics` supplies session directory strategy, explicit session ID flags, session ID sources, and resume semantics.\n- `PluginTarget` supplies target ID, adapter name, launch behavior, plugin-root environment variables, and generated-target aliases.\n- Runtime adapters may override directory resolution when Atlas describes the strategy but the concrete path depends on local CLI configuration or `cwd`.\n- The default client lazily registers current adapter instances into the session registry with Atlas-derived aliases, so adapters added during CLI bootstrap or plugin loading can be reached by `PluginTarget.targetId` and `adapterName`.\n\nAtlas metadata is a tested input to registry construction, not a replacement for parser code. If Atlas metadata is missing or incomplete, the compatibility wrapper around the existing adapter methods remains the source of truth for that adapter.\n\n#### Parser Responsibilities\n\nEach target parser must:\n\n1. Preserve the native session ID exactly as exposed by the agent.\n2. Return normalized `Session` / `FullSession` data without mutating native files.\n3. Skip malformed records inside otherwise readable append-only files where the existing codec already does so.\n4. Throw for unreadable or unsupported full-session reads while allowing list operations to skip unparseable files.\n5. Preserve raw target data in `raw` when lossy normalization would hide useful native details.\n\n#### Compatibility Rules\n\n- `AgentAdapter.sessionDir()`, `AgentAdapter.listSessionFiles()`, and `AgentAdapter.parseSessionFile()` remain public during the migration.\n- Existing callers that invoke those methods directly continue to work.\n- New SDK, CLI, and gateway session paths should use the shared registry so listing, get/full, export, and resume behavior converge.\n- Unified IDs remain deterministic: `<agent>:<nativeSessionId>`. Native IDs are never rewritten to match unified IDs.\n- Plugin-generated targets register under both `PluginTarget.targetId` and `PluginTarget.adapterName` when those differ.\n\n#### Gateway and CLI Behavior\n\nThe CLI continues to call `client.sessions.*`; it does not parse native files independently. Gateway session content loading also prefers the shared `client.sessions.get(agent, sessionId)` path so `/api/v1/sessions/:id` and `/api/v1/sessions/:id/full` match SDK output. The gateway keeps a direct adapter fallback only for minimal embedded clients that expose adapter file methods but not a `sessions` facade.\n\n#### Troubleshooting\n\n- If `list()` omits a session, confirm the target is registered in the session adapter registry and that `listSessionFiles()` returns the native file path.\n- If `get()` cannot find a session that appears in `list()`, compare the native session ID from the parser with the filename or database record ID. The parser must return the native ID, not the unified ID.\n- If a plugin-generated target resolves under one name but not another, verify the Atlas `PluginTarget.targetId`, `adapterName`, and generated registry aliases.\n- If Atlas metadata differs from runtime behavior, prefer the parser/runtime result for compatibility and add or correct the `SessionSemantics` / `PluginTarget` graph record with a projection test.\n\n### 1.3 Cross-References\n\n| Type / Concept | Spec | Section |\n|---|---|---|\n| `AgentName`, `BuiltInAgentName` | `01-core-types-and-client.md` | 1.4 |\n| `AgentMuxClient`, `createClient()` | `01-core-types-and-client.md` | 5 |\n| `RunOptions` | `02-run-options-and-profiles.md` | 2 |\n| `RunHandle` | `03-run-handle-and-interaction.md` | 2 |\n| `AgentEvent`, `BaseEvent` | `04-agent-events.md` | 2 |\n| `CostRecord` | `01-core-types-and-client.md` | 4.2.3 |\n| `AgentAdapter.parseSessionFile()` | `05-adapter-system.md` | 2 |\n| `AgentAdapter.listSessionFiles()` | `05-adapter-system.md` | 2 |\n| `AgentAdapter.sessionDir()` | `05-adapter-system.md` | 2 |\n| `AgentCapabilities.sessionPersistence` | `06-capabilities-and-models.md` | 2 |\n| `AgentCapabilities.canResume` | `06-capabilities-and-models.md` | 2 |\n| `AgentCapabilities.canFork` | `06-capabilities-and-models.md` | 2 |\n| `ErrorCode`, `AgentMuxError` | `01-core-types-and-client.md` | 3.1 |\n| `ConfigManager` | `08-config-and-auth.md` | 2 |\n| `SessionSemantics` | `packages/atlas/graph/lifecycle/session-semantics/*.yaml` | graph |\n| `PluginTarget` | `packages/atlas/graph/extensions/plugin-artifacts/*.yaml` | graph |\n\n### 1.4 Access Point\n\n```typescript\nconst adapter = createClient();\n\n// All session operations via the sessions namespace:\nconst sessions = await adapter.sessions.list('claude');\nconst session  = await adapter.sessions.get('claude', 'abc123');\nconst results  = await adapter.sessions.search({ text: 'refactor auth' });\nconst cost     = await adapter.sessions.totalCost({ agent: 'claude' });\n```\n\n---\n\n## 2. SessionManager Interface\n\n```typescript\ninterface SessionManager {\n  /**\n   * List sessions for a specific agent, with optional filtering and sorting.\n   *\n   * Returns lightweight SessionSummary objects. Full session content is not\n   * loaded -- use get() for that.\n   *\n   * @param agent - The agent whose sessions to list.\n   * @param options - Filtering, sorting, and pagination options.\n   * @returns Array of session summaries, sorted per options.sort (default: date descending).\n   * @throws AgentMuxError with code 'AGENT_NOT_FOUND' if agent is unknown.\n   */\n  list(agent: AgentName, options?: SessionListOptions): Promise<SessionSummary[]>;\n\n  /**\n   * Retrieve the full content of a single session.\n   *\n   * Delegates to the agent adapter's parseSessionFile() to read and normalize\n   * the native session format into the unified Session type.\n   *\n   * @param agent - The agent that owns the session.\n   * @param sessionId - The native session ID (agent-specific format).\n   * @returns The full session with all messages, tool calls, and cost data.\n   * @throws AgentMuxError with code 'SESSION_NOT_FOUND' if no session with that ID exists.\n   * @throws AgentMuxError with code 'PARSE_ERROR' if the session file is corrupt or unreadable.\n   */\n  get(agent: AgentName, sessionId: string): Promise<Session>;\n\n  /**\n   * Full-text search across sessions from one or more agents.\n   *\n   * When query.agent is specified, searches only that agent's sessions.\n   * When omitted, searches all agents that have detectable session storage.\n   *\n   * For agents with native search capabilities (hermes with FTS5), the adapter\n   * delegates to the native search engine. For file-based agents, adapters\n   * performs in-process text matching over parsed session content.\n   *\n   * @param query - Search criteria including text, agent filter, date range, and limit.\n   * @returns Array of matching session summaries, ranked by relevance.\n   */\n  search(query: SessionQuery): Promise<SessionSummary[]>;\n\n  /**\n   * Aggregate cost data across sessions.\n   *\n   * Reads cost records from session metadata and run-index entries.\n   * Supports grouping by agent, model, day, or tag.\n   *\n   * @param options - Filtering and grouping criteria.\n   * @returns Aggregated cost summary with breakdowns per the requested grouping.\n   */\n  totalCost(options?: CostAggregationOptions): Promise<CostSummary>;\n\n  /**\n   * Export a session in the specified format.\n   *\n   * - 'json': Full Session object serialized as a JSON string.\n   * - 'jsonl': One JSON object per message/event, one per line.\n   * - 'markdown': Human-readable Markdown with message attribution, tool call\n   *   formatting, and cost summary.\n   *\n   * @param agent - The agent that owns the session.\n   * @param sessionId - The native session ID.\n   * @param format - Output format.\n   * @returns The exported session as a string in the requested format.\n   * @throws AgentMuxError with code 'SESSION_NOT_FOUND' if no session exists.\n   */\n  export(agent: AgentName, sessionId: string, format: 'json' | 'jsonl' | 'markdown'): Promise<string>;\n\n  /**\n   * Compute a structural diff between two sessions.\n   *\n   * Sessions may belong to the same agent or different agents. The diff\n   * compares normalized message sequences and identifies additions, removals,\n   * and modifications at the message level.\n   *\n   * Primary use case: comparing a forked session against its parent, or\n   * comparing how two different agents handled the same prompt.\n   *\n   * @param a - First session reference (agent + sessionId).\n   * @param b - Second session reference (agent + sessionId).\n   * @returns A SessionDiff describing the structural differences.\n   * @throws AgentMuxError with code 'SESSION_NOT_FOUND' if either session is missing.\n   */\n  diff(\n    a: { agent: AgentName; sessionId: string },\n    b: { agent: AgentName; sessionId: string }\n  ): Promise<SessionDiff>;\n\n  /**\n   * Map a native agent session ID to a unified cross-agent ID.\n   *\n   * The unified ID format is deterministic: `<agent>:<nativeSessionId>`.\n   * This is a pure function with no I/O -- it does not verify the session exists.\n   *\n   * @param agent - The agent name.\n   * @param nativeSessionId - The agent's native session identifier.\n   * @returns The unified session ID string.\n   */\n  resolveUnifiedId(agent: AgentName, nativeSessionId: string): string;\n\n  /**\n   * Parse a unified session ID back into its agent and native ID components.\n   *\n   * Returns null if the string does not match the unified ID format or\n   * references an unknown agent.\n   *\n   * @param unifiedId - A unified ID of the form `<agent>:<nativeSessionId>`.\n   * @returns The parsed components, or null if the ID is invalid.\n   */\n  resolveNativeId(unifiedId: string): { agent: AgentName; nativeSessionId: string } | null;\n}\n```\n\n### 2.1 Method Summary\n\n| Method | I/O | Returns | Throws |\n|---|---|---|---|\n| `list()` | Reads session directory metadata | `SessionSummary[]` | `AGENT_NOT_FOUND` |\n| `get()` | Parses full session file | `Session` | `SESSION_NOT_FOUND`, `PARSE_ERROR` |\n| `search()` | Scans session content (or native FTS) | `SessionSummary[]` | -- |\n| `totalCost()` | Reads cost records from sessions + run index | `CostSummary` | -- |\n| `export()` | Parses and serializes session | `string` | `SESSION_NOT_FOUND` |\n| `diff()` | Parses both sessions and compares | `SessionDiff` | `SESSION_NOT_FOUND` |\n| `resolveUnifiedId()` | Pure (no I/O) | `string` | -- |\n| `resolveNativeId()` | Pure (no I/O) | `{ agent, nativeSessionId } \\| null` | -- |\n\n---\n\n## 3. Supporting Types\n\n### 3.1 SessionSummary\n\nA lightweight representation of a session, returned by `list()` and `search()`. Does not include full message content.\n\n```typescript\ninterface SessionSummary {\n  /** The agent that owns this session. */\n  agent: AgentName;\n\n  /** The agent's native session identifier. */\n  sessionId: string;\n\n  /** The deterministic unified ID: `<agent>:<sessionId>`. */\n  unifiedId: string;\n\n  /** Human-readable session title (first user message truncated, or agent-provided title). */\n  title: string;\n\n  /** When the session was created. */\n  createdAt: Date;\n\n  /** When the session was last modified. */\n  updatedAt: Date;\n\n  /** Total number of conversational turns (user-assistant pairs). */\n  turnCount: number;\n\n  /** Total number of messages (all roles). */\n  messageCount: number;\n\n  /** Model ID used in the session (the primary or most-used model). */\n  model?: string;\n\n  /** Aggregated cost for the entire session, if available. */\n  cost?: CostRecord;\n\n  /** Consumer-provided tags from RunOptions.tags, if any. */\n  tags: string[];\n\n  /** The working directory the session was started in, if detectable. */\n  cwd?: string;\n\n  /** Whether this session was forked from another. */\n  forkedFrom?: string;\n\n  /** Relevance score (0.0 to 1.0), present only in search results. */\n  relevanceScore?: number;\n}\n```\n\n| Field | Type | Required | Default | Description |\n|---|---|---|---|---|\n| `agent` | `AgentName` | Yes | -- | The owning agent. |\n| `sessionId` | `string` | Yes | -- | Native session ID. |\n| `unifiedId` | `string` | Yes | -- | Unified cross-agent ID (`<agent>:<sessionId>`). |\n| `title` | `string` | Yes | `''` | Session title or truncated first prompt. |\n| `createdAt` | `Date` | Yes | -- | Session creation timestamp. |\n| `updatedAt` | `Date` | Yes | -- | Last modification timestamp. |\n| `turnCount` | `number` | Yes | `0` | Number of user-assistant turn pairs. |\n| `messageCount` | `number` | Yes | `0` | Total message count across all roles. |\n| `model` | `string` | No | `undefined` | Primary model used. |\n| `cost` | `CostRecord` | No | `undefined` | Aggregated session cost. |\n| `tags` | `string[]` | Yes | `[]` | Tags from run metadata. |\n| `cwd` | `string` | No | `undefined` | Working directory at session start. |\n| `forkedFrom` | `string` | No | `undefined` | Parent session ID if forked. |\n| `relevanceScore` | `number` | No | `undefined` | Search relevance (0.0--1.0). |\n\n### 3.2 Session\n\nThe full session object including all messages and metadata. Returned by `get()`.\n\n```typescript\ninterface Session {\n  /** The agent that owns this session. */\n  agent: AgentName;\n\n  /** The agent's native session identifier. */\n  sessionId: string;\n\n  /** The deterministic unified ID: `<agent>:<sessionId>`. */\n  unifiedId: string;\n\n  /** Human-readable session title. */\n  title: string;\n\n  /** When the session was created. */\n  createdAt: Date;\n\n  /** When the session was last modified. */\n  updatedAt: Date;\n\n  /** Total number of conversational turns. */\n  turnCount: number;\n\n  /** Model ID used in the session. */\n  model?: string;\n\n  /** Aggregated cost for the entire session. */\n  cost?: CostRecord;\n\n  /** Consumer-provided tags. */\n  tags: string[];\n\n  /** Working directory at session start. */\n  cwd?: string;\n\n  /** Parent session ID if this session was forked. */\n  forkedFrom?: string;\n\n  /** The ordered list of messages in this session. */\n  messages: SessionMessage[];\n\n  /**\n   * Raw session data in the agent's native format.\n   * Preserved for consumers that need agent-specific fields not\n   * captured in the normalized SessionMessage type.\n   */\n  raw?: unknown;\n}\n\ninterface SessionMessage {\n  /** Role of the message author. */\n  role: 'user' | 'assistant' | 'system' | 'tool';\n\n  /** Text content of the message. Empty string for tool-only messages. */\n  content: string;\n\n  /** Timestamp when this message was recorded. */\n  timestamp?: Date;\n\n  /** Tool calls initiated by this message (assistant role only). */\n  toolCalls?: SessionToolCall[];\n\n  /** Tool result (tool role only). */\n  toolResult?: {\n    toolCallId: string;\n    toolName: string;\n    output: unknown;\n  };\n\n  /** Token usage for this message, if available. */\n  tokenUsage?: {\n    inputTokens: number;\n    outputTokens: number;\n    thinkingTokens?: number;\n    cachedTokens?: number;\n  };\n\n  /** Cost for this individual message, if available. */\n  cost?: CostRecord;\n\n  /** Thinking/reasoning content, if the agent exposed it. */\n  thinking?: string;\n\n  /** Model used for this specific message (may differ within a session). */\n  model?: string;\n}\n\ninterface SessionToolCall {\n  /** Tool call ID (agent-assigned). */\n  toolCallId: string;\n\n  /** Name of the tool that was called. */\n  toolName: string;\n\n  /** Input arguments passed to the tool. */\n  input: unknown;\n\n  /** Tool output, if available. */\n  output?: unknown;\n\n  /** Duration of the tool call in milliseconds, if recorded. */\n  durationMs?: number;\n}\n```\n\n### 3.3 SessionQuery\n\nParameters for the `search()` method.\n\n```typescript\ninterface SessionQuery {\n  /** Free-text search string. Matched against message content and session titles. */\n  text: string;\n\n  /**\n   * Restrict search to a single agent. When omitted, searches all agents\n   * that have detectable session storage on the local machine.\n   */\n  agent?: AgentName;\n\n  /** Only include sessions created on or after this date. */\n  since?: Date;\n\n  /** Only include sessions created on or before this date. */\n  until?: Date;\n\n  /** Filter to sessions that used a specific model. */\n  model?: string;\n\n  /** Filter to sessions with any of the specified tags. */\n  tags?: string[];\n\n  /** Maximum number of results to return. Default: 50. */\n  limit?: number;\n\n  /** Sort order for results. Default: 'relevance'. */\n  sort?: 'relevance' | 'date' | 'cost';\n}\n```\n\n| Field | Type | Required | Default | Description |\n|---|---|---|---|---|\n| `text` | `string` | Yes | -- | Full-text search string. |\n| `agent` | `AgentName` | No | all agents | Restrict to one agent. |\n| `since` | `Date` | No | `undefined` | Lower bound on creation date. |\n| `until` | `Date` | No | `undefined` | Upper bound on creation date. |\n| `model` | `string` | No | `undefined` | Filter by model ID. |\n| `tags` | `string[]` | No | `undefined` | Filter by tags (OR match). |\n| `limit` | `number` | No | `50` | Max results. |\n| `sort` | `string` | No | `'relevance'` | Result ordering. |\n\n### 3.4 SessionListOptions\n\nParameters for the `list()` method.\n\n```typescript\ninterface SessionListOptions {\n  /** Only include sessions created on or after this date. */\n  since?: Date;\n\n  /** Only include sessions created on or before this date. */\n  until?: Date;\n\n  /** Filter to sessions that used a specific model. */\n  model?: string;\n\n  /** Filter to sessions with any of the specified tags. */\n  tags?: string[];\n\n  /** Maximum number of results to return. Default: 100. */\n  limit?: number;\n\n  /** Sort field and direction. Default: 'date'. */\n  sort?: 'date' | 'cost' | 'turns';\n\n  /** Sort direction. Default: 'desc'. */\n  sortDirection?: 'asc' | 'desc';\n\n  /** Filter to sessions started in a specific working directory. */\n  cwd?: string;\n}\n```\n\n| Field | Type | Required | Default | Description |\n|---|---|---|---|---|\n| `since` | `Date` | No | `undefined` | Lower bound on creation date. |\n| `until` | `Date` | No | `undefined` | Upper bound on creation date. |\n| `model` | `string` | No | `undefined` | Filter by model ID. |\n| `tags` | `string[]` | No | `undefined` | Filter by tags (OR match). |\n| `limit` | `number` | No | `100` | Max results returned. |\n| `sort` | `string` | No | `'date'` | Sort field. |\n| `sortDirection` | `string` | No | `'desc'` | Sort direction. |\n| `cwd` | `string` | No | `undefined` | Filter by working directory. |\n\n### 3.5 CostAggregationOptions\n\nParameters for the `totalCost()` method.\n\n```typescript\ninterface CostAggregationOptions {\n  /** Restrict to a single agent. When omitted, aggregates across all agents. */\n  agent?: AgentName;\n\n  /** Only include sessions created on or after this date. */\n  since?: Date;\n\n  /** Only include sessions created on or before this date. */\n  until?: Date;\n\n  /** Filter to sessions that used a specific model. */\n  model?: string;\n\n  /** Filter to sessions with any of the specified tags. */\n  tags?: string[];\n\n  /**\n   * Group results by the specified dimension.\n   * When omitted, returns a single aggregate CostSummary.\n   */\n  groupBy?: 'agent' | 'model' | 'day' | 'tag';\n}\n```\n\n| Field | Type | Required | Default | Description |\n|---|---|---|---|---|\n| `agent` | `AgentName` | No | all agents | Restrict to one agent. |\n| `since` | `Date` | No | `undefined` | Lower bound on session date. |\n| `until` | `Date` | No | `undefined` | Upper bound on session date. |\n| `model` | `string` | No | `undefined` | Filter by model ID. |\n| `tags` | `string[]` | No | `undefined` | Filter by tags (OR match). |\n| `groupBy` | `string` | No | `undefined` | Dimension for grouped breakdowns. |\n\n### 3.6 CostSummary\n\nThe return type of `totalCost()`.\n\n```typescript\ninterface CostSummary {\n  /** Total aggregated cost in USD. */\n  totalUsd: number;\n\n  /** Total input tokens across all matching sessions. */\n  inputTokens: number;\n\n  /** Total output tokens across all matching sessions. */\n  outputTokens: number;\n\n  /** Total thinking/reasoning tokens, if applicable. */\n  thinkingTokens: number;\n\n  /** Total cached tokens, if applicable. */\n  cachedTokens: number;\n\n  /** Number of sessions included in this aggregation. */\n  sessionCount: number;\n\n  /** Number of runs included (a session may span multiple runs). */\n  runCount: number;\n\n  /**\n   * Grouped breakdowns, present when CostAggregationOptions.groupBy is set.\n   * Keys are the group values (agent names, model IDs, date strings, or tag strings).\n   */\n  breakdowns?: Record<string, CostBreakdown>;\n}\n\ninterface CostBreakdown {\n  /** The group key (agent name, model ID, ISO date string, or tag). */\n  key: string;\n\n  /** Total cost in USD for this group. */\n  totalUsd: number;\n\n  /** Input tokens for this group. */\n  inputTokens: number;\n\n  /** Output tokens for this group. */\n  outputTokens: number;\n\n  /** Thinking tokens for this group. */\n  thinkingTokens: number;\n\n  /** Cached tokens for this group. */\n  cachedTokens: number;\n\n  /** Sessions in this group. */\n  sessionCount: number;\n}\n```\n\n| Field | Type | Required | Default | Description |\n|---|---|---|---|---|\n| `totalUsd` | `number` | Yes | -- | Total cost in USD. |\n| `inputTokens` | `number` | Yes | -- | Total input tokens. |\n| `outputTokens` | `number` | Yes | -- | Total output tokens. |\n| `thinkingTokens` | `number` | Yes | `0` | Total thinking tokens. |\n| `cachedTokens` | `number` | Yes | `0` | Total cached tokens. |\n| `sessionCount` | `number` | Yes | -- | Sessions in aggregation. |\n| `runCount` | `number` | Yes | -- | Runs in aggregation. |\n| `breakdowns` | `Record<string, CostBreakdown>` | No | `undefined` | Per-group breakdowns when `groupBy` is set. |\n\n### 3.7 SessionDiff\n\nThe return type of `diff()`.\n\n```typescript\ninterface SessionDiff {\n  /** Reference to the first session (left side). */\n  a: { agent: AgentName; sessionId: string; unifiedId: string };\n\n  /** Reference to the second session (right side). */\n  b: { agent: AgentName; sessionId: string; unifiedId: string };\n\n  /** Ordered list of diff operations describing structural differences. */\n  operations: DiffOperation[];\n\n  /** Summary statistics. */\n  stats: {\n    /** Messages only in session A. */\n    removals: number;\n    /** Messages only in session B. */\n    additions: number;\n    /** Messages present in both but with different content. */\n    modifications: number;\n    /** Messages identical in both sessions. */\n    unchanged: number;\n  };\n}\n\ninterface DiffOperation {\n  /** The type of difference. */\n  type: 'addition' | 'removal' | 'modification' | 'unchanged';\n\n  /** Zero-based index in session A (undefined for additions). */\n  indexA?: number;\n\n  /** Zero-based index in session B (undefined for removals). */\n  indexB?: number;\n\n  /** The message from session A (undefined for additions). */\n  messageA?: SessionMessage;\n\n  /** The message from session B (undefined for removals). */\n  messageB?: SessionMessage;\n}\n```\n\n---\n\n## 4. Native Session File Locations\n\nadapters reads session data from each agent's native storage location. The `SessionManager` never writes to these locations. The shared persistent-session registry resolves the target and metadata, then delegates to the target parser. Existing agent adapter methods (`sessionDir()`, `listSessionFiles()`, and `parseSessionFile()`) remain the compatibility surface for direct callers and for adapters that have not yet registered a richer session adapter.\n\n### 4.1 Session Storage by Agent\n\n| Agent | Session Storage Path | Format | Persistence |\n|---|---|---|---|\n| Claude Code | `~/.claude/projects/<hash>/` | JSONL per session | file |\n| Codex CLI | `~/.codex/sessions/` | JSONL | file |\n| Gemini CLI | `~/.gemini/sessions/` | JSONL | file |\n| Copilot CLI | `~/.config/github-copilot/sessions/` | JSON | file |\n| Cursor | `~/.cursor/sessions/` | SQLite | sqlite |\n| OpenCode | `~/.local/share/opencode/` | SQLite | sqlite |\n| Pi | `~/.pi/agent/sessions/` | JSONL tree (id + parentId) | file |\n| omp | `~/.omp/agent/sessions/` | JSONL tree (id + parentId) | file |\n| OpenClaw | `~/.openclaw/sessions/` | JSON per channel/session | file |\n| Hermes | `~/.hermes/` | SQLite with FTS5 | sqlite |\n\n### 4.2 Per-Agent Session Parsing Notes\n\n#### 4.2.1 Claude Code (`claude`)\n\n- **Path pattern:** `~/.claude/projects/<project-hash>/<session-id>.jsonl`\n- **Format:** Each line is a JSON object representing one event (message, tool call, cost record, etc.).\n- **Project hash:** Derived from the absolute path of the working directory. The adapter computes this hash to locate sessions for a given project.\n- **Session ID:** The filename stem (without `.jsonl` extension).\n- **Cost data:** Embedded as `cost` events within the JSONL stream; aggregated by summing all `cost` entries.\n- **Fork support:** Forked sessions reference a `forkedFrom` field in the first line's metadata object.\n- **Title extraction:** First user message content, truncated to 100 characters.\n\n#### 4.2.2 Codex CLI (`codex`)\n\n- **Path pattern:** `~/.codex/sessions/<session-id>.jsonl`\n- **Format:** JSONL with one event per line. Similar structure to Claude Code but with OpenAI-specific field names.\n- **Session ID:** The filename stem.\n- **Cost data:** Token counts are present per-message; cost in USD is computed using the model registry's `estimateCost()` when not natively provided.\n- **Fork support:** Not supported (`canFork: false`).\n- **Title extraction:** First user message content, truncated to 100 characters.\n\n#### 4.2.3 Gemini CLI (`gemini`)\n\n- **Path pattern:** `~/.gemini/sessions/<session-id>.jsonl`\n- **Format:** JSONL with one event per line. Uses Google-specific message role names (`user`, `model` mapped to `assistant`).\n- **Session ID:** The filename stem.\n- **Cost data:** Token counts present; USD cost estimated via model registry.\n- **Fork support:** Not supported.\n- **Title extraction:** First user message content, truncated to 100 characters.\n- **Special handling:** Gemini's `model` role is normalized to `assistant` during parsing. Safety-filter metadata is preserved in `raw`.\n\n#### 4.2.4 Copilot CLI (`copilot`)\n\n- **Path pattern:** `~/.config/github-copilot/sessions/<session-id>.json`\n- **Format:** Single JSON file per session containing the full conversation as an array of messages.\n- **Session ID:** The filename stem.\n- **Cost data:** Not natively provided. Token counts may be absent; cost estimation is best-effort via model registry.\n- **Fork support:** Not supported.\n- **Title extraction:** First user message content, truncated to 100 characters.\n- **Special handling:** The `gh copilot` CLI stores sessions as complete JSON documents rather than append-only streams. The adapter reads the entire file on each access.\n\n#### 4.2.5 Cursor (`cursor`)\n\n- **Path pattern:** `~/.cursor/sessions/sessions.db` (single SQLite database)\n- **Format:** SQLite database with tables for sessions and messages.\n- **Session ID:** Integer primary key or UUID from the sessions table, depending on Cursor version.\n- **Cost data:** Token usage stored per-message in the messages table; cost estimated via model registry.\n- **Fork support:** Not supported.\n- **Title extraction:** From the `title` column in the sessions table, falling back to first user message.\n- **Special handling:** The adapter opens the SQLite database in read-only mode (`SQLITE_OPEN_READONLY`). Concurrent access is safe because SQLite supports multiple readers. The `watch()` method uses polling (default interval: 2 seconds) since filesystem watch on a SQLite WAL file is unreliable.\n\n#### 4.2.6 OpenCode (`opencode`)\n\n- **Path pattern:** `~/.local/share/opencode/opencode.db` (single SQLite database)\n- **Format:** SQLite database with sessions, messages, and tool_calls tables.\n- **Session ID:** UUID from the sessions table.\n- **Cost data:** Token usage and cost stored per-message.\n- **Fork support:** Supported (`canFork: true`). Forked sessions reference parent via a `parent_id` column.\n- **Title extraction:** From the `title` column in the sessions table.\n- **Special handling:** Opened in read-only mode. OpenCode uses WAL mode; the adapter respects this by never acquiring write locks. The `watch()` method polls at 2-second intervals.\n\n#### 4.2.7 Pi (`pi`)\n\n- **Path pattern:** `~/.pi/agent/sessions/<session-id>/`\n- **Format:** JSONL tree structure. Each session is a directory containing one or more `.jsonl` files. Messages have `id` and `parentId` fields forming a tree (branching conversations).\n- **Session ID:** The directory name.\n- **Cost data:** Token counts per-message; cost estimated via model registry.\n- **Fork support:** Supported via the tree structure. Forked branches share a common ancestor identified by `parentId` chains.\n- **Title extraction:** Content of the root message (the message with no `parentId`).\n- **Special handling:** The adapter linearizes the message tree by following the most recent branch (highest timestamp at each fork point) when constructing `Session.messages`. The full tree structure is preserved in `Session.raw` for consumers that need branch-aware access.\n\n#### 4.2.8 omp (`omp`)\n\n- **Path pattern:** `~/.omp/agent/sessions/<session-id>/`\n- **Format:** JSONL tree structure, identical format to Pi (shared codebase heritage). Messages have `id` and `parentId` fields.\n- **Session ID:** The directory name.\n- **Cost data:** Token counts per-message; cost estimated via model registry.\n- **Fork support:** Supported via tree structure, same as Pi.\n- **Title extraction:** Content of the root message.\n- **Special handling:** Same tree linearization strategy as Pi. The adapter shares the tree-parsing implementation with the Pi adapter via a common `JsonlTreeParser` utility in `BaseAgentAdapter`.\n\n#### 4.2.9 OpenClaw (`openclaw`)\n\n- **Path pattern:** `~/.openclaw/sessions/<channel>/<session-id>.json`\n- **Format:** Single JSON file per session, organized by channel (e.g., `cli`, `telegram`, `discord`, `slack`). Each file contains the complete conversation as a structured JSON document.\n- **Session ID:** The filename stem. The channel is part of the path but not part of the session ID.\n- **Cost data:** Token usage and cost stored per-message within the JSON structure.\n- **Fork support:** Not supported.\n- **Title extraction:** From the `title` field in the JSON root, falling back to first user message.\n- **Special handling:** The channel subdirectory is detected and included in `SessionSummary` metadata (via `raw`). Multi-channel sessions (same conversation across channels) are treated as separate sessions. The `list()` method scans all channel subdirectories.\n\n#### 4.2.10 Hermes (`hermes`)\n\n- **Path pattern:** `~/.hermes/` (SQLite database with FTS5 extensions; exact filename derived from the project's default config)\n- **Format:** SQLite database using FTS5 full-text search virtual tables for session and memory content. Session data includes conversation turns, tool calls, and agent-curated memory entries.\n- **Session ID:** UUID from the sessions table.\n- **Cost data:** Token usage stored per-turn; cost estimated via model registry when not natively provided.\n- **Fork support:** Not supported.\n- **Title extraction:** From the session metadata table or LLM-generated session summary stored by Hermes.\n- **Special handling:** Hermes is the only agent that uses FTS5, which provides native full-text search with relevance ranking. The `search()` method delegates directly to Hermes' FTS5 `MATCH` queries when `query.agent === 'hermes'`, avoiding in-process text scanning. The adapter uses the `fts5` SQLite extension and issues `SELECT ... FROM sessions_fts WHERE sessions_fts MATCH ?` queries for search. The database is opened in read-only mode. Memory entries (agent-curated persistent notes stored in a separate FTS5 table) are excluded from session listing but included in search results when relevant. The Honcho dialectic user modeling data, if present, is not exposed through the session interface. The `watch()` method polls at 2-second intervals. Hermes requires Python >= 3.11 and is installed via pip/uv rather than npm; the adapter handles this difference in detection and version checking.\n\n---\n\n## 5. Unified Session ID Scheme\n\n### 5.1 Format\n\nEvery session is addressable by a unified ID of the form:\n\n```\n<agent>:<nativeSessionId>\n```\n\nExamples:\n- `claude:a1b2c3d4`\n- `codex:session-2025-01-15-001`\n- `cursor:42`\n- `hermes:550e8400-e29b-41d4-a716-446655440000`\n- `pi:proj-xyz/branch-main`\n\n### 5.2 resolveUnifiedId()\n\n```typescript\n// Pure function, no I/O, no validation of session existence.\nconst unifiedId = adapter.sessions.resolveUnifiedId('claude', 'a1b2c3d4');\n// => 'claude:a1b2c3d4'\n```\n\nThe unified ID is constructed by joining the agent name and native session ID with a single colon. The agent name portion always matches one of the `BuiltInAgentName` values or a registered plugin adapter name.\n\n### 5.3 resolveNativeId()\n\n```typescript\nconst result = adapter.sessions.resolveNativeId('claude:a1b2c3d4');\n// => { agent: 'claude', nativeSessionId: 'a1b2c3d4' }\n\nconst invalid = adapter.sessions.resolveNativeId('not-a-valid-id');\n// => null\n```\n\nParsing rules:\n1. Split on the first colon only (native IDs may contain colons).\n2. Validate the agent portion against registered adapter names.\n3. Return `null` if no colon is present or the agent is unknown.\n\n---\n\n## 6. Method Behaviors\n\n### 6.1 list()\n\n```typescript\nconst recent = await adapter.sessions.list('claude', {\n  since: new Date('2025-01-01'),\n  sort: 'cost',\n  sortDirection: 'desc',\n  limit: 20,\n});\n```\n\n**Behavior:**\n\n1. Resolves the persistent-session adapter via the session registry, falling back to the legacy adapter wrapper when needed.\n2. Calls `sessionAdapter.listSessionFiles(options?.cwd)` to enumerate session file paths.\n3. For each file, reads lightweight metadata (file timestamps, header lines for JSONL, or summary queries for SQLite) without parsing full content.\n4. Applies filters (`since`, `until`, `model`, `tags`, `cwd`) in-memory after metadata extraction.\n5. Sorts results per `options.sort` and `options.sortDirection`.\n6. Truncates to `options.limit`.\n7. Returns `SessionSummary[]`.\n\n**Performance considerations:**\n- For file-based agents (claude, codex, gemini, copilot, openclaw), metadata extraction reads only the first and last few lines of each file (or file stat for timestamps).\n- For SQLite agents (cursor, opencode, hermes), a single SQL query retrieves all summaries with filtering and sorting pushed down to the database.\n- For tree-based agents (pi, omp), the adapter reads directory entries and the root message of each session.\n\n### 6.2 get()\n\n```typescript\nconst session = await adapter.sessions.get('opencode', 'abc-def-123');\nconsole.log(session.messages.length); // Full message history\nconsole.log(session.cost?.totalUsd);  // Aggregated cost\n```\n\n**Behavior:**\n\n1. Resolves the persistent-session adapter through the registry.\n2. Calls the registered parser for the target file where `filePath` is resolved from `sessionAdapter.sessionDir()` and the native session ID.\n3. The parser returns a fully parsed `Session` object with all messages, tool calls, and cost records.\n4. The `SessionManager` sets `unifiedId` on the returned object.\n5. Returns the `Session`.\n\n**Error handling:**\n- Throws `SESSION_NOT_FOUND` if the session file/record does not exist.\n- Throws `PARSE_ERROR` if the file exists but cannot be parsed (corrupt JSONL, invalid JSON, SQLite read error).\n\n### 6.3 search()\n\n```typescript\nconst results = await adapter.sessions.search({\n  text: 'refactor authentication middleware',\n  since: new Date('2025-01-01'),\n  limit: 10,\n  sort: 'relevance',\n});\n```\n\n**Behavior:**\n\n1. If `query.agent` is specified, searches only that agent. Otherwise, iterates all agents with detected session storage.\n2. **For hermes:** Delegates to FTS5 via `SELECT ... FROM sessions_fts WHERE sessions_fts MATCH ?`. Relevance scores are BM25-based, provided natively by FTS5.\n3. **For SQLite agents (cursor, opencode):** Uses `LIKE` queries or application-level matching against message content columns.\n4. **For file-based agents (claude, codex, gemini, copilot, openclaw, pi, omp):** Performs in-process text matching. Loads session content lazily, scanning files line-by-line and checking for substring or regex matches. Stops early when `limit` is reached.\n5. Results from multiple agents are merged and re-ranked. When `sort` is `'relevance'`, scores are normalized to [0.0, 1.0] across agents.\n6. Applies `since`, `until`, `model`, and `tags` filters.\n7. Returns `SessionSummary[]` with `relevanceScore` populated.\n\n### 6.4 totalCost()\n\n```typescript\nconst cost = await adapter.sessions.totalCost({\n  since: new Date('2025-03-01'),\n  groupBy: 'agent',\n});\nconsole.log(cost.totalUsd);                    // Total across all agents\nconsole.log(cost.breakdowns?.['claude'].totalUsd); // Claude-only cost\n```\n\n**Behavior:**\n\n1. Enumerates sessions matching the filter criteria (delegates to `list()` internally for each agent, or a single agent if `options.agent` is set).\n2. Reads cost records from session metadata. Also reads the adapters run index (`~/.adapters/run-index.jsonl`) for runs that have cost data not captured in session files.\n3. Aggregates `totalUsd`, `inputTokens`, `outputTokens`, `thinkingTokens`, and `cachedTokens`.\n4. When `groupBy` is set, produces per-group `CostBreakdown` entries in `breakdowns`.\n5. Returns a single `CostSummary`.\n\n**Cost data sources (in priority order):**\n1. Native session cost records (most accurate -- reported by the agent itself).\n2. Adapters run index entries (captures cost events emitted during runs).\n3. Model registry estimation (fallback: `estimateCost(agent, model, inputTokens, outputTokens)` from the `ModelRegistry`).\n\n### 6.5 export()\n\n```typescript\nconst markdown = await adapter.sessions.export('claude', 'abc123', 'markdown');\n// Write to file, pipe to stdout, etc.\n```\n\n**Behavior:**\n\n1. Calls `get()` to retrieve the full session.\n2. Serializes to the requested format:\n   - **`json`:** `JSON.stringify(session, null, 2)` with `Date` objects serialized as ISO 8601 strings.\n   - **`jsonl`:** One JSON object per `SessionMessage`, one line per message. The first line is a metadata header containing session-level fields.\n   - **`markdown`:** Structured Markdown document with:\n     - Session metadata header (agent, model, dates, cost).\n     - Messages formatted with role headers (`### User`, `### Assistant`, `### System`, `### Tool`).\n     - Tool calls in fenced code blocks.\n     - Thinking content in collapsible `<details>` blocks.\n     - Cost summary at the end.\n3. Returns the serialized string.\n\n### 6.6 diff()\n\n```typescript\nconst d = await adapter.sessions.diff(\n  { agent: 'claude', sessionId: 'original' },\n  { agent: 'claude', sessionId: 'forked' },\n);\nconsole.log(d.stats); // { removals: 0, additions: 3, modifications: 1, unchanged: 10 }\n```\n\n**Behavior:**\n\n1. Calls `get()` for both sessions.\n2. Aligns messages using a longest-common-subsequence (LCS) algorithm on `(role, content)` pairs.\n3. Classifies each position as `addition`, `removal`, `modification`, or `unchanged`.\n4. A `modification` is detected when messages at aligned positions share the same role but differ in content (using string equality on `content`).\n5. Produces the `operations` array and `stats` summary.\n6. Works across agents: comparing a Claude session against a Codex session is valid and produces meaningful diffs on the normalized message structure.\n\n### 6.7 Live Watching\n\nLive session watching is **not** part of the public `SessionManager` API.\n\nEarlier drafts and placeholder code attempted to expose `watch(agent, sessionId)` as an\n`AsyncIterable<AgentEvent>`, but the implementation could not provide truthful cross-adapter\nsemantics. In particular, persisted session files do not expose a generic mapping from file\ngrowth to:\n\n1. a real `runId`\n2. adapter-accurate `AgentEvent` types\n3. meaningful `text_delta.delta` / `text_delta.accumulated` payloads\n\n`SessionManager` therefore remains a read-only inspection surface: `list()`, `get()`,\n`search()`, `totalCost()`, `export()`, `diff()`, `resolveUnifiedId()`, and `resolveNativeId()`.\nAny future live-watch feature must be specified as a separate adapter capability with explicit\nevent semantics instead of reusing `AgentEvent` opportunistically.\n\n### 6.8 resolveUnifiedId()\n\n```typescript\nconst id = adapter.sessions.resolveUnifiedId('hermes', '550e8400-e29b-41d4-a716-446655440000');\n// => 'hermes:550e8400-e29b-41d4-a716-446655440000'\n```\n\nPure synchronous function. Concatenates `agent + ':' + nativeSessionId`. No I/O, no existence check.\n\n### 6.9 resolveNativeId()\n\n```typescript\nconst parsed = adapter.sessions.resolveNativeId('hermes:550e8400-e29b-41d4-a716-446655440000');\n// => { agent: 'hermes', nativeSessionId: '550e8400-e29b-41d4-a716-446655440000' }\n\nconst bad = adapter.sessions.resolveNativeId('unknown-agent:123');\n// => null (agent not registered)\n```\n\nPure synchronous function. Splits on the first colon, validates the agent portion against registered adapters. Returns `null` for malformed or unknown-agent IDs.\n\n---\n\n## 7. CLI Commands\n\nThe `SessionManager` is surfaced through the `adapters sessions` and `adapters cost` CLI commands.\n\n### 7.1 adapters sessions\n\n```\nadapters sessions list <agent> [options]\n  --since, --until       Date range filters (ISO 8601)\n  --model                Filter by model ID\n  --tag                  Filter by tag (repeatable)\n  --limit                Max results (default: 100)\n  --sort                 date | cost | turns (default: date)\n  --json                 Output as JSON array\n\nadapters sessions show <agent> <session-id>\n  --format               json | jsonl | markdown (default: markdown)\n\nadapters sessions search <query> [--agent <a>] [--since] [--until]\n  --limit                Max results (default: 50)\n  --json                 Output as JSON array\n\nadapters sessions export <agent> <session-id> [--format]\n  # Exports session to stdout in the specified format.\n\nadapters sessions diff <agent>:<id> <agent>:<id>\n  # Displays structural diff between two sessions.\n\nadapters sessions resume <agent> <session-id>\n  # Starts a new run resuming the given session (delegates to adapters run --session).\n\nadapters sessions fork <agent> <session-id>\n  # Starts a new run forking the given session (delegates to adapters run --fork).\n```\n\n### 7.2 adapters cost\n\n```\nadapters cost report\n  --agent                Filter by agent\n  --since, --until       Date range\n  --model                Filter by model\n  --tag                  Filter by tag (repeatable)\n  --group-by             agent | model | day | tag\n  --json                 Output as JSON\n```\n\n---\n\n## 8. Error Handling\n\n### 8.1 Error Codes\n\n| Error Code | Thrown By | Description |\n|---|---|---|\n| `AGENT_NOT_FOUND` | `list()`, `get()`, `export()`, `diff()` | The specified agent name does not match any registered adapter. (`search()` does not throw this -- when `query.agent` is omitted, all agents are searched; when `query.agent` is set to an unknown agent, `search()` returns an empty array rather than throwing.) |\n| `SESSION_NOT_FOUND` | `get()`, `export()`, `diff()` | No session with the given ID exists in the agent's session storage. |\n| `PARSE_ERROR` | `get()`, `export()`, `diff()` | The session file exists but cannot be parsed (corrupt data, schema mismatch, incompatible format version). |\n\n### 8.2 Graceful Degradation\n\n- `list()` returns an empty array (not an error) when the agent's session directory does not exist (agent installed but never used).\n- `search()` silently skips agents whose session storage is not detectable when searching across all agents.\n- `totalCost()` returns zero-valued aggregates when no matching sessions are found.\n\n---\n\n## 9. Platform Considerations\n\n### 9.1 Path Resolution\n\nSession paths use `~` (home directory) notation in this specification. At runtime, `~` is resolved to:\n- **macOS / Linux:** `$HOME`\n- **Windows:** `%USERPROFILE%` (typically `C:\\Users\\<username>`)\n\nFor XDG-aware agents (OpenCode at `~/.local/share/opencode/`), the adapter respects `$XDG_DATA_HOME` when set, falling back to `~/.local/share/` on Linux and the platform default on other operating systems.\n\n### 9.2 SQLite Access\n\nAgents using SQLite storage (cursor, opencode, hermes) are accessed via a lightweight SQLite binding (`better-sqlite3`). All connections are opened in read-only mode to prevent interference with the agent's own database operations.\n\nFor hermes, the FTS5 extension must be available in the SQLite build. `better-sqlite3` ships with FTS5 enabled by default.\n\n### 9.3 Concurrency\n\n- Multiple `SessionManager` operations can run concurrently. File reads use non-exclusive handles; SQLite connections use `SQLITE_OPEN_READONLY`.\n- The `watch()` method creates one file watcher or poll timer per active watch. Consumers should break from the iterator when no longer interested to free resources.\n- The run index file (`~/.adapters/run-index.jsonl`) is read with shared access; writes are managed exclusively by the `RunHandle` with file locking.\n\n---\n\n## 10. Integration with Adapter System\n\nThe `SessionManager` delegates all format-specific work to the agent adapter. The relevant adapter methods are:\n\n```typescript\n// From AgentAdapter (see 05-adapter-system.md, Section 2):\n\n/** Returns the root directory where this agent stores session files. */\nsessionDir(cwd?: string): string;\n\n/** Parses a single session file into the unified Session type. */\nparseSessionFile(filePath: string): Promise<Session>;\n\n/** Lists all session file paths for this agent. */\nlistSessionFiles(cwd?: string): Promise<string[]>;\n```\n\nThe `SessionManager` is the public API; adapters are the parsing engine. Consumers interact exclusively with `SessionManager` via `adapter.sessions`. The adapter methods are not exposed on the public API surface.\n\n### 10.1 Adapter Session Persistence Mapping\n\nThe `AgentCapabilities.sessionPersistence` field (see `06-capabilities-and-models.md`, Section 2) determines how the `SessionManager` accesses session data:\n\n| `sessionPersistence` | Agents | Access Strategy |\n|---|---|---|\n| `'file'` | claude, codex, gemini, copilot, openclaw | File system reads (JSONL line-by-line or JSON parse) |\n| `'sqlite'` | cursor, opencode, hermes | Read-only SQLite queries |\n| `'file'` (tree) | pi, omp | Directory enumeration + JSONL tree parsing |\n| `'none'` | (future agents) | `list()` returns `[]`; `get()` throws `SESSION_NOT_FOUND` |\n| `'in-memory'` | (future agents) | Same as `'none'` after process exit |\n\n---\n\n## 11. Complete Type Index\n\nAll types defined or referenced in this specification:\n\n| Type | Defined In | Section |\n|---|---|---|\n| `SessionManager` | This spec | 2 |\n| `SessionSummary` | This spec | 3.1 |\n| `Session` | This spec | 3.2 |\n| `SessionMessage` | This spec | 3.2 |\n| `SessionToolCall` | This spec | 3.2 |\n| `SessionQuery` | This spec | 3.3 |\n| `SessionListOptions` | This spec | 3.4 |\n| `CostAggregationOptions` | This spec | 3.5 |\n| `CostSummary` | This spec | 3.6 |\n| `CostBreakdown` | This spec | 3.6 |\n| `SessionDiff` | This spec | 3.7 |\n| `DiffOperation` | This spec | 3.7 |\n| `AgentName` | `01-core-types-and-client.md` | 1.4 |\n| `CostRecord` | `01-core-types-and-client.md` | 4.2.3 |\n| `AgentEvent` | `04-agent-events.md` | 2 |\n| `AgentAdapter` | `05-adapter-system.md` | 2 |\n| `AgentCapabilities` | `06-capabilities-and-models.md` | 2 |\n\n---\n\n## Implementation Status (2026-04-12)\n\n`SessionManagerImpl` in `packages/core/src/session-manager.ts` is now a real filesystem implementation:\n\n- **`list(agent, opts?)`** and **`get(agent, sessionId)`** read through each adapter's `listSessionFiles()` + `parseSessionFile()`. Each adapter rooted at its own on-disk directory — see `docs/12-built-in-adapters.md` for the per-adapter paths.\n- **`search(query)`** performs a full-text scan across sessions with structured filters.\n- **`export(agent, sessionId, format)`** accepts `'json' | 'jsonl' | 'markdown'`. JSONL emits one `SessionMessage` per line; markdown renders a human-readable transcript.\n- **`diff(agentA, idA, agentB, idB)`** returns a `SessionDiff` of message-level insertions, deletions, and updates.\n- **Live watching is intentionally not public.** The earlier placeholder `watch()` API was removed because it could not provide truthful cross-adapter `AgentEvent` semantics.\n\nSession files are written atomically by adapters via the tmp-then-rename helper in `packages/adapters/src/session-fs.ts`.\n",
    "documents": []
  },
  "outgoingEdges": [],
  "incomingEdges": [
    {
      "from": "page:docs-adapters-reference",
      "to": "page:docs-adapters-reference-07-session-manager",
      "kind": "contains_page"
    }
  ]
}

Shortcuts

Back to overview
Open graph tab