Patrice is a self-hosted work-tracking platform — an API and a web UI that give a team structured tools for tracking work, managing users and access, and syncing with the third-party services they already run on. It's built to be rolled by the team that uses it: you stand up your own instance, then shape it to your organization through configuration rather than code.
The premise is simple: a serious team's coordination problem is, underneath, a database problem — who's doing what, who's allowed to do what, what's been submitted, what's blocked. Patrice owns that database and puts an approachable web service in front of it, so the people running the work never have to touch the schema to make it fit how they actually operate.
Almost everything that looks organization-specific in Patrice is data you configure, not software you fork:
- Roles are the authorization atom. You define them; a user can hold many.
- Divisions (kinds of labor) and teams (content-facing groups) each own an inherent role whose possession is membership.
- The permission matrix is editable: every gated action is a
resource:verbgrant you assign to roles, scoped globally / to a group / to ownership. There's one access engine — no bespoke per-feature rules. - Request Templates define what each kind of task asks for and collects (seven question types); divisions supply defaults, and task authors can customize per task.
- Integrations (which external groups map to which Patrice roles, in which direction) are configured per connection.
- Org behavior — session lifetimes, the retire→revive grace window, self-review, Discord sign-in credentials — is runtime settings, edited in the admin UI, not redeploys.
The software ships the engine, the lifecycle rules, and the UI; your instance's particulars are just rows.
Work tracking. Tasks carry a required division, optional team, a requester, markdown description, claimant slots, a per-division request template, versioned submissions, a derived status (rolled up from slots by a min-rule), and a one-level-threaded message stream with attachments. Requesters approve / return / reject submissions; views are paginated and faceted for teams running hundreds of items.
Users & access. Accounts are invitation-only — a bearer-token invite, or the one-time bootstrap key printed on a fresh install, is the only way in (no open sign-up). Sign in with email/password or Discord. Identity is native and never depends on an integration. A single Cedar-backed pipeline in the API authorizes every action; the web re-authorizes server-side on each call (client guards are UX only). A hard administrability guard keeps an instance from ever losing its last admin.
Third-party integrations. Connect a Discord guild and map Discord roles ↔ Patrice roles, inbound / outbound / bidirectional. Members link their own Discord account (and can sign in with it); role changes reconcile both ways — promptly via a Gateway listener and a ~5s debounced per-user pulse, with a periodic floor sweep as the correctness backstop. Admins get a health surface (gateway state, last/next reconcile, broken mappings with one-click retry) and plain-language alerts when a push is refused — no log-reading or developer required. Security- critical roles are carved out so they're never gated on sync.
Lifecycle & data hygiene. Nothing is hard-deleted on demand: entities are retired (hidden, and hard-denied by the access engine), revivable within a configurable grace window, then garbage-collected once unreferenced — task aggregates as a unit, users scrubbed in place to a tombstone (GDPR-style erasure), orphaned blobs reconciled.
Awareness. A durable notification table plus a thin SSE "go sync" stream (reconcile-on- connect; durability never rides the stream).
Three strictly-bound tiers, each replaceable without rewriting its neighbors:
flowchart LR
user(["User · browser"])
subgraph web["Web tier · Angular (signals-first)"]
spa["SPA<br/>OpenAPI client · guards are UX-only"]
end
subgraph apit["API tier · NestJS — one image, two roles"]
api["api role · HTTP<br/>Cedar engine · sessions · invitations · SSE"]
worker["worker role · no HTTP<br/>pg-boss · cron · GC · integration Gateway"]
end
subgraph data["Data tier"]
pg[("Postgres 18<br/>+ pg-boss queue")]
blob[("Object store<br/>local-fs / S3")]
end
discord{{"Discord — OAuth · Gateway · REST"}}
user -->|HTTPS| spa
spa -->|"HTTP / OpenAPI · cookie auth"| api
api -->|"SSE · go-sync"| spa
api & worker -->|"Prisma / SQL"| pg
api -->|attachments| blob
worker -->|"blob GC"| blob
api -->|"OAuth sign-in"| discord
worker <-->|"role sync · gateway events"| discord
- API is the only tier that talks to the database and the only one that enforces permissions.
Ports & adapters keep the domain core framework-independent. It runs in two roles from one image
—
api(HTTP) andworker(queue consumers, cron, GC, and the integration Gateway socket). - Web holds no domain logic — it reflects API state; the OpenAPI contract is the boundary.
- Postgres is the spine (single-node target: ~200 users / ~100k tasks). Attachments live in an object store (local-fs for single-instance dev, S3-compatible for production).
One organization per deployment — Patrice is something a team runs for itself, not a multi-tenant SaaS. The multi-org seams exist in the model but multi-tenancy is deliberately not built.
The whole stack runs in Docker:
docker compose up --buildThis brings up Postgres, a local mail sink, the API + worker, and the web UI on
http://localhost:8080. On an empty database the API prints a one-time bootstrap key to its
logs — open /setup, paste it, and register the first admin.
Full dev-environment setup (Docker details, secrets, optional AWS/KMS and Discord credentials, tests, and running the tiers without Docker) is in CONTRIBUTING.md.
api/— NestJS backend: the Prisma schema/migrations, the Cedar access engine, auth / sessions / invitations, tasks / request templates / submissions, attachments + object storage, notifications (SSE), the GC sweep, the pg-boss queue, and the integrations adapter + Gateway listener. Seeapi/CONTEXT.md.web/— Angular frontend (standalone components, signals-first). Seeweb/CONTEXT.md.docs/— the architecture, ADRs, the ordered build slices (the project's construction history), and agent guides.CONTEXT-MAP.md/UBIQUITOUS_LANGUAGE.md— the cross-tier domain language.