For the complete documentation index, see llms.txt. This page is also available as Markdown.

Development Guide

[!IMPORTANT] Discord is the primary place to ask questions. Join at discord.observal.io.

  • #contributing, setup help, workflow questions, anything about the contribution process

  • #bug, discuss bugs before filing a GitHub issue

  • #feature-requests, pitch ideas and get early feedback before writing code

GitHub issues and PRs are for concrete, actionable items. Exploratory discussion belongs on Discord first.

Parts of this guide structure were inspired by the AnkiDroid Development Guide. Attribution given with thanks, check them out if you want another welcoming OSS project to contribute to.


Table of Contents


Community Standards

We are a small, active community and we take the quality of interactions seriously.

[!WARNING] The following will result in a moderator warning. A second violation results in a temporary or permanent ban from the repository and Discord:

  • Pinging contributors, maintainers, or reviewers unnecessarily (outside of a direct reply on your own open PR or issue)

  • Submitting low-effort or unreviewed PRs (slop), including unreviewed AI output or autonomous agent submissions

  • Violating the Code of Conduct in any channel

  • Harassing reviewers over merge timelines

Maintainers volunteer their time. Treat them accordingly.

A note on autonomous coding agents

Autonomous coding agents (Devin, SWE-agent, OpenHands, etc.) are not permitted to submit PRs. This is a legal constraint, not a quality judgment. The US Copyright Office's 2025 guidance confirms that purely AI-generated code has no copyright owner, which breaks our CLA and our ability to license and enforce the project. See the AI Policy for the full explanation.


Prerequisites

Tool
Version
Install

Docker

24+ with Compose v2

docs.docker.com

uv

latest

curl -LsSf https://astral.sh/uv/install.sh | sh

Node.js

20+

Via nvm or mise

pnpm

10+

npm install -g pnpm

Git

2.28+

git-scm.com

Verify your Docker installation supports Compose v2:

If you see docker-compose: command not found or a v1 version, upgrade Docker Desktop or install the Compose plugin separately.


Git Setup

SSH authentication

Using SSH is strongly recommended, it avoids password prompts and is required if you enable 2FA on GitHub.

Add the output to GitHub → Settings → SSH and GPG keys → New SSH key.

Test it:

Git identity

Set your identity so it matches your GitHub account:

If you want to sign commits with GPG as well (optional but appreciated):

Fork and clone

You only need to do this once. For every subsequent contribution, just create a new branch on your existing fork.

  1. Fork the repository on GitHub (top-right Fork button).

  2. Clone your fork using SSH:

  1. Add the upstream remote so you can pull in changes from the main repo:

  1. Verify your remotes:


First-time Setup

Install pre-commit hooks

Do this before your first commit. The hooks run ruff, format checkers, secret scanners, SPDX header injection, and migration chain validation automatically.

Start the Docker stack

The first build takes several minutes while Docker downloads and compiles images. Subsequent starts are fast.

Watch progress with:

For normal code and dependency changes, prefer the fast rebuild target:

make rebuild-fast is not a separate dev profile and does not enable hot reload. It uses the same Docker Compose stack as make rebuild, but only builds the two app images that contain local code:

  • observal-api, shared by the API, init, and worker services

  • observal-web, used by the web service

Then it starts the full stack with the freshly built images.

Wait until all services are healthy:

Default endpoints:

Service
URL

LB (all traffic)

http://localhost

Web UI (direct)

http://localhost:3000

Prometheus, optional

http://localhost:9090

Grafana, optional

http://localhost:3001

ClickHouse HTTP

http://localhost:8123

Install the CLI

Log in

On a fresh server, this bootstraps the admin account automatically. Use super@demo.example / super-changeme for the super_admin account.

Demo accounts

Seeded automatically on first startup:

Email
Password
Role

super@demo.example

super-changeme

super_admin

admin@demo.example

admin-changeme

admin

dev@demo.example

dev-changeme

developer

user@demo.example

user-changeme

user


Make Targets Reference

Which rebuild target should I use?

Situation
Target
Notes

Backend source changed

make rebuild-fast

Rebuilds the shared API image used by API, init, and worker.

Worker, init, migration, or ClickHouse setup code changed

make rebuild-fast

These services use the same observal-api image. This is the safe path for schema and init path changes because it refreshes the image used by observal-init and observal-worker.

Python dependencies changed in observal-server/pyproject.toml or observal-server/uv.lock

make rebuild-fast

Docker reruns the Python dependency layer when those files change.

Frontend source changed

make rebuild-fast

Rebuilds the web image.

Frontend dependencies changed in package.json, web/package.json, or pnpm-lock.yaml

make rebuild-fast

Docker reruns the pnpm dependency layer when those files change.

Compose topology changed

make rebuild

Use this for new services, changed image names, changed build contexts, changed profiles, or volume and network changes.

Cache looks stale or the stack behaves unexpectedly

make rebuild first

Use make rebuild-clean only when you intentionally want a no-cache rebuild with volumes removed.

make down stops containers started by either make rebuild or make rebuild-fast. Both targets use the same Compose project and the same volumes.


Architecture Overview

Observal is a monorepo:

Databases:

  • PostgreSQL, relational data (users, agents, registry, feedback)

  • ClickHouse, session events, aggregates, audit events, and security events

They are not interchangeable. Never write telemetry to Postgres or relational data to ClickHouse.

Supporting services: Redis (pub/sub + arq job queue), arq worker, nginx reverse proxy. Prometheus and Grafana are optional.

See AGENTS.md for a complete map of every important file and service.


Git Workflow

Making a new branch

Always branch from the latest main. Never commit directly to main.

Keeping your branch up to date

If main has moved forward while you were working, rebase your branch onto it:

Rebasing keeps history linear and makes your PR easier to review than a merge commit would.

Dealing with merge conflicts

Conflicts arise when the same file was changed in both main and your branch. During git rebase main, git will pause and show you:

Open the file and look for conflict markers:

Edit the file to the correct final state (keeping whichever changes are right, or combining them), then:

If you get confused and want to start over:

After resolving and pushing, use --force-with-lease rather than --force, it's safer:

Submitting a pull request

  1. Make sure your branch is rebased on the latest main (see above).

  2. Push to your fork:

  3. GitHub will show a banner on your fork offering to open a PR. Click it, or go to the Observal repository directly.

  4. Fill in the PR template completely. PRs that do not follow the template will be closed.

  5. Link the related issue if one exists (Fixes #123 in the PR body closes it automatically on merge).

After review feedback

If a reviewer requests changes, make them on the same branch and push again. Do not open a new PR. If your changes are small fixes to an existing commit, amend rather than adding new commits:


Working on the Backend

Running tests

Tests mock all external services. Docker does not need to be running.

Testing conventions

New Python tests should follow the Testing Guide. The current suite has mixed historical patterns, so do not rewrite old tests only for style. When adding or touching tests, prefer the clean pattern documented there: one behavior area per file, small local helper factories, hermetic CLI and API test setup, explicit async mocks, and behavior-focused assertions.

Running a single test

Code coverage

Coverage is collected automatically on the 3.13 matrix run in CI. To generate it locally:

Adding a database migration

Edit the generated file in observal-server/alembic/versions/. Then verify the chain is intact:

[!CAUTION] Never edit an existing migration file. Always create a new one. A broken migration chain blocks CI and prevents the server from starting.

Apply migrations to your local stack:

Connecting to PostgreSQL directly

Useful for inspecting tables, running manual queries, or verifying migration results.

Connecting to ClickHouse directly

ClickHouse exposes an HTTP interface. Use the Play UI in your browser:

Or query from the terminal:

Or connect with the CLI client:

Debugging the API

The API server runs with --reload in development mode. Log output is available via:

To add a breakpoint, use breakpoint() in Python code, the debugger will pause in the container's stdout. Or attach a remote debugger by adding debugpy to your dev dependencies and exposing a debug port.

The OpenAPI docs are available at (API port, not through the LB which blocks these paths):

  • http://localhost:8000/docs (Swagger UI, requires direct API port access)

  • http://localhost:8000/redoc (ReDoc, requires direct API port access)

Note: The nginx LB blocks /docs, /redoc, and /openapi.json in production. For local dev, expose the API port directly or use docker compose exec.


Working on the Frontend

Running the frontend in isolation

Create web/.env.local with:

The frontend proxies all /api/v1/* calls to the backend URL set by NEXT_PUBLIC_API_URL.

Design system

Token
Value

Color space

OKLCH

Themes

light, dark, midnight, forest, sunset

Display font

Archivo

Body font

Albert Sans

Code font

JetBrains Mono

Spacing base

4pt

Components

shadcn/ui

Charts

Recharts

Data fetching

TanStack Query

Tables

TanStack Table

All themes are defined in web/src/app/globals.css as CSS custom properties. Semantic tokens (background, foreground, card, border, primary, secondary, accent, destructive, success, warning, info) map to OKLCH values per theme.

When adding new UI, use the semantic tokens, never hardcode colors. Check all five themes look correct before submitting.

Adding a new API endpoint to the frontend

  1. Add the TypeScript response type to web/src/lib/types.ts.

  2. Add the fetch call to web/src/lib/api.ts.

  3. Add the TanStack Query hook to web/src/hooks/use-api.ts.

  4. Use the hook in your component.

Screenshots for UI changes

[!IMPORTANT] Any PR that touches the web frontend must include screenshots of all affected screens in the PR description. This is required regardless of how small the change is. Attach screenshots directly to the PR body, not as review comments.


Working on the CLI

Reinstalling after changes

The CLI is installed as a uv tool. Changes to source files are reflected immediately because it is installed in editable mode (--editable). If you add new entry points or change pyproject.toml, reinstall:

Testing session delivery

Run a harness session with hooks installed, then reconcile and inspect exporter status:

Session records remain in the local outbox until the server acknowledges them.


Harness Recommendations

VS Code

Install these extensions for the best experience:

  • Python (ms-python.python), LSP, type checking

  • Pylance (ms-python.vscode-pylance), fast type inference

  • Ruff (charliermarsh.ruff), linting and formatting on save

  • ESLint (dbaeumer.vscode-eslint), JavaScript/TypeScript linting

  • Prettier (esbenp.prettier-vscode), TypeScript formatting

  • Docker (ms-azuretools.vscode-docker), container management

Recommended settings.json additions:

PyCharm / IntelliJ

  1. Open the project root as a PyCharm project.

  2. Set the Python interpreter to the uv-managed venv: observal-server/.venv/bin/python.

  3. Mark observal-server as the sources root.

  4. Install the Ruff plugin for in-editor linting.

  5. Enable File Watchers to run ruff format on save.


Pre-commit Hooks

Install once with make hooks. The hooks run automatically on every git commit:

Hook
What it does

ruff

Lints Python, auto-fixes what it can

ruff-format

Formats Python in place

trailing-whitespace

Strips trailing whitespace

end-of-file-fixer

Ensures files end with a newline

check-yaml / check-toml / check-json

Validates config file syntax

check-added-large-files

Blocks files over 500KB

check-merge-conflict

Blocks leftover <<<<<<< markers

detect-private-key

Scans for private key material

no-commit-to-branch

Blocks direct commits to main

check-secrets

Scans staged content for API keys, tokens, .env files

check-migrations

Validates Alembic migration chain integrity

spdx-update

Adds your SPDX copyright line to staged files

hadolint-docker

Lints Dockerfiles

If a hook fails, fix the reported issue and commit again. To bypass in an emergency (not recommended):


Pull Request Checklist

Before opening a PR:


Getting Help

If you are stuck, the best place to ask is #contributing on Discord. Search the channel history first, most common questions have been answered there already.

For bugs use #bug. For feature ideas use #feature-requests.

[!NOTE] Do not open a GitHub issue just to ask a question. Issues are for confirmed bugs and accepted feature requests. Questions belong on Discord.

Last updated

Was this helpful?