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>"
```