PulseGuard is a self-hosted, multi-tenant incident response platform. It acts as an automated SRE console that ingests raw telemetry logs, clusters related errors, consults organizational runbooks via semantic vector search, and deploys sandboxed AI agents to diagnose production outages and submit Pull Request hotfixes.
๐ฅ Demo Walkthrough (16:9 1080p): Navigating through the Organization console to the SRE AI War Room, querying live error telemetry, inspecting runbook matches, and generating automated hotfix patches.
+-----------------------------+
| Microservice Log Stream |
+--------------+--------------+
|
| HTTP POST
v
+--------------+--------------+
| Ingest API & Rate Limit |
+--------------+--------------+
|
| SHA-256 Fingerprint
v
+--------------+--------------+
| Redis Slide-Window Cache |
+--------------+--------------+
|
| Trigger (>= 3 errors / 3 mins)
v
+--------------+--------------+
| Incident War Room Active |
+--------------+--------------+
|
+-----------------------------+-----------------------------+
| | |
v v v
+-------------+-------------+ +-------------+-------------+ +-------------+-------------+
| pgvector RAG | | GitHub Context | | Multi-Provider AI |
| (Semantic Chunk Search) | | (Repo File Inspection) | | (Dynamic model resolver) |
+---------------------------+ +-------------+-------------+ +-------------+-------------+
|
| Propose Hotfix
v
+--------------+--------------+
| Human-in-the-Loop Gate |
+--------------+--------------+
|
| Approve & Commit
v
+--------------+--------------+
| Automated Pull Request |
+-----------------------------+
- Signature Extraction: Converts high-frequency, dynamic stack traces into static signatures using regex parsing (scrubbing UUIDs, IPv4/IPv6 addresses, hex tokens, timestamps, and numbers) and hashing them using SHA-256.
- Sliding-Window Clustering: Aggregates identical log signatures inside Redis. An incident War Room is triggered only when the error count passes the threshold of 3 errors in 3 minutes, shielding engineers from alert fatigue.
- Auto-Pruning TTL: Automatically runs an asynchronous, non-blocking PostgreSQL cleanup routine to remove logs older than 7 days, maintaining a lean database footprint.
- Dynamic Decryption: Organizations supply their own API keys for AI providers (Google, Anthropic, OpenAI, Groq, OpenRouter). Keys are encrypted at rest via symmetric AES-256-CBC and decrypted in memory.
- Model Discovery: Resolves active models directly from provider endpoints, caching options in Redis for 24 hours.
- pgvector Indexing: Chunks PDF and Markdown runbooks into 600-character blocks (with 60-character overlaps) and generates text embeddings.
- Semantic Retrieval: Queries PostgreSQL using cosine similarity (
<=>) to fetch runbook instructions and injects them as active context into the SRE agent's system prompt.
- Octokit Branch Dispatch: Automates the creation of fix branches and commits updated files using base64 encoding.
- Human-in-the-Loop Security: The AI agent operates in a read-only context. Code modifications are presented as diff cards inside the War Room chat; write operations to repository branches are blocked until an
OWNERorADMINapproves.
PulseGuard is structured as a high-performance Monorepo with dedicated frontend/ (Next.js 16 App Router) and backend/ (Server Actions, AI SDK Agents, and Prisma pgvector Data Layer) packages:
pulseguard/
โโโ backend/ # Core business logic, data models & backend services
โ โโโ src/
โ โโโ actions/ # Next.js Server Actions (telemetry, incidents, runbooks, auth)
โ โโโ lib/
โ โ โโโ ai/ # Multi-provider AI engine (BYOM) & SRE agent tool definitions
โ โ โโโ auth.ts # Better Auth configuration & Prisma adapter
โ โ โโโ authorization.ts # Multi-tenant RBAC permissions & organization guards
โ โ โโโ cache-invalidation.ts # Redis key invalidation helpers
โ โ โโโ github.ts # Octokit client & repository file tree inspector
โ โ โโโ parse-file.ts # PDF & Markdown runbook chunker
โ โ โโโ rate-limit.ts # Upstash/Redis sliding-window rate limiters
โ โ โโโ redis.ts # Resilient Redis client with auto-reconnection
โ โโโ prisma/
โ โโโ schema.prisma # PostgreSQL schema with pgvector (embeddings & incidents)
โ โโโ seed.ts # Database seeder with sample organizations and telemetry
โ
โโโ frontend/ # Next.js 16 App Router Web Application
โ โโโ app/
โ โ โโโ (auth)/ # Public routes (Login, Signup, Forgot Password)
โ โ โโโ (protected)/ # Authenticated multi-tenant workspace shell
โ โ โ โโโ workspaces/ # Workspace Hub (Organization switcher & onboarding)
โ โ โ โโโ [orgSlug]/ # Tenant dashboard console
โ โ โ โโโ dashboard/ # Service health scores & metrics overview
โ โ โ โโโ incidents/ # Real-time incident feed & SRE AI War Room
โ โ โ โโโ telemetry/ # Live log explorer, filters & severity search
โ โ โ โโโ ingestion/ # API key ingestion endpoints & client SDK snippets
โ โ โ โโโ settings/ # GitHub App setup, RBAC team members & BYOM AI keys
โ โ โโโ api/ # API routes (AI agent stream, telemetry ingest, webhooks)
โ โ โโโ docs/ # In-app product documentation
โ โ
โ โโโ components/ # Flat cybernetic UI design system
โ โ โโโ incidents/ # WarRoomChat, DiffApprovalCard, KnowledgeView
โ โ โโโ telemetry/ # TelemetryTable, TelemetryFilters
โ โ โโโ settings/ # GitHubIntegrationCard, ModelConfigCard, TeamMembersList
โ โ โโโ ui/ # Reusable buttons, badges, modals, markdown renderer
โ โโโ lib/ # Frontend utilities, theme configs & client helpers
โ
โโโ tests/ # Unit, integration, and E2E test suites (Vitest)
โโโ docker-compose.yml # Local PostgreSQL (pgvector) and Redis stack
โโโ package.json # Monorepo workspace configuration (Bun / Turborepo)
Create a .env file at the repository root and in frontend/.env:
# Database Connection (pgvector enabled)
DATABASE_URL="postgresql://<user>:<password>@<host>:<port>/<db_name>?schema=public"
# Redis Cache URI
REDIS_URL="redis://<host>:<port>"
# Better Auth Configuration
BETTER_AUTH_SECRET="<your_auth_secret_key>"
BETTER_AUTH_URL="http://localhost:3000"
# GitHub App Integration Credentials
GITHUB_APP_ID="<your_github_app_id>"
GITHUB_APP_PRIVATE_KEY="<your_github_app_private_key>"
NEXT_PUBLIC_GITHUB_APP_SLUG="<your_github_app_slug>"Install packages, synchronize database models, and start the development server:
# 1. Install dependencies using Bun
bun install
# 2. Sync database schemas and generate Prisma client
bunx prisma db push
bunx prisma generate
# 3. Launch Next.js development server
bun run devValidate key normalization, encryption logic, RBAC scopes, and GitHub mocking configurations:
bun run testTenant boundaries and privileges are strictly isolated on the server level:
| Action | Allowed Roles | Verification Security Check |
|---|---|---|
| Ingest Logs | API Client | Matches x-api-key header to active organization |
| War Room Access | OWNER, ADMIN, MEMBER, VIEWER |
requireOrganizationMembership(orgId) |
| Manage AI Provider Keys | OWNER, ADMIN |
requireOrganizationRole(orgId, ['OWNER', 'ADMIN']) |
| Team Management / Invites | OWNER, ADMIN |
requireOrganizationRole(orgId, ['OWNER', 'ADMIN']) |
| Approve Git PR Dispatch | OWNER, ADMIN |
requireOrganizationRole(orgId, ['OWNER', 'ADMIN']) |
- Issue: Vercel AI SDK throws
[ZodError]: Invalid input: expected string, received Date at path: ["timestamp"]when the agent executesquery_telemetry_logs. - Cause: The AI SDK strictly validates tool outputs against JSON-serializable primitives. Prisma queries return native JavaScript
Dateobjects forDateTimefields. - Solution: All tool executions in
lib/ai/tools.tsexplicitly sanitize timestamps usingtoISOString().
- Issue:
models/text-embedding-004 is not found for API version v1beta. - Cause: Google's v1beta Gemini endpoint deprecated
text-embedding-004and replaced it withgemini-embedding-001. - Solution:
lib/ai/provider.tsnormalizes embedding model resolution togemini-embedding-001.
- Issue: Chat input and quick action chips become disabled after a single turn.
- Cause: UI input states checked
disabled={status !== "ready"}. Any stream transition left the input locked. - Solution: Switched to
isBusy = status === "submitted" || status === "streaming"incomponents/incidents/WarRoomChat.tsx.
- Issue:
[ioredis] Unhandled error event: AggregateErrorin terminal logs. - Cause: Managed Redis services (Upstash) terminate idle sockets after 30โ60s.
- Solution: Attached a non-blocking
redis.on("error", ...)handler inlib/redis.tsfor background reconnection.
- Issue: Continuous
setIntervalshort-polling hammered/api/notificationsevery 60s. - Cause: High-frequency timer polling for low-frequency events.
- Solution: Replaced background polling in
components/notifications/NotificationPanel.tsxwith an On-Demand Event-Driven pattern.
- Issue:
AI_UnsupportedModelVersionError: Unsupported model version v1 for provider "google.generative-ai". - Cause:
frontendranai@7.xwhilebackendinstantiated models using@ai-sdk/google@1.xandai@3.x. - Solution: Synchronized all AI SDK dependencies across workspaces and upgraded tool definitions to use
inputSchema.
- Issue:
Raw query failed. Code: 22000. Message: expected 736 dimensions, not 3072. - Cause: PostgreSQL schema defined
DocumentChunk.embeddingwith a fixed column width ofvector(736). - Solution: Applied Matryoshka Representation Learning (MRL) truncation (
.slice(0, 736)) across document ingestion and search queries.
- Issue:
POST http://localhost:3000/api/auth/sign-in/email net::ERR_CONNECTION_REFUSEDin live Vercel deployments. - Cause:
auth-client.tshad a hardcoded|| "http://localhost:3000"fallback. - Solution: Initialized
createAuthClient()with no hardcoded fallback, letting Better Auth inferwindow.location.origin.
- Issue:
PrismaClientKnownRequestError: ECONNREFUSEDon database queries during local development. - Cause: Next.js starts from
frontend/and only reads environment files in its own directory. - Solution: Mirrored configuration into
frontend/.envwith explicitNEXT_PUBLIC_prefixes.
- Issue: Interface appeared miniature and horizontally clipped on mobile viewports.
- Cause: Omitting
export const viewport: Viewportin Next.js 14+ App Router causes mobile WebKit/Blink browsers to simulate a 980px desktop screen. - Solution: Exported
width: "device-width", initialScale: 1infrontend/app/layout.tsxand implemented responsive drawers.
- Issue: Repository configuration redirects navigated away to GitHub instead of returning to the PulseGuard dashboard.
- Cause: Re-selecting repositories on an existing installation defaults to GitHub settings pages.
- Solution: Built a direct "Link Installation ID" tool in
GitHubIntegrationCard.tsxpowered bylinkGithubInstallationserver action.
- Issue: Workspace secret API keys were not easily discoverable for microservices and log forwarders.
- Solution: Built dedicated workspace route
/[orgSlug]/ingestionwith copy-paste snippets for cURL, Next.js, Express, Python, and Vercel Log Drains.
- Issue: Users with active sessions visiting
/loginor/signupsaw credential forms. - Solution: Added server-side session checks in
frontend/app/(auth)/layout.tsxredirecting active sessions to/workspaces.
- Issue: Uncaught server action exceptions triggered generic HTTP 500 errors and React boundary crashes.
- Solution: Wrapped
linkGithubInstallationin defensive try/catch blocks returning structured{ success: false, error: ... }responses.
- Issue: SRE agent executed tools but suppressed text when tool output was empty.
- Solution: Updated
route.tsto returnresult.toDataStreamResponse()withmaxSteps: 5and enforced clear Markdown summaries in system prompts.
- Issue: Client console logged
AI_TypeValidationErrorduring stream errors and text generation stopped when log levels were mismatched. - Cause: Emitting raw
{ type: "text" }violatedUIMessageStreamZod schemas, Zod schema forcedlevel: "ERROR"(excluding"FATAL"logs), and strippingthoughtSignaturesuppressed Gemini 3 text. - Solution: Formatted error stream chunks into
text-start/text-delta/text-end, madeleveloptional inquery_telemetry_logswith org-wide fallback, and preserved nativethoughtSignaturecontext.
- Issue: Clicking "Draft Hotfix Patch" invoked runbook queries instead of outputting a code patch or rendering the interactive Diff Approval Card.
- Cause: The agent system prompt lacked explicit tool execution rules for
propose_hotfixwhen processing patch/fix prompts. - Solution: Configured
route.tssystem prompt to mandatepropose_hotfixtool execution on hotfix/patch prompts, inferring realistic target service paths (e.g.src/lib/db.ts) to render the interactive<DiffApprovalCard />in the UI.
- Issue: Multi-turn conversation state became contaminated, causing the model to stop generating hotfix proposals or skip GitHub PR tool calls.
- Cause:
onFinishinroute.tsinjected synthetic fallback text into assistant messages when text length was 0. When saved to the database and replayed in message history, this misled the LLM into assuming the diagnosis and hotfix had already been completed. - Solution: Removed synthetic text injection from
onFinishinroute.ts. The conversation history now remains 100% authentic, allowing multi-turn prompts ("Draft Hotfix Patch") to executepropose_hotfixand present the interactive<DiffApprovalCard />with its "Approve & Open PR" button.
- Issue: Clicking "Draft Hotfix Patch" executed a single tool (
query_telemetry_logs) and immediately finished withEmitted text length: 0without callingpropose_hotfixor generating Markdown diagnostic output. - Cause: In modern
ai@7.x(streamText), tool looping defaults tostopWhen = isStepCount(1). The legacymaxSteps: 5property was ignored, causingstreamTextto terminate execution immediately after the first tool result. - Solution: Configured
stopWhen: isStepCount(10)infrontend/app/api/agent/route.tsand updated the SRE agent workflow instructions to sequentially execute context gathering (query_telemetry_logs), hotfix proposal (propose_hotfix), and comprehensive Markdown diagnosis.
- Issue: Agent tool executions failed with
{ error: "Unauthorized" }when executing telemetry or runbook searches during background streaming. - Cause: Tools invoked user-facing Server Actions (
getTelemetry,searchKnowledgeBase) which enforce client HTTP cookie session checks (requireOrganizationMembership), failing in decoupled API stream contexts. - Solution: Refactored
backend/src/lib/ai/tools.tsto execute queries directly via Prisma within the verifiedorganizationIdmulti-tenant boundary, ensuring 100% reliability for all autonomous tool calls.
- Issue: Typing arbitrary manual questions in the War Room chat (e.g. "What happened?", "Why did it fail?", "Hello") triggered unintended hotfix code patches.
- Cause:
page.tsxinitialized every conversation with a hardcodedinitialPromptcontaining"Analyze the logs and propose a fix". In multi-turn conversations, the LLM saw"propose a fix"in message history and persistently triggeredpropose_hotfix. - Solution: Updated
initialPromptin(protected)/[orgSlug]/incidents/[id]/page.tsxto focus on root cause investigation. Structuredroute.tswith strict latest-message evaluation rules ensuringpropose_hotfixis strictly guarded and only invoked when the latest user message explicitly requests code remediation or a patch.
- Issue: When a tool call returned empty results (e.g. no indexed runbooks found) or encountered an external error (e.g. repository disconnected), the assistant either stopped abruptly or returned an empty response.
- Cause: Tool execution schemas returned raw error or empty array structures without steering context, causing the LLM to end the step prematurely.
- Solution: Configured structured payload messages in
backend/src/lib/ai/tools.tsfor empty/error states and engineeredfrontend/app/api/agent/route.tswith mandatory final text synthesis rules. When database records or repository access are unavailable, the agent transparently notes the state and seamlessly synthesizes an in-depth engineering analysis and industry-standard mitigation recommendations.
- Design Upgrade: Removed clunky rectangular "reasoning / executing" boxes and raw status badges, replacing them with a sleek, cybernetic pill-shaped loading indicator powered by
thinking-orbs(2D Canvas-based dotted particle engine). - Stream Activity Detection Engine:
WarRoomChat.tsxinspects the activeuseChatstream state and parsesmessage.partsin real-time to determine the exact granular action the autonomous SRE agent is performing:Agent Tool / Stream Event Thinking Orb State Dynamic Pill Label Detail Description search_knowledge_basesearchingSEARCHING RUNBOOKSQuerying pgvector embeddings & runbook knowledge base... query_telemetry_logssearchingSCANNING LOGSQuerying error telemetry, stack traces & service metrics... fetch_repo_fileconnectingFETCHING REPOInspecting repository source files and dependencies... propose_hotfixsolvingSYNTHESIZING HOTFIXDrafting code modifications, commit diff & PR structure... Stream Formulating Text workingREASONINGSynthesizing root cause findings and formulating diagnosis... Client Request Submitted listeningINITIALIZINGIngesting prompt & initializing incident war room session...
- Zero-Clutter Visual Hierarchy: Tool execution in progress displays the dynamic glowing pill in both the terminal header strip and the message thread. When execution finishes, completed interactive widgets (
<DiffApprovalCard />,TELEMETRY LOGS,RUNBOOK MATCHES) render cleanly without residual loading clutter.