# Investibook Design Document

> **Version**: 1.3  
> **Last Updated**: 2026-03-03  
> **Status**: Design Draft

---

## 1. Product Overview

Investibook is an **AI Research Workbench** for TiDB Cloud operations, featuring a NotebookLM-style three-column layout that helps users investigate alert events and perform root cause analysis.

Core philosophy: **Organize work around a single alert event within a Workspace — aggregate data sources (Sources), converse with AI Agents (Chat), and produce deliverable analysis artifacts (Artifacts)**.

```
┌──────────────┬──────────────────────┬──────────────────────┐
│              │                      │                      │
│   Sources    │        Chat          │      Studio          │
│  (Left col)  │    (Center col)      │   (Right col)        │
│              │                      │                      │
│ • Alert      │  ACP Agent Chat      │ • Insight Report     │
│ • MCP Conn.  │  SSE real-time       │ • Anomaly Report     │
│ • Cluster    │  streaming output    │ • Notes              │
│   Meta       │  Tool call viz       │ • Charts             │
│ • User Meta  │                      │                      │
│              │   ┌──────────────┐   │                      │
│ ───────────  │   │ File Viewer  │   │                      │
│  Files Tab   │   └──────────────┘   │                      │
│ (virtual     │                      │                      │
│  file tree)  │                      │                      │
│              │                      │                      │
└──────────────┴──────────────────────┴──────────────────────┘
```

---

## 2. Core Concepts

### 2.1 Workspace

A Workspace is the top-level container for user work, representing a complete investigation session.

| Property | Description |
|----------|-------------|
| `id` | Unique identifier `ws-{timestamp}-{random}` |
| `name` | User-editable title |
| `description` | Optional description |
| `createdAt` / `updatedAt` | Timestamps |
| `isShared` | Whether the workspace is publicly shared (read-only for non-owners) |
| `isOwner` | Whether the currently authenticated user owns this workspace (computed server-side) |
| `isExample` | Marks the singleton example workspace pre-loaded with sample data |

**Persistence**: All Workspace metadata and per-Workspace data are stored **server-side in SQLite** (`data/db/investibook.db`) and accessed via REST API. Workspaces are scoped to the authenticated user (`user_id`). The browser only holds the Bearer auth token and user email in `localStorage` (keys `investibook_token` / `investibook_email`).

**Code location**: `frontend/lib/workspaces.ts` (CRUD via REST) / `frontend/app/page.tsx` (Workspace list page) / `backend/investibook/api/workspaces.py` + `backend/investibook/db/queries.py` (`_db_*` helpers + `/api/workspaces` routes)

### 2.2 Source (Data Source)

A Source represents a data source connected to a Workspace. All information entering the system from outside is represented as a Source in the left column.

```typescript
type SourceType = 'alert' | 'git_repo' | 'mcp_connector' | 'cluster_meta' | 'user_meta' | 'knowledge' | 'skill';
```

| Source Type | Icon | Description | How Added |
|-------------|------|-------------|-----------|
| `alert` | 🚨 | Jira Alert ID — drives the entire investigation | Added manually by user |
| `mcp_connector` | 🔌 | MCP protocol connectors (Clinic / Ops Portal / O11Y / Jira) | System default (display-only) |
| `cluster_meta` | 🏗️ | Cluster topology and metadata | Auto-generated by backend (triggered by Alert) |
| `user_meta` | 👤 | User context information | System default (readonly, display-only) |
| `knowledge` | 📚 | Domain knowledge base (runbooks, wikis, docs) | User-added (Coming Soon) |
| `skill` | ⚡ | Reusable analysis skill / prompt template | User-added (Coming Soon) |
| `git_repo` | 📦 | Git repository | Coming Soon |

> **Default system sources** injected into every new workspace: `user_meta` (readonly) + 5 MCP connectors: **Grafana Screenshot**, **Clinic**, **Ops Portal**, **O11Y**, **Jira** (all display-only, actual MCP wiring handled by the ACP Agent subprocess).

**Code location**: `frontend/lib/workspace.tsx` (`Source` interface, `SOURCE_TYPE_META`, `createDefaultSources()`)

### 2.3 Artifact

An Artifact is a visualized result produced by AI analysis or user action.

```typescript
type ArtifactType = 'report' | 'chart' | 'summary' | 'timeline' | 'note';
```

| Artifact Type | Description | Render Component |
|---------------|-------------|-----------------|
| `report` | Guardian Insight Report | `InsightReport/index.tsx` |
| `chart` | Single metric chart | `ChartCard/index.tsx` |
| `summary` | Text summary | `InsightReport/SummaryCard.tsx` |
| `timeline` | Event timeline | — |
| `note` | User notes | `StudioPanel/NoteModal.tsx` |

**Code location**: `frontend/lib/workspace.tsx` (`Artifact` interface) / `frontend/components/StudioPanel/index.tsx` (list and detail rendering)

---

## 3. Sources and the Virtual File System Mapping

Investibook maps remote diagnostic data to a **virtual file system**, enabling the ACP Agent to read all context as if operating on local files. This is the core mechanism connecting Sources (left column) with Agent chat capabilities (center column).

### 3.1 Mapping Structure

```
data/                                ← ACP Agent's cwd (working directory)
├── .opencode/
│   └── opencode.json               ← ACP/MCP server configuration
├── cluster_meta.json                ← [cluster_meta Source] cluster topology
├── diagnostic_package.json          ← [Jira Issue Source] diagnostic package from S3
├── insight.json                     ← [Jira Issue Source] Insight Report from S3
├── resource_worker_insight.json     ← [mcp_connector Source] Resource Worker analysis
└── toolset/
    └── resource-worker-mock-mcp/    ← MCP Tool Server implementation
        ├── data/mock/               ← Mock response data
        │   ├── analyze_alert_result.json
        │   ├── chat_question1_result.json
        │   ├── chat_question2_result.json
        │   └── chat_question3_result.json
        └── service/mcp/
            └── worker_server_mock.py  ← MCP Server (analyze_alert and other tools)
```

### 3.2 Source → File Detailed Mapping

| Source Type | Mapped File | Data Content | Origin |
|-------------|-------------|-------------|--------|
| `alert` | `insight.json` | Guardian Insight Report (root cause analysis, evidence chain, metric status) | S3 historical report |
| `alert` | `diagnostic_package.json` | Diagnostic package (metric categories, anomaly detection, slow queries, events) | S3 diagnostic file |
| `cluster_meta` | `cluster_meta.json` | Cluster ID, version, topology, cloud provider, region | S3 / Ops Portal API |
| `mcp_connector` (Resource Worker) | `resource_worker_insight.json` | Alert type, cluster ID, anomaly detection results | MCP Tool call results |
| `user_meta` | _(in-memory, not persisted to file)_ | User preferences and context | Frontend injection |

### 3.3 Frontend File Tree

The left column Sources panel has two tabs: **Sources** and **Files**. The Files tab **dynamically fetches** the real file tree from `GET /api/workspace/{id}/files` on mount and renders it via the `FileTree` component. Each item in the tree shows an emoji icon based on file extension (📄 JSON, 🖼️ images, 📝 Markdown, ⚙️ YAML, 🔒 lock files) plus the file size.

Clicking a file opens it in a Monaco Editor (read-only) in the lower half of the Chat column, allowing users to directly inspect the raw data the Agent is reading. Directories are collapsible; the top level is expanded by default.

**Code location**: `frontend/components/SourcesPanel/index.tsx` (fetch + state) / `frontend/components/SourcesPanel/FileTree.tsx` (recursive tree renderer) / `frontend/components/StudioPanel/FileViewer.tsx` (Monaco viewer)

**File tree data flow**:
```
SourcesPanel mount
  └─ GET /api/workspace/{id}/files
       → WorkspaceFile[] (name, path, type, children?, size?)
       → passed to <FileTree> for rendering

User clicks a file
  └─ setOpenFilePath(path)    ← WorkspaceContext
       → ChatPanel renders <FileViewer path={path}>
            GET /api/workspace/{id}/file-content?path=...
            → Monaco Editor (read-only)
```

---

## 4. ACP Chat — Agent Communication Protocol

### 4.1 Architecture

```
Browser                          Backend (FastAPI)                   ACP Agent (subprocess)
  │                                    │                                    │
  │  GET /api/chat/stream?message=...  │                                    │
  │ ──────────────────────────────────>│                                    │
  │                                    │  spawn / reuse subprocess          │
  │                                    │ ──────────────────────────────────>│
  │                                    │  ACP JSON-RPC over stdio           │
  │                                    │  conn.prompt(session_id, message)  │
  │                                    │ ──────────────────────────────────>│
  │                                    │                                    │
  │                                    │  session_update callbacks:         │
  │                                    │  AgentMessageChunk / ThoughtChunk  │
  │                                    │  ToolCallStart / ToolCallProgress  │
  │                                    │<──────────────────────────────────│
  │                                    │                                    │
  │  SSE: data: {"type":"text",...}    │                                    │
  │<──────────────────────────────────│                                    │
  │  SSE: data: {"type":"tool",...}    │                                    │
  │<──────────────────────────────────│                                    │
  │  SSE: data: {"type":"complete"}    │                                    │
  │<──────────────────────────────────│                                    │
```

### 4.2 SSE Event Types

| Event `type` | Meaning | Key Fields |
|--------------|---------|-----------|
| `session_created` | Session established | `session_id` |
| `text` | Agent text output (streaming) | `text` |
| `thought` | Agent reasoning process | `text` |
| `tool` | Tool call (start / in-progress / complete) | `id`, `title`, `status`, `input`, `output` |
| `complete` | Turn ended | `session_id` |
| `error` | Error occurred | `error` |

**Frontend type definitions**: `frontend/components/ChatPanel/types.ts` (`SSEEvent` union type)  
**Backend event generation**: `backend/investibook/core/acp.py` (`WebACPClient.session_update()`)

### 4.3 ACP Session Management

```
                      ┌────────────────────────────────┐
                      │        ACPSession               │
                      │  • frontend_id (workspace scope) │
                      │  • agent_type (opencode/claude) │
                      │  • acp_session_id               │
                      │  • conn (ClientSideConnection)  │
                      │  • proc (subprocess)            │
                      │  • client (WebACPClient)        │
                      │  • _initialized                 │
                      └────────────────────────────────┘
                                    │
                      ┌─────────────┴─────────────┐
                      │  sessions: Dict[str, ACPSession] │
                      │  (global session pool)          │
                      └──────────────────────────────────┘
```

- Each Workspace maps to one `session_id`; an ACP subprocess is created on first interaction
- The Agent's `cwd` is set to the `data/` directory, enabling direct access to all mapped files
- System Prompt is sent during Session initialization, providing TiDB Guardian Insight domain knowledge
- Agent switching: select `opencode` or `claude` via the `ACP_AGENT_TYPE` environment variable

### 4.4 WebACPClient Capabilities

The ACP Client implements four core methods needed by the Agent runtime:

| Method | Function |
|--------|----------|
| `request_permission` | Auto-approves Agent permission requests (allow_once / allow_always) |
| `read_text_file` | Reads files from the local filesystem (Agent reads diagnostic files under `data/`) |
| `write_text_file` | Writes files (Agent can generate intermediate analysis results) |
| `session_update` | Receives Agent events and converts them to flat SSE pushes for the frontend |

---

## 5. Jira Issue Source Addition Flow (Core Interaction)

This is the most critical user flow in the system — adding a Jira Issue Source automatically fetches related data and generates reports.

### 5.1 Target Interaction Flow

```
User Action                         System Behavior                     UI Changes
────────────                        ───────────────                     ──────────
1. Click "+ Add Sources"            Opens AddSourceModal                Modal appears
   Enter Issue ID (e.g. O11Y-1956041)
   Click "Add"

2. Issue Source added successfully  Source appears in left column list  🚨 O11Y-1956041
                                    Issue Source card starts loading    ⏳ Spinner animation

3.                                  Backend async processing:           Issue card continues spinning
                                    ├─ Query S3 via Issue ID
                                    ├─ Download insight.json
                                    ├─ Download diagnostic_package.json
                                    ├─ Download cluster_meta.json
                                    └─ Write files to data/ directory

4.                                  Backend processing complete,        Spinner disappears ✓
                                    notify frontend:
                                    ├─ cluster_meta Source auto-added   🏗️ Cluster Meta in left col
                                    ├─ Insight Report Artifact added    📊 Report in right col list
                                    └─ File tree updated                Files Tab shows new files

5. User clicks Report Artifact      Studio enters detail view           Right col renders full Report
                                                                        (metric cards, evidence chain,
                                                                         event timeline)

6. User asks question in Chat       Agent reads files from data/        Streaming analysis output
                                    Calls MCP Tool (analyze_alert)      ToolCall status visualization
```

### 5.2 Sequence Diagram

```
  User         Frontend           Backend           S3/External
   │              │                   │                  │
   │  Add Issue   │                   │                  │
   │─────────────>│                   │                  │
   │              │                   │                  │
   │              │  POST /api/sources/prepare           │
   │              │  {issueId, workspaceId}               │
   │              │──────────────────>│                  │
   │              │                   │                  │
   │              │  202 Accepted     │                  │
   │              │  (taskId)         │                  │
   │              │<──────────────────│                  │
   │              │                   │                  │
   │  Alert card  │                   │  GET insight.json │
   │  shows       │                   │─────────────────>│
   │  spinner     │                   │  GET diagnostic_  │
   │              │                   │  package.json     │
   │              │                   │─────────────────>│
   │              │                   │  GET cluster_     │
   │              │                   │  meta.json        │
   │              │                   │─────────────────>│
   │              │                   │<─────────────────│
   │              │                   │                  │
   │              │                   │  Write to data/  │
   │              │                   │  Build file map  │
   │              │                   │                  │
   │              │  SSE / Polling    │                  │
   │              │  status update    │                  │
   │              │<──────────────────│                  │
   │              │                   │                  │
   │  spinner     │                   │                  │
   │  disappears  │                   │                  │
   │  Report      │                   │                  │
   │  in right col│                   │                  │
   │              │                   │                  │
```

### 5.3 Frontend Alert Loading State Design

The Jira Issue Source Card should support the following states:

| State | UI Appearance | Trigger |
|-------|---------------|---------|
| `loading` | Spinner + "Preparing data…" | Just added, backend is fetching |
| `ready` | Green checkmark ✓ + normal display | Backend data is ready |
| `error` | Red exclamation ⚠ + error message | S3 download failure or timeout |

```typescript
// Source interface extension (proposed)
interface Source {
  // ... existing fields
  status?: 'loading' | 'ready' | 'error';
  statusMessage?: string;
}
```

### 5.4 Backend Source Preparation API Design (Planned)

```
POST /api/sources/prepare
Request:
{
  "alert_id": "O11Y-1956041",
  "workspace_id": "ws-1234567890-abc"
}

Response: 202 Accepted
{
  "task_id": "task-xxxxx",
  "status": "processing"
}

GET /api/sources/prepare/{task_id}
Response:
{
  "status": "completed",           // "processing" | "completed" | "error"
  "files": [
    {"path": "insight.json", "type": "insight_report", "size": 48000},
    {"path": "diagnostic_package.json", "type": "diagnostic", "size": 125000},
    {"path": "cluster_meta.json", "type": "cluster_meta", "size": 2000}
  ],
  "cluster_meta": {
    "cluster_id": "10355969091959802668",
    "tidb_version": "v8.5.4",
    "topology": "2-tidb / 6-tikv / 3-pd / 2-tiflash"
  },
  "error": null
}
```

---

## 6. Studio Artifact System

### 6.1 Report Types and Rendering

#### Guardian Insight Report

A comprehensive root cause analysis report, with data sourced from `insight.json`.

**Data structure** (`frontend/lib/types.ts` — `InsightData`):

```
InsightData
├── direct_cause
│   ├── summary          — One-line root cause summary
│   ├── evidences[]      — Evidence chain (metrics/sql/events)
│   └── jira_issue_related_metrics_name
├── detail
│   ├── cluster_topology — Cluster topology description
│   ├── workload[]       — Workload metrics (Metric[])
│   ├── resource[]       — Resource metrics (Metric[])
│   ├── latency[]        — Latency metrics (Metric[])
│   └── events[]         — Event list
└── metadata
    ├── analysis_uuid
    ├── jira_issue_id
    ├── cluster_id
    ├── time_range_start_ts / time_range_end_ts
    └── created_at
```

**Render components** (`frontend/components/InsightReport/`):

| Component | Function |
|-----------|----------|
| `index.tsx` | Report main frame (Header + Accordion groups) |
| `SummaryCard.tsx` | Root cause summary card (direct cause + evidence chain) |
| `MetricCard.tsx` | Individual metric analysis card (status labels, time-series / spatial / capacity analysis) |
| `MetricChart.tsx` | Metric chart (based on `metrics-chart` library) |
| `EventsCard.tsx` | Event list |

**Chart synchronization**: Uses `metrics-chart` library's `SyncChartPointer` for multi-chart linked interaction.

#### Anomaly Report

Anomaly detection details, with data sourced from `diagnostic_package.json`.

**Data structure** (`frontend/lib/diagnosticTypes.ts` — `DiagnosticPackage`):

```
DiagnosticPackage
├── metadata           — Analysis metadata (UUID/cluster/time range)
├── metrics[]          — Categorized metrics
│   └── MetricCategory
│       └── Metric → Profile → Anomaly[]  — Three-level nested anomaly data
├── top_slow_queries[] — Top N slow queries
├── top_cpu_time_sqls  — Top N CPU-heavy SQL
└── events[]           — System events
```

**Render component**: `frontend/components/AnomaliesPanel/index.tsx` (anomaly faceted display, Z-score severity grading)

### 6.2 Studio Action Cards (Planned)

Shortcut action cards at the top of the right column, currently in "Coming Soon" state:

| Action | Description | Trigger |
|--------|-------------|---------|
| 📊 Report | Generate in-depth analysis report from Sources | Triggers Agent analysis → creates Artifact |
| ⚙️ Operation | Create a Runbook / operations guide | Agent generates → executable steps |
| 📝 Notes | Record investigation notes | Opens NoteModal for manual editing |

---

## 7. MCP (Model Context Protocol) Integration

### 7.1 MCP Connector Sources

The frontend displays five MCP Connectors as system Sources (display-only):

| Connector | Description | Actual MCP Server |
|-----------|-------------|------------------|
| Grafana Screenshot | Capture Grafana dashboard screenshots | Configured in backend |
| Clinic | TiDB Clinic diagnostic service — query metrics, events, slow queries, Top SQL | Configured in backend |
| Ops Portal | TiDB Cloud operations portal — query tenant and cluster info | Configured in backend |
| O11Y | Monitoring metrics and dashboards | Configured in backend |
| Jira | Jira issue tracking and project management | Configured in backend |

These Sources serve only for **context awareness** on the frontend. The actual MCP connections are established by the ACP Agent subprocess on the backend.

### 7.2 Resource Worker MCP (Mock)

An MCP Tool Server for local development and integration testing:

**Tool list**:

| Tool | Function |
|------|----------|
| `analyze_alert` | Analyzes alert-related metrics (Mock: returns static JSON) |
| `chat` | Q&A based on analysis results (Mock: returns preset responses) |

**Data directory**: `data/toolset/resource-worker-mock-mcp/data/mock/`

- `analyze_alert_result.json` — Alert analysis results
- `chat_question1_result.json` / `question2` / `question3` — Preset conversation replies
- `screenshots/` — Grafana screenshots (for visual anomaly analysis)

**Configuration**: The ACP Agent discovers and connects to the MCP Server through `mcpServers` in `.opencode/opencode.json`.

---

## 8. Full Data Flow Overview

```
┌─────────────────────────────── Investibook Workspace ───────────────────────────────┐
│                                                                                    │
│  ┌──── Sources (left) ──┐   ┌──── Chat (center) ─────┐   ┌──── Studio (right) ─┐  │
│  │                      │   │                        │   │                     │   │
│  │  🚨 Alert            │   │   User Message         │   │  📊 Insight Report  │   │
│  │  ├ insight.json     ─┼───┼─> ACP Agent reads ────┼───┼─> Report Artifact   │   │
│  │  ├ diagnostic_pkg   ─┼───┼─> data/ files         │   │                     │   │
│  │  └ (S3 download)     │   │                        │   │  📈 Anomaly Report  │   │
│  │                      │   │   Agent calls MCP Tool │   │                     │   │
│  │  🏗️ Cluster Meta    ─┼───┼─> cluster_meta.json   │   │  📝 Notes           │   │
│  │  (auto-generated)    │   │                        │   │                     │   │
│  │                      │   │   SSE Stream:          │   │                     │   │
│  │  🔌 Clinic (MCP)     │   │   • text (markdown)    │   │                     │   │
│  │  🔌 Ops Portal (MCP) │   │   • thought (reasoning)│   │                     │   │
│  │  🔌 O11Y (MCP)       │   │   • tool (calls)       │   │                     │   │
│  │                      │   │                        │   │                     │   │
│  │  👤 User Context     │   │   ┌─── File Viewer ──┐ │   │                     │   │
│  │  (readonly)          │   │   │ Monaco Editor     │ │   │                     │   │
│  │                      │   │   │ (readonly)        │ │   │                     │   │
│  └──────────────────────┘   │   └──────────────────┘ │   └─────────────────────┘   │
│                              └────────────────────────┘                              │
│                                                                                      │
│  ┌─── data/ (virtual filesystem / ACP Agent cwd) ──────────────────────────────┐   │
│  │  insight.json │ diagnostic_package.json │ cluster_meta.json │ ...             │   │
│  └──────────────────────────────────────────────────────────────────────────────┘   │
│                                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────┘
```

---

## 9. Alert Full Automation Flow (Planned Implementation)

### 9.1 Design Goals

When a user adds a Jira Issue Source in the left column:

1. **Immediate frontend feedback**: Jira Issue Source card appears with a spinning Spinner
2. **Backend auto-processing**: Downloads historical reports, diagnostic files, and Cluster Meta from S3 based on Alert ID
3. **Auto-generate Sources**: Cluster Meta Source is automatically added to the left column
4. **Auto-generate Artifacts**: Insight Report and Anomaly Report Artifacts appear in the right column
5. **Spinner stops**: All files are ready, Alert card status changes to ✓ Ready

### 9.2 Backend Automation Steps

```python
async def prepare_alert_source(alert_id: str, workspace_id: str):
    """
    Automated flow after backend receives a Jira Issue Source addition request:
    
    1. Parse Alert ID → query S3 bucket path
    2. Download insight.json → data/insight.json
    3. Download diagnostic_package.json → data/diagnostic_package.json
    4. Download/query cluster_meta.json → data/cluster_meta.json
    5. Return file manifest + cluster_meta info
    """
```

### 9.3 S3 Data Organization (Convention)

```
s3://guardian-insights/
├── {cluster_id}/
│   └── {analysis_uuid}/
│       ├── insight.json                  ← Guardian Insight Report
│       ├── diagnostic_package.json       ← Diagnostic package (metrics, slow queries, events)
│       └── cluster_meta.json             ← Cluster metadata snapshot
```

### 9.4 Frontend Polling/SSE Status Sync

Two options available:

**Option A: Polling** (simpler, preferred for initial implementation)
```typescript
// Start polling immediately after adding Alert
const pollInterval = setInterval(async () => {
  const res = await fetch(`/api/sources/prepare/${taskId}`);
  const data = await res.json();
  if (data.status === 'completed') {
    clearInterval(pollInterval);
    updateSourceStatus(sourceId, 'ready');
    addClusterMetaSource(data.cluster_meta);
    addReportArtifacts(data.files);
  }
}, 2000);
```

**Option B: SSE** (recommended — reuses Chat streaming architecture)
```typescript
// Reuse /api/chat/stream SSE architecture, add new source_ready event type
type SSEEvent = 
  | { type: 'source_ready'; sourceId: string; files: FileInfo[]; clusterMeta: ClusterMeta }
  | ... // existing types
```

### 9.5 Source State Transitions

```
     Add Alert
         │
         ▼
   ┌───────────┐    Backend processing    ┌───────────┐
   │  loading   │ ─────────────────────── │  loading   │
   │  (spinner) │                         │  (spinner) │
   └───────────┘                         └───────────┘
         │                                     │
         │  Backend succeeds                   │  Backend fails
         ▼                                     ▼
   ┌───────────┐                         ┌────────────┐
   │   ready   │                         │   error    │
   │   (✓)    │                         │   (⚠)      │
   └───────────┘                         └────────────┘
         │
         │  Auto-triggers:
         │  ├─ Add cluster_meta Source
         │  ├─ Add Report Artifact
         │  ├─ Add Anomaly Artifact
         │  └─ Update file tree
         ▼
   Workspace Ready for Chat
```

---

## 10. User & Authentication System

### 10.1 Overview

Investibook uses a lightweight **email-only login** model. Users authenticate by supplying their email address; the backend creates the user on first login and returns a Bearer token. All workspace data is scoped to the authenticated user (multi-tenant).

```
Browser                                Backend (FastAPI / SQLite)
  │                                          │
  │  POST /api/auth/login {email}            │
  │─────────────────────────────────────────>│
  │                                          │  users: get-or-create by email
  │                                          │  user_sessions: INSERT new token
  │  {token, user: {id, email}}              │
  │<─────────────────────────────────────────│
  │                                          │
  │  localStorage.setItem('investibook_token')│
  │  Every subsequent request:               │
  │    Authorization: Bearer <token>         │
  │─────────────────────────────────────────>│
  │                                          │  _db_get_user_by_token → user row
```

### 10.2 Database Schema (SQLite — `data/db/investibook.db`)

```sql
users            -- id, email, created_at
user_sessions    -- token (PK), user_id, created_at   ← Bearer token store
workspaces       -- id, user_id, name, description, is_example, created_at, updated_at
workspace_sessions   -- workspace_id (PK), session_id (ACP), updated_at
workspace_sources    -- workspace_id (PK), data (JSON blob)
workspace_artifacts  -- workspace_id (PK), data (JSON blob)
workspace_messages   -- workspace_id (PK), data (JSON blob)
```

### 10.3 Auth API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/api/auth/login` | Email login — get-or-create user, return Bearer token |
| `GET` | `/api/auth/me` | Return current user `{id, email}` |
| `POST` | `/api/auth/logout` | Delete session token from `user_sessions` |

**Client helpers**: `frontend/lib/auth.ts` — `loginWithEmail()`, `fetchWithAuth()`, `authHeaders()`, `getToken()`, `getEmail()`, `clearAuth()`

**`fetchWithAuth` behaviour**: All API calls go through this wrapper, which injects `Authorization: Bearer <token>` automatically. On a `401 Unauthorized` response it clears stored credentials and redirects the browser to `/login`, then throws so the caller stops processing.

**Future**: The `get_current_user` FastAPI dependency (Bearer token) is designed to be swapped for OAuth2/OIDC introspection without changing any endpoint logic.

### 10.4 Multi-Tenancy

All workspace queries filter by `user_id`. `require_workspace_owner()` middleware returns **403** if the workspace belongs to a different user. The `is_example` workspace is also scoped per-user.

### 10.5 Workspace Read-Only Sharing

Workspace owners can share their workspace as a **public read-only link**. Non-owners who open the link get a read-only view with all chat history and artifacts visible but no write capabilities.

```
Owner toggles Share ON
  └─ PUT /api/workspaces/{id}/share  {is_shared: true}
       → workspaces.is_shared = 1 in SQLite

Anyone with the link opens /workspace/{id}
  └─ GET /api/workspaces/{id}  →  {is_owner: false, is_shared: true, ...}
       → Frontend: workspace.isOwner = false
            ChatPanel: readOnly={true}  (input disabled)
            SourcesPanel: title click disabled, no share controls
            StudioPanel: no artifact add/delete
```

**Share API**:

| Method | Path | Description |
|--------|------|-------------|
| `PUT` | `/api/workspaces/{id}/share` | Set `is_shared` (`{"is_shared": bool}`) |

**UI behaviours** (controlled by `isOwner` prop):

| UI Element | Owner | Non-owner (read-only) |
|-----------|-------|----------------------|
| Workspace title | Click-to-edit | Read-only text |
| Share toggle | Visible (teal icon + text) | Hidden |
| Copy link button | Appears when shared | N/A |
| "+ Add Sources" | Visible | Hidden |
| Chat input | Enabled | Disabled (read-only badge shown) |

**Share link format**: `{origin}/workspace/{workspace_id}`

**Client helper**: `setWorkspaceShared(id, isShared)` in `frontend/lib/workspaces.ts`

---

## 11. State Management

### 11.1 WorkspaceProvider (React Context)

```typescript
interface WorkspaceContextType {
  workspaceId: string | undefined;

  // Sources
  sources: Source[];
  addSource: (source) => Source;
  removeSource: (id) => void;
  selectedSourceId: string | null;
  setSelectedSourceId: (id) => void;

  // MCP Connector toggle state (which connectors are "active" in the UI)
  activeMcpConnectorIds: string[];
  toggleMcpConnector: (id: string) => void;

  // Artifacts
  artifacts: Artifact[];
  addArtifact: (artifact) => Artifact;
  updateArtifact: (id, updates) => void;
  removeArtifact: (id) => void;
  selectedArtifactId: string | null;
  setSelectedArtifactId: (id) => void;

  // View state
  studioView: 'list' | 'detail';
  setStudioView: (view) => void;

  // File viewer
  openFilePath: string | null;
  setOpenFilePath: (path) => void;
}
```

**Key behaviors**:
- New Workspaces automatically inject system Sources (`createDefaultSources()`: user_meta + **5 MCP connectors**: Grafana Screenshot, Clinic, Ops Portal, O11Y, Jira)
- On mount, Sources and Artifacts are **loaded from the backend API** (`GET /api/workspace/{id}/sources` / `artifacts`). If the backend returns empty, the workspace is seeded with defaults (or example data for the example workspace)
- Changes to Sources and Artifacts are **persisted to the backend** via `PUT /api/workspace/{id}/sources` / `artifacts`
- System Sources (`id` prefix `default-*` / `mcp-*`) are always re-injected at load time to prevent stale data
- **Deprecated MCP cleanup**: `mcp-devops` has been replaced by `mcp-ops-portal`. Stale entries are filtered out at load time via the `deprecatedMcpIds` set in `frontend/lib/workspace.tsx`
- `activeMcpConnectorIds` is stored in `localStorage` (`workspace_{id}_active_mcp_connectors`) and is **not** persisted to the backend

### 11.2 Backend Persistence Schema

| API endpoint | SQLite table | Content |
|---|---|---|
| `GET/PUT /api/workspace/{id}/sources` | `workspace_sources` | `Source[]` JSON blob |
| `GET/PUT /api/workspace/{id}/artifacts` | `workspace_artifacts` | `Artifact[]` JSON blob |
| `GET/PUT /api/workspace/{id}/messages` | `workspace_messages` | `Message[]` JSON blob |
| `GET/PUT /api/workspace/{id}/session` | `workspace_sessions` | ACP `session_id` string |

### 11.3 Chat History Persistence

Chat messages are persisted **per workspace** (not per ACP session), so history survives pod restarts and ACP session loss.

```
ChatPanel mount
  └─ loadMessagesFromAPI(workspaceId)
       GET /api/workspace/{id}/messages → Message[]
       (falls back to WELCOME_MESSAGE if empty)

After each message turn
  └─ saveMessagesToAPI(workspaceId, messages)
       PUT /api/workspace/{id}/messages
```

The ACP `session_id` is kept separately (`workspace_sessions` / `GET|PUT /api/workspace/{id}/session`) so the Agent can resume context across browser refreshes.

### 11.4 Browser localStorage

| Key | Content |
|-----|--------|
| `investibook_token` | Bearer auth token |
| `investibook_email` | Authenticated user's email |
| `workspace_{id}_active_mcp_connectors` | JSON array of active MCP connector IDs for the workspace (UI toggle state only — not persisted to backend) |

---

## 12. Deployment Architecture

```
              ┌─────────────┐
              │   Ingress    │
              │   (ALB/K8s)  │
              └──────┬───────┘
                     │
          ┌──────────┴──────────┐
          │                     │
    ┌─────┴──────┐        ┌─────┴─────┐
    │ Frontend   │        │ Backend   │
    │ Next.js    │        │ FastAPI   │
    │ :3000      │        │ :8000     │
    └────────────┘        └─────┬─────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
              ┌─────┴───────┐        ┌──────┴─────┐
              │ ACP Agent   │        │ MCP Server │
              │ (subprocess)│        │ (stdio)    │
              │ OpenCode /  │        │ Resource   │
              │ Claude      │        │ Worker     │
              └─────────────┘        └────────────┘
```

**Environment Configuration**:

| Environment Variable | Description | Default |
|---------------------|-------------|---------|
| `NEXT_PUBLIC_API_URL` | URL for frontend to access backend | _(empty, uses relative paths)_ |
| `ACP_AGENT_TYPE` | Agent type | `opencode` |
| `ACP_AGENT_BIN` | Agent binary path | _(auto-detect via `which`)_ |
| `ACP_MODEL` | LLM model | _(Agent default)_ |

**Local development**: `NEXT_PUBLIC_API_URL=http://localhost:8000` — frontend connects directly to backend (CORS enabled)  
**Production**: `NEXT_PUBLIC_API_URL` not set — frontend uses relative paths, routed through Ingress

---

## 13. Key Technology Choices

| Layer | Technology | Purpose |
|-------|------------|---------|
| Frontend framework | Next.js 16 (App Router) | Page routing + Client Components |
| UI library | `@tidbcloud/uikit` (Mantine) | Primary component library |
| Supplemental UI | Ant Design | Specific scenarios (Button) |
| Code editor | Monaco Editor | File Viewer (read-only) |
| Charts | `metrics-chart` | Metric time-series charts + linked interaction |
| Date handling | dayjs (UTC plugin) | Time formatting |
| Backend framework | FastAPI | REST API + SSE |
| Agent protocol | ACP (Agent Client Protocol) | Agent communication (stdio JSON-RPC) |
| Tool protocol | MCP (Model Context Protocol) | Agent Tools |
| Streaming | SSE (Server-Sent Events) | Real-time event push |
| Auth storage | `localStorage` (`investibook_token`) | Bearer token for API calls |
| Persistence | SQLite (server-side) | Workspaces, sources, artifacts, messages, sessions |

---

## 14. Future Roadmap

| Feature | Priority | Description |
|---------|----------|-------------|
| Jira Issue Source auto-fetch | P0 | Auto-download files from S3 after adding Issue + spinner |
| Cluster Meta auto-injection | P0 | Automatically add as Source once Issue data is ready |
| Report Artifact auto-generation | P0 | Auto-create Report + Anomaly Artifact after download completes |
| Source status indicator | P1 | Three-state (loading/ready/error) + UI spinner |
| Knowledge Source | P1 | User-uploadable runbooks, wikis, playbooks as AI context |
| Skill Source | P1 | Reusable prompt templates / analysis skeletons stored per workspace |
| Studio Action Cards | P1 | One-click Report/Operation generation |
| OAuth / SSO | P2 | Replace email-only login with OAuth2 (OIDC) — `get_current_user` dependency is ready |
| Git Repo Source | P2 | Connect code repository as context |
| MCP Connector live connection | P2 | Frontend configurable MCP connection parameters |
| Multi-Workspace collaboration | P3 | Team-shared Workspaces |
| Export functionality | P3 | Export Report as PDF/Markdown |
