7.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Keeping This File Up to Date
Update this file whenever a lesson is learned during development. Specifically, update CLAUDE.md when:
- A non-obvious architectural decision is made or discovered
- A gotcha, footgun, or surprising behavior is encountered (and the fix/workaround)
- A new command, workflow, or tool is added to the project
- A convention is established that isn't obvious from reading the code
- An integration detail is clarified (e.g., how the WebSocket protocol actually behaves, edge cases in the agent tool call cycle)
Do not add things already derivable from reading the code, generic best practices, or ephemeral task notes — only durable, reusable knowledge.
Repository Layout
This repo contains two independent projects:
adiuva/— Electron desktop app (TypeScript/React)adiuva-api/— FastAPI backend (Python)
adiuva (Electron App)
Commands
npm run start # Start dev server (Electron + Vite)
npm run lint # ESLint
npm run knip # Dead code analysis
npm run make # Build installers (Windows/Linux/macOS)
npm run package # Package without creating installers
Database schema changes require running Drizzle Kit — check package.json for db commands.
Architecture
Renderer (React 19 + TanStack Router)
↓ tRPC over contextBridge
Main Process (Electron)
├── SQLite (better-sqlite3 + Drizzle ORM) — local app data
├── LanceDB — local vector embeddings
└── WebSocket client → adiuva-api backend
IPC model: The renderer calls tRPC procedures defined in src/main/router/. The preload script (src/preload/) bridges them with contextIsolation: true.
Backend integration: The Electron main process connects to the FastAPI backend via WebSocket. The backend sends tool calls (e.g., insert, vector_search) which the main process executes against local SQLite via Drizzle and returns results. All AI intelligence lives on the backend — the app is a smart terminal.
Key source directories:
src/main/agents/— Agent schedulersrc/main/ai/— Orchestrator, token managementsrc/main/db/— SQLite schema (Drizzle) + LanceDBsrc/main/router/— tRPC router (all IPC procedures)src/renderer/components/— UI components (tasks, notes, projects, timeline, auth)src/renderer/routes/— TanStack Router pagessrc/shared/— Zod schemas shared between main/renderer (WebSocket frame types, casing utils)
Path aliases (tsconfig): @/* → src/renderer/, @shared/* → src/shared/
WebSocket frame types are defined in src/shared/api-types.ts using Zod. Client sends: chat_request, floating_request, tool_result. Server sends: text_chunk, tool_call, final, ping.
adiuva-api (FastAPI Backend)
Commands
# Development
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
# Production
gunicorn app.main:app -k uvicorn.workers.UvicornWorker -w 4 --timeout 120
# Database migrations
alembic upgrade head
# Testing
pytest
pytest -v
pytest tests/test_agents.py # single test file
# Linting/formatting
ruff check .
ruff format .
# Docker (full stack)
docker compose up --build
Architecture
FastAPI app (app/main.py)
├── Middleware: RateLimiter → Sanitizer → CORS
├── HTTP Routes (app/api/routes/)
│ ├── auth.py — register, login, token refresh
│ ├── chat.py — POST /chat, POST /chat/embed, WS /chat/stream
│ └── billing.py — Stripe subscriptions
├── Agent System (app/agents/)
│ ├── task_agent.py — 8 tools
│ ├── project_agent.py — 6 tools
│ ├── timeline_agent.py — 4 tools
│ └── note_agent.py — 5 tools
└── Orchestration (app/core/)
├── agent_registry.py
├── agent_runner.py
├── llm.py — LiteLLM factory (100+ providers)
└── memory_middleware.py
LLM routing: GPT-4o-mini classifies incoming intent → routes to appropriate domain agent → agent uses GPT-4o with its tool set → sends tool calls back to Electron client for local execution.
Zero-trust data model: The backend never decrypts user data. PostgreSQL stores only auth, billing, and metadata. All user content stays local on the Electron client.
Tier system: Free / Pro / Power / Team — enforced in app/api/middleware/rate_limit.py (20–200 req/min sliding window) and app/billing/tier_manager.py.
Key config: app/config/settings.py — all env vars via Pydantic Settings. Copy .env.example to .env for local dev.
Database: PostgreSQL with async SQLAlchemy 2.0 + asyncpg. Migrations in alembic/versions/. Models in app/models.py, Pydantic schemas in app/schemas.py.
Testing: pytest with pytest-asyncio. Fixtures in tests/conftest.py. Use in-memory SQLite for DB tests.
Microservices Migration (In Progress)
The monolith (adiuva-api/app/) is being split into independent services under adiuva-api/services/. Architectural decisions are tracked in repo memory (/memories/repo/microservices-architecture.md).
Target Services (MVP)
| Service | Owns | Scaling |
|---|---|---|
| Auth | JWT RS256 issuance, users, refresh_tokens, subscriptions | Stateless |
| WS Gateway | WebSocket connections, Redis frame routing, device registry | Sticky (user_id) |
| Chat | deep_agent, memory, domain agents (task/note/project/timeline), LLM | Stateless |
| Batch Agent | agent_runner, journey builder, filesystem_agent, integrations (+ Langfuse tracing TODO) | Stateless |
| Billing | Stripe, tier_manager | Stateless |
API Gateway: Traefik with ForwardAuth → Auth /verify. Injects X-User-Id, X-User-Email, X-User-Tier headers. Downstream services trust these headers.
Monorepo Structure
adiuva-api/
├── shared/ ← SQLAlchemy models, Pydantic schemas, config, redis utils
├── services/
│ ├── auth/
│ ├── ws-gateway/
│ ├── chat/
│ ├── batch-agent/
│ └── billing/
├── alembic/ ← Centralized migrations (shared DB)
├── docker-compose.yml
└── traefik/
Key Conventions
- shared/ module: Imported by all services. Contains models, schemas, config, DB session factory, Redis client. Changes here affect all services — be careful.
- Redis is the glue: WS Gateway ↔ Chat/Batch communication is entirely via Redis pub/sub and lists. See
/memories/repo/microservices-architecture.mdfor channel naming. - No JWT validation in downstream services: Only Auth Service has the private key. Other services receive pre-validated identity via Traefik headers.
- Tool call round-trip: Chat/Batch → publish
tool_calltows:out:{user_id}→ WS Gateway forwards to Electron → Electron repliestool_result→ WS Gateway LPUSH totool:result:{call_id}→ Chat/Batch BRPOP with 30s timeout. - Migration strategy: Strangler fig. Extract one service at a time. The monolith
app/continues to work until all services are extracted. Don't delete monolith code until the service replacement is tested. - Storage & Plugin services: Removed from codebase. Will be re-evaluated in future feature planning.
Commands (Microservices)
# Full stack
docker compose up --build
# Single service dev (example: chat)
docker compose up redis postgres auth
cd services/chat && uvicorn app.main:app --reload --port 8002
# Migrations (still centralized)
alembic upgrade head