AGENTS.md13.9 KBView on GitHub # AGENTS.md
Guidance for AI coding agents working in this repository.
## 1) Project overview
- **Project**: Cedar (0.email), an AI-powered email client
- **Type**: Full-stack TypeScript monorepo
- **Package manager**: pnpm (`pnpm@10`)
- **Main runtime**: AWS-native Node services with S3, SQS, DynamoDB-backed KV adapters, and Postgres
## 2) Monorepo layout
```text
apps/
mail/ React Router 7 frontend (main web app)
server/ Hono + tRPC backend and background services
desktop/ Electron desktop app
packages/
cli/ nizzy CLI
db/ shared Drizzle schemas
eslint-config/ shared lint config
testing/ shared test workspace (Playwright)
tsconfig/ shared TS config
```
## 3) Frontend architecture (`apps/mail`)
- Framework: **React Router 7**
- Route structure: `app/(auth)`, `app/(routes)/mail`, `app/(routes)/settings`, `app/(full-width)`
- State architecture:
- **TanStack Query** for server/cache state
- **Jotai** for global app state
- **Zustand** for module/slice-based client state
- UI: Shadcn + Tailwind CSS v4
## 4) Agent architecture
### 4.1 Canonical entrypoints
1. **Event execution path** → `onEventAgentExecutionWorkflow`
- Workflow file: `apps/server/src/mastra/workflows/event-execution/on-event-agent-execution-workflow.ts`
- Triggered from route handlers such as:
- `handleExecuteSyncThread`
- `handleExecuteMeeting`
- `handleExecuteSyncSlack`
- `handleExecuteExternalCrm`
- `handleExecuteScheduledExecution`
2. **Interactive chat path** → `executeChatAgentStreaming`
- Workflow file: `apps/server/src/mastra/workflows/chat/chat-workflow.ts`
- HTTP entrypoint: `POST /trpc/mastra.chatStream` in `apps/server/src/http/app.ts`
- If `CHAT_SERVICE_URL` exists, requests are proxied to `/chat`; otherwise the route falls back to `handleChatStreamRequest` in-process.
### 4.2 Services architecture
`apps/server/src/services/*` is the domain/business-logic layer. Keep this separation:
- **Route handlers/workflows/tools**: orchestration and control flow
- **Services**: reusable domain logic and persistence coordination
Major service domains include:
- `services/agent-action-queue`
- `services/crm`
- `services/user-tasks`
- `services/task-scheduling`
- `services/integrations`
- `services/sse`
- `services/notifications`
Prefer extending an existing service module over embedding duplicated business logic in route handlers.
## 5) Runtime resources
### Object storage
- `THREADS_BUCKET`
- `MEETINGS_BUCKET`
- `CRM_CONTENT_BUCKET`
- `SLACK_BUCKET`
### Queue bindings
- `thread_queue`
- `subscribe_queue`
- `send_email_queue`
- `send_remind_queue`
- `scheduled_agent_action_queue`
### KV-style adapters
- `pending_emails_status`
- `pending_emails_payload`
- `scheduled_emails`
- `snoozed_emails`
- `remind_emails`
- `remind_email_payload`
- `remind_email_status`
- `initial_sync_state`
- `gmail_sub_age`
- `gmail_history_id`
- `gmail_processing_threads`
- `subscribed_accounts`
- `connection_labels`
- `prompts_storage`
- `email_tracking`
- `calendar_watch_channels`
- `calendar_sync_tokens`
- `chat_monitor_buckets`
### Environment selection
- `NODE_ENV=development|staging|production`
- Local runtime reads `process.env` and uses local fallbacks when AWS resources are not configured.
## 6) Dev commands
Use these from repo root:
```bash
pnpm dev
pnpm run dev:desktop
```
Notes:
- `pnpm dev` is the default web+server environment.
- Server runtime services start on:
- API: `8787`
- Worker: `8788`
- Chat: `8789`
- Frontend uses Vite on `5173` by default and will move to the next open port if occupied.
- `pnpm dev:with-container` remains as a compatibility alias for `pnpm dev`.
- If the frontend hangs on a blank page / `localhost` never responds, run `pnpm cleanup:orphans` then restart
`pnpm dev` / `pnpm run dev:axiom`. Orphaned agent `tsc` / unscoped `vitest run` processes commonly
exhaust RAM and starve Vite.
### Typecheck hygiene (agents)
**Applies to every agent in this repo** (Cursor, Claude Code, Codex, cloud agents, humans running
agent tooling). Full-project `tsc` is expensive (~GB RAM). Abandoned agent typechecks OOM the
machine and make Vite hang on `localhost` with 0 bytes.
Mail and server `types` are intended to stay **green** so turbo can cache them. Prefer package
scripts over raw `tsc`.
#### Commands
| What | Command |
|---|---|
| Mail sources | `timeout 300 pnpm --filter @zero/mail run types` |
| Mail tests | `timeout 300 pnpm --filter @zero/mail run types:test` |
| Server sources | `timeout 300 pnpm --filter @zero/server run types` |
| Server tests | `timeout 300 pnpm --filter @zero/server run types:test` |
| Kill orphaned `tsc` / vitest / stray esbuild | `pnpm cleanup:orphans` |
| Full monorepo (CI / explicit only) | `pnpm types` |
#### Rules
1. **Prefer package-scoped checks** — never typecheck the whole monorepo unless asked.
2. Prefer filtered output: `… 2>&1 | rg "YourFile|error TS" | head`
3. Always wrap with `timeout 300 …`. Do not run bare `tsc --noEmit` in parallel with `tsc -b`.
4. Never background a typecheck and walk away. Wait for it (or the timeout) before ending the turn.
5. Before blaming Vite for a blank page, run `pnpm cleanup:orphans`, then restart `pnpm dev` /
`pnpm run dev:axiom`.
6. Repo-wide `pnpm types` is for CI / explicit full gates — not routine agent verification.
### Vitest hygiene (agents)
Server tests use Vitest with **fork workers**. An unscoped `vitest run` fans out across many cores;
each worker can be hundreds of MB (PGlite/DB suites especially). Agents must **scope by path**.
#### Commands
| What | Command |
|---|---|
| One file | `timeout 300 pnpm --filter @zero/server exec vitest run src/path/to/foo.test.ts` |
| One directory | `timeout 300 pnpm --filter @zero/server exec vitest run src/trpc/routes/__tests__` |
| Full server suite (CI / explicit only) | `timeout 600 pnpm --filter @zero/server exec vitest run` |
| Mail unit tests (Jest) | Prefer a path: `pnpm --filter @zero/mail exec jest --config jest.config.cjs path/to/file.test.ts` |
#### Rules
1. **Never** run bare `npx vitest run` / `vitest run` with no path unless the user asked for the full suite.
2. Scope to the test file(s) or `__tests__` folder that cover your change.
3. Always `timeout 300` for scoped runs. Use `timeout 600` only when the user asked for the full suite. Wait for completion.
4. If the laptop is strained and leftover vitest workers remain, `pnpm cleanup:orphans`.
Cursor agents also load `.cursor/rules/typecheck-hygiene.mdc` (`alwaysApply`; covers typecheck +
vitest). Keep that rule and these sections in sync when the commands change.
## 7) Database operations by agents
- Agents may run migration commands such as `pnpm db:migrate` or `pnpm db:push` only when the user explicitly asks for that exact operation.
- Do **not** run raw SQL/manual DB mutations from the agent unless the user explicitly provides the exact SQL or asks for that exact mutation.
- Do **not** implement features by bypassing service/repository layers with ad-hoc DB scripts.
- Use existing service APIs and app flows instead of direct database manipulation.
## 8) Coding rules
- Use TypeScript with strict typing.
- Use absolute imports.
- Prefer functional React components and hooks.
- Prefer `async`/`await` over promise chains.
- Follow existing formatting (2 spaces, single quotes, ~100 char lines).
- Do not use array index as React key.
- Tailwind is v4 (CSS-first config with `@import "tailwindcss"` and `@theme`).
### Dependency direction
Do not introduce circular imports. Preserve this dependency direction:
- Agents/workflows may import skills, tools, and services.
- Skills may import tools and skill utilities.
- Tools may import services, DB, env, and local tool helpers.
- Services must not import Mastra agents, skills, or tools.
Before adding a tool to a skill, verify the tool does not import the skills registry, agents,
or workflows. Run `pnpm deps:check` when touching Mastra tools, skills, or services.
## 9) Workflow expectations for agents
1. Read nearby code and preserve established patterns.
2. Keep changes scoped; avoid large refactors unless requested.
3. Prefer modifying service modules for business logic and keeping handlers thin.
4. Run the smallest relevant verification for touched code paths.
When asked for a significant design/plan, create a markdown design doc containing:
- current state
- proposed changes
- critical files table
- phased plan + TODOs
- verification/test plan
## 10) Skills to load for common tasks
Before starting these tasks, read and follow the relevant skill file:
| Task | Skill |
|---|---|
| Set up a new Cedar user account | `.claude/skills/account-setup-from-transcript/SKILL.md` then `.cursor/skills/account-config/SKILL.md` |
| Debug why something didn't sync, fix a broken conversation, or investigate an execution | `.cursor/skills/account-config/SKILL.md` |
| Change how the agent behaves (AOPs, KB, field mappings, style) | `.cursor/skills/account-config/SKILL.md` |
| Run a bulk data integrity audit | `.cursor/skills/data-integrity/SKILL.md` |
| Query the database | `.cursor/skills/query-db/SKILL.md` |
| Update org-level AOP propagation | `.cursor/skills/update-org-aop/SKILL.md` |
## 11) Config layer sync contract
Three layers describe Cedar's account configuration. They must stay in sync — when any one changes, update the other two.
| Layer | Location | What it is |
|---|---|---|
| **1. Config layer** | `apps/server/src/services/aop/`, `apps/server/src/services/kb/`, `apps/server/src/db/aop-schema.ts`, `apps/server/src/db/integration-schemas.ts` | DB schema, service functions, tRPC routes — source of truth |
| **2. Account setup skill** | `.claude/skills/account-setup-from-transcript/SKILL.md` | Agent skill for setting up accounts from transcripts |
| **3. In-app agent tools** | `apps/server/src/mastra/skills/account-config/tools/` | Chat agent tools for reading and writing config in-product |
### Surface → file mapping
| Config surface | Config layer | Account setup skill | In-app agent tools |
|---|---|---|---|
| Knowledge Base (Brain) | `services/kb/helpers.ts` | Phase 1 (`index.ts` `knowledgeBase` array) | `applyConfigChangeTool` `create/update/delete_kb_entry`; `getUserConfigTool` `sections:["kb"]` |
| Company background | `services/aop/user-aops.ts` `writeUserBackground` | Phase 1 `config.companyBackground` | `applyConfigChangeTool` `update_company_background`; `getUserConfigTool` `sections:["background"]` |
| AOP skill text | `services/aop/skills-docs.ts` `upsertSkillConfig`; `documents` paths `user/system-skills/{aopId}/{skillName}` / `organisation/system-skills/{orgAopId}/{skillName}`; `SkillName` enum in `db/aop-schema.ts` | Phase 2/5/5b scripts | `applyConfigChangeTool` `update_aop_section`; `load-agent-operating-procedure-details` |
| Conversation field options (status/priority) | `services/aop/user-aops.ts` `addConversationFieldOption` etc. | Phase 3 (`phase3-conv-fields.ts`) | `applyConfigChangeTool` `update_conversation_field_option` |
| Conversation field governance | `services/aop/user-aops.ts` `writeUserAopFields`; `ConversationFieldDefinitions` in `types.ts` | Phase 3 | `applyConfigChangeTool` `update_conversation_field_config` |
| Custom fields | `services/aop/user-aops.ts` `addCustomField` etc.; `CustomFieldDefinitionSchema` in `types.ts` | Phase 1 `config.customFields` | `applyConfigChangeTool` `update_custom_field`; `load-agent-operating-procedure-details` `customFieldSummary` |
| CRM field mappings | `connection.metadata.fieldMappings`; `services/integrations/crm/` | Phase 4 (`phase4-crm.ts`) | `applyConfigChangeTool` `update_crm_field_mappings`; `getUserConfigTool` `sections:["connections"]` |
| Slack sync config | `connection.metadata.configuration.sync`; `db/integration-schemas.ts` `SlackConfigSchema` | Phase 9A (`phase9-slack.ts`) | `applyConfigChangeTool` `update_slack_sync_config`; `getUserConfigTool` `sections:["connections"]` |
| CRM sync/push toggles | `connection.metadata.settings`; `cron/process-external-crm-sync.ts` | Phase 8 | `applyConfigChangeTool` `update_crm_settings`; `getUserConfigTool` `sections:["connections"]` |
| Slack notifications | `user_settings.notification_settings`; `lib/schemas.ts` `slackNotificationsSchema` | Phase 9B | `applyConfigChangeTool` `update_user_settings`; `getUserConfigTool` `sections:["user_settings"]` |
| Mail settings (signature etc.) | `user_settings.mail_settings`; `lib/schemas.ts` | Phase 5 | `applyConfigChangeTool` `update_user_settings`; `getUserConfigTool` `sections:["user_settings"]` |
| Agents (create/update/enable) | `services/aop/aop-agents.ts`; `aop_agents` table | Phase 2B/12 | `applyAgentChangeTool`; `listAgentsTool` |
### Rule
> Whenever any entry in this table changes — a service function gains a new field, a `SkillName` is added, a metadata key moves — update **all three layers** before closing the PR.
Practical checklist when touching config surfaces:
- [ ] Is the service function signature complete? (all fields the agent should write are accepted)
- [ ] Does the in-app tool's schema expose the new field? (`applyConfigChangeTool` change type or `getUserConfigTool` output)
- [ ] Does the account-setup skill reference the correct table/column/metadata path?
## 13) Cursor Cloud notes
### Prerequisites
- **Node.js v22** and **pnpm 10.15.0** are pre-installed.
- Cursor Secrets are the source of truth for credentials. Regenerate the root `.env` from injected secrets before starting services:
```bash
printenv | grep -E "^($(echo $CLOUD_AGENT_INJECTED_SECRET_NAMES | tr ',' '|'))=" > .env
```
### Starting services
- Regenerate `.env`, then run `pnpm install && pnpm dev`.
- `pnpm nizzy sync` copies the root `.env` into app-local env files as needed.
- Do not manually overwrite generated app-level env files.
### Database
- Schema is already deployed on Supabase. No `db:push` needed.
- `DATABASE_URL` in Cursor Secrets is read-only unless the user explicitly provides a write URL for a task that requires writes.
- To query directly:
```bash
PGPASSWORD=[redacted] psql "<DATABASE_URL>"
```