Skip to main content
[!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

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 code, dependency, and Compose changes, use:
make rebuild-fast is the same target under another name. It is not a separate dev profile and does not enable hot reload. It builds only 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:

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:

Make Targets Reference

Which rebuild target should I use?

make down stops containers started by make rebuild.

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 the three Git conflict-marker lines: seven less-than signs followed by HEAD, seven equals signs, and seven greater-than signs followed by the other branch name. The text between them contains the competing changes. 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

The Vite dev server proxies all /api calls to the backend at http://localhost:8000, configured in web/vite.config.ts. The Docker stack does not publish port 8000, so run the API locally or point the proxy target at http://localhost (the nginx LB).

Design system

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: 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:
  • make test passes locally
  • make lint passes with no errors
  • Branch is rebased on the latest main
  • Commits follow Conventional Commits format
  • All commits are signed off with SPDX headers (pre-commit hook handles this automatically)
  • PR template is filled in completely and reviewed by the accountable human contributor
  • CHANGELOG.md updated under [Unreleased] for user-facing changes
  • Frontend changes include screenshots in the PR body
  • AI-assisted contributions labelled with tool name and version (see AI Policy)
  • No unattended agent submissions without meaningful human direction and review (see AI Policy)

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.