AGENTS.md · git:20261017.c7573b9 · 2026-10-17 · sha256 6919085aa56dbc97
AGENTS.md git:20261017.c7573b9A
Immutable. This exact content never changes and is served at /api/v1/blob/6919085aa56dbc97.
# Repository guidance
## Communication
Use ASD-STE100 Simplified Technical English for all text you write: questions, updates,
explanations, final answers, commit messages, PR descriptions, and review comments. Be as concise as
possible. Keep quotations, code, commands, paths, identifiers, and required technical terms unchanged.
When a step doesn't need the developer's input, keep going. Put status notes in the same message as
your next action. Stop and ask only when you can't continue without the developer, or before
anything destructive: deleting data, force-pushing, or changing anything outside this repository.
For work with more than a few steps, keep a checklist in `TASKS.md` at the worktree root (git
ignores it). Tick each item when it is done, and add new items that you find. The file stays
available after the context is summarized.
For an audit or migration that divides into independent areas, give each area to its own subagent.
Check the evidence in each subagent's report before you accept it. Finish with one table.
Mark each claim that you could not confirm, and say where you looked.
**Non-negotiable** rules protect user data, released contracts, and security. Change them only on
an explicit developer decision. All other rules are defaults: follow the developer's preference
and state which default you set aside. Do not argue by citing this file.
## Non-negotiable
- **Migrations are irreversible.** No backup of `openbot.db` is made before an upgrade. Preserve all
user data and support every shipped source schema. Never assume a backup exists.
- **Released Team API adapters are permanent.** Do not change a shipped wire protocol's meaning.
- **Keep the renderer-to-main trust boundary:** Electron sandboxing, context isolation, navigation
policy, IPC sender validation, and their tests. Coding agents already have `danger-full-access`.
- **Redact secrets** on every log, export, and send path, including diagnostics and analytics.
- **Keep PolyForm Noncommercial 1.0.0.** Do not add incompatible dependencies or relicense files.
Read [CONTRIBUTING.md](CONTRIBUTING.md#security-sensitive-changes) when a change touches the trust
boundary or a security test, and [architecture change rules](docs/architecture/change-rules.md#change-rules) when
adding a module or moving ownership between workspaces.
## Product constraints
- Workspaces, conversations, attachments, browser data, and team data stay on the computer that
runs OpenBot. Providers, visited pages, and plugins can use the network.
- Cloudflare holds accounts, avatars, host configuration, memberships, invitations, and logical
sessions; it does not hold chats, files, or commands.
- The user's SQLite database is the source of truth, not a remote cache.
- Agents keep their workspace, thread, and identity across provider switches and restarts.
Do not reset an agent to simplify state.
## Effect
- Use Effect 4.0.0 for migrated server and desktop domain services, schemas, typed errors,
asynchronous workflows, and external I/O. Keep the Bun catalog and lockfile aligned.
- Read the installed Effect source and documentation. Do not use v3 compatibility aliases.
- Prefer named `Effect.fn`, `Effect.gen`, `Context.Service`, explicit layers, and `Schema.TaggedError`.
- Derive validated domain types from schemas. Decode untrusted values at their boundary;
preserve released decoder behavior. Do not validate trusted internal values again.
- Wrap expected I/O failures with `Effect.tryPromise` or `Effect.try` at the adapter.
Keep causes private and safe. Preserve public error codes and localized messages.
- Run Effects at framework boundaries. Keep routing, request schemas, rendering, and callbacks native.
Expose one Effect-returning operation per async service method. Do not add paired Promise methods
or run an Effect only to wrap its Promise in another Effect. Adapt browser and mobile native
callbacks at their call sites; preserve released wire and event contracts.
- Reuse process-owned managed runtimes and await disposal in existing shutdown paths. Preserve
desktop teardown ordinals. Worker bindings and background I/O belong to their invocation;
retain `waitUntil` and streaming lifetimes rather than adding a process shutdown hook.
- Tie owned resources to finalizers. Preserve existing deadlines and retries, and forward cancellation
where the API supports it. Do not add generic retry, fallback, or service-wrapper policies.
- Keep business authorization and validation in domain operations, and centralize public error mapping.
- The database host may import `effect` in addition to `node:*`. It remains a separate killable process
and must not import Electron or the application database. Effect cannot interrupt blocked SQLite.
- Test through injected boundaries and include affected tests in CI selections. Mobile and browser
application workflow migration is outside this change; shared package consumers still need checks.
## Shared UI
`packages/ui` (`@openbot/ui`) owns shared SolidJS controls and feature components used by
desktop, public web, and Storybook. Reuse these components before adding UI. Import primitives
from `@openbot/ui` and feature components from explicit subpaths such as
`@openbot/ui/features/sidebar/Sidebar`. Do not copy them back into an app.
Keep shared components independent of application contexts, desktop storage, host connections,
and `window.openbot`. Pass data and actions through typed props. Platform adapters and the main
conversation controller stay in the renderer. Component extraction must preserve the existing
UI, styles, and behavior unless the user requests a design change. Native mobile shares brand
tokens and contracts; it does not render SolidJS DOM components.
Read [packages/ui/AGENTS.md](packages/ui/AGENTS.md) before changing shared UI.
## Interface text
Text that a user reads comes from `@openbot/i18n` catalog keys, on desktop, web and mobile. Do not
write a literal. Errors that the main process, backend or team client send use `sourceText(key)`.
Read [docs/i18n.md](docs/i18n.md) before adding a key or a language.
## Checks
Do not run broad checks locally. They overload the user's computer. This explicit user preference
replaces the previous full lint and typecheck defaults. Leave repository-wide lint, aggregate
`bun run typecheck`, full UI checks, builds, and full test suites to CI. Do not request these checks
as a routine completion or PR step.
1. In a fresh worktree, run `bun install --frozen-lockfile` first.
2. Run only the narrowest relevant test file and lint the changed files. Run checks one at a time, with one test worker where supported.
Use `bun run test:changed` by default: it runs, on one worker, only the desktop and mobile test
files that import a file you changed since `origin/main`. Use `bun run test:related -- <source>`
for the tests of named source files, and `bun run test:desktop -- <path>` for one test file.
3. To check types, run one project for the code you changed, one at a time:
`bun run typecheck:node` (`src/main`, `src/backend`, `src/preload`, `scripts`),
`bun run typecheck:renderer` (`src/renderer`, `packages/ui`), or the `typecheck` script of the
one package or app you changed. Each takes under 10 seconds. Do not run `bun run typecheck`, the
mobile typecheck, or parallel checks. Leave broad type validation to CI and state what remains
unverified.
4. Do not run `bun run format`: it rewrites the whole repository. Use
`biome check --write --max-diagnostics=none <paths>` for changed files.
5. The pre-commit hook (`.githooks/pre-commit`) runs `check:staged`, `check:ui`, and
`scripts/staged-typecheck.ts` when the commit stages code. The script runs, one at a time, only
the `typecheck:*` projects that can see a staged file; a root `package.json`, `tsconfig*.json` or
`bun.lock` selects all of them. `--dry-run [<path>...]` shows the selection. This is the only
exception to rule 3. Do not run these checks by hand, and do not bypass the hook with
`--no-verify`. The hook also runs the schema parity test when a database schema file in
`src/backend` is staged. It stops the commit if Biome fixes a file that also has unstaged
changes; stage the fixes you want and commit again.
[Check design notes](docs/development-checks.md#check-coverage) explain CI coverage, command aliases,
and the separate Node and Bun type environments. Read them when changing checks or dependencies.
## Surfaces to check
State which surfaces a change touches. Check all affected consumers and reverse actions.
- Desktop renderer (`src/renderer`), mobile (`apps/mobile`), public web (`apps/auth-api`; no separate
landing app), hosted-site routing (`apps/site-router`), and Signal (`remote/api`).
- IPC contracts (`packages/contracts`) and their preview implementation
(`src/renderer/src/preview/mock-openbot.ts`).
- Reverse actions: snooze/unsnooze, pause/resume, revoke/reconnect, mute/unmute.
- Migrations and the separate latest schema for new databases.
- Documentation: `README.md` commands, `docs/architecture/`, and `PRIVACY.md` when outbound data
changes.
## Development data and processes
### Account service environment settings
- Maintain only `apps/auth-api/.env.dev` and `apps/auth-api/.env.production` for shared account
service settings. Add new settings to these files; do not add another environment file.
- Keep both files encrypted. Development uses `DOTENV_PRIVATE_KEY_DEV`; production uses
`DOTENV_PRIVATE_KEY_PRODUCTION`. Never use production credentials as local development defaults.
- Use the development loader. It reads the development key from the shell; without the key,
local development must work with generated defaults. Commands that need external secrets must
stop before remote changes when those secrets are missing. An invalid key must fail safely.
- Keep generated identity and saved local overrides in ignored `.openbot/dev-state.json`.
New manual overrides use shell variables. Do not add `.env.local` or `.env.generated`.
- `.env.keys` stores decryption keys, not service settings. Keep it ignored. Preserve existing
production key handling and CI/GitHub secrets unless the developer requests a change.
- The root `.env.keys` holds the only two keys: `DOTENV_PRIVATE_KEY_PRODUCTION` and
`DOTENV_PRIVATE_KEY_DEV`. Every encrypted env file, `remote/.env.production` included, uses one of
them. Never make a new key pair. Pass `-fk` with the root `.env.keys` so Dotenvx does not make one.
- For direct Cloudflare changes, `apps/auth-api/.env.production` holds `CLOUDFLARE_API_TOKEN`
(write access to all resources in the OpenBot account) and `CLOUDFLARE_ACCOUNT_ID`. Load them
with `bunx dotenvx run -f apps/auth-api/.env.production -fk <root>/.env.keys -- <command>`. A worktree
has no `.env.keys`; use the one in the main checkout. Never print the token: do not use
`dotenvx get` or `echo`. Ask before a change that deletes data or affects production users.
Deploy scripts remove the token, so Wrangler deploys keep the developer's `wrangler login`.
See [account service setup](apps/auth-api/README.md) for loading and deployment details.
### Local processes
- `bun run dev:seed --dry-run` is read-only.
- Never kill by process pattern, such as `pkill -f electron` or `pkill -f bun`. `bun run dev:stop`
stops only this worktree's stack; name another one with `--pid=<supervisor pid>` or `--all`.
For a process outside the registry, target a PID you started or ask.
- Never drive another worktree's app. Use `bun run dev:automation` for smoke checks instead of
starting Electron directly; `snapshot` and `screenshot` are read-only, and `click` and `type`
need `--allow-mutations` and a named instance.
- Do not delete a dead stack record you did not resolve. Stop keeps the record and exits non-zero
when it cannot confirm a PID's identity; resolve the process, then `bun run dev:forget`. Dead
records do not reserve ports.
- Reuse a running dev instance. `bun run dev` opens a profile tied to this worktree; `--shared`
opens the shared `OpenBot Dev` profile. Use the ports the stack reports rather than a fixed port.
See [README.md — Commands](README.md#commands) for the flags these commands take, and
[docs/architecture/change-rules.md](docs/architecture/change-rules.md#change-rules) for why the registry works this way, when a dev command
does not behave as expected.
## Terms
- **bot** is not for new product code. Released names stay: Team API v1-v3 `bot`/`botId`,
`bots.json`, `legacy-import:bots:v1`, and `~/OpenBot/Bots` path prefixes.
- **teammate** and **group** are prompt and UI words, never types.
Use [docs/glossary.md](docs/glossary.md) when naming a new type, table, IPC channel or product
string, or when a term in the code disagrees with the UI.
## Task-specific instructions
Read the instruction file for each directory you change. Use the
[workspace map](docs/architecture/structure.md#workspace-map) to find its owner.
| File | Scope |
| --- | --- |
| [src/renderer/AGENTS.md](src/renderer/AGENTS.md) | SolidJS, stores, components, palette |
| [src/main/AGENTS.md](src/main/AGENTS.md) | Renderer-to-main boundary and main-process ownership |
| [src/main/ipc/AGENTS.md](src/main/ipc/AGENTS.md) | Handler binding and endpoint registration |
| [src/backend/AGENTS.md](src/backend/AGENTS.md) | SQLite migrations and database creation |
| [packages/contracts/AGENTS.md](packages/contracts/AGENTS.md) | Frozen Team API protocols and IPC mirrors |
| [apps/auth-api/AGENTS.md](apps/auth-api/AGENTS.md) | Account Worker and D1 deployment races |
| [src/preload/AGENTS.md](src/preload/AGENTS.md) | Preload bridge and payload decoding |
| [packages/ui/AGENTS.md](packages/ui/AGENTS.md) | Shared SolidJS controls and feature components |
| [packages/team-client/AGENTS.md](packages/team-client/AGENTS.md) | Team WebRTC client and framing |
| [apps/mobile/AGENTS.md](apps/mobile/AGENTS.md) | Expo and build/simulator permissions |
| [remote/api/AGENTS.md](remote/api/AGENTS.md) | Signal and TURN credentials |
Write release notes in a new `changelog.d/<branch>.md` file, not in `CHANGELOG.md`, as
[docs/RELEASING.md](docs/RELEASING.md#release-notes) says. Notes for the iPhone app go in
`apps/mobile/changelog.d/<branch>.md`. CI fails a PR with no notes unless it
has the `no-changelog` label. A version bump with no notes fails.
Before a version bump or tag, use
[release-upgrade-safety](.agents/skills/release-upgrade-safety/SKILL.md) to audit upgrade and data-loss
risks since the last release.
## Tests
- Use E2E tests only. Do not add unit tests. An E2E test is a script under `scripts/` (such as
`scripts/*-e2e.ts` or `scripts/*-smoke.ts`) that drives the real app, process or service. At the
end, it writes a verifiable and repeatable artifact, such as a JSON report or a screenshot under
`.openbot-build/`.
- Vitest files are permitted only for these protected areas:
- Database migrations: data-preservation fixtures for every affected released schema, plus the
failure and rollback cases that [src/backend/AGENTS.md](src/backend/AGENTS.md) lists. A
migration always needs them.
- The renderer-to-main trust boundary: sender validation, navigation and permission policy, CSP,
sandboxing, and preload payload decoding.
- Secret redaction on log, export, and send paths.
- Lint and UI check rule fixtures (see [Check rules](#check-rules)).
- Do not assert what TypeScript, Biome, or `check:ui` already enforce.
- Wait for state, an event, or a promise, not elapsed time.
- Assert behavior and data. Do not assert markup, classes, layout, animation timing, or snapshots.
- Use Storybook for visual details only. Do not add story `play` functions: CI does not run them.
Do not add `data-testid` hooks; `check:ui` allows none.
- `*.test.ts` uses Node; `*.test.tsx` uses JSX and jsdom; `*.dom.test.ts` uses DOM without a
component.
### Check rules
- Fix errors. Assess warnings; do not make correct code worse to silence one. Do not add
`biome-ignore`. Explain retained warnings in the PR.
- GritQL rules must match syntax, not infer domain decisions, and must not duplicate a built-in
Biome rule. Consider traversal cost before adding a rule.
- Each rule in `tools/biome/anti-slop/rules` needs positive and negative fixtures in `../fixtures`.
Mark rejected lines with `// flag`; verify with `scripts/anti-slop-rules.test.ts`.
- Each UI check needs both `renderer` and `renderer-clean` fixtures in `tools/ui-foundation/fixtures`.
Verify with `scripts/ui-foundation-check.test.ts`.
Read [check design notes](docs/development-checks.md#lint-and-ui-rules) when changing these checks.
They describe the enforced syntax, fixture behavior, and reasons for removed rules.
## Pull requests
- Open a PR only when asked.
- Write the PR body from `.github/PULL_REQUEST_TEMPLATE.md`. Do not add the Contributor Assignment
Agreement box: organization members and users with write access do not sign. The `CLA` workflow
adds the box only for an author who must sign. Only the human author can sign it: tick that box
only after their explicit approval in this conversation.
- For UI changes, show before and after. State the model and harness in the PR body.
Do not commit screenshots or other PR review image assets to the repository.
- Do not run wider checks locally before a PR. Report focused checks and leave broad checks to CI.
- A PR needs a named reason and is not auto-approvable if it adds `biome-ignore`, `@ts-expect-error`,
or `@ts-ignore`; disables rules through `biome.json` overrides or removes a GritQL plugin; widens
a boundary to `any` or `unknown`; or uses an assertion to bypass a checker. Fix the domain issue,
or explain why the rule is wrong and let the developer decide.