# AGENTS.md Druks runs durable agent apps on DBOS and Postgres. It owns workflow execution, persisted state, events, gates, webhooks, sandbox access, and the shared dashboard. Apps are standalone Python packages. They register through the `druks.apps` entry point. `software_factory` is the bundled reference app for coding work through GitHub pull requests. ## Read map Start with `README.md`, then read only the material relevant to the task: - Workflow lifecycle, state, replay, or recovery: `docs/concepts.md` - App contracts or the public author surface: `docs/writing-an-app.md` - App pages, blocks, values, fields, actions, and liveness: `docs/druks-ui.md` - Configuration or environment variables: `docs/configuration.md` - Local install and operations: `docs/full-local.md` - Remote deployment: `docs/deployment.md` - Failure diagnosis: `docs/troubleshooting.md` - Backend contribution, migrations, or verification: `docs/development.md` - Migration head: `alembic heads`, not a scan of `backend/migrations/versions/` - Shared SPA work: `frontend/README.md` - Documentation navigation and audience ownership: `docs/index.md` - The checklist and craft gate every change is held to: `.druks/software_factory/review.md`. For app-surface changes, inspect the proof app at `backend/tests/druks-field_notes/` and its tests as well as the author guide. ## Architectural boundaries - Keep platform and app ownership explicit. Keep GitHub issue, branch, PR, and coding-agent policy in `software_factory`, not in Druks core. - Describe durability precisely. Druks reuses completed durable checkpoints during replay. An interrupted operation can run again. Do not imply arbitrary-line resume or exactly-once external side effects. - Derive `Run.state` from DBOS workflow status. Do not add a second writable state mirror. - Import the public concern namespaces documented in `docs/writing-an-app.md`. Do not import Druks internals. - Keep backend app discovery in runtime packaging. Keep shared-dashboard app UI registration in the frontend build. Keep standalone `dist/` delivery separate. - Keep generic agent, harness, workspace, sandbox, event, gate, webhook, and settings plumbing in Druks. Keep domain-specific policy in the app. - One Druks installation serves one organization. Execution defaults and agent overrides are shared. Accounts own credentials, personal preferences, and run attribution. Do not add personal execution defaults. - Grow the author surface by parameter, not by namespace. If the SDK lacks a capability, widen the primitive that owns it. Add a keyword argument or a method to the class that holds the data. Do not add a namespace, facade, context object, or one-use helper module. - Do not import an app from an author-surface module. Import only `druks.accounts`, `druks.workflows`, `druks.workspaces`, `druks.agents`, `druks.events`, `druks.signals`, `druks.db`, `druks.schemas`, `druks.prompts`, `druks.durable`, `druks.apps`, `druks.ui`, and `druks.webhooks`. A reference to `druks.build` or another app in these modules inverts the platform. The builtin apps `core` and `chat` are platform code, so these modules can import them. - Internal code takes `session: AsyncSession` as a required parameter. Only author code and the seams that bind the registry read `druks.db.db_session()`; ruff's banned-api names the seams in `pyproject.toml`. - Derive liveness from run state. Do not mirror it in a column. Store an external outcome after its owner announces it. Do not infer that outcome from run lifecycle. A merge can occur after Druks stops the related run. - Use `@step` to mark a replay checkpoint, not an expensive call. On replay the body re-executes from the top and completed steps return cached results, so code outside a step runs again. Moving the boundary changes correctness, not performance. - Store transient live state and mutual exclusion in Redis, keyed by run id. Do not give either one a durable column, row lock, or in-process lock. - Do not add a store before something reads it. - Route a workflow cadence or pause through its schedule overrides. Do not use a settings column for this purpose. - Use one canonical name and shape for each contract. Fail loudly on every other shape. - A managed service differs from a self-hosted one only in configuration: a URL, a key, or a verifier. Any manager that serves the same routes and publishes a JWKS works. The docs describe Druks as an operator runs it: the `[services.]` table and the Services page. They name no manager. A service declares how it authenticates in a nested `Authentication`, found by name like `Settings`. It names its ways as instances from `druks.services.auth` under fixed names: `api_key`, `jwt`, `oauth`, `sign_in`. A kind owns its credential; `Settings` holds the rest. An install stores the service's own grant, a vault row with no account; a service acts one way at a time. A service never asks `is_managed()`; the manager seams do: the OAuth client, the configuration sync, resources, and webhooks. - Do not type-switch over a typed stream in app code. When a projection needs ordering or anchoring, grow the SDK primitive instead of an `isinstance` chain. - Put only identity and facts in a read-side field. Keep UI wording in the app pages. - Give a shared resource one global registry. When a second consumer needs a different answer, add a scoping axis. Do not add one in anticipation. - Druks UI shows data and acts on an app's own records. Do not rebuild the screens or the settings of a connected service in app pages or app settings. The app's Bot changes the service through its API. See "What pages are for" in `docs/writing-an-app.md`. ## Layout - **Backend:** `backend/druks/` contains FastAPI, DBOS, SQLAlchemy 2.0, Pydantic v2, and bundled apps. - **Migrations:** `backend/druks/migrations/` contains platform Alembic migrations. - **Tests:** `backend/tests/` contains the pytest suite backed by real Postgres. - **Proof app:** `backend/tests/druks-field_notes/` contains the independently packaged proof app. - **Frontend:** `frontend/` contains the React 19 and Vite shared SPA. The backend image includes its repository-root `dist/` output. - **Calls server:** `druks-calls/` contains `calls`, the Pipecat program that carries phone calls, with its own `pyproject.toml` and lock. Its image builds from `deploy/calls/` and has its own release workflow. - **Deployment:** `deploy/` contains Compose files, Caddy configuration, and sandbox image inputs. The public runbook is `docs/deployment.md`. - **Documentation:** `docs/` contains the public and contributor guides. - **CI:** `.github/workflows/` contains PR checks and the release-image build. ## Verification Backend tests need Postgres on `localhost:5432` with user and password `druks`, and the `druks_test` database. `DRUKS_TEST_DATABASE_URL` overrides it — `DRUKS_DATABASE_URL` is the runtime's and the suite never reads it. DBOS integration tests also read `DRUKS_TEST_PG`. Start the development database with: ```bash docker compose -f deploy/compose.dev.yaml up -d ``` Run the backend gates: ```bash uv run ruff check backend druks-calls uv run ruff format --check backend druks-calls uv pip install -e backend/tests/druks-field_notes uv run pytest backend/ ``` The suite collects the proof app, so `pytest backend/` fails at collection until that editable install has run. Before you push, run `druks-house-rules check --base origin/main`. Druks runs the same check on every pull request and posts it as the `druks-house-rules` check. Install the command with `uv tool install git+https://github.com/getdruks/druks-house-rules@v0.0.5`. If the public app surface changed, also install and exercise the proof app as described in `docs/development.md`. Run the frontend gates: ```bash npm --prefix frontend run lint npm --prefix frontend test npm --prefix frontend run build ``` Run the calls server's tests from `druks-calls/`: ```bash uv run --locked pytest ``` The PR workflows in `.github/workflows/on-pull-request-*.yml` are the source of truth for CI, including the proof-app install phase. ## Commits Use ` -- ` for each commit message, for example `DRU-547 -- Deliver GitHub MCP to review workspaces`. If no Linear ticket exists, use only ``. Do not add an issue or pull request number. GitHub adds the pull request number. ## Documentation discipline - Put product behavior, setup, operations, troubleshooting, and app author contracts in the appropriate public guide. Keep this file limited to task routing, architectural boundaries, and contributor rules. - Link to one canonical explanation instead of copying it into multiple pages. - Make sure that behavioral claims match current source and focused tests. Distinguish framework capabilities from `software_factory` behavior and guarantees from policy. - If contributor routing, repository structure, commands, or an architectural invariant changes, update this file. ## Style - Make the minimum change that solves the problem. No speculative abstractions, configurability, or error handling for impossible cases. Each changed line must trace to the request. Do not improve adjacent code. - Comments explain a non-obvious *why*—a constraint, invariant, or workaround—not what the next line does. Do not add section-divider banner comments. - If the signature and body do not make a contract obvious, add a class, module, or function docstring. - Exception classes live in the package's `exceptions.py` and subclass `DruksError`, not in contracts or models. - Keep forward-looking notes in the issue tracker, not in source comments.