# Project export: Argus — Autonomous Third-Party Risk Management Workforce

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: OpenAI Build Week
- Tagline: An AI agent Crew that autonomously assesses and monitors your vendors including AI tools, agents & MCP servers so teams with no security analyst get enterprise-grade vendor due diligence in minutes.
- Devpost: https://devpost.com/software/argus-autonomous-third-party-risk-management-workforce
- GitHub: https://github.com/k9-007/argus-autonomous-tprm-
- Video: https://www.youtube.com/embed/I_YTJ43fPUY?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — eloelo (6 commits), Cursor (4 commits)

## Devpost submission (written by the team)

### Inspiration

Every company now runs on dozens of SaaS and AI vendors — and before adopting each one, someone has to vet its security posture: collect SOC 2 / ISO reports, read the DPA, check for breaches, map controls to frameworks, fill out a 100–300 question security questionnaire, and then keep watching for changes. At mid-market and AI-first companies there is no GRC analyst to do this, so it lands on an engineer or ops lead buried in spreadsheets and email. Reviews take weeks, block deals, and go stale the day they finish. AI vendors make it worse. AI tools, autonomous agents and MCP servers are the fastest-growing — and riskiest — vendor class, and traditional third-party risk management (TPRM) has no playbook for prompt injection, tool permissions, data retention/training, or autonomous actions. We built Argus to be the vendor-risk department these teams never had.

### What it does

You add a vendor by uploading its compliance pack or pasting its trust-center link. Then a crew of nine specialized AI agents runs autonomously and live: Intake — profiles and tiers the vendor by data sensitivity, system access and type (SaaS / AI agent / MCP). Discovery — ingests evidence, handling the reality that ~90% of trust-center docs are gated behind request-access + NDA; runs live web research via Bright Data. Compliance — maps evidence to controls across SOC 2, ISO 27001, GDPR, HIPAA, PCI DSS, NIST CSF and ISO 42001 (AI governance), citing the exact artifact behind every result. Questionnaire — auto-completes SIG Lite and CAIQ with cited evidence, flagging unbacked self-attestation. AI-Vendor Risk — a first-class module for prompt-injection exposure, tool/action permissions, retention/training use and autonomous actions. Risk Scoring — computes an explainable inherent + residual score with a driver-level breakdown. Negotiation — routes access requests and NDAs to a human Approver (never auto-signs) and falls back to a vendor "Trust Passport" invite. Monitoring — arms continuous watch (CVEs, breaches, cert expiry, GitHub leaks, subprocessor changes) and re-scores on change. Executive — writes a board-ready decision: approve / approve-with-conditions / block. Everything renders in a polished portfolio + per-vendor dashboard with a live agent-activity feed, a real-time residual-risk trend graph, and control coverage front-and-center. Every assessment also enriches a shared, cross-org Trust Passport — so the Nth assessment of a common vendor is instant and higher-confidence.

### How we built it

Backend: Python + FastAPI, SQLAlchemy (multi-tenant, SQLite by default, Postgres-ready), with an SSE-friendly activity feed. Frontend: Next.js (App Router) + React + TypeScript — a dark security dashboard. Reasoning: model-flexible via a single provider switch — OpenAI GPT-5.6 or Google Gemini — with structured (JSON) outputs, and deterministic heuristics so the whole product still runs and demos fully offline. Tools: Bright Data (vendored from the open-source Studio1HQ/tprm-agent) for discovery/web-unlocking, plus a real PDF/text evidence parser that detects document type and extracts dates, opinions and citation snippets. Built with OpenAI Codex + GPT-5.6: we used Codex to pressure-test the idea against the competitive landscape, refactor the upstream linear Discovery → Access → Action pipeline into an orchestrated multi-agent crew, and accelerate the schema, the nine agents, the compliance/scoring engines, the FastAPI routers + SSE feed, and the entire dashboard UI.

### Challenges we ran into

The trust-center reality: most SOC 2 / pen-test reports are gated behind NDA. We modeled document access-state as first-class (public / requested / nda_pending / parsed / expired) and route NDAs to a human Approver instead of pretending the evidence is available. Explainability: a risk score no one trusts is useless, so every score ships with a driver-level breakdown and every control result cites the exact artifact and snippet. Honest evidence parsing: real uploaded SOC 2 packs are messy (full reports + bridge letters). We had to judge a vendor's SOC 2 posture across all artifacts using the strongest one, rather than letting one weak document override a valid Type II — and stop treating an "unknown" opinion as a qualified one. Offline reliability: for a live demo we couldn't depend on external APIs, so every agent has a deterministic fallback and a curated vendor knowledge base.

### Accomplishments we're proud of

A coherent, runnable product — not a proof-of-concept: one input → a live crew → a polished portfolio and per-vendor dashboards. First-class AI-vendor risk (prompt injection, tool permissions, retention, autonomous actions) mapped toward ISO 42001 — a lens no incumbent treats as core. A Trust Passport network effect: shared, evidence-cited vendor profiles that compound with every assessment. Model flexibility (OpenAI or Gemini) behind one env switch, with graceful offline behavior.

### What we learned

Vendor risk is fundamentally a context + network problem, and it's newly automatable end-to-end by agents — not just answer-drafting. The hardest part of "autonomy" is being honest about uncertainty (gated docs, unverifiable claims, stale evidence) rather than fabricating confidence. An orchestrated crew of small, single-purpose agents is easier to reason about, debug and make explainable than one monolithic prompt.

### What's next

for Argus More frameworks and audit-ready exports (SSO, evidence bundles). A vendor-side Trust Passport so vendors can publish once and be assessed everywhere. Deeper continuous monitoring (real CVE/breach/dark-web feeds) and automatic re-scoring. Growing the AI-vendor risk corpus into the reference layer for the AI vendor economy.

## README (from the GitHub repository)

# Argus — Autonomous Third-Party Risk Management

> The all-seeing guardian. Say **"Assess Stripe"** and a crew of specialized agents
> autonomously ingests evidence, maps compliance, scores explainable residual risk,
> and produces a board-ready decision — then monitors the vendor forever.

Argus is a multi-tenant TPRM platform built for mid-market and AI-first companies
that have **no dedicated GRC/security-analyst team**. It turns vendor risk work —
normally weeks of spreadsheets and email chasing — into an autonomous workflow.

---

## What makes it different

- **Real accounts & workspaces.** Sign up to create a company workspace (or use the
  demo account); every org's vendors and evidence are tenant-isolated, and every
  workspace API endpoint is bearer-token authenticated and role-gated.
- **Real evidence ingestion.** Upload SOC 2 / ISO / DPA files (PDF or text) — Argus
  parses them, detects the type, extracts issue/validity dates and the audit opinion,
  and cites the exact artifact behind every control result.
- **Evidence-based control mapping.** Each control is rated **Compliant / Partially
  compliant / No evidence / Gated (NDA pending) / Expired / N/A** with a citation and
  observation — the model real compliance agents (Vanta, CISO Assistant) use, plus
  `gated`/`expired` as Argus differentiators. Claims without evidence = non-compliant.
- **Live agent-crew flow.** A visible orchestration diagram lights up
  **pending → active → done** as the nine agents work, plus a streaming activity feed.
- **Real-time continuous monitoring.** A live residual-risk trend graph (auto-refreshing)
  tracks how a vendor's risk moves across assessments and monitoring signals.
- **An autonomous department, not a copilot.** Nine specialized agents operate like
  a real TPRM team (Intake, Discovery, Compliance, Questionnaire, AI-Vendor Risk,
  Risk Scoring, Negotiation, Monitoring, Executive).
- **First-class AI-vendor risk.** A dedicated module for AI tools / agents / MCP
  servers — prompt injection, tool permissions, data retention/training, autonomous
  actions — mapping toward **ISO 42001**. No incumbent treats this as core.
- **Solves the trust-center reality.** ~90% of trust centers gate SOC 2 / pen tests
  behind request-access + NDA. Argus ingests public content automatically, then
  routes NDAs to a human **Approver** (never auto-signs), and falls back to a
  **Trust Passport** vendor invite.
- **Trust Passport network effect.** Every assessment enriches a shared, cross-org
  vendor profile, so the Nth assessment is instant and higher-confidence.
- **Model-flexible & offline-safe.** Switch reasoning between **OpenAI GPT-5.6** and
  **Google Gemini** with one env var; with no keys, deterministic heuristics keep the
  whole product running and demoable.

---

## Tech stack

- **Backend:** Python 3.12 · FastAPI · SQLAlchemy (SQLite by default, Postgres-ready) ·
  multi-tenant model · SSE activity feed · a real PDF/text evidence parser (`pypdf`).
- **Frontend:** Next.js (App Router) · React · TypeScript — a dark security dashboard
  with the portfolio view, per-vendor deep-dive, live crew flow, real-time monitoring
  graph, and Trust Passport network.
- **Reasoning (switchable):** OpenAI **GPT-5.6** or Google **Gemini**, using structured
  (JSON) outputs, behind a single `complete_json()` abstraction with offline fallbacks.
- **Tools:** Bright Data — async **Discover API** for adverse-media discovery and the
  **Web Unlocker** for trust-center fetching (vendored from `Studio1HQ/tprm-agent`).

---

## Architecture & diagrams

> Interactive versions of all diagrams (with PNG/SVG export) live in
> [`diagrams.html`](./diagrams.html) — open it in a browser.

### 1. System architecture

Client dashboard → FastAPI backend → orchestrated crew → switchable LLM reasoning,
tools, and a multi-tenant database.

```mermaid
flowchart TB
  subgraph CLIENT["Client"]
    UI["Next.js + React Dashboard<br/>Portfolio · Vendor · Activity · Passport"]
  end
  subgraph BACKEND["FastAPI Backend"]
    R["Routers<br/>auth · vendors · assessments · dashboard"]
    O["Orchestrator"]
    AG["9-Agent Crew"]
    SVC["Services<br/>Scoring · Compliance · Evidence"]
  end
  subgraph REASON["Reasoning — switchable"]
    OA["OpenAI GPT-5.6"]
    GEM["Google Gemini"]
  end
  subgraph TOOLS["Tools"]
    BD["Bright Data<br/>Discover · Web Unlocker"]
    EV["PDF / Text<br/>Evidence Parser"]
  end
  DB[("SQLAlchemy DB<br/>SQLite / Postgres")]
  UI -->|"REST + SSE"| R
  R --> O --> AG
  AG --> SVC
  AG -->|"structured JSON"| OA
  AG -->|"structured JSON"| GEM
  AG --> BD
  SVC --> EV
  SVC --> DB
  R --> DB
```

### 2. Autonomous agent-crew orchestration

The Orchestrator plans and dispatches nine specialized agents — like a real TPRM
department — from one input to a board-ready decision.

```mermaid
flowchart TD
  Start(["Add vendor<br/>upload docs OR trust-center link"]) --> ORCH{{"Orchestrator<br/>plan and dispatch"}}
  ORCH --> A1["1 · Intake<br/>profile and tier"]
  A1 --> A2["2 · Discovery<br/>evidence + web research"]
  A2 --> A3["3 · Compliance<br/>control mapping"]
  A2 --> A4["4 · Questionnaire<br/>SIG Lite / CAIQ"]
  A2 --> A5["5 · AI-Vendor Risk<br/>toward ISO 42001"]
  A3 --> A6["6 · Risk Scoring<br/>inherent + residual"]
  A4 --> A6
  A5 --> A6
  A2 -. "gated / missing docs" .-> A7["7 · Negotiation<br/>access · NDA · invite"]
  A6 --> A8["8 · Monitoring<br/>continuous watch"]
  A6 --> A9["9 · Executive<br/>decision"]
  A9 --> OUT(["Approve · Conditions · Block"])
  A8 --> PASS[("Trust Passport")]
  A9 --> PASS
```

### 3. Assessment sequence (live)

One request kicks off a background crew that streams its work over SSE while
persisting explainable results.

```mermaid
sequenceDiagram
  autonumber
  participant U as User
  participant API as FastAPI
  participant O as Orchestrator
  participant C as Agent Crew
  participant L as LLM (GPT-5.6 / Gemini)
  participant DB as Database
  U->>API: POST /vendors (link or upload)
  API->>DB: create vendor + queued assessment
  API-->>U: assessment_id
  API->>O: run_assessment (background)
  loop each agent in crew
    O->>C: run(ctx, emit)
    C->>L: structured JSON reasoning
    L-->>C: profile / findings / summary
    C->>DB: append activity (SSE)
  end
  O->>DB: persist scores, controls, decision
  U->>API: GET /assessments/{id}/stream
  API-->>U: live activity + final decision
```

### 4. Explainable scoring model

Inherent exposure is mitigated by verified controls and questionnaire assurance, and
aggravated by findings and live signals — every score ships with driver-level
explanations.

```mermaid
flowchart LR
  DS["Data sensitivity"] --> INH(("Inherent<br/>Risk"))
  SA["System access"] --> INH
  VT["Vendor type<br/>SaaS / AI / MCP"] --> INH
  INH --> RES(("Residual<br/>Risk"))
  COV["Control coverage"] -->|"reduces"| RES
  QA["Questionnaire assurance"] -->|"reduces"| RES
  FND["Findings"] -->|"adds risk"| RES
  MON["Live monitoring signals"] -->|"adds risk"| RES
  RES --> DEC{"Decision"}
  DEC --> AP["Approve"]
  DEC --> AC["Approve w/ conditions"]
  DEC --> BL["Block"]
```

### 5. AI-vendor risk lens (ISO 42001)

A first-class risk dimension for AI tools, agents and MCP servers that no traditional
TPRM tool treats as core.

```mermaid
flowchart TD
  AIV["AI-Vendor Risk<br/>mapping toward ISO 42001"] --> D1["Prompt-injection exposure"]
  AIV --> D2["Tool / action permissions"]
  AIV --> D3["Data retention and training use"]
  AIV --> D4["Autonomous actions"]
  AIV --> D5["Model providers and subprocessors"]
  D1 --> FIND["AI-risk findings<br/>added to residual risk"]
  D2 --> FIND
  D3 --> FIND
  D4 --> FIND
  D5 --> FIND
```

### 6. Evidence access-state lifecycle

Argus models the trust-center reality — ~90% of docs are gated — as a first-class
state machine, routing NDAs to a human Approver.

```mermaid
stateDiagram-v2
  [*] --> public
  [*] --> requested
  requested --> nda_pending: NDA required
  nda_pending --> granted: Ap

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 64 recognized source files, 297 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
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Cursor — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (72 of 72)

```
.gitignore
backend/.env.example
backend/.gitignore
backend/app/__init__.py
backend/app/agents/__init__.py
backend/app/agents/ai_risk.py
backend/app/agents/base.py
backend/app/agents/compliance.py
backend/app/agents/discovery_agent.py
backend/app/agents/executive.py
backend/app/agents/intake.py
backend/app/agents/monitoring.py
backend/app/agents/negotiation.py
backend/app/agents/orchestrator.py
backend/app/agents/questionnaire.py
backend/app/agents/scoring_agent.py
backend/app/auth.py
backend/app/compliance/__init__.py
backend/app/compliance/frameworks.py
backend/app/compliance/questionnaires.py
backend/app/config.py
backend/app/data/__init__.py
backend/app/data/fixtures.py
backend/app/db.py
backend/app/evidence.py
backend/app/llm.py
backend/app/main.py
backend/app/models.py
backend/app/monitoring_service.py
backend/app/routers/__init__.py
backend/app/routers/activity.py
backend/app/routers/assessments.py
backend/app/routers/auth.py
backend/app/routers/dashboard.py
backend/app/routers/deps.py
backend/app/routers/orgs.py
backend/app/routers/vendors.py
backend/app/schemas.py
backend/app/scoring.py
backend/app/seed.py
backend/app/services.py
backend/app/tools/__init__.py
backend/app/tools/access.py
backend/app/tools/discovery.py
backend/app/tools/trust_center.py
backend/requirements.txt
devpost-submission.html
diagrams.html
docs/demo-video-script.md
docs/YC_APPLICATION.md
frontend/.env.local.example
frontend/.gitignore
frontend/app/activity/page.tsx
frontend/app/components/AddVendorModal.tsx
frontend/app/components/AssessmentStream.tsx
frontend/app/components/AuthScreen.tsx
frontend/app/components/CrewFlow.tsx
frontend/app/components/Providers.tsx
frontend/app/components/Sidebar.tsx
frontend/app/components/ui.tsx
frontend/app/globals.css
frontend/app/layout.tsx
frontend/app/lib/api.ts
frontend/app/lib/auth.tsx
frontend/app/page.tsx
frontend/app/passport/page.tsx
frontend/app/vendors/[id]/page.tsx
frontend/next.config.js
frontend/package.json
frontend/tsconfig.json
README.md
SUBMISSION.md
```

### Dependencies

- backend/requirements.txt: beautifulsoup4, fastapi, google-genai, httpx, openai, pydantic, pydantic-settings, pypdf, python-dotenv, python-multipart, requests, sqlalchemy, sse-starlette, uvicorn[standard]
- frontend/package.json: @types/node@20.14.0, @types/react@18.3.3, @types/react-dom@18.3.0, next@14.2.15, react@18.3.1, react-dom@18.3.1, typescript@5.5.4

### Recent commits (newest first)

- Remove Build Week/YC references; make add-vendor assessment non-blocking
- Update README: add all architecture/flow diagrams and refresh for current code
- Add demo video script for submission
- final
- Improve Argus security and autonomous crew UI
- Argus — Autonomous Third-Party Risk Management

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

### SUBMISSION.md

```markdown
# Devpost Submission Checklist — OpenAI Build Week

**Project:** Argus — Autonomous Third-Party Risk Management
**Track:** Work & Productivity (workflow automation / back-office operations)

## Required items

- [ ] **Text description** — features & functionality (reuse the README intro + "What makes it different").
- [ ] **Demo video (< 3 min, YouTube, public)** — record the [Demo script](./README.md#demo-script--3-minutes).
  - Must have **audio** covering what you built and **how you used Codex + GPT-5.6**.
  - No third-party trademarks / copyrighted music without permission.
- [ ] **Public code repo** (or share privately with `testing@devpost.com` and `build-week-event@openai.com`).
- [ ] **README** documenting Codex collaboration + key decisions — done (see README "How we built this").
- [ ] **/feedback Codex Session ID** — capture the session where core functionality was built and paste it in the submission form.
- [ ] **Testable instance** so judges don't rebuild:
  - Local: follow the README Quickstart (runs offline, no keys needed).
  - Hosted (recommended): deploy backend (Railway/Render — `backend/Procfile`-style `uvicorn app.main:app`) and frontend (Vercel), set `NEXT_PUBLIC_API_URL`.
- [ ] **Category selection:** Work & Productivity.
- [ ] **Prior vs new work:** repo builds on MIT `Studio1HQ/tprm-agent`; the README + in-file notes distinguish vendored code from new hackathon work. Keep dated commit history as evidence.

## Judging criteria mapping (equally weighted)

- **Technological Implementation** — 9-agent orchestration, tool use, structured
  outputs, SSE activity feed, GPT-5.6 reasoning built with Codex.
- **Design** — coherent, runnable product: one input → live crew → polished
  portfolio + per-vendor dashboards (not a POC).
- **Potential Impact** — mid-market/AI-first companies with no analyst; concrete
  before/after (weeks of spreadsheets → minutes).
- **Quality of the Idea** — autonomous *department* + first-class AI-vendor risk +
  Trust Passport network effect.

## Before recording

1. `rm backend/argus.db` for a clean portfolio, then start both servers.
2. Have the three examples ready: Cursor (link), Stripe (upload), Acme MCP (AI risk).
3. Optional: set `OPENAI_API_KEY` + `ARGUS_LLM_MODEL=gpt-5.6` to show live reasoning.

```

### docs/YC_APPLICATION.md

```markdown
# Argus — YC Application (draft answers)

Reusable draft responses for the standard YC application. Tighten to the voice of
the founder(s) before submitting.

## Company

**What does your company do? (one line)**
Argus is an autonomous Third-Party Risk Management department — a crew of AI agents
that assesses and continuously monitors a company's vendors (including AI tools and
agents), so teams without a security analyst get enterprise-grade vendor due diligence
in minutes.

**Describe what your company does in 50 characters or less.**
Autonomous vendor risk management, AI-native.

## Problem

Every company now depends on dozens of SaaS and AI vendors. Before adopting each one,
someone has to vet its security posture — collect SOC 2 / ISO reports, read the DPA,
check for breaches, map controls to frameworks, fill a 100–300 question security
questionnaire, and keep watching for changes. At mid-market and AI-first companies
there is **no GRC analyst** to do this, so it lands on an engineer or ops lead doing
it in spreadsheets and email. Reviews take weeks, block deals, and go stale the day
they finish. AI vendors (tools, agents, MCP servers) make it worse: they're the
fastest-growing, riskiest vendor class and traditional TPRM has no playbook for them.

## Insight / Why now

Vendor risk is a **context + network** problem, and it's newly automatable:
- Agents can now do the whole job end-to-end (research, read documents, map controls,
  score, monitor), not just draft answers.
- AI/MCP vendors are exploding and need a new risk lens (prompt injection, tool
  permissions, data retention, autonomous actions) — an emerging category with no incumbent.
- The companies most exposed (mid-market, AI-first) are exactly the ones incumbents
  (enterprise-priced, analyst-augmenting) ignore.

## Product

Add a vendor by uploading its compliance pack or pasting its trust-center link. A crew
of nine agents autonomously profiles and tiers the vendor, ingests evidence (handling
the reality that ~90% of trust-center docs are gated behind NDA), maps it to SOC 2 /
ISO 27001 / GDPR / HIPAA / PCI / NIST / ISO 42001, auto-completes SIG/CAIQ with cited
evidence, computes an explainable residual-risk score and decision, and arms continuous
monitoring. Everything renders in a portfolio + per-vendor dashboard.

## Moat / defensibility

- **Trust Passport network effect:** shared, evidence-cited vendor profiles that get
  richer with every assessment across every customer — siloed enterprise deployments
  can't replicate this.
- **AI-vendor risk corpus:** a growing structured library of AI/agent/MCP risk profiles
  becomes the reference layer as this vendor class scales.
- **System of record:** becomes the vendor-risk register + monitoring backbone for
  companies that never had one.

## Market

TPRM / vendor security is large and enterprise-locked (SecureOS, Vanta, UpGuard,
Diligence; procurement-side: Zip). The underserved mid-market + the entirely new
AI-vendor layer is the
[truncated — 864 more characters]
```

### backend/requirements.txt

```
fastapi
uvicorn[standard]
python-dotenv
pydantic
pydantic-settings
sqlalchemy
requests
httpx
beautifulsoup4
openai
google-genai
pypdf
python-multipart
sse-starlette

```

### frontend/package.json

```
{
  "name": "argus-frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev -p 3000",
    "build": "next build",
    "start": "next start -p 3000",
    "lint": "next lint"
  },
  "dependencies": {
    "next": "14.2.15",
    "react": "18.3.1",
    "react-dom": "18.3.1"
  },
  "devDependencies": {
    "@types/node": "20.14.0",
    "@types/react": "18.3.3",
    "@types/react-dom": "18.3.0",
    "typescript": "5.5.4"
  }
}

```

### frontend/app/layout.tsx

```typescript
import "./globals.css";
import type { Metadata } from "next";
import { Providers } from "./components/Providers";

export const metadata: Metadata = {
  title: "Argus - Autonomous TPRM",
  description: "An AI crew that assesses and continuously monitors your vendors.",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

```

### backend/app/main.py

```python
"""Argus API - Autonomous Third-Party Risk Management.

Multi-tenant FastAPI backend. Runs fully offline with deterministic heuristics;
set OPENAI_API_KEY (GPT-5.6) and Bright Data keys to enable live reasoning and
web research.
"""

from __future__ import annotations

from contextlib import asynccontextmanager, suppress
import asyncio

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

from .config import settings
from .db import init_db
from .seed import seed
from .monitoring_service import run_due_reviews
from .routers import orgs, vendors, assessments, dashboard, auth, activity


@asynccontextmanager
async def lifespan(app: FastAPI):
    init_db()
    seed()
    async def scheduled_reviews():
        while True:
            await asyncio.sleep(60 * 60)
            await asyncio.to_thread(run_due_reviews)
    scheduler = asyncio.create_task(scheduled_reviews())
    yield
    scheduler.cancel()
    with suppress(asyncio.CancelledError):
        await scheduler


app = FastAPI(
    title="Argus TPRM API",
    description="Autonomous Third-Party Risk Management - an AI crew that assesses and monitors vendors.",
    version="0.1.0",
    lifespan=lifespan,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.CORS_ORIGINS,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(auth.router)
app.include_router(orgs.router)
app.include_router(vendors.router)
app.include_router(assessments.router)
app.include_router(dashboard.router)
app.include_router(activity.router)


@app.get("/health")
def health():
    return {
        "status": "healthy",
        "llm_enabled": settings.llm_enabled,
        "llm_provider": settings.LLM_PROVIDER,
        "llm_model": settings.active_llm_model if settings.llm_enabled else None,
        "bright_data_enabled": settings.bright_data_enabled,
    }


@app.get("/")
def root():
    return {"service": "Argus TPRM", "docs": "/docs", "health": "/health"}

```

### frontend/app/page.tsx

```typescript
"use client";

import { Fragment, useEffect, useState, useCallback } from "react";
import { useRouter } from "next/navigation";
import { api } from "./lib/api";
import { RiskBadge, TierBadge, DecisionText, TypeBadge } from "./components/ui";
import { AddVendorModal } from "./components/AddVendorModal";

export default function Portfolio() {
  const router = useRouter();
  const [data, setData] = useState<any>(null);
  const [showAdd, setShowAdd] = useState(false);
  const [loading, setLoading] = useState(true);
  const [query, setQuery] = useState("");
  const [riskFilter, setRiskFilter] = useState("all");
  const [exporting, setExporting] = useState(false);

  const load = useCallback(async () => {
    try {
      const p = await api.portfolio();
      setData(p);
    } catch (e) {
      setData({ error: String(e) });
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    load();
  }, [load]);

  const [deletingId, setDeletingId] = useState<string | null>(null);

  const remove = useCallback(
    async (e: React.MouseEvent, v: any) => {
      e.stopPropagation();
      if (!confirm(`Delete ${v.name}? This permanently removes the vendor and all its assessments, evidence and findings.`)) {
        return;
      }
      setDeletingId(v.id);
      try {
        await api.deleteVendor(v.id);
        await load();
      } catch (err) {
        alert("Delete failed: " + err);
      } finally {
        setDeletingId(null);
      }
    },
    [load]
  );

  const counts = data?.counts;
  const vendors = data?.vendors || [];
  const visibleVendors = vendors.filter((vendor: any) => {
    const needle = query.trim().toLowerCase();
    const matchesSearch = !needle || [vendor.name, vendor.category, vendor.vendor_type]
      .filter(Boolean)
      .some((value: string) => value.toLowerCase().includes(needle));
    const matchesRisk = riskFilter === "all" || vendor.band === riskFilter;
    return matchesSearch && matchesRisk;
  });
  const analytics = data?.analytics;

  async function exportPortfolio() {
    setExporting(true);
    try {
      await api.downloadPortfolioExport();
    } catch (error) {
      alert("Export failed: " + error);
    } finally {
      setExporting(false);
    }
  }

  return (
    <>
      <div className="topbar">
        <div>
          <h2>Vendor Risk Portfolio</h2>
          <div className="sub">{data?.org?.name} · {data?.org?.required_frameworks?.join(" · ")}</div>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button className="btn" onClick={exportPortfolio} disabled={exporting}>{exporting ? "Exporting…" : "Export CSV"}</button>
          <button className="btn btn-primary" onClick={() => setShowAdd(true)}>+ Add vendor</button>
        </div>
      </div>

      <div className="content">
        {loading && <div className="loading">Loading portfolio…</div>}
        {data?.error && (
          <div className="card">
            <b>Could not reach the Argus API.</b>
            <p className="hint">Start the backend on {api.base} (see the project README).</p>
          </div>
        )}

        {data && !data.error && (
          <>
            <div className="cards">
              <div className="card stat">
                <div className="label">Vendors tracked</div>
                <div className="value">{counts.total}</div>
                <div className="foot">{counts.monitoring} under continuous monitoring</div>
              </div>
              <div className="card stat">
                <div className="label">Critical / High</div>
                <div className="value" style={{ color: "var(--red)" }}>
                  {(counts.by_band.critical || 0) + (counts.by_band.high || 0)}
                </div>
                <div className="foot">{counts.by_band.critical || 0} critical, {counts.by_band.high || 0} high</div>
              </div>
              <div className="card stat">
                <div className="label">Tier 1 (critical)</div>
                <div className="value">{counts.tier1}</div>
                <div className="foot">Regulated data or production access</div>
              </div>
              <div className="card stat">
                <div className="label">Expiring evidence</div>
                <div className="value" style={{ color: "var(--amber)" }}>{data.expiring_evidence.length}</div>
                <div className="foot">Reports expired or within 90 days</div>
              </div>
            </div>

            <div className="portfolio-insights">
              <div className="card insight-card">
                <div className="section-title" style={{ marginTop: 0 }}>Risk heatmap</div>
                <div className="heatmap" aria-label="Risk tier by risk-band heatmap">
                  <div />{["Critical", "High", "Medium", "Low"].map((band) => <div className="heat-label" key={band}>{band}</div>)}
                  {[1, 2, 3, 4].map((tier) => <Fragment key={tier}>
                    <div className="heat-label" key={`tier-${tier}`}>T{tier}</div>
                    {["critical", "high", "medium", "low"].map((band) => {
                      const count = analytics?.heatmap?.find((cell: any) => cell.tier === tier && cell.band === band)?.count || 0;
                      return <div key={`${tier}-${band}`} className={`heat-cell ${band}`} style={{ opacity: count ? Math.min(.35 + count * .16, 1) : .12 }} title={`Tier ${tier}, ${band}: ${count}`}>{count || "—"}</div>;
                    })}
                  </Fragment>)}
                </div>
              </div>
              <div className="card insight-card">
                <div className="section-title" style={{ marginTop: 0 }}>Concentration risk</div>
                {analytics?.concentration?.length ? analytics.concentration.slice(0, 5).map((item: any) => (
                  <div className="driver" key={item.category}><span>{item.category}</span><b>{item.count} vendor{item.count === 1 ? "" : "s"}</b></div>
               
[truncated — 5495 more characters]
```

### frontend/app/passport/page.tsx

```typescript
"use client";

import { useEffect, useState } from "react";
import { api } from "../lib/api";
import { TypeBadge } from "../components/ui";

export default function PassportNetwork() {
  const [data, setData] = useState<any>(null);

  useEffect(() => {
    api.passport().then(setData).catch(() => setData({ error: true }));
  }, []);

  return (
    <>
      <div className="topbar">
        <div>
          <h2>Trust Passport Network</h2>
          <div className="sub">Shared public evidence metadata — tenant-uploaded reports and risk scores stay private</div>
        </div>
      </div>
      <div className="content">
        {!data && <div className="loading">Loading network…</div>}
        {data && !data.error && (
          <>
            <div className="cards">
              <div className="card stat">
                <div className="label">Vendors in network</div>
                <div className="value">{data.total}</div>
                <div className="foot">Seeded common vendors + assessed</div>
              </div>
              <div className="card stat">
                <div className="label">Assessed passports</div>
                <div className="value" style={{ color: "var(--accent)" }}>{data.assessed}</div>
                <div className="foot">Reused on the next org's assessment</div>
              </div>
              <div className="card stat">
                <div className="label">Vendor-claimed</div>
                <div className="value">{data.claimed}</div>
                <div className="foot">Two-sided evidence maintenance</div>
              </div>
              <div className="card stat">
                <div className="label">Network effect</div>
                <div className="value" style={{ color: "var(--emerald)" }}>↑</div>
                <div className="foot">Each assessment enriches the graph</div>
              </div>
            </div>

            <div className="section-title">Vendors</div>
            <div className="card" style={{ padding: 0 }}>
              <table className="table">
                <thead>
                  <tr>
                    <th>Vendor</th>
                    <th>Category</th>
                    <th>Type</th>
                    <th>Assessments</th>
                    <th>Last residual</th>
                  </tr>
                </thead>
                <tbody>
                  {data.passports.map((p: any) => (
                    <tr key={p.vendor_key}>
                      <td>
                        <div className="name">{p.name}</div>
                        <div className="muted mono">{p.vendor_key}</div>
                      </td>
                      <td className="muted">{p.category}</td>
                      <td><TypeBadge type={p.vendor_type} /></td>
                      <td className="mono">{p.assessments_count}</td>
                      <td className="mono">{p.last_residual ?? "—"}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </>
        )}
      </div>
    </>
  );
}

```

### frontend/app/activity/page.tsx

```typescript
"use client";

import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { api } from "../lib/api";

export default function AgentActivity() {
  const router = useRouter();
  const [data, setData] = useState<any>({ running: [], activity: [], audit: [] });
  const timer = useRef<any>(null);

  useEffect(() => {
    let stop = false;
    async function tick() {
      try {
        const [activityData, auditData] = await Promise.all([api.activityRecent(80), api.audit(50)]);
        if (!stop) setData({ ...activityData, audit: auditData.activity || [] });
      } catch {}
      if (!stop) timer.current = setTimeout(tick, 1500);
    }
    tick();
    return () => {
      stop = true;
      if (timer.current) clearTimeout(timer.current);
    };
  }, []);

  const running = data.running || [];
  const activity = data.activity || [];
  const audit = data.audit || [];

  return (
    <>
      <div className="topbar">
        <div>
          <h2>Agent Activity</h2>
          <div className="sub">Live view of the crew working across every assessment</div>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {running.length > 0 ? <span className="live-dot" /> : null}
          <span style={{ color: "var(--text-dim)", fontSize: 13 }}>
            {running.length} assessment{running.length === 1 ? "" : "s"} running
          </span>
        </div>
      </div>

      <div className="content">
        {running.length > 0 && (
          <>
            <div className="section-title" style={{ marginTop: 0 }}>In progress</div>
            <div className="cards" style={{ gridTemplateColumns: "repeat(3, 1fr)" }}>
              {running.map((r: any) => (
                <div className="card" key={r.assessment_id} onClick={() => router.push(`/vendors/${r.vendor_id}`)} style={{ cursor: "pointer" }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <span className="spinner" />
                    <b>{r.vendor}</b>
                  </div>
                  <div className="foot" style={{ marginTop: 6, color: "var(--text-faint)", fontSize: 12 }}>
                    {r.status} · {r.trigger}
                  </div>
                </div>
              ))}
            </div>
          </>
        )}

        <div className="section-title">Recent agent actions</div>
        <div className="card">
          {activity.length === 0 ? (
            <div className="empty">No agent activity yet. Add a vendor to see the crew work.</div>
          ) : (
            <div className="feed">
              {activity.map((a: any, i: number) => (
                <div className={`feed-item ${a.status}`} key={i}>
                  <div className="agent">{a.agent}</div>
                  <div className="msg">
                    <span className="badge neutral" style={{ marginRight: 8 }}>{a.vendor}</span>
                    {a.message}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>

        <div className="section-title">Workspace audit trail</div>
        <div className="card">
          {audit.length === 0 ? (
            <div className="empty">No workspace actions recorded yet.</div>
          ) : (
            <div className="audit-list">
              {audit.map((entry: any) => (
                <div className="audit-row" key={entry.id}>
                  <div><b>{entry.action.replaceAll(".", " · ")}</b><span>{entry.target || "Workspace"}{entry.detail ? ` · ${entry.detail}` : ""}</span></div>
                  <div><span>{entry.actor}</span><time>{new Date(entry.at).toLocaleString()}</time></div>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>
    </>
  );
}

```

### frontend/app/vendors/[id]/page.tsx

```typescript
"use client";

import { useEffect, useState, useCallback } from "react";
import { useParams, useRouter } from "next/navigation";
import { api } from "../../lib/api";
import { RiskBadge, TierBadge, DecisionText, TypeBadge, Gauge, Bar, TrendChart } from "../../components/ui";
import { AssessmentStream } from "../../components/AssessmentStream";

function overallCoverage(coverage: Record<string, any> = {}): number {
  let scored = 0;
  let total = 0;
  Object.values(coverage).forEach((c: any) => {
    const t = c.total || 0;
    total += t;
    scored += ((c.coverage_pct || 0) / 100) * t;
  });
  return total ? Math.round((scored / total) * 100) : 0;
}

function coverageColor(pct: number): string {
  return pct >= 70 ? "#34d399" : pct >= 40 ? "#fbbf24" : "#f87171";
}

const TABS = ["Overview", "Compliance", "Evidence", "Questionnaire", "AI Risk", "Monitoring", "Actions"];

export default function VendorDetail() {
  const { id } = useParams<{ id: string }>();
  const router = useRouter();
  const [v, setV] = useState<any>(null);
  const [tab, setTab] = useState("Overview");
  const [reassessing, setReassessing] = useState<string | null>(null);
  const [deleting, setDeleting] = useState(false);

  const load = useCallback(async () => {
    const d = await api.vendor(id);
    setV(d);
  }, [id]);

  useEffect(() => {
    load();
  }, [load]);

  // Continuous monitoring is live: refresh the feed + risk trend on an interval
  // while the Monitoring tab is open.
  useEffect(() => {
    if (tab !== "Monitoring" || reassessing) return;
    const iv = setInterval(() => {
      load();
    }, 5000);
    return () => clearInterval(iv);
  }, [tab, reassessing, load]);

  if (!v) return <div className="loading">Loading vendor…</div>;

  const score = v.score;
  const isAI = v.vendor_type === "ai_agent" || v.vendor_type === "mcp";
  const cov = overallCoverage(v.coverage);

  async function reassess() {
    const res = await api.reassess(id);
    setReassessing(res.assessment_id);
  }

  async function remove() {
    if (!confirm(`Delete ${v.name}? This permanently removes the vendor and all its assessments, evidence and findings.`)) {
      return;
    }
    setDeleting(true);
    try {
      await api.deleteVendor(id);
      router.push("/");
    } catch (err) {
      alert("Delete failed: " + err);
      setDeleting(false);
    }
  }

  return (
    <>
      <div className="topbar">
        <div>
          <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
            <button className="btn btn-ghost" onClick={() => router.push("/")}>← Portfolio</button>
          </div>
        </div>
        <div style={{ display: "flex", gap: 10 }}>
          <button className="btn" onClick={reassess}>↻ Re-assess</button>
          <button className="btn btn-danger" onClick={remove} disabled={deleting}>
            {deleting ? "Deleting…" : "Delete vendor"}
          </button>
        </div>
      </div>

      <div className="content">
        <div className="vendor-head">
          <div>
            <h2>{v.name}</h2>
            <div className="meta">
              <TypeBadge type={v.vendor_type} />
              <TierBadge tier={v.tier} />
              <span>{v.category}</span>
              {v.trust_center_url && <span className="mono">{v.trust_center_url}</span>}
            </div>
          </div>
          <div style={{ textAlign: "right", minWidth: 240 }}>
            <div className="muted" style={{ fontSize: 12, color: "var(--text-faint)" }}>Control coverage</div>
            <div style={{ fontSize: 30, fontWeight: 800, color: coverageColor(cov), lineHeight: 1.1 }}>{cov}%</div>
            <div style={{ marginTop: 6 }}>
              <Bar pct={cov} color={coverageColor(cov)} />
            </div>
            <div className="muted" style={{ fontSize: 12, color: "var(--text-faint)", marginTop: 8 }}>
              Recommendation: <DecisionText decision={v.assessment?.decision} />
            </div>
          </div>
        </div>

        {reassessing && (
          <div className="card" style={{ marginBottom: 16 }}>
            <AssessmentStream
              assessmentId={reassessing}
              onComplete={() => {
                setTimeout(() => {
                  setReassessing(null);
                  load();
                }, 800);
              }}
            />
          </div>
        )}

        <div className="tabs">
          {TABS.map((t) => {
            if (t === "AI Risk" && !isAI) return null;
            return (
              <button key={t} className={tab === t ? "active" : ""} onClick={() => setTab(t)}>
                {t}
              </button>
            );
          })}
        </div>

        {tab === "Overview" && <Overview v={v} score={score} />}
        {tab === "Compliance" && <Compliance v={v} />}
        {tab === "Evidence" && <Evidence v={v} onUploaded={load} />}
        {tab === "Questionnaire" && <Questionnaire v={v} />}
        {tab === "AI Risk" && <AIRisk v={v} />}
        {tab === "Monitoring" && <Monitoring v={v} />}
        {tab === "Actions" && <Actions v={v} onUpdated={load} />}
      </div>
    </>
  );
}

function Overview({ v, score }: any) {
  return (
    <div className="two-col">
      <div className="card">
        <div className="section-title" style={{ marginTop: 0 }}>Risk score</div>
        {score ? (
          <>
            <div className="gauge-wrap">
              <Gauge score={score.residual} label="Residual" />
              <div style={{ flex: 1 }}>
                <div className="kv"><span className="k">Inherent risk</span><b>{score.inherent}/100</b></div>
                <div className="kv"><span className="k">Residual risk</span><b><RiskBadge band={score.band} /> {score.residual}/100</b></div>
                <div className="kv"><span className="k">Tier</span><TierBadge tier={v.tier} /></div>
                <div className="kv"><span className="k">Data sensitivity</span><span>{v.data_sensitivity}</span></div>
             
[truncated — 15237 more characters]
```

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