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.
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 ofbackend/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/review/checklist.md.
For app-surface changes, inspect the proof app at
backend/tests/druks-field_notes/ and its tests as well as the author guide.
- 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.statefrom 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.workflows,druks.workspaces,druks.agents,druks.events,druks.signals,druks.db,druks.schemas,druks.prompts,druks.durable,druks.apps,druks.ui, anddruks.webhooks. A reference todruks.buildor another app in these modules inverts the platform. The builtin appscoreandchatare platform code, so these modules can import them. - Internal code takes
session: AsyncSessionas a required parameter. Only author code and the seams that bind the registry readdruks.db.db_session(); ruff's banned-api names the seams inpyproject.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
@stepto 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.
- 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
isinstancechain. - 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.
- 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-rootdist/output. - Calls server:
druks-calls/containscalls, the Pipecat program that carries phone calls, with its ownpyproject.tomland lock. Its image builds fromdeploy/calls/and has its own release workflow. - Deployment:
deploy/contains Compose files, Caddy configuration, and sandbox image inputs. The public runbook isdocs/deployment.md. - Documentation:
docs/contains the public and contributor guides. - CI:
.github/workflows/contains PR checks and the release-image build.
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:
docker compose -f deploy/compose.dev.yaml up -dRun the backend gates:
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.
If the public app surface changed, also install and exercise the proof
app as described in docs/development.md.
Run the frontend gates:
npm --prefix frontend run lint
npm --prefix frontend test
npm --prefix frontend run buildRun the calls server's tests from druks-calls/:
uv run --locked pytestThe PR workflows in .github/workflows/on-pull-request-*.yml are the source of
truth for CI, including the proof-app install phase.
Use <linear-ticket> -- <commit-msg> for each commit message, for example
DRU-547 -- Deliver GitHub MCP to review workspaces. If no Linear ticket
exists, use only <commit-msg>. Do not add an issue or pull request number.
GitHub adds the pull request number.
- 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_factorybehavior and guarantees from policy. - If contributor routing, repository structure, commands, or an architectural invariant changes, update this file.
- 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.pyand subclassDruksError, not in contracts or models. - Keep forward-looking notes in the issue tracker, not in source comments.