This guide is for changing Druks itself. The backend and Vite dev server run on the host; Compose supplies isolated Postgres and Redis.
From the repository root:
docker compose -f deploy/compose.dev.yaml up -d
uv sync --locked --dev
cp .env.example .env
python3 -c 'import base64, os; print("DRUKS_SECRETS_KEY=" + base64.b64encode(os.urandom(32)).decode())' >> .env
uv run druks init-dbLocal development runs DRUKS_AUTH_MODE=none (the .env.example default):
the loopback dashboard has no authentication and exactly one operator
account, created by your first harness connection. To exercise header mode
against the dev server, set DRUKS_AUTH_MODE=header, name a header in
DRUKS_AUTH_HEADER (no default), and send it yourself (for example with a
browser header extension or curl -H 'X-Edge-Email: you@example.com').
The dev Compose project creates two databases:
druks_devfor the host-run applicationdruks_testfor pytest, which rebuilds its schema during the suite
.env.example points the application at druks_dev. The suite reaches
druks_test and Redis index 15 through DRUKS_TEST_DATABASE_URL and
DRUKS_TEST_REDIS_URL, never through the application's own settings, so the two
cannot be confused.
Start the backend:
uv run uvicorn druks.api.app:app --host 127.0.0.1 --port 8001In another terminal:
npm --prefix frontend ci
npm --prefix frontend run devVite proxies API traffic to the backend. The production backend image instead contains the built SPA and serves it from FastAPI.
| Path | Responsibility |
|---|---|
backend/druks/workflows.py |
Public workflow, step, gate, scheduling, and start API |
backend/druks/agents.py |
Public agent descriptor and output contract |
backend/druks/durable/ |
DBOS integration, run projection, lifecycle internals |
backend/druks/extensions/ |
Entry-point loading, discovery, author settings |
backend/druks/events/, signals.py |
Event log, feed, and reactions |
backend/druks/webhooks/ |
Authenticated delivery framework and deduplication |
backend/druks/harnesses/ |
Claude/Codex invocation, auth, usage, capability manifests |
backend/druks/sandbox/ |
Drukbox lifecycle, SSH execution, workspace delivery |
backend/druks/api/ |
FastAPI composition and platform routes |
backend/druks/{mcp,skills,notifications,user_settings}/ |
Shared operator services |
backend/druks/contrib/ship/ |
Bundled reference extension, not framework core |
frontend/src/ |
Shared dashboard shell and bundled extension UI |
backend/migrations/ |
Core/bundled schema history |
deploy/, scripts/ |
Images, Compose, Caddy, setup, and deployment |
The API process embeds DBOS and executes workflows. Extension modules register capabilities during boot, after DBOS initialization and before launch.
The main package registers bundled extensions through pyproject.toml. CI also
installs backend/tests/druks-field_notes as a real editable distribution and
runs the proof-extension tests. Those tests are the executable contract for:
- app-less and boot-time entry-point loading
- role-module discovery
- route and subject read-side mounting
- independent migrations and table-prefix enforcement
- workflow start, settings, and feed formatting
When changing the author API, update the scaffold, proof extension, author guide, and tests together.
Core and bundled historical tables use the core Alembic history:
uv run alembic -c backend/alembic.ini revision --autogenerate -m "describe change"
uv run druks init-dbFor an independently packaged extension:
uv run druks makemigrations <extension-name> -m "describe change"
uv run druks init-dbThe extension owns its migration directory and version table. Review every autogenerated revision before applying it.
Backend checks:
uv pip install -e backend/tests/druks-field_notes # once per environment
uv run ruff check backend
uv run ruff format --check backend
uv run pytest backend/The suite builds its subjects out of field_notes, the proof extension. It is a
standalone distribution that depends on druks, so it installs like any author's
extension rather than being a dependency of druks — install it once and the whole
suite runs. The pull-request backend workflow does the same. Pyright is configured
for local/editor use but is not currently a CI gate.
Frontend checks:
npm --prefix frontend run lint
npm --prefix frontend test
npm --prefix frontend run buildThe frontend CI workflow runs those three commands on Node 22.
Documentation-only changes should also run:
git diff --checkand a relative-link/anchor check over Markdown. The repository does not currently ship a Markdown linter or link-check command.
Backend tests mock most provider boundaries. For a real local sandbox, run Drukbox on the host as described in Full local setup and set:
DRUKS_SANDBOX_SERVICE_URL=http://127.0.0.1:8000
DRUKS_SANDBOX_SERVICE_TOKEN=dev-token
DRUKS_SANDBOX_IMAGE=ghcr.io/czpython/druks-sandbox:latestuv run druks doctor --sandbox creates a real host. Run it deliberately; it is
not part of the normal test suite.
Backend extension entry points and shared-shell React routes have different
delivery mechanisms. Python discovery can load an installed extension at
runtime. An extension can ship a standalone static app in its package's
dist/, served at /app/<name>. React code that joins the bundled dashboard
shell must already be in the SPA and register through
frontend/src/extensions/index.ts; a wheel cannot inject routes into that
existing JavaScript bundle.
See the frontend guide before adding dashboard pages.