Skip to content

Development Guide

Wuthrich Julien edited this page Aug 22, 2026 · 1 revision

Development Guide

Start with CONTRIBUTING.md and CLAUDE.md (the hard rules, kept short on purpose). Each folder's README is the reference for that area.

Setup

make install        # uv sync --all-packages --all-extras + pnpm install
make infra-up       # clickhouse, postgres, redis, minio
make migrate        # ClickHouse DDL runner + Alembic
make seed           # default project + ingest key tracely_dev_key
make backend        # FastAPI :8000 (OpenAPI at /docs)   ┐
make workers        # Celery                              ├ three terminals
make frontend       # next dev — `cd frontend && pnpm dev -p 3001` to match Docker
make demo           # populate traces, clusters, cases, gates

Tests — no infra, ~6s

uv run pytest -q backend/tests sdk/tests          # what CI runs
uv run pytest -q backend/tests/test_x.py::test_y -x
uv run ruff check . && uv run ruff format .
cd frontend && pnpm test                          # vitest; pnpm build = tsc + lint

make test only runs backend/tests — run sdk/tests too. One SDK test needs the backend package on the path (CI installs both).

The rules reviewers will hold you to

  • No SQL in api/routers/. ClickHouse reads go through infrastructure/clickhouse/async_reader.py (API) or trace_reader.py (workers); Postgres through infrastructure/db/repositories.py.
  • Every LLM call goes through infrastructure/llm/provider.py, inside provider.use_project_key(project_id), with llm_enabled() checked inside the wrap. The in-app assistant is the one sanctioned use_server_key() exception.
  • Everything is scoped by project_id, resolved from the credential by api/auth. Agents (MCP, assistant) reach data only through the routers.
  • Writes are idempotent — deterministic ids + ReplacingMergeTree. Anything sampled must be deterministic per (trace_id, score_name).
  • One verdict policy, implemented twice (Python domain/evaluation/verdict.py and SQL in async_reader). Change both.
  • Internal runs are never evaluated and never counted (internal_kind).
  • Frontend: never add an env: block to next.config.mjs — keys must not reach the browser.
  • New backend env varconfig.py and the x-app-env anchor in docker-compose.yml.

Migrations

  • Postgres: cd backend && uv run alembic revision -m "…" / uv run alembic upgrade head. Never stamp alembic ahead of the deployed image.
  • ClickHouse: add NNN_name.up.sql in backend/tracely/infrastructure/clickhouse/ddl/, applied by python -m tracely.infrastructure.clickhouse.migrations.
  • Dependencies: regenerate uv.lock — CI runs uv sync --frozen.

Gotchas

  • The Celery worker does not hot-reload: docker compose restart worker after touching worker / eval / failure-intel / otel-mapping code.
  • Celery runs --pool=solo --concurrency=1 locally on purpose (numba / UMAP / HDBSCAN are fork-fragile). Consequence: gate turns are ingested inline, never enqueued, or the worker deadlocks on itself.
  • OTLP span ids are base64; hex silently yields an id nothing can look up.
  • OTLP arrives in three message conventions; normalize in otel/messages.py, not in the renderers.
  • SKIP scores are dropped before the conversation roll-up, or an all-skipped run would report PASS.

Releasing the SDK

Tag sdk-vX.Y.Z → the publish-sdk workflow publishes tracely-ai to PyPI via trusted publishing. Bump sdk/pyproject.toml and demo/ first. Details: sdk/README.md § Releasing.

Clone this wiki locally