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
uv
latest
curl -LsSf https://astral.sh/uv/install.sh | sh
pnpm
10+
npm install -g pnpm
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.
Fork the repository on GitHub (top-right Fork button).
Clone your fork using SSH:
Add the upstream remote so you can pull in changes from the main repo:
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 servicesobserval-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:
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:
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?
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
Make sure your branch is rebased on the latest
main(see above).Push to your fork:
GitHub will show a banner on your fork offering to open a PR. Click it, or go to the Observal repository directly.
Fill in the PR template completely. PRs that do not follow the template will be closed.
Link the related issue if one exists (
Fixes #123in 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.jsonin production. For local dev, expose the API port directly or usedocker 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
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
Add the TypeScript response type to
web/src/lib/types.ts.Add the fetch call to
web/src/lib/api.ts.Add the TanStack Query hook to
web/src/hooks/use-api.ts.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 checkingPylance (
ms-python.vscode-pylance), fast type inferenceRuff (
charliermarsh.ruff), linting and formatting on saveESLint (
dbaeumer.vscode-eslint), JavaScript/TypeScript lintingPrettier (
esbenp.prettier-vscode), TypeScript formattingDocker (
ms-azuretools.vscode-docker), container management
Recommended settings.json additions:
PyCharm / IntelliJ
Open the project root as a PyCharm project.
Set the Python interpreter to the uv-managed venv:
observal-server/.venv/bin/python.Mark
observal-serveras the sources root.Install the Ruff plugin for in-editor linting.
Enable File Watchers to run
ruff formaton save.
Pre-commit Hooks
Install once with make hooks. The hooks run automatically on every git commit:
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?