Skip to main content

Event Store

The Event Store is Auto-Skill’s persistent storage layer. It captures every tool invocation from your coding agent sessions and stores them in a SQLite database for pattern analysis.

Storage Architecture

1

Event Capture

Observer hook intercepts tool calls and records metadata
2

SQLite Storage

Events stored in ~/.claude/auto-skill/index.db with WAL mode
3

Project Tagging

Each event tagged with project_path for filtering
4

Pattern Retrieval

Events queried and grouped by session for analysis

Database Schema

The events table stores all tool invocations:
CREATE TABLE IF NOT EXISTS events (
  id TEXT PRIMARY KEY,           -- ULID
  session_id TEXT NOT NULL,      -- Groups related events
  project_path TEXT NOT NULL,    -- For project-specific patterns
  tool_name TEXT NOT NULL,       -- Read, Write, Edit, Bash, etc.
  tool_input TEXT NOT NULL,      -- JSON-serialized parameters
  tool_response TEXT,            -- Truncated response (max 1000 chars)
  success INTEGER NOT NULL,      -- 1 or 0
  timestamp TEXT NOT NULL,       -- ISO-8601
  agent_id TEXT                  -- claude-code, cursor, etc.
);

-- Indexes for fast queries
CREATE INDEX IF NOT EXISTS idx_session_id ON events(session_id);
CREATE INDEX IF NOT EXISTS idx_project_path ON events(project_path);
CREATE INDEX IF NOT EXISTS idx_timestamp ON events(timestamp);
CREATE INDEX IF NOT EXISTS idx_tool_name ON events(tool_name);
The schema is created automatically when you first use the EventStore. From event-store.ts:70-96

ToolEvent Type

Each event is represented by the ToolEvent interface:
// From types/index.ts:14-24
export interface ToolEvent {
  id: string;                      // ULID for uniqueness
  sessionId: string;               // Groups related events
  projectPath: string;             // Working directory
  toolName: string;                // Tool that was called
  toolInput: Record<string, unknown>;  // Tool parameters
  toolResponse: string | null;     // Truncated output
  success: boolean;                // Whether tool succeeded
  timestamp: string;               // ISO-8601
  agentId?: string;                // Optional agent identifier
}

Recording Events

Events are recorded with automatic ID and timestamp generation:
// From event-store.ts:98-146
recordEvent(
  sessionId: string,
  projectPath: string,
  toolName: string,
  toolInput: Record<string, unknown>,
  toolResponse?: string | null,
  success: boolean = true,
  agentId?: string,
): ToolEvent {
  const id = ulid();                        // Generate unique ID
  const timestamp = new Date().toISOString();
  
  const event: ToolEvent = {
    id,
    sessionId,
    projectPath,
    toolName,
    toolInput,
    toolResponse: truncateResponse(toolResponse) ?? null,  // Max 1000 chars
    success,
    timestamp,
    agentId,
  };
  
  // Insert into SQLite
  this.db
    .prepare(
      `INSERT INTO events (id, session_id, project_path, tool_name,
                           tool_input, tool_response, success, timestamp, agent_id)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
    )
    .run(
      event.id,
      event.sessionId,
      event.projectPath,
      event.toolName,
      JSON.stringify(event.toolInput),  // Serialize to JSON
      event.toolResponse,
      event.success ? 1 : 0,            // Boolean to integer
      event.timestamp,
      event.agentId ?? null,
    );
  
  return event;
}
Response Truncation: Large tool responses are truncated to 1000 characters to save space. The truncation includes "...[truncated]" suffix.

Querying Events

The EventStore provides multiple query methods:

Get Session Events

// From event-store.ts:148-161
getSessionEvents(sessionId: string): ToolEvent[] {
  const rows = this.db
    .prepare(
      `SELECT * FROM events
       WHERE session_id = ?
       ORDER BY timestamp ASC`
    )
    .all(sessionId) as Record<string, unknown>[];
  
  return rows.map(rowToEvent);
}

Get Tool Sequences

Extract tool name sequences grouped by session:
// From event-store.ts:163-220
getToolSequences(
  projectPath?: string,
  lookbackDays: number = 7,
  minSequenceLength: number = 2,
): string[][] {
  const cutoff = new Date(
    Date.now() - lookbackDays * 24 * 60 * 60 * 1000
  ).toISOString();
  
  let sql = `
    SELECT session_id, tool_name
    FROM events
    WHERE timestamp > ?
  `;
  const params: unknown[] = [cutoff];
  
  if (projectPath) {
    sql += " AND project_path = ?";
    params.push(projectPath);
  }
  
  sql += " ORDER BY session_id, timestamp ASC";
  
  const rows = this.db.prepare(sql).all(...params);
  
  // Group by session
  const sequences: string[][] = [];
  let currentSession: string | null = null;
  let currentSequence: string[] = [];
  
  for (const row of rows) {
    if (row.session_id !== currentSession) {
      if (currentSequence.length >= minSequenceLength) {
        sequences.push(currentSequence);
      }
      currentSession = row.session_id;
      currentSequence = [];
    }
    currentSequence.push(row.tool_name);
  }
  
  // Don't forget the last session
  if (currentSequence.length >= minSequenceLength) {
    sequences.push(currentSequence);
  }
  
  return sequences;
}

projectPath

Optional filter — Get sequences for a specific projectundefined returns all projects

lookbackDays

Time window — How far back to lookDefault: 7 days

minSequenceLength

Filter short sessions — Minimum tools per sessionDefault: 2 tools

Get Events with Inputs

Retrieve full event objects (including inputs) grouped by session:
// From event-store.ts:222-274
getEventsWithInputs(
  projectPath?: string,
  lookbackDays: number = 7,
): ToolEvent[][] {
  const cutoff = new Date(
    Date.now() - lookbackDays * 24 * 60 * 60 * 1000
  ).toISOString();
  
  let sql = `
    SELECT * FROM events
    WHERE timestamp > ?
  `;
  const params: unknown[] = [cutoff];
  
  if (projectPath) {
    sql += " AND project_path = ?";
    params.push(projectPath);
  }
  
  sql += " ORDER BY session_id, timestamp ASC";
  
  const rows = this.db.prepare(sql).all(...params) as Record<string, unknown>[];
  
  // Group by session
  const sessions: ToolEvent[][] = [];
  let currentSession: string | null = null;
  let currentEvents: ToolEvent[] = [];
  
  for (const row of rows) {
    const event = rowToEvent(row);
    if (event.sessionId !== currentSession) {
      if (currentEvents.length > 0) {
        sessions.push(currentEvents);
      }
      currentSession = event.sessionId;
      currentEvents = [];
    }
    currentEvents.push(event);
  }
  
  if (currentEvents.length > 0) {
    sessions.push(currentEvents);
  }
  
  return sessions;
}
getEventsWithInputs() returns full event objects including tool inputs and responses. Use this when you need detailed context for pattern matching, not just tool names.

Statistics

Get high-level statistics about stored events:
// From event-store.ts:276-314
getStats(): EventStoreStats {
  const totalEvents = (
    this.db.prepare("SELECT COUNT(*) AS cnt FROM events").get() as { cnt: number }
  ).cnt;
  
  const uniqueSessions = (
    this.db
      .prepare("SELECT COUNT(DISTINCT session_id) AS cnt FROM events")
      .get() as { cnt: number }
  ).cnt;
  
  const uniqueProjects = (
    this.db
      .prepare("SELECT COUNT(DISTINCT project_path) AS cnt FROM events")
      .get() as { cnt: number }
  ).cnt;
  
  const toolRows = this.db
    .prepare(
      `SELECT tool_name, COUNT(*) AS count
       FROM events
       GROUP BY tool_name
       ORDER BY count DESC
       LIMIT 10`
    )
    .all() as Array<{ tool_name: string; count: number }>;
  
  return {
    totalEvents,
    uniqueSessions,
    uniqueProjects,
    topTools: toolRows.map((r) => ({ tool: r.tool_name, count: r.count })),
  };
}

EventStoreStats Type

// From types/index.ts:413-419
export interface EventStoreStats {
  totalEvents: number;
  uniqueSessions: number;
  uniqueProjects: number;
  topTools: Array<{ tool: string; count: number }>;  // Top 10 by usage
}

Cleanup

Delete old events to manage database size:
// From event-store.ts:316-332
cleanupOldEvents(days: number = 30): number {
  const cutoff = new Date(
    Date.now() - days * 24 * 60 * 60 * 1000
  ).toISOString();
  
  const result = this.db
    .prepare("DELETE FROM events WHERE timestamp < ?")
    .run(cutoff);
  
  return result.changes;  // Number of deleted rows
}
Cleanup is manual — Auto-Skill doesn’t automatically delete old events. Run cleanupOldEvents() periodically or via CLI command.

SQLite Configuration

The database is opened with optimal settings for concurrent access:
// From db.ts:32-64
export function openDatabase(dbPath: string): Database {
  // Ensure parent directory exists with secure permissions
  const dir = path.dirname(dbPath);
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
  
  let db: Database;
  
  // Support both Node (better-sqlite3) and Bun (bun:sqlite)
  if (typeof Bun !== "undefined") {
    const { Database: BunDB } = require("bun:sqlite");
    db = new BunDB(dbPath) as unknown as Database;
  } else {
    const BetterSqlite3 = require("better-sqlite3");
    db = new BetterSqlite3(dbPath) as Database;
  }
  
  // Enable WAL mode for better concurrency
  db.pragma("journal_mode = WAL");
  
  // Enable foreign key constraints
  db.pragma("foreign_keys = ON");
  
  return db;
}

WAL Mode

Write-Ahead Logging for concurrent reads during writesBetter performance for event capture

Foreign Keys

Constraint enforcement for data integrityPrevents orphaned records

Hybrid Scope Model

Auto-Skill uses a hybrid storage approach:
  • Global storage: All events in a single database (~/.claude/auto-skill/index.db)
  • Project tagging: Each event tagged with project_path for filtering
This enables both:
  • Project-specific patterns: Filter events by project_path
  • Cross-project patterns: Analyze all events together
// Project-specific patterns
const projectEvents = eventStore.getEventsWithInputs("/home/user/my-project", 7);

// Global patterns across all projects
const allEvents = eventStore.getEventsWithInputs(undefined, 7);

Usage Example

import { createEventStore } from "./core/event-store";

// 1. Create event store (uses default path ~/.claude/auto-skill/index.db)
const eventStore = createEventStore();

// 2. Record an event
const event = eventStore.recordEvent(
  "session-abc123",              // sessionId
  "/home/user/my-project",       // projectPath
  "Read",                        // toolName
  { filePath: "src/app.ts" },    // toolInput
  "// File contents...",         // toolResponse
  true,                          // success
  "claude-code"                  // agentId (optional)
);

console.log(`Recorded event: ${event.id}`);

// 3. Get statistics
const stats = eventStore.getStats();
console.log(`Total events: ${stats.totalEvents}`);
console.log(`Unique sessions: ${stats.uniqueSessions}`);
console.log(`Top tool: ${stats.topTools[0].tool} (${stats.topTools[0].count} uses)`);

// 4. Get events for pattern detection
const eventSessions = eventStore.getEventsWithInputs(
  "/home/user/my-project",  // Filter to this project
  7                         // Last 7 days
);

console.log(`Found ${eventSessions.length} sessions for pattern analysis`);

// 5. Cleanup old events
const deleted = eventStore.cleanupOldEvents(90);  // Delete events older than 90 days
console.log(`Deleted ${deleted} old events`);

// 6. Close database connection
eventStore.close();

Integration with Pattern Detection

The Event Store feeds the pattern detection pipeline:
import { createEventStore } from "./core/event-store";
import { createPatternDetector } from "./core/pattern-detector";

const eventStore = createEventStore();
const detector = createPatternDetector();

// Get events grouped by session
const eventSessions = eventStore.getEventsWithInputs(
  projectPath,    // Optional: filter to specific project
  lookbackDays: 7 // Last 7 days
);

// Detect patterns
const patterns = detector.detectPatterns(eventSessions, {
  minOccurrences: 3,
  minSequenceLength: 2,
  maxSequenceLength: 10,
  enableV2: true
});

console.log(`Detected ${patterns.length} patterns`);

Factory Function

The event store is created via factory function:
// From event-store.ts:343-348
export function createEventStore(dbPath?: string): EventStore {
  return new EventStore(dbPath);
}

// Usage:
import { createEventStore } from "./core/event-store";

const store = createEventStore();  // Uses default path
const customStore = createEventStore("/custom/path/events.db");  // Custom path

Row Conversion

SQLite rows are converted to ToolEvent objects:
// From event-store.ts:35-49
function rowToEvent(row: Record<string, unknown>): ToolEvent {
  return {
    id: row.id as string,
    sessionId: row.session_id as string,
    projectPath: row.project_path as string,
    toolName: row.tool_name as string,
    toolInput: JSON.parse(row.tool_input as string) as Record<string, unknown>,
    toolResponse: (row.tool_response as string) ?? null,
    success: Boolean(row.success),
    timestamp: row.timestamp as string,
    agentId: (row.agent_id as string) ?? undefined,
  };
}

Default Database Path

import { getDefaultDbPath } from "../util/fs";

// Default: ~/.claude/auto-skill/index.db
const dbPath = getDefaultDbPath();

API Reference

recordEvent
function
Record a tool usage eventParameters:
  • sessionId: string
  • projectPath: string
  • toolName: string
  • toolInput: Record<string, unknown>
  • toolResponse?: string | null
  • success?: boolean (default: true)
  • agentId?: string
Returns: ToolEvent
getSessionEvents
function
Get all events for a specific sessionParameters:
  • sessionId: string
Returns: ToolEvent[] — Ordered by timestamp ASC
getToolSequences
function
Get tool name sequences grouped by sessionParameters:
  • projectPath?: string (optional)
  • lookbackDays?: number (default: 7)
  • minSequenceLength?: number (default: 2)
Returns: string[][] — One array per session
getEventsWithInputs
function
Get full events grouped by sessionParameters:
  • projectPath?: string (optional)
  • lookbackDays?: number (default: 7)
Returns: ToolEvent[][] — One array per session
getStats
function
Get event store statisticsReturns: EventStoreStats
cleanupOldEvents
function
Delete events older than specified daysParameters:
  • days?: number (default: 30)
Returns: number — Count of deleted events
close
function
Close the database connectionReturns: void

Next Steps

Pattern Detection

See how events are analyzed for patterns

Integration Guide

Learn how events are captured during tool calls

Session Analysis

Understand session context extraction

CLI Discover

Discover patterns from events

Build docs developers (and LLMs) love