Vigil
AI-first bug triage from real user sessions
Architecture · Product Spec · Data Schema · SDK Contract · Contributing
Vigil is an AI-native bug triage platform that watches real user sessions, detects broken UX automatically, and turns failures into developer-ready GitHub issues.
It is not a session replay tool. Replay is evidence. The product is the AI triage loop:
- Captures broken UX signals — JS errors, network failures, rage clicks, dead clicks — as they happen in production browser sessions.
- Groups repeated failures across sessions into a single issue. 500 users hitting the same bug → 1 issue, not 500 reports.
- Writes developer-ready bug reports: root cause analysis, reproduction steps, suggested fix, severity, confidence, and supporting evidence.
- Raises GitHub issues pre-filled by AI, one per issue group, with optional auto-raise for high-confidence P0/P1s.
Example issue created by the GitHub integration:
Users encounter bugs they never report. Traditional error monitoring tools like Sentry give you stack traces without context. Session replay tools like LogRocket give you recordings without triage. Developers are left manually watching replays, classifying bugs, grouping duplicates, and writing reports.
Vigil closes this gap by making the AI own the triage decision:
- Is this session normal, noisy, or broken?
- What was the user trying to do? Did they succeed?
- What went wrong, and how do you reproduce it?
- Is this a new bug or a duplicate of something already known?
The result is a prioritized issue queue that developers work from directly — not a wall of raw sessions or uncontextualized stack traces.
Most monitoring tools bolt AI onto an existing workflow as a summarization layer. Vigil inverts this: the AI owns the core triage decision (create, attach, or ignore), and deterministic systems (fingerprinting, signal extraction, replay storage) exist to make the AI cheaper, faster, and auditable.
The SDK separates data into two streams at capture time:
- Raw rrweb events → stored as gzipped blobs for replay only. The AI never sees these.
- Structured summary events → normalized, fingerprinted, and fed to the AI as compact JSON timelines.
This design keeps AI token costs bounded, prevents PII leakage into model inputs, and makes triage outputs auditable against concrete event evidence.
Before the AI runs, every signal gets a deterministic fingerprint (route + normalized error + top stack frame, or route + method + URL + status code). These fingerprints produce candidate issue groups that the AI can accept, reject, or override. The AI makes the final call, but fingerprints eliminate most noise and reduce the search space.
The ingest pipeline uses Postgres transactions for all session state mutations, signal extraction, and job registration — guaranteeing consistency even under retries. Heavy work (replay blob compression, AI triage) runs asynchronously outside the request path. This keeps ingest latency low while ensuring no data loss.
To handle browsers that crash, close, or go to sleep without sending a final flush, a background worker scans for abandoned sessions and transitions them using a partial Postgres index. Late-arriving final flushes correctly un-abandon the session.
The system follows a pipeline architecture:
Browser → SDK → Ingest API → Signal Extraction → AI Triage → Issue Groups → Dashboard → GitHub
| Layer | Technology | Purpose |
|---|---|---|
| SDK | TypeScript + rrweb | Browser-side capture of DOM, errors, clicks, network failures |
| Backend | Node.js + Hono | Ingest API, signal processing, AI orchestration, GitHub integration |
| Frontend | Next.js + Tailwind | Issues dashboard, session replay, settings |
| Database | Neon (Postgres) | Sessions, events, issue groups, job queue |
| Blob storage | Local disk (dev) → R2/S3 (prod) | Gzipped rrweb replay blobs |
| AI | OpenRouter (LLM routing) | Triage decisions, bug report generation |
| GitHub | Octokit | Issue creation, auto-raise, follow-up comments |
| Auth | Better Auth | Dashboard authentication |
| Build | Turborepo + pnpm | Monorepo orchestration |
The SDK (@vigil/sdk) is a thin TypeScript wrapper around rrweb/record (~25KB gzipped). It captures eight signal types:
| Signal | Detection Method |
|---|---|
| DOM mutations & scroll | rrweb core recording |
| JS exceptions | window.onerror + onunhandledrejection |
| Console errors | console.error monkey patch |
| Network failures | fetch and XMLHttpRequest interception (4xx/5xx) |
| Rage clicks | 3+ clicks in same 500px area within 2s |
| Dead clicks | Click with no DOM mutation or navigation within 500ms |
| SPA navigations | pushState, replaceState, popstate |
| Significant clicks | Clicks on button, a, [role=button] elements |
Events buffer in memory and flush every 5 seconds via fetch, with a final navigator.sendBeacon flush on page unload. All text inputs are masked by default; passwords are always masked with no opt-out.
When a batch arrives at POST /api/v1/ingest:
- Validate — Project key lookup, Zod schema validation, size limits (500 events, 50 summary events, 2MB per flush).
- Transaction — Inside a single Postgres transaction:
- Upsert session record
- Batch-insert normalized summary events into
events_summary - Update session flags (
has_js_error,has_rage_click,has_network_err,has_dead_click) - Compute deterministic fingerprints
- On
isFinal: true: register a triage job (unless noise-skipped)
- Async — After the response, schedule replay blob persistence via
setImmediate(gzip compress + write to disk). - Respond — Return
200 { ok: true }. AI triage runs asynchronously.
Noise skip heuristics: Sessions under 5 seconds or with zero friction signals skip AI triage entirely.
The triage worker picks jobs from a Postgres-backed queue and runs each session through the AI:
- Build context — Assemble a compact JSON timeline from
events_summary, session metadata, and candidate issue groups matched by fingerprint. - Triage — Send structured input to the LLM. The AI returns:
- Session summary, goal completion, friction score (0-100)
- Issue detection with
create/attach/ignoreaction - Per-issue: title, root cause, suggested fix, severity (P0–P3), reproduction steps, confidence (0–1), evidence
- Persist — Based on the AI decision:
- Create: New
issue_group+issue_instance. If P0/P1 with sufficient confidence and auto-raise is enabled, a GitHub issue is created automatically. - Attach: Link to existing
issue_group, create newissue_instance. Optionally post a follow-up comment on the existing GitHub issue. - Ignore: Update session-level AI fields only.
- Create: New
The AI never receives raw rrweb replay blobs — only structured summary timelines.
Core tables and their roles:
| Table | Purpose |
|---|---|
users |
Dashboard accounts |
projects |
Monitored apps, each with a unique public_key for SDK auth |
sessions |
One row per browser session — metadata, flags, AI results |
events_summary |
Normalized signal events extracted from SDK payloads |
issue_groups |
Deduplicated developer-facing issues (the primary product object) |
issue_instances |
Per-session evidence linking a session to an issue group |
triage_jobs |
Postgres-backed job queue for async AI triage |
ai_triage_runs |
Audit log for model behavior (prompt version, tokens, errors) |
Full schema DDL with indexes: vigil-data-schema.md
vigil/
├── apps/
│ ├── api/ # Hono backend — ingest, auth, triage worker, GitHub integration
│ ├── web/ # Next.js dashboard — issues, sessions, replay, settings
│ └── playground/ # Local dev playground for testing the SDK
├── packages/
│ └── sdk/ # @vigil/sdk — browser instrumentation library
├── docs/ # Architecture, schema, product spec, SDK contract
│ └── images/ # Architecture diagrams (PNG)
└── .github/
└── workflows/ # CI pipeline (lint → typecheck → test → build → size audit)
- Node.js ≥ 22 (see
.nvmrc) - pnpm 11.x (
corepack enableif using corepack) - Neon Postgres database (neon.tech — free tier works)
- OpenRouter API key (openrouter.ai) for AI triage
# Clone
git clone https://github.com/Psionic-labs/Vigil.git
cd Vigil
# Install dependencies
pnpm install
# Configure environment
cp apps/api/.env.example apps/api/.env
# Edit apps/api/.env with your DATABASE_URL, OPENROUTER_API_KEY, etc.
# Run database migrations
pnpm --filter @vigil/api db:migrate
# Seed demo data (optional)
pnpm --filter @vigil/api db:seed# Start all services (API on :3001, Web on :3002, SDK in watch mode)
pnpm run dev
# Or start individual services
pnpm --filter @vigil/api dev # API server
pnpm --filter web dev # Next.js dashboard
pnpm --filter @vigil/sdk dev # SDK build watcher
pnpm --filter @vigil/api worker:dev # AI triage worker# Run the full local test suite
pnpm run test:local
# Individual checks
pnpm run lint # ESLint across all packages
pnpm run typecheck # TypeScript strict mode
pnpm --filter @vigil/sdk test # SDK unit tests
pnpm --filter @vigil/api test # API unit tests
pnpm run build # Production build
pnpm --filter @vigil/sdk track-size # Bundle size audit (<25KB gzipped)| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Neon Postgres connection string |
OPENROUTER_API_KEY |
Yes | API key for LLM triage |
TRIAGE_MODEL |
Yes | Model identifier for triage (e.g., anthropic/claude-sonnet-4) |
GITHUB_CLIENT_ID |
For GitHub | OAuth App client ID |
GITHUB_CLIENT_SECRET |
For GitHub | OAuth App client secret |
GITHUB_TOKEN_ENCRYPTION_KEY |
For GitHub | AES key for token encryption at rest |
MOCK_AI |
No | Set to "true" to skip real AI calls during dev |
FRONTEND_URL |
No | Dashboard URL for CORS (default: http://localhost:3002) |
Open-source under the GNU Affero General Public License v3 (AGPLv3).
See CONTRIBUTING.md for local setup, code style, and the PR workflow.
See SECURITY.md for our vulnerability disclosure process.





