# Project export: Triage

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: An agent that reproduces your bugs by actually using your app.
- Devpost: https://devpost.com/software/triage-y10mpq
- GitHub: https://github.com/HansChundekad/Triage
- Video: https://www.youtube.com/embed/Q-ArgjSnVzw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Best use of Band for agent to agent communication)
- Team: 2 GitHub contributor(s) — Hans (123 commits), Claude Opus 4.8 (93 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# TRIAGE

Autonomous bug-reproduction agent. A developer pastes a GitHub issue; TRIAGE
opens a real cloud browser, clicks through a live app to trigger the bug,
captures the crash, diagnoses the root cause, and writes a structured report.

Built for the UC Berkeley AI Hackathon 2026.

## Architecture (three agents, one Band room)

- **ParserAgent** (`triage/parser_agent/`) — GitHub issue → structured repro steps.
- **ReproAgent** (`triage/repro_agent/`) — drives a real Browserbase browser. **All** browser work lives here.
- **HypothesisAgent** (`triage/hypothesis_agent/`) — diagnoses root cause, can redirect ReproAgent to retry.

Integrations: Browserbase (cloud browser), Band (multi-agent coordination),
Arize Phoenix (retry-loop tracing), Claude (reasoning).

See `docs/TRIAGE_OVERVIEW.md` (source of truth) and `docs/TRIAGE_INTEGRATIONS.md`.

## Status

Phase 1 — repo scaffold. No agent logic or integration code yet.

## Setup

```bash
pip install -e ".[dev]"   # installs python-dotenv + pytest only in Phase 1
cp .env.example .env      # then fill in your keys
pytest                    # runs the config-loader tests
```


## Detected evidence (automated analysis)

Indexed codebase: 132 recognized source files, 840 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 143)

```
.env.example
.gitignore
backend/__init__.py
backend/README.md
backend/requirements.txt
backend/run_manager.py
backend/server.py
CLAUDE.md
docs/DEMO_RUNBOOK.md
docs/STATUS.md
docs/superpowers/plans/2026-06-20-phase-1-repo-scaffold.md
docs/superpowers/plans/2026-06-20-phase-2-band-module.md
docs/superpowers/plans/2026-06-20-phase-3-hypothesis-agent.md
docs/superpowers/plans/2026-06-20-phase-3-repro-echo.md
docs/superpowers/plans/2026-06-20-phase4-repro-agent-browserbase.md
docs/superpowers/plans/2026-06-20-phase5-hypothesis-agent-real.md
docs/superpowers/plans/2026-06-20-phase5-parser-agent-real.md
docs/superpowers/plans/2026-06-20-phase6-retry-loop.md
docs/superpowers/plans/2026-06-20-phase7-frontend.md
docs/superpowers/plans/2026-06-20-phase7a-tracing-substrate.md
docs/superpowers/plans/2026-06-20-phase7b-phoenix-evaluator.md
docs/superpowers/plans/2026-06-20-phase7c-claude-synthesis.md
docs/superpowers/plans/2026-06-21-phase7.5-outer-loop.md
docs/superpowers/plans/2026-06-21-phoenix-to-ax-migration.md
docs/superpowers/specs/2026-06-20-phase7-arize-design.md
docs/superpowers/specs/2026-06-20-phase7-frontend-design.md
docs/superpowers/specs/2026-06-21-phoenix-to-ax-migration.md
docs/superpowers/specs/phase7-report.schema.json
docs/TRIAGE_INTEGRATIONS.md
docs/TRIAGE_OVERVIEW.md
frontend/.env.example
frontend/.gitignore
frontend/index.html
frontend/package.json
frontend/README.md
frontend/src/api.test.ts
frontend/src/api.ts
frontend/src/App.test.tsx
frontend/src/App.tsx
frontend/src/components/BrowserView.test.tsx
frontend/src/components/BrowserView.tsx
frontend/src/components/LiveLog.test.tsx
frontend/src/components/LiveLog.tsx
frontend/src/components/ReportCard.test.tsx
frontend/src/components/ReportCard.tsx
frontend/src/components/UrlInput.test.tsx
frontend/src/components/UrlInput.tsx
frontend/src/fixtures/recorded-run.json
frontend/src/main.tsx
frontend/src/replay.test.ts
frontend/src/replay.ts
frontend/src/styles/app.css
frontend/src/styles/tokens.css
frontend/src/test-setup.ts
frontend/src/types.test.ts
frontend/src/types.ts
frontend/tsconfig.json
frontend/vercel.json
frontend/vite.config.ts
pyproject.toml
README.md
scripts/demo.sh
scripts/handshake.py
scripts/hypothesis_echo_demo.py
scripts/phase6_live_run.py
scripts/phase7_eval.py
scripts/phase7_traced_run.py
scripts/test_echo_chain.py
scripts/three_way_smoke.py
tests/__init__.py
tests/_tracing_helpers.py
tests/test_backend_server.py
tests/test_band_module.py
tests/test_claude_call_spans.py
tests/test_config.py
tests/test_eval_ax_logging.py
tests/test_eval_code_checks.py
tests/test_eval_ground_truth.py
tests/test_eval_judges.py
tests/test_eval_run_eval.py
tests/test_hypothesis_agent.py
tests/test_hypothesis_reasoning.py
tests/test_memory_backend_ax.py
tests/test_memory_backend_phoenix.py
tests/test_memory_distill.py
tests/test_memory_facade.py
tests/test_memory_history.py
tests/test_parser_agent.py
tests/test_parser_claude.py
tests/test_parser_github.py
tests/test_parser_prior_context.py
tests/test_phase7_capture.py
tests/test_phase75_booth_injection.py
tests/test_repro_browser_tracing.py
tests/test_repro_browser.py
tests/test_repro_echo_tracing.py
tests/test_repro_echo.py
tests/test_repro_loop.py
tests/test_run_artifacts.py
tests/test_run_trace.py
tests/test_synthesis_schema.py
tests/test_synthesize.py
tests/test_tracing_setup.py
triage/__init__.py
triage/config.py
triage/eval/__init__.py
triage/eval/code_checks.py
triage/eval/ground_truth.py
triage/eval/judges.py
triage/eval/run_eval.py
triage/hypothesis_agent/__init__.py
triage/hypothesis_agent/__main__.py
triage/hypothesis_agent/agent.py
triage/hypothesis_agent/README.md
triage/hypothesis_agent/reasoning.py
triage/memory/__init__.py
triage/memory/backends/__init__.py
triage/memory/backends/ax.py
triage/memory/backends/phoenix.py
triage/memory/distill.py
[23 more files omitted for size]
```

### Dependencies

- backend/requirements.txt: fastapi@>=0.115, sse-starlette@>=2.1, uvicorn[standard]@>=0.30
- frontend/package.json: @testing-library/jest-dom@^6.4.8, @testing-library/react@^16.0.1, @types/react@^18.3.5, @types/react-dom@^18.3.0, @vitejs/plugin-react@^4.3.1, jsdom@^25.0.0, react@^18.3.1, react-dom@^18.3.1, typescript@^5.5.4, vite@^5.4.3, vitest@^2.0.5
- pyproject.toml: anthropic, arize@>=8.35, arize-ax-cli@>=0.25, arize-otel@>=0.13, arize-phoenix@>=17.9, arize-phoenix-client@>=2.9, arize-phoenix-evals@>=3.1, band-sdk, httpx, openinference-instrumentation-anthropic@>=1.0.6, opentelemetry-sdk@>=1.42, pandas@>=2.0, playwright, pytest@>=8.0, python-dotenv@>=1.0, stagehand

### Recent commits (newest first)

- fix(frontend): point demo fixture replay links at real Browserbase sessions
- fix(demo): RUN SUMMARY trace hint reflects actual attempt count
- docs: add demo-day runbook (commands, surfaces, AX deep link, outer-loop story)
- feat(demo): opt-in auto-open of Browserbase live view per attempt
- fix(demo): RUN SUMMARY reads persisted attempts (true fail→succeed count)
- feat(demo): one clean entrypoint + live-surface visibility (Phase 8 demo-prep)
- fix(trace): key span ids on run-unique attempt id (redirect_parser hero trace)
- docs(ax): correct read-latency claim — by-trace-id reads ~2s, not hours
- chore(ax): gitignore the ax CLI export temp dir
- docs(ax): integrations + status + deps for Phoenix→AX migration
- feat(ax): log evaluators to AX via spans.update_evaluations (eval.* columns)
- feat(ax): implement AX trace-query read backend + flip seam default to ax
- feat(ax): setup_tracing registers arize.otel tracer (batch=False); phoenix fallback
- feat(ax): add ARIZE_* config + TRIAGE_TRACE_BACKEND selector (default ax)
- docs(ax): migration spec + plan (Phoenix→Arize AX), verified APIs
- docs(phase7.5): precise harness cut-path (elif→if restore) per final review
- docs(phase7.5): record outer-loop completion, cut-path, and Phoenix→AX seam
- refactor(phase7.5): isolate trace read-back behind a swappable backend seam
- feat(phase7.5): harness injects learned context (memory ON supersedes force-retry)
- feat(phase7.5): booth path posts learned context into room + shapes attempt 1

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

### CLAUDE.md

```markdown
# TRIAGE

Autonomous bug-reproduction agent (UC Berkeley AI Hackathon 2026). Reproduces a
reported bug by driving a real cloud browser through a live app, then diagnoses
and reports root cause.

**Read first:** `docs/TRIAGE_OVERVIEW.md` (architecture, source of truth) and
`docs/TRIAGE_INTEGRATIONS.md` (how to connect Browserbase/Band/Arize).
**Current state / what's next:** `docs/STATUS.md`.

## Architecture

Three agents coordinate in one Band room:
- **ParserAgent** (`triage/parser_agent/`) — GitHub issue → structured repro steps.
- **ReproAgent** (`triage/repro_agent/`) — drives the Browserbase browser.
- **HypothesisAgent** (`triage/hypothesis_agent/`) — diagnoses, can redirect for retry.
- Shared Band layer: `triage/shared/band.py`. Config loader: `triage/config.py`.

## Non-negotiable rules

1. All browser/Stagehand work lives in **ReproAgent only**.
2. Every cross-agent Band message must **@mention** a recipient (no @mention = no one sees it).
3. Agent names are exact: `ParserAgent` / `ReproAgent` / `HypothesisAgent` — never generic.
4. Three **distinct Band identities** (`BAND_PARSER_*`, `BAND_REPRO_*`, `BAND_HYPOTHESIS_*`).
5. **New Browserbase session per retry** (no sessionId).
6. Band **messages** = directed @mention talk; **events** = logs. Don't mix.
7. **Verify SDK details against live docs before writing integration code**; flag drift.
8. Arize `bug.detected` must be honest — the fail→succeed flip must be real.

## Commands

Always use the repo venv (`.venv/`):
- Install: `.venv/bin/pip install -e ".[dev]"`
- Test: `.venv/bin/pytest`
- Config is loaded via `from triage.config import load_config` (fails loud on missing env vars).

## Conventions

- All-Python; target Python 3.11+ (dev on 3.14).
- TDD for real logic (write the failing test first).
- Per-task commits, scoped messages. Never commit secrets — `.env` is gitignored;
  keep `.env.example` in sync when adding a config var.
- When adding a config var: update `.env.example`, `triage/config.py`, and its test together.

```

### docs/DEMO_RUNBOOK.md

```markdown
# TRIAGE — Demo Runbook

Everything for the booth. Test-script demo, native surfaces, no in-app UI.

---

## 0. Pre-demo checklist (do once, before judges arrive)

- [ ] Terminal open at `/Users/hanschundekad/Triage`.
- [ ] Browser logged into **browserbase.com**.
- [ ] **Band web app** open at app.band.ai → room **`741d69f0-006d-436a-9d45-cc43b88cbc75`** (pinned; all 3 agents are participants). All demo runs stream into this one room.
- [ ] **Arize AX** open (project `triage-bug-repro`). Have the deep link (§4) ready.
- [ ] (Optional) `cd frontend && npm run dev` running, for the report-card surface.
- [ ] **Pre-seed the outer loop:** do one `./scripts/demo.sh` run ~10+ min before showing the "Arize makes it smarter" story, so AX has the history indexed. (Memory already returns a real hint from 4 prior runs — verified — so this is belt-and-suspenders.)

Sanity check the memory feed is live:
```bash
TRIAGE_OUTER_LOOP=1 .venv/bin/python -c "from dotenv import load_dotenv; load_dotenv(); from triage.config import load_config; from triage.memory import load_learned_context; print(load_learned_context(load_config()))"
```
Expect a `Prior-run memory: across N past run(s)…` line.

---

## 1. Commands

| # | Purpose | Command |
|---|---|---|
| **A** | **Main demo** — deterministic fail→succeed (hero) | `./scripts/demo.sh` |
| **B** | **"Arize makes it smarter"** — reads memory, first-try success | `TRIAGE_OUTER_LOOP=1 .venv/bin/python scripts/phase7_traced_run.py` |
| **C** | **Report card** surface | `cd frontend && npm run dev` → open URL → click **Demo** |

Both A and B use the hardcoded GitHub issue (`…/StrideAI/issues/1`). No UI input.

---

## 2. Run of show (Demo A — the hero)

1. **Run `./scripts/demo.sh`.** Say: *"A real GitHub bug report is handed to three coordinating agents."*
2. **💬 Band room** (or terminal `💬` stream): ParserAgent → ReproAgent → "attempt 1 failed" → HypothesisAgent diagnoses → `redirect_parser` → ParserAgent re-parses → retry. *"Three distinct agents coordinating over @mentions in a real room."*
3. **🌐 Browserbase**: two tabs auto-open — attempt 1 (browser blanks/fails), attempt 2 (browser succeeds). *"It drives a real cloud browser, not a mock."*
4. **Terminal RUN SUMMARY**: `verdict: REPRODUCED ✅`, honest `bug.detected flip: False → True`, both session URLs.
5. **📋 Report card** (frontend Demo button): synthesized root cause — verdict, per-step ok/fail/crash, mechanism, eval scores.
6. **📊 Arize** (§3–4): walk the trace.

Run takes ~1–3 min.

---

## 3. What's in the Arize trace (the walkthrough)

- **One `triage_run` → two `repro_attempt` spans** (the inner-loop retry, captured).
- Expand each → `browser_execution` → `stagehand_action` per step (4 on attempt 1, 8 on attempt 2).
- **Evaluations: `repro_fidelity` = not_reproduced (0)** on attempt 1 vs **reproduced (1)** on attempt 2 — LLM-judge scores, honestly different. Plus `root_cause_correctness`, `honesty`.

---

## 4. Arize deep link (use this — the trace LIST
[truncated — 4696 more characters]
```

### pyproject.toml

```
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "triage"
version = "0.1.0"
description = "Autonomous bug-reproduction agent — UC Berkeley AI Hackathon 2026"
requires-python = ">=3.11"
dependencies = [
    # --- Installed in Phase 1 (config loader + tests need these) ---
    "python-dotenv>=1.0",
    # --- Listed for later phases; NOT imported yet. Versions verified
    #     against live docs in the phase that first uses each. ---
    "anthropic",                                  # Claude reasoning (all agents)
    "httpx",                                      # GitHub issue fetch (ParserAgent)
    "band-sdk",                                   # Band coordination (Phase: Band)
    "stagehand",                                  # Browserbase driver (ReproAgent)
    "playwright",                                  # CDP screenshots + console errors (ReproAgent)
    # --- Arize AX (PRIMARY trace backend) — verified 2026-06-21 ---
    "arize-otel>=0.13",                           # arize.otel.register → otlp.arize.com (WRITE path)
    "arize>=8.35",                                # ArizeClient: spans.update_evaluations (eval logging)
    "arize-ax-cli>=0.25",                         # `ax` CLI: spans export (READ-back) + verification
    # --- Arize Phoenix (FALLBACK backend + LOCAL judge engine) — verified 2026-06-20 ---
    "arize-phoenix>=17.9",                        # tracing fallback (TRIAGE_TRACE_BACKEND=phoenix)
    "openinference-instrumentation-anthropic>=1.0.6",  # auto-instrument Claude calls (both backends)
    "opentelemetry-sdk>=1.42",                    # manual retry spans
    "arize-phoenix-evals>=3.1",                   # LOCAL LLM-judge engine (backend-agnostic, Phase 7B)
    "arize-phoenix-client>=2.9",                  # Phoenix annotation fallback (Phase 7B)
    "pandas>=2.0",                                # eval dataframes (Phase 7B)
]

[project.optional-dependencies]
dev = ["pytest>=8.0"]

[tool.hatch.build.targets.wheel]
packages = ["triage"]

[tool.pytest.ini_options]
testpaths = ["tests"]

```

### backend/requirements.txt

```
fastapi>=0.115
uvicorn[standard]>=0.30
sse-starlette>=2.1

```

### frontend/package.json

```
{
  "name": "triage-frontend",
  "private": true,
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "preview": "vite preview",
    "test": "vitest run"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@testing-library/jest-dom": "^6.4.8",
    "@testing-library/react": "^16.0.1",
    "@types/react": "^18.3.5",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.3.1",
    "jsdom": "^25.0.0",
    "typescript": "^5.5.4",
    "vite": "^5.4.3",
    "vitest": "^2.0.5"
  }
}

```

### backend/server.py

```python
from __future__ import annotations

import json
import re

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from sse_starlette.sse import EventSourceResponse

from backend.run_manager import RunRegistry

ISSUE_RE = re.compile(r"^https://github\.com/[^/]+/[^/]+/issues/\d+/?$")

app = FastAPI(title="TRIAGE frontend backend")
app.add_middleware(
    CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"],
)
_registry = RunRegistry()


class RunRequest(BaseModel):
    issueUrl: str


@app.post("/api/runs")
async def start_run(req: RunRequest):
    if not ISSUE_RE.match(req.issueUrl.strip()):
        raise HTTPException(422, "not a GitHub issue URL")
    return {"runId": _registry.create(req.issueUrl.strip())}


@app.get("/api/runs/{run_id}")
def snapshot(run_id: str):
    try:
        return _registry.snapshot(run_id)
    except KeyError:
        raise HTTPException(404, "unknown run")


@app.get("/api/runs/{run_id}/stream")
async def stream(run_id: str):
    if not _registry.has(run_id):
        raise HTTPException(404, "unknown run")
    async def gen():
        async for name, data in _registry.stream(run_id):
            yield {"event": name, "data": json.dumps(data)}
    return EventSourceResponse(gen())


@app.get("/api/replays")
def replays():
    # The frontend ships its own bundled fixture; this lists server-side ones (none yet).
    return []

```

### frontend/src/main.tsx

```typescript
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./styles/tokens.css";
import "./styles/app.css";

createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```

### frontend/src/App.tsx

```typescript
import { useRef, useState } from "react";
import UrlInput from "./components/UrlInput";
import LiveLog from "./components/LiveLog";
import BrowserView from "./components/BrowserView";
import ReportCard from "./components/ReportCard";
import { startLiveRun, startReplayRun } from "./api";
import type { ReproReport, RunPhase, StreamEvent, SessionInfo } from "./types";

const API_BASE = (import.meta.env.VITE_API_BASE as string | undefined) ?? "";

type Status = "idle" | "running" | "report" | "error";

export default function App() {
  const [status, setStatus] = useState<Status>("idle");
  const [phase, setPhase] = useState<RunPhase | null>(null);
  const [events, setEvents] = useState<StreamEvent[]>([]);
  const [report, setReport] = useState<ReproReport | null>(null);
  const [errorMsg, setErrorMsg] = useState("");
  const [session, setSession] = useState<SessionInfo | null>(null);
  const [mode, setMode] = useState<"live" | "replay">("replay");
  const cancelRef = useRef<null | (() => void)>(null);

  function onEvent(e: StreamEvent) {
    setEvents((prev) => [...prev, e]);
    if (e.type === "session") setSession(e.session);
    if (e.type === "status") setPhase(e.phase);
    if (e.type === "report") { setReport(e.report); setStatus("report"); }
    if (e.type === "error") { setErrorMsg(e.message); setStatus("error"); }
  }

  function onRun(runMode: "live" | "replay", url: string) {
    cancelRef.current?.();
    setEvents([]); setReport(null); setErrorMsg(""); setPhase(null); setStatus("running");
    setSession(null); setMode(runMode);
    cancelRef.current = runMode === "replay"
      ? startReplayRun(onEvent)
      : startLiveRun(API_BASE, url, onEvent);
  }

  return (
    <div className="shell">
      <header className="brand" role="banner">
        <span className="wordmark">Triage</span>
        <span className="tagline">reproduces your bugs by using your app</span>
      </header>

      <UrlInput onRun={onRun} disabled={status === "running"} />

      {status === "error" && <div className="banner banner--error" role="alert">{errorMsg}</div>}

      {status !== "idle" && (
        <main className="stage">
          {/* LiveLog (Task 6) */}
          <section aria-label="run log" className="pane pane--log">
            <LiveLog events={events} />
            {phase && <div className="phase">phase: {phase}</div>}
          </section>
          <BrowserView session={session} live={mode === "live"} />
          {report && <ReportCard report={report} />}
        </main>
      )}
    </div>
  );
}

```

### triage/__init__.py

```python
"""TRIAGE — autonomous bug-reproduction agent."""

```

### frontend/vite.config.ts

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

export default defineConfig({
  plugins: [react()],
  test: {
    environment: "jsdom",
    globals: true,
    setupFiles: ["./src/test-setup.ts"],
  },
});

```

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