Skip to content

Architecture

윤혁준 edited this page Jul 10, 2026 · 1 revision

한국어로 보기

Architecture

toard collects AI coding usage on a user's machine, authenticates it at the server, calculates cost there, and stores the result for dashboards. The complete, versioned design contract is docs/ARCHITECTURE.md; this page is its map.

Responsibilities by package

Location Responsibility
apps/web Next.js application, API routes, dashboard, Auth.js, and server-side attribution and pricing.
packages/core Dependency-free domain types and the StorageBackend contract.
packages/ingest OTLP/JSON parsing, provider identification, normalization, and deduplication.
packages/pricing LiteLLM pricing synchronization and token-to-cost calculation.
packages/storage-postgres Default Postgres StorageBackend implementation.
packages/storage-clickhouse Optional ClickHouse analytics backend implementation.
shim/rust Rust wrapper, local session-file collector, scheduled collection, and self-update.
migrations and clickhouse/init Postgres migrations and ClickHouse initialization schema.

Collection flow and trust boundary

The default collection path is local session-file pull. The shim parses supported tool session files, normalizes them to UsageEvent[], and posts them to /api/v1/events with an ingest token. The server uses that token to set the user identity and calculates the cost itself; it does not trust a client-provided user ID or cost.

flowchart LR
  tool["AI coding tool"] --> files["Local session files"]
  files --> shim["toard-shim"]
  shim -->|"UsageEvent[] + Bearer token"| ingest["/api/v1/events"]
  ingest --> pricing["Server attribution and pricing"]
  pricing --> storage["StorageBackend"]
  storage --> dashboard["Dashboard"]
Loading

The shim supports Claude, Codex, Gemini, and Qwen session-file collection. Its default scheduled collector handles use that does not pass through the wrapper. Experimental OTLP is separately gated and is not required for the normal pull flow. Read the shim README for supported sources, cursor behavior, and the collection triggers.

Prompt and response content follows a separate opt-in route, /api/v1/prompts, with separate cursors. It is not part of default usage ingestion.

Postgres first; ClickHouse when opted in

Postgres is the default single-store deployment. In ClickHouse mode, events and analytics move to ClickHouse while users, teams, authentication, and tokens remain in Postgres. StorageBackend keeps the application-level storage contract consistent across those modes.

ClickHouse rollup reads are deliberately off by default. They require operational validation before enablement; see Operating toard and the exact rollup runbook.

Design contracts that need coordinated changes

  • UsageEvent is a mirrored contract in TypeScript core and the Rust shim. Update both when it changes.
  • Data model and collection-contract changes are difficult to reverse. Discuss them first and update the relevant architecture decision record.
  • Keep migrations forward-only and use expand-then-contract sequencing for rolling deployments.
  • Event timestamps are stored in UTC; dashboard presentation uses the viewer timezone while organization timezone still governs materialized daily boundaries.

Source documents

Clone this wiki locally