Architecture
DocuGardener is a two-plane system: a Python analysis engine and a Next.js control plane, backed by PostgreSQL and Redis.
Service Map
| Service | Tech | Port | Role |
|---|---|---|---|
| web | Next.js 14 App Router | 3003 | Dashboard, auth (NextAuth), settings, billing, docs site |
| api | Python FastAPI | 8000 | Webhook handler, analysis API, health checks |
| worker | Python RQ | — | Async job processor for PR analysis and fix-PR creation |
| scheduler | APScheduler | — | Periodic jobs: stale sweeper (60s), nightly rollup |
| postgres | PostgreSQL 15 | 5433 | Primary database for both planes (shared schema) |
| redis | Redis 7 | 6379 | RQ job queue and caching |
| weaviate | Weaviate | 8080 | Vector DB for document embeddings (optional) |
Analysis Pipeline
When a pull request is opened or updated in a monitored repository:
- Webhook received — GitHub sends a
pull_requestevent toPOST /webhooks/github. FastAPI validates the HMAC-SHA256 signature and returns 200 immediately. - Job enqueued — A
analyze_prjob is pushed to the RQdefaultqueue. Quota is checked before enqueue; exceeded quota creates aQUOTA_EXCEEDEDjob and stops. - Worker picks up job —
process_pull_request()insrc/pipeline/handler.pyruns. It clones the PR branch (shallow fallback on network error), parses changed files, embeds documents into Weaviate, and calls the LLM for drift analysis. - LLM analysis —
src/agents/verifier.pyconstructs the prompt from code diff + semantic search results and calls the configured LLM provider (Gemini, OpenAI, Anthropic, or Ollama). Response is parsed into a structuredDriftAnalysisobject with per-file scores. - Results stored — Job record updated in PostgreSQL with
status=COMPLETED, drift score, reasons, and suggested fixes. TheresultJSON field stores the full analysis payload. - GitHub check run posted —
src/pipeline/reporter.pyposts a check run on the PR with the drift score, per-file breakdown, and suggested fixes. Always runs in afinallyblock — the check run resolves even if analysis fails. - AI Author Mode (optional) — If enabled and drift is above threshold, a fix-PR job is enqueued to the
highpriority queue.
Database Schema (key models)
| Model | Purpose |
|---|---|
| Tenant | An organisation account. Holds plan, Stripe IDs, workflowConfig (feature grants, quota ceiling), llmConfig (BYOK keys). |
| User | A member of a tenant. Has role (OWNER/ADMIN/MEMBER/AUDITOR/BILLING_ADMIN). |
| Repository | A GitHub repository registered for monitoring. enabled flag controls whether events are processed. |
| Job | One PR analysis run. status: PENDING → PROCESSING → COMPLETED / FAILED / QUOTA_EXCEEDED. result JSON holds full analysis payload. |
| AuditLog | Tamper-evident audit chain. SHA-256 hash chains each entry to the previous one. |
Full schema: web/prisma/schema.prisma
Multi-Tenancy
Each authenticated GitHub user is provisioned into exactly one Tenant. All database reads and writes in the Next.js API routes are scoped to the authenticated tenant via NextAuth session. The FastAPI backend identifies the tenant from the GitHub App installation ID on the webhook event.
In self-hosted single-tenant mode, SINGLE_TENANT_ID pins all backend writes to one tenant, bypassing multi-tenant lookups.
LLM Routing
The LLM provider is configurable per-tenant via tenant.llmConfig. The src/agents/llm.py factory resolves the provider at job runtime:
- Hosted — uses the bundled Gemini key from
BUNDLED_GEMINI_KEY - BYOK Cloud — uses tenant-supplied key for Gemini, OpenAI, Anthropic, or Azure OpenAI
- BYOK Local — routes to Ollama at the tenant-configured base URL
All LLM calls go through _llm_call_with_retry() — exponential backoff (max 3 attempts) on transient HTTP errors (429, 502, 503, 504, 529). Per-tenant token-bucket rate limiting (60 req/min) applies on top.