Contributing to Cumora
September 17, 2026 · View on GitHub
Thanks for your interest in Cumora. This guide covers how to get set up, the checks your change needs to pass, and a couple of architecture invariants that are enforced in CI so you don't get surprised.
By contributing you agree that your contributions are licensed under the project's MIT License.
Getting set up
You need Node ≥ 22 (CI runs on Node 24), plus Postgres and Redis
running locally. Node 18 and 20 can no longer install the dependency tree —
@capacitor/cli requires node >= 22 and @aws-sdk/client-s3 requires
node >= 20.
createdb -h localhost cumora
export OPENAI_API_KEY=sk-... # the only hard-required env var
npm run setup # root + Email Worker dependencies
npm run dev:all # Vite renderer on :5180 + API server on :5181
Use npm run setup rather than a root-only npm install: the root test
command also runs workers/email-gate tests, whose dependencies live in the
Worker's separate package.json.
Open http://localhost:5180 for the web app, or npm run electron:dev for the
desktop shell. Database migrations are applied via npm run migrate (run
automatically by dev:all and electron:dev), and seeded on boot with a starter team. Everything else (OAuth login, email, storage, push, the
sub2api LLM gateway) soft-disables when its env vars are unset — see
.env.example.
Component-specific setup lives in docs/: BYOA.md (the local-engine
daemon), MOBILE_IOS.md, PUSH_NOTIFICATIONS.md, email.md.
Before you open a PR
Run the same gates CI runs. All of these must pass:
npm run lint # Biome lint (autofix with `npm run lint:fix`)
npm run typecheck # frontend types
npm run server:typecheck # server types
npm test # unit tests (node:test) for server + workers + frontend lib
npm run test:integration # integration suite, see the env var below
npm run guard:big-brain # architecture guard, see below
npm run guard:llm-tracked # architecture guard, see below
npm run guard:engine-registry # architecture guard, see below
npm run guard:migration-locks # schema guard, see below
npm run test:integration needs a dedicated database and does nothing
without INTEGRATION_DATABASE_URL — it prints [integration] skipped and
exits 0, which looks exactly like a pass. The suite TRUNCATEs every table,
so point it at a throwaway DB:
createdb -h localhost cumora_test
INTEGRATION_DATABASE_URL=postgres://$USER@localhost:5432/cumora_test \
npm run test:integration
Biome is configured (biome.json) as a linter only — it is not a
formatter here, so it won't reflow existing code. The rule set is a
pragmatic subset of Biome's recommended rules: correctness and real-bug
rules are on; noisy or intentional-pattern style rules are off, while the a11y
rules are enforced incrementally (useButtonType, ARIA roles/props, and core
accessibility rules are active).
Both TypeScript projects are strict. Tests live in four places, and
npm test runs the first, third and fourth: tests/ (frontend lib units),
server/src/__integration__ (integration, opt-in via the env var above),
server/src/__tests__ (server units), and workers/email-gate/src
(Worker units — this is why you want npm run setup over a bare
npm install).
Three architecture invariants (enforced in CI)
These aren't style preferences — they're the product's core cost model, and a guard script will fail your build if you break them:
- Only agent turns may use the big model. The cheap "cerebellum" model
handles triage, classification, summaries, and every other utility call;
the expensive model is reserved for the actual agent reasoning turn. If you
add an LLM call, route it through the right tier.
npm run guard:big-brainchecks this. - Every LLM call must be tracked in the cost ledger. Untracked spend is a
correctness bug here, not just an oversight.
npm run guard:llm-trackedchecks this. - A BYOA engine is wired into all of its registries or none. A
half-wired engine does not error —
normalizeByoaSource()maps anything unknown tobyoa-claude, so its runs quietly bill to the wrong engine.npm run guard:engine-registrychecks this.
Writing a schema migration
Migrations run from a pre-deploy Job, against production, while the old Pods
are still serving traffic, and ensureSchema pins that session at
lock_timeout = '5s'. DDL that needs an ACCESS EXCLUSIVE lock on a hot table
has five seconds to get it before the statement aborts with 55P03; even when
it does get the lock, every query behind it waits for the whole statement.
Once a migration has been applied its SQL is checksum-pinned and can never be
rewritten — so a lock-taking migration cannot be fixed afterwards, only worked
around. npm run guard:migration-locks rejects the two patterns that have cost
this project production time:
ADD COLUMN … DEFAULT <volatile>. PostgreSQL's metadata-only fast path applies only to non-volatile defaults;gen_random_uuid(),random(),nextval()and friends force a full table and index rewrite. Write it as nullable column → batched backfill →SET DEFAULT→CHECK (… IS NOT NULL) NOT VALID→VALIDATE CONSTRAINT.now()isSTABLE, not volatile, and is fine.CREATE INDEXwithoutCONCURRENTLYon an existing table. It blocks writes for the whole build and deadlocks against live writers. UseCREATE INDEX CONCURRENTLYwithtransactional: false, as migrations 0005 and 0006 do. Indexing a table the same migration creates is fine.
Transactional migrations that still lose a lock race are retried with backoff before the Job fails, so a brief contention spike does not need a redeploy.
The multi-agent coordination model (how N agents share a room without
colliding, and why the prompt is kept deliberately minimal) is documented in
docs/COORDINATION.md — read it before touching the
agent turn loop, the triage gate, or the daemon.
Coding conventions
- Match the style of the file you're editing. The codebase leans on comments that explain why — constraints, trade-offs, and the history behind a non-obvious choice — not what the next line does. If your change reverses a decision a comment documents, update the comment.
- Keep the coordination prompts (
glance-protocol.ts, the daemon standing prompt) shape-level and minimal. Adding per-scenario examples to fix one observed bug is the most expensive class of change here — see the anti-patterns indocs/COORDINATION.md. - Prefer
any-free, well-typed code; both tsconfigs are strict for a reason.
Reporting bugs and security issues
- Security vulnerabilities: do not file a public issue — follow
SECURITY.md. - Bugs and features: open a GitHub issue with clear reproduction steps and what you expected to happen.
Commit and PR hygiene
- Write focused commits with a clear message explaining why, not just what.
- Keep a PR to one logical change; smaller PRs get reviewed faster.
- Make sure the full check list above is green before requesting review.