Repository-wide rules. apps/* have their own AGENTS.md with the rules for that area.
bun installbun run dev: hub + web client (needs.env, see.env.exampleand docs/local-dev.md)bun run check: typecheck, lint (oxlint), format check (oxfmt --check), and unit testsbun run test: unit tests across workspacesbun run test:e2e: integration and browser e2e suites (needDATABASE_URL)bun run reset: wipe local state (local database only)
- Interchange is stock. Code lives in
apps/*andpackages/*. vendor/intxholds hand-copied fallbacks for capabilities not yet published to npm, tracked with kill dates in VENDORED.md; the upstream repository is never modified.
- Stock first. Before writing logic, look for an Interchange route or primitive that already does it (see the reference below). Never reimplement credential resolution, agent launch, session orchestration, inference, ID generation, or the workflow runtime — consume
@intx/*as published packages. - Hub mounts only stock + Corbits libraries. The hub mounts stock
@intx/hub-apiroutes plus one Corbits mount block (@corbits/mailbox,@corbits/memory,@corbits/artifacts,@corbits/webhooks,@corbits/cron,@corbits/mcp,@corbits/agent-token, and OAuth login/refresh on@corbits/oauth-core); any other hub mount is cutover debt with a Linear issue, never a pattern to extend. - A workbench is a plain Interchange tenant, nothing more. The web client converges its own tenant, workflow deployments, credentials, and grants over stock routes on start; the hub never seeds data on a client's behalf.
- Apps stay generic; packages own the domain. A product rule inside
apps/*belongs in a package. packages/workeris the default harness — the one definition package a bench's worker, and any agent Workbench creates, deploys on; it is trim (index.ts+system-prompt.ts) with no hub-calling code.- Ancestor-walkable except grants. Credentials, tools, and definitions inherit down the tenant tree; grants never do.
- Custom DB tables live on their own Postgres schema, with foreign keys back to Interchange's tenant/principal tables. Each package ships one idempotent migration, which the hub applies itself at boot. A custom table that gets deleted is hard-removed: delete the schema code, no drop migration. Cutovers are breaking; reset the database.
- Core UI components live in corbitsdev/react-ui. Build reusable components there; only workbench-specific composition lives here.
- This repo is public. No secrets or credentials, ever —
.env.exampleis the only tracked env file. Anything sensitive (client names, internal context, infra/deploy rulings) goes in Linear, not in commits, PRs, or docs.
Check these for a stock route or primitive before touching the hub, sidecars, or workflows. Links follow upstream main; the commit we run is the stock ref in VENDORED.md.
- LAYOUT.md, CONVENTIONS.md: where things live and how upstream writes code
- ARCHITECTURE.md: hub, sidecar, and workflow run model
- WORKFLOW_AUTHORING.md: how workflows are written
- workflow-lifecycle-policy.md, SIDECAR_PLACEMENT.md: lifetimes, capacity release, and where sidecars run
- API.md, ROUTES.md: the stock routes the web client and hub use
- INBOUND_MAIL_POLICY.md, MESSAGE.md: mail routing behind the webhook and cron bridges
- AUTH.md, CREDENTIALS.md, GIT_ACCESS.md: sessions, the vault, and git tokens
- Match existing style. Every package under
packages/*needs aLICENSEfile (LGPL-2.1). - Comments say why, when the code cannot; never what it does.
async/awaitwithtry/catch; report every caught error throughreportErrorfrom@corbits/error-sink— never a barecatch {}.- Parse at every trust boundary: arktype schemas for env, request bodies, and external data; never
as Tuntrusted input. - No fallbacks. Cut over cleanly — never leave a legacy path beside a new one. Config/manifest objects are explicit literals; the one exception is an optional key under
exactOptionalPropertyTypes: true, where...(x !== undefined ? { k: x } : {})is the only correct way to omit it rather than assigningundefined. - A package's
browser-safesubpath never imports a server-only dependency (postgres,drizzle-orm,hono, any@intx/*). - A dependency declared in the root
catalogis consumed ascatalog:. - Leave no dead code, unused exports, or aliases behind.
- Worktrees live in
.worktrees/<branch>; branch =cl-<issue#>-<slug>. bun run check(typecheck, lint, fmt, test) must pass before every commit. There are no custom structural check scripts; a rule lives in the code or schema itself or it does not exist.- Pull request titles and every commit subject must follow Conventional Commits:
type(scope): descriptionortype: descriptionwith an allowed type (feat,fix,refactor,perf,docs,test,chore,ci,build,style,revert) — enforced by.corbits-triage.yml. - Commit sequence per change: tests first (
test(scope): add tests for X), then implementation (feat(scope): ...orfix(scope): ...), then docs (docs(scope): update docs for X). One logical change per commit; commit messages are written for a public audience. - Deployment mechanics are not settled enough to state here — see IMPLEMENTATION.md's Deployment section first.
- Two homes only. A load-bearing unit test sits beside the module it covers (
src/**/*.test.ts); a load-bearing integration or DB-backed suite lives under roote2e/<area>/, with shared helpers ine2e/lib/. Notest/directory exists anywhere — not in apps, packages, agents, or tools. - Meaningful red/green tests only — no coverage theater. A test that isn't load-bearing (pins a literal, a shape, a re-export, or re-proves Interchange's own behavior) is deleted, not moved. An outdated test is deleted in the same PR that breaks it, not adapted to keep passing; rebuilding coverage for the area it covered happens under CL-8150.
- Bun loads repo-root
.env, so an unset test inheritsHUB_DATA_DIR=.data/hubinside this work tree — the hub's git-on-disk init can then walk up to the enclosing.gitand land a genesis commit on the working branch. Tests that boot the hub or touch seed/deploy git paths must callinstallDisposableHubDataDir()frome2e/lib/disposable-hub-data-dir.ts; never delete theHUB_DATA_DIRkey.
Env flags (unset behavior):
| Flag | If unset |
|---|---|
DATABASE_URL |
DB-gated suites skip locally; CI=true makes it a hard failure on jobs that provision Postgres |
HUB_DATA_DIR |
hub boot fails — required runtime config |
CI |
set by GitHub Actions; not a caller flag |
E2E_PROVIDER / E2E_PROVIDER_API_KEY / OLLAMA_BASE_URL |
live-inference e2e stays on the noop/stub path |
- Docs follow the change: a pull request that changes behavior, options, routes, or screens updates the affected
README.mdand docs in the same pull request, or says in its description why none needed a change. - User docs say what a person sees and does, not how the code does it; mechanics belong in ARCHITECTURE.md and IMPLEMENTATION.md.
- Map: README.md (quickstart and layout), PRODUCT.md (what Workbench is), ARCHITECTURE.md (system structure), IMPLEMENTATION.md (stack and deployment), DESIGN.md (visual canon), VENDORED.md (vendoring ledger), CONTRIBUTING.md, SECURITY.md, docs/GLOSSARY.md, docs/local-dev.md.