# Project export: UpTime.ai

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Your AI on-call engineer for production errors.
- Devpost: https://devpost.com/software/uptime-ai
- GitHub: https://github.com/olashin1/aihackathon26
- Video: https://www.youtube.com/embed/p1NL_iASD_k?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — aidandawley (14 commits), olashin (12 commits)

## Devpost submission (written by the team)

### Inspiration

Developers move fast, and production errors can appear at the worst times. We wanted to build an AI-powered system that acts like an on-call engineer by watching server issues, judging severity, and helping teams respond faster.

### What it does

Uptime.ai is a real-time intelligent dashboard that monitors server errors through Sentry and uses Fetch.ai agents to decide whether an issue needs deeper investigation. It helps developers quickly understand what went wrong, how serious it is, and what should happen next.

### How we built it

We built Uptime.ai using Sentry for error monitoring and Fetch.ai for the multi-agent workflow. When Sentry detects an issue, it sends the event to our backend and dashboard via webhook. From there, our Fetch.ai agent chain analyzes the error, determines its urgency, and prepares the next steps for review.

### Challenges we ran into

*Our biggest challenge was designing the agent judgment process. Not every error should trigger the full pipeline, so we had to make the agents reason about severity, context, and whether the issue was worth escalating. *A big problem was creating the infrastructure to bridge two applications in real time. We wanted to host a publicly accessible VM for a demo, but we couldn't get port forwarding to work on the guest wifi. *Sentry was sometimes inconsistent on webhooks. We had to configure quite a few settings to get it to work well.

### Accomplishments we're proud of

Built a multi-agent judgment pipeline using Fetch.ai. Integrated Sentry for real-time error and server health monitoring. Connected the dashboard to a GitHub repository so agents can use project context. Created the foundation for an AI-powered on-call engineering assistant.

### What we learned

We learned how to connect monitoring tools with agentic AI systems, and how difficult it is to make agents reason carefully instead of blindly reacting to every error. We also learned more about building reliable workflows for real-world developer tooling.

### What's next

for Uptime.ai Automatically create emergency pull requests for severe errors. Analyze possible issues in server logs before each Git push. Store summary history for each unique error. Improve agent judgment with more repository and runtime context.

## README (from the GitHub repository)

# UpTime.ai


## Detected evidence (automated analysis)

Indexed codebase: 39 recognized source files, 84 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (48 of 48)

```
.env.example
.gitignore
backend/.gitignore
backend/app/agents/addresses.py
backend/app/agents/github_agent.py
backend/app/agents/investigation_agent.py
backend/app/agents/models.py
backend/app/agents/monitoring_agent.py
backend/app/agents/patch_agent.py
backend/app/agents/validation_agent.py
backend/app/agents/workflow_trace.py
backend/app/config.py
backend/app/database.py
backend/app/main.py
backend/app/models/incident.py
backend/app/models/recommendation.py
backend/app/models/repository.py
backend/app/models/scan.py
backend/app/models/user.py
backend/app/routes/github.py
backend/app/routes/health.py
backend/app/routes/incidents.py
backend/app/routes/patch_agent.py
backend/app/routes/recommendations.py
backend/app/routes/sentry.py
backend/app/run_agents.py
backend/app/services/agent_service.py
backend/app/services/github_service.py
backend/app/services/incident_service.py
backend/app/services/llm_service.py
backend/app/services/patch_agent_service.py
backend/app/services/sentry_service.py
backend/app/workers/jobs.py
backend/app/workers/queue.py
backend/requirements.txt
frontend/.gitignore
frontend/eslint.config.js
frontend/index.html
frontend/package.json
frontend/README.md
frontend/src/App.tsx
frontend/src/index.css
frontend/src/main.tsx
frontend/tsconfig.app.json
frontend/tsconfig.json
frontend/tsconfig.node.json
frontend/vite.config.ts
README.md
```

### Dependencies

- backend/requirements.txt: alembic, fastapi, httpx, openai, passlib[bcrypt], psycopg2-binary, pydantic, pydantic-settings, PyGithub, python-dotenv, python-jose[cryptography], redis, rq, semgrep, sentry-sdk, sqlmodel, structlog, uagents, uvicorn[standard]
- frontend/package.json: @eslint/js@^10.0.1, @tailwindcss/vite@^4.3.1, @types/node@^24.12.3, @types/react@^19.2.14, @types/react-dom@^19.2.3, @vitejs/plugin-react@^6.0.1, eslint@^10.3.0, eslint-plugin-react-hooks@^7.1.1, eslint-plugin-react-refresh@^0.5.2, globals@^17.6.0, react@^19.2.6, react-dom@^19.2.6, tailwindcss@^4.3.1, typescript@~6.0.2, typescript-eslint@^8.59.2, vite@^8.0.12

### Recent commits (newest first)

- Rename project to UpTime.ai
- Merge pull request #2 from olashin1/demo-agent-workflow
- Merge branch 'main' of https://github.com/olashin1/aihackathon26 into demo-agent-workflow
- logo is tuff
- added delete button
- added cases for eval
- fixed recommendations
- synced db to event history
- front end
- added tracing comments
- webhook works
- improved agent logic
- investigation agent stuck
- big push w a lot of agents
- Merge pull request #1 from olashin1/sentry_integration
- added front end
- webhook data appears in console
- Merge branch 'main' of https://github.com/olashin1/aihackathon26
- 5 agents files
- added webhook reciever sentry

## Key source files (fetched from GitHub, selected and truncated for size)

### backend/requirements.txt

```
fastapi
uvicorn[standard]

# Database
sqlmodel
psycopg2-binary
alembic

# Settings
pydantic-settings

# Auth & Security
python-jose[cryptography]
passlib[bcrypt]

# Background Jobs
redis
rq

# HTTP / GitHub
httpx

# Scanning
semgrep

# Monitoring
sentry-sdk

# Fetch.ai Agents
uagents

# Logging
structlog

# env
python-dotenv

pydantic

openai

PyGithub


```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@tailwindcss/vite": "^4.3.1",
    "react": "^19.2.6",
    "react-dom": "^19.2.6",
    "tailwindcss": "^4.3.1"
  },
  "devDependencies": {
    "@eslint/js": "^10.0.1",
    "@types/node": "^24.12.3",
    "@types/react": "^19.2.14",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^6.0.1",
    "eslint": "^10.3.0",
    "eslint-plugin-react-hooks": "^7.1.1",
    "eslint-plugin-react-refresh": "^0.5.2",
    "globals": "^17.6.0",
    "typescript": "~6.0.2",
    "typescript-eslint": "^8.59.2",
    "vite": "^8.0.12"
  }
}

```

### frontend/src/main.tsx

```typescript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### backend/app/main.py

```python
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from app.config import settings
from app.database import init_db
from app.routes import health, patch_agent, sentry, incidents, github, recommendations
import sentry_sdk

if settings.sentry_dsn:
    sentry_sdk.init(
        dsn=settings.sentry_dsn,
        send_default_pii=False,
    )


@asynccontextmanager
async def lifespan(app: FastAPI):
    init_db()
    yield


app = FastAPI(
    title="AI On-Call Engineer Backend",
    version="0.1.0",
    lifespan=lifespan,
)

app.add_middleware(
    CORSMiddleware,
    allow_origin_regex=r"^http://(localhost|127\.0\.0\.1):517[0-9]$",
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(health.router)
app.include_router(sentry.router)
app.include_router(patch_agent.router)
app.include_router(incidents.router)
app.include_router(github.router)
app.include_router(recommendations.router)

```

### frontend/src/App.tsx

```typescript
import { type ReactNode, useMemo, useState } from "react";
import logoUrl from "./assets/logo.png";

type ServiceState = "LIVE" | "DOWN";

type SentryEvent = {
  id: number;
  summary: string;
  route: string;
  age: string;
  status: number;
  level: string;
  fullError: string;
};

type RecommendedChange = {
  id: number;
  summary: string;
  detail: string;
  code?: string;
};

const sentryEvents: SentryEvent[] = [
  {
    id: 1,
    summary: "Failed login check",
    route: "GET /me",
    age: "2 min ago",
    status: 401,
    level: "warning",
    fullError:
      "Bogus backend request observed. Auth probe reached GET /me without valid credentials and returned 401.",
  },
  {
    id: 2,
    summary: "Unknown route requested",
    route: "GET /does-not-exist",
    age: "8 min ago",
    status: 404,
    level: "warning",
    fullError:
      "A client requested an unregistered route. The request matched no FastAPI handler and returned 404.",
  },
  {
    id: 3,
    summary: "Malformed todo payload",
    route: "POST /todos",
    age: "13 min ago",
    status: 422,
    level: "error",
    fullError:
      'Request body did not match the TodoCreate schema. Required field "title" was absent from the JSON payload.',
  },
  {
    id: 4,
    summary: "Chat provider crash",
    route: "POST /chat",
    age: "21 min ago",
    status: 500,
    level: "critical",
    fullError:
      "Gemini client raised an upstream exception. Check API key, timeout behavior, and fallback response path.",
  },
];

const recommendedChanges: RecommendedChange[] = [
  {
    id: 1,
    summary: "Group expected auth failures",
    detail:
      "Reduce alert noise from normal unauthenticated checks while keeping scanner activity visible in Sentry.",
    code: `- sentry_sdk.capture_message("Bogus backend request observed")
+ sentry_sdk.capture_message(
+   "Expected unauthenticated probe observed",
+   level="info",
+ )`,
  },
  {
    id: 2,
    summary: "Add source fingerprinting",
    detail:
      "Make repeated bogus requests easier to sort by route and status without creating hundreds of separate issues.",
    code: `+ with sentry_sdk.configure_scope() as scope:
+   scope.fingerprint = [
+     "bogus-backend-request",
+     request.method,
+     request.url.path,
+     str(response.status_code),
+   ]`,
  },
  {
    id: 3,
    summary: "Forward critical events to intake app",
    detail:
      "Send 500-level events directly to the second app while keeping lower-severity warnings in Sentry.",
  },
];

function App() {
  const [serviceState, setServiceState] = useState<ServiceState>("DOWN");
  const [healthPercentage, setHealthPercentage] = useState(100);
  const [openEvents, setOpenEvents] = useState<number[]>([3, 4]);
  const [openChanges, setOpenChanges] = useState<number[]>([1]);

  const healthTone = useMemo(() => {
    if (healthPercentage >= 80) {
      return {
        text: "text-[#7cf083]",
        border: "border-[#78e86f]",
        bar: "bg-[#78e86f]",
        label: "Healthy",
      };
    }

    if (healthPercentage >= 70) {
      return {
        text: "text-[#f2d84b]",
        border: "border-[#e8ca38]",
        bar: "bg-[#e8ca38]",
        label: "Watch",
      };
    }

    return {
      text: "text-[#ff6f82]",
      border: "border-[#ef6478]",
      bar: "bg-[#ef6478]",
      label: "Risk",
    };
  }, [healthPercentage]);

  const toggleEvent = (id: number) => {
    setOpenEvents((open) =>
      open.includes(id)
        ? open.filter((eventId) => eventId !== id)
        : [...open, id],
    );
  };

  const toggleChange = (id: number) => {
    setOpenChanges((open) =>
      open.includes(id)
        ? open.filter((changeId) => changeId !== id)
        : [...open, id],
    );
  };

  return (
    <main className="min-h-screen bg-[#0d0b2c] text-[#f7f4ff]">
      <div className="mx-auto flex min-h-screen w-full max-w-[1920px] flex-col border-x border-[#2d2a65] bg-[#11103a] px-3 py-3 sm:px-5 lg:px-8">
        <header className="flex flex-wrap items-start justify-between gap-5 pb-7">
          <div className="flex min-w-0 items-start gap-5">
            <div className="grid h-[4.25rem] w-[4.25rem] shrink-0 place-items-center rounded-[1.1rem] border border-[#4b4a86] bg-[#29285c] shadow-[inset_0_0_0_6px_rgba(255,255,255,0.04)]">
              <img
                src={logoUrl}
                alt="Logo placeholder"
                className="h-11 w-11 rounded-md object-contain"
              />
            </div>
            <div className="min-w-0">
              <p className="text-sm font-black uppercase tracking-[0.18em] text-[#b7b3d7]">
                Security Signal Board
              </p>
              <h1 className="mt-1 text-5xl font-black leading-none tracking-[-0.02em] text-[#fbf8ff] sm:text-6xl lg:text-7xl">
                Backend Observability
              </h1>
            </div>
          </div>

          <div className="rounded-bl-[1.4rem] rounded-br-lg rounded-tl-lg rounded-tr-[1.4rem] border border-[#45437d] bg-[#29285e] px-5 py-4 text-right shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]">
            <p className="bg-[#7f92c8] px-1 text-xl leading-none text-[#d9def8]">
              Sentry intake
            </p>
            <p className="mt-2 text-2xl font-black text-white">local</p>
          </div>
        </header>

        <section className="grid min-h-0 flex-1 grid-cols-1 gap-4 lg:grid-cols-[470px_minmax(480px,1fr)_minmax(500px,1fr)]">
          <Panel
            title="Overall Health"
            badge={serviceState === "LIVE" ? "Live" : "Down"}
            badgeTone={serviceState === "LIVE" ? "live" : "down"}
          >
            <div className="rounded-[1.65rem] border border-[#46447b] bg-[#373568] px-6 py-16 text-center">
              <p
                className={`text-7xl font-black leading-none tracking-[-0.04em] sm:text-8xl ${
                  serviceState === "LIVE" ? "text-[#74f082]" : "text-[#ff6f82]"
                }`}
              >
              
[truncated — 8352 more characters]
```

### frontend/vite.config.ts

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
});

```

### frontend/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>frontend</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### frontend/eslint.config.js

```javascript
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      js.configs.recommended,
      tseslint.configs.recommended,
      reactHooks.configs.flat.recommended,
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      globals: globals.browser,
    },
  },
])

```

### frontend/src/index.css

```css
@import "tailwindcss";

body {
  margin: 0;
  min-width: 320px;
  background: #0d0b2c;
  font-family:
    Inter,
    ui-sans-serif,
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    sans-serif;
}

button,
input,
select {
  font: inherit;
}

```

### backend/app/run_agents.py

```python
import subprocess
import sys
import time

agents = [
    "app.agents.investigation_agent",
    "app.agents.github_agent",
    "app.agents.patch_agent",
    "app.agents.validation_agent",
]

processes = []

try:
    for agent in agents:
        print(f"Starting {agent}...")
        p = subprocess.Popen([sys.executable, "-m", agent])
        processes.append(p)
        time.sleep(1)

    print("All agents started.")

    for p in processes:
        p.wait()

except KeyboardInterrupt:
    print("Stopping agents...")

    for p in processes:
        p.terminate()

    time.sleep(2)

    for p in processes:
        if p.poll() is None:
            p.kill()
```

[24 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]