Skip to content

Architecture

This page provides a technical overview of SideSeat’s architecture for developers who want to understand how the local workbench is built or contribute to the project.

SideSeat runs as a single local binary that includes:

  • HTTP Server (Axum) - REST API, OTLP ingestion, static file serving
  • gRPC Server (Tonic) - Optional high-throughput OTLP ingestion
  • Workbench UI (React) - Embedded static files
  • Storage - DuckDB (analytics) + SQLite (transactional)

The ingestion pipeline processes incoming OTLP data through these stages:

OTLP data arrives via:

  • HTTP: POST /otel/{project_id}/v1/traces (protobuf or JSON)
  • gRPC: localhost:4317 (protobuf)

Protobuf messages are decoded into internal SpanData structures. The decoding preserves all original attributes.

Framework detection and attribute extraction happens at ingestion time:

// Detect framework from span attributes
let framework = detect_framework(&span.attributes);
// Extract GenAI-specific fields
let model = get_first(attrs, &[
"gen_ai.request.model",
"gen_ai.response.model",
"llm.model_name",
]);

Enrichment adds derived fields:

  • Cost calculation: Look up model pricing, compute costs
  • Preview generation: Truncate input/output for list views
  • Timestamps: Normalize to microseconds

Spans are batched and written to DuckDB. The schema preserves raw attributes in a JSON column for flexibility.

SideSeat uses two databases:

Stores telemetry data optimized for analytical queries:

  • spans: Normalized span data with extracted fields
  • Raw attributes: Preserved in JSON for ad-hoc queries

Default: DuckDB (embedded, zero config)

Optional: ClickHouse (external, high volume)

Transactional Database (SQLite/PostgreSQL)

Section titled “Transactional Database (SQLite/PostgreSQL)”

Stores application state:

  • projects: Project configuration
  • api_tokens: Authentication tokens
  • settings: User preferences

Default: SQLite (embedded, zero config)

Optional: PostgreSQL (external, HA)

SideSeat normalizes messages from different frameworks into a universal format called SideML:

This happens at query time via sideml/pipeline.rs:

  1. Extract events from span
  2. Categorize by role (system/user/assistant/tool)
  3. Parse content into blocks (text, tool_use, tool_result, etc.)
  4. Deduplicate using content hashing
  5. Sort by birth time

All query endpoints are prefixed with /api/v1/project/{project_id}/otel:

EndpointPurpose
GET /tracesList runs with filtering
GET /traces/{id}Get a single run
GET /traces/{id}/messagesGet normalized messages
GET /spansList steps with filtering
GET /sessionsList sessions
GET /sseReal-time event stream

SideSeat uses Server-Sent Events (SSE) for real-time updates:

The Topic is an in-memory pub/sub system that broadcasts events to all connected clients.

Configuration is loaded from multiple sources with this priority (highest first):

  1. CLI arguments: --port 8080
  2. Environment variables: SIDESEAT_PORT=8080
  3. Working directory config: ./sideseat.json
  4. User config: ~/.sideseat/sideseat.json
  5. Defaults: Built-in values
// Priority chain example
let port = cli.port
.or(env_port)
.or(workdir_config.port)
.or(user_config.port)
.unwrap_or(DEFAULT_PORT);
server/src/
├── app.rs # Main orchestrator
├── core/
│ ├── config.rs # Configuration loading
│ ├── cli.rs # CLI argument parsing
│ ├── constants.rs # All constants
│ └── topic.rs # Pub/sub messaging
├── data/
│ ├── duckdb/ # DuckDB implementation
│ ├── clickhouse/ # ClickHouse implementation
│ ├── sqlite/ # SQLite implementation
│ ├── postgres/ # PostgreSQL implementation
│ └── traits.rs # Repository interfaces
├── domain/
│ ├── pricing/ # Cost calculation
│ ├── sideml/ # Message normalization
│ └── traces/ # Trace extraction/enrichment
└── api/
└── routes/ # HTTP handlers
  • Embedded: No external dependencies
  • Analytical: Columnar storage for fast aggregations
  • Compatible: SQL interface, easy migration to ClickHouse
  • Embedded: Single file, zero config
  • ACID: Full transaction support
  • Compatible: Easy migration to PostgreSQL
  • Performance: Low latency, high throughput
  • Memory safety: No GC pauses, predictable performance
  • Single binary: Easy distribution via npm
  • Embedded: Static files bundled in binary
  • Simple: No SSR complexity for a local workbench UI
  • Portable: Works with any HTTP server