# How Session Data Is Synchronized in Agentsview via the 15-Minute Periodic Sync

> Learn how Agentsview synchronizes session data every 15 minutes. Discover background goroutines, SQLite batch writes, and SSE event emissions for UI updates.

- Repository: [Kenn Software/agentsview](https://github.com/kenn-io/agentsview)
- Tags: internals
- Published: 2026-06-20

---

**Agentsview runs a background goroutine triggered by a 15-minute ticker that calls `Engine.SyncAll()` to discover, parse, and batch-write agent session files into SQLite, then emits SSE events to refresh the UI.**

The `kenn-io/agentsview` repository maintains a real-time view of agent session data by running a background synchronization loop every 15 minutes. This periodic sync keeps the local SQLite database fresh without requiring manual intervention, ensuring the web interface always displays current session transcripts and analytics.

## The 15-Minute Sync Loop Architecture

The synchronization lifecycle begins in the CLI entry point and delegates heavy lifting to a dedicated sync engine.

### CLI Entry Point and Ticker Setup in [`cmd/agentsview/main.go`](https://github.com/kenn-io/agentsview/blob/main/cmd/agentsview/main.go)

The periodic timer is initialized in the main application entry point. The `runSyncLoop` function creates a ticker using `time.NewTicker(15 * time.Minute)` and blocks on a select statement, triggering `e.SyncAll(context.Background())` on each tick.

```go
// runSyncLoop starts a background goroutine that triggers a full
// sync every 15 minutes.
func runSyncLoop(e *sync.Engine, stop <-chan struct{}) {
    ticker := time.NewTicker(15 * time.Minute)       // ← periodic interval
    defer ticker.Stop()
    for {
        select {
        case <-ticker.C:
            e.SyncAll(context.Background())          // ← full sync
        case <-stop:
            return
        }
    }
}

```

### The Engine's Role in [`internal/sync/engine.go`](https://github.com/kenn-io/agentsview/blob/main/internal/sync/engine.go)

The `sync.Engine` struct defined in [`internal/sync/engine.go`](https://github.com/kenn-io/agentsview/blob/main/internal/sync/engine.go) orchestrates the entire synchronization process. It maintains configuration for agent directories, skip caches, and sync statistics while providing thread-safe access through mutex locks.

## How the Sync Engine Processes Session Files

When `SyncAll` or `SyncPaths` is invoked, the engine executes a pipeline that transforms raw session files into structured database rows.

### Path Classification via `classifyPaths`

The engine first classifies discovered file paths into strongly typed `parser.DiscoveredFile` structs. This method walks all configured `AgentDir` entries from `EngineConfig.AgentDirs` and filters for files matching known session patterns, silently ignoring irrelevant paths.

### Worker Pool Execution with `startWorkers`

Parsing is parallelized through a worker pool launched via `startWorkers(context.Background(), files)`. Each worker processes JSONL transcripts or other supported formats using parsers defined in `internal/parser/*.go`, extracting sessions, messages, and analytics.

### Database Batching via `collectAndBatch`

Results are collected and written to SQLite in batches through `collectAndBatch`. This method handles the transaction logic for inserting rows into the database (`internal/db/*`) while tracking sync statistics.

The following implementation from `engine.go:62‑70` demonstrates the `SyncPaths` method that orchestrates these steps:

```go
// SyncPaths syncs only the specified changed file paths
// instead of discovering and hashing all session files.
// Paths that don't match known session file patterns are
// silently ignored.
func (e *Engine) SyncPaths(paths []string) {          // ← engine.go:62‑70
    files := e.classifyPaths(paths)
    if len(files) == 0 {
        return
    }

    e.syncMu.Lock()
    var stats SyncStats
    defer func() {
        if stats.Synced > 0 {
            e.emit("sessions")
        }
    }()
    defer e.syncMu.Unlock()

    results := e.startWorkers(context.Background(), files)
    stats = e.collectAndBatch(context.Background(),
        results, len(files), len(files), nil, syncWriteDefault)
    e.persistSkipCache()

    e.mu.Lock()
    e.lastSync = time.Now()
    e.lastSyncStats = stats
    e.mu.Unlock()
}

```

## Incremental Syncs and Real-Time Updates

The system optimizes for performance and keeps the frontend synchronized through caching and event emission.

### Skip-Cache Optimization with `persistSkipCache`

Files that fail to parse or represent non-interactive sessions are stored in a `skipCache` to avoid re-processing. The `persistSkipCache` method writes this state to disk, ensuring the cache survives application restarts and only modified files trigger re-parsing.

### Server-Sent Events via `e.emit("sessions")`

After successful synchronization, the engine calls `e.emit("sessions")` to broadcast an SSE event. The HTTP server forwards this to connected browser clients, automatically refreshing the UI to reflect new session data without requiring manual page reloads.

## Summary

- The 15-minute sync loop runs in [`cmd/agentsview/main.go`](https://github.com/kenn-io/agentsview/blob/main/cmd/agentsview/main.go) using `time.NewTicker(15 * time.Minute)` to trigger `Engine.SyncAll()`.
- `SyncPaths` in [`internal/sync/engine.go`](https://github.com/kenn-io/agentsview/blob/main/internal/sync/engine.go) orchestrates file classification, worker pool parsing, and batched database writes.
- A persistent skip-cache prevents re-parsing unchanged or invalid files via `persistSkipCache`.
- Real-time UI updates are achieved through SSE events emitted after each successful sync.
- The architecture ensures thread-safe operations using mutex locks (`syncMu` and `mu`) throughout the sync lifecycle.

## Frequently Asked Questions

### What triggers the 15-minute sync interval in Agentsview?

The interval is hardcoded in [`cmd/agentsview/main.go`](https://github.com/kenn-io/agentsview/blob/main/cmd/agentsview/main.go) within the `runSyncLoop` function, which initializes `time.NewTicker(15 * time.Minute)`. This creates a background goroutine that blocks on the ticker channel, calling `engine.SyncAll()` every 15 minutes until the application receives a stop signal.

### How does Agentsview handle files that fail to parse?

Failed files and non-interactive sessions are cached in the engine's `skipCache` structure. The `persistSkipCache` method writes this cache to disk between runs, ensuring the engine skips these files in subsequent syncs unless the underlying file content changes. This optimization prevents wasted CPU cycles on malformed or static data.

### What is the difference between `SyncAll` and `SyncPaths` in the engine?

`SyncAll` performs a full discovery of all configured agent directories, walking every file to find changes, while `SyncPaths` accepts a specific slice of file paths and only processes those changed files. Both methods ultimately call the same classification and batching pipeline, but `SyncPaths` is used for targeted updates when the system already knows which files changed.

### How does the UI know when to refresh after a sync?

After `collectAndBatch` completes successfully with `stats.Synced > 0`, the engine executes `e.emit("sessions")` inside a deferred function, which broadcasts a Server-Sent Event (SSE) through the HTTP layer. Connected browser clients receive this event and automatically refresh their session views, ensuring the interface displays the latest synchronized data immediately.