# Explore: Container Monitoring & Notification System

## 1. Current Container Lifecycle Flow

### Start → Run → Stop → Cleanup

1. **Create** (`POST /projects/{pid}/repositories/{rid}/instances`)
   - Generates `instance_name`, finds free port, builds image (Dockerfile) or renders compose template.
   - Writes `docker-compose.yml`, `.env`, config files to `instance_dir`.
   - DB record created with `status = "pending"`.
   - **File:** `apps/api/src/api/tool_instances.py` (lines ~300–600)

2. **Start** (`POST /.../instances/{id}/start`)
   - `status` set to `"building"`.
   - Applies config profile (env vars, git mounts, port override, start command).
   - Runs `docker compose up -d` via `execute_compose_command()`.
   - Retrieves `container_id` and `container_name` via `docker ps` filters.
   - Connects container to `"backend"` network.
   - `status` set to `"starting"`, then polls `docker inspect` every 2s for up to 30s (`wait_for_container_running`).
   - If container exits → `status = "error"`, logs captured.
   - Executes readiness probe (configurable per `ToolType`, default `curl` for web tools).
   - Probe succeeds → `status = "running"`; fails → `status = "unhealthy"`.
   - For web tools, starts `cloudflared` tunnel and stores `tunnel_id` + `public_url`.
   - **File:** `apps/api/src/api/tool_instances.py` (lines ~1100–1500)

3. **Stop** (`POST /.../instances/{id}/stop`)
   - Kills cloudflared tunnel by PID (`stop_cloudflared_tunnel`).
   - Runs `docker compose stop`.
   - `status = "stopped"`, clears `url`/`public_url`/`tunnel_id`, sets `last_stopped_at`.
   - **File:** `apps/api/src/api/tool_instances.py` (lines ~1500–1550)

4. **Restart** (`POST /.../instances/{id}/restart`)
   - Stops old tunnel, re-applies config profile, runs `docker compose restart`, recreates tunnel.
   - **File:** `apps/api/src/api/tool_instances.py` (lines ~1550–1650)

5. **Delete** (`DELETE /.../instances/{id}`)
   - Stops tunnel, runs `docker compose down -v`, deletes `instance_dir` (includes clone + SSH keys).
   - Removes DB row.
   - **File:** `apps/api/src/api/tool_instances.py` (lines ~1650–1720)

6. **Health Check Endpoint** (`GET /.../instances/{id}/health`)
   - Calls `get_container_status()` (docker inspect) + `check_tunnel_health()` (curl to public URL).
   - Returns composite `healthy` flag: container running AND tunnel healthy for web tools.
   - Includes `probe_status` and `last_probe_output` from DB `probe_result` JSON column.
   - **File:** `apps/api/src/api/tool_instances.py` (lines ~1800–1870)

### Background Patterns
- **TerminalManager** runs an `_idle_check_loop()` every 60s to close idle terminal sessions.
  - **File:** `apps/api/src/services/terminal_manager.py` (lines ~40–80)
- No general container-level background monitor or reaper exists.

---

## 2. Health Check Gaps

| What's Present | What's Missing |
|---|---|
| One-time readiness probe at startup (`execute_probe`) | **No continuous health monitoring** after startup |
| `GET /health` returns container + tunnel status on demand | **No periodic background polling** of container state |
| `docker inspect` reads `State.Status`, `ExitCode`, `Health.Status` | **No liveness probe** (only readiness at start) |
| Tunnel health checked via HTTP curl | **No automatic recovery** on container crash |
| `probe_result` JSON stored in DB | **No health history / time-series** |
| Frontend polls health every 30s for running instances | **No server-side event push** when health changes |

### Critical Gaps
1. **Container crash goes unnoticed** until a user manually refreshes or the frontend poll happens.
2. **Tunnel failure is only detected on-demand** (health endpoint or user action). No proactive retry or notification.
3. **No OOM or exit-code tracking** beyond the immediate startup phase.
4. **No health state transitions** (e.g., `running → degraded → unhealthy → stopped`).
5. **Readiness probe is fire-and-forget**; if it fails, status becomes `"unhealthy"` but no further action is taken.

---

## 3. Logging Gaps

### Current Logging (`apps/api/src/logging_config.py`)
- Plain text format: `%(asctime)s [%(levelname)s] %(name)s: %(message)s`
- Request/response middleware logs timing and status codes.
- Exception middleware logs unhandled tracebacks.
- **No structured logging** (JSON), **no correlation IDs**, **no container event stream persistence**.

### What's Logged (Container-Related)
- Docker compose up/down/start/stop return codes and stderr snippets.
- Container startup success/failure and wait time.
- Readiness probe attempts and results.
- Tunnel creation/failure.
- Permission fix warnings.

### What's NOT Logged
- **Container stdout/stderr is not persisted** — only fetched on-demand via `docker logs`.
- **No lifecycle event log** (audit trail of who started/stopped/restarted what and when).
- **No structured container events** (create, start, die, oom, kill) from Docker daemon.
- **No log aggregation** — logs are ephemeral console output.
- **No log levels per instance** — all logs go through root logger.

---

## 4. Notification Gaps

### Current State: **No notification system exists.**

| Area | Finding |
|---|---|
| **Backend events/pub-sub** | None. No event bus, message queue, or broadcast mechanism. |
| **WebSocket (non-terminal)** | None. Only terminal uses WS (`/ws/terminal/{instance_id}`). |
| **SSE** | Not implemented. |
| **Polling** | Frontend polls instance list and health every 30s. |
| **Frontend toast/alert** | No toast, snackbar, or global notification component found. |
| **Error display** | Inline `error-message` divs and `ErrorState` component (`data-states.tsx`). |

### Frontend Evidence
- `instance-list.tsx` polls health every 30s for running instances and shows a `"tunnel error"` badge inline.
- `session-card.tsx` displays `Tunnel Error` / `App Error` badges but no push notification.
- `api/client.ts` has an axios interceptor for 401 redirect and retry logic, but no toast on errors.
- No `toast`, `notification`, `snackbar`, or `alert` components exist in `apps/web/src/components/`.

---

## 5. Database: Instance State Tracking

### Table: `tool_instances`
**File:** `apps/api/src/models/tool_instance.py`

| Column | Purpose |
|---|---|
| `status` | `pending → building → starting → probing → running → unhealthy → stopped → error` |
| `container_id` | Docker container ID (nullable) |
| `container_name` | Docker container name (nullable) |
| `compose_path` | Path to `docker-compose.yml` |
| `port` | Host port mapped to container |
| `url` / `public_url` | Cloudflare tunnel URL |
| `tunnel_id` | cloudflared PID |
| `last_started_at` / `last_stopped_at` | Timestamps |
| `probe_result` | JSON blob with last probe outcome |
| `selected_config_profile_id` | FK to config profile |

### Migrations
- `0006_tool_instances.py` — base table with `status`, `container_id`, `url`, `port`.
- `0007_instance_container_name.py` — adds `container_name`.
- `0011_tool_instance_tunnel_fields.py` — adds `public_url`, `tunnel_id`.
- `0013_add_probe_result.py` — adds `probe_result` (JSON).

### Gaps
- **No `health_history` table** — can't track uptime, downtime, or flapping.
- **No `instance_events` table** — no audit log of state transitions.
- **No `notification_preferences` or `user_notifications` table**.

---

## 6. Key Files and Their Roles

| File | Role |
|---|---|
| `apps/api/src/services/docker.py` | All Docker CLI interactions: compose up/down, container status/logs, tunnel management, port finding. |
| `apps/api/src/api/tool_instances.py` | CRUD + lifecycle endpoints for instances (create, start, stop, restart, delete, health, logs, proxy, tunnel recreate). |
| `apps/api/src/services/terminal_manager.py` | In-memory session registry + 60s idle cleanup loop. Pattern to emulate for container monitoring. |
| `apps/api/src/services/readiness_probe.py` | `execute_probe()` — runs a command inside a container with retry logic. |
| `apps/api/src/logging_config.py` | Plain-text logging setup, request/response middleware, exception middleware. |
| `apps/api/src/models/tool_instance.py` | SQLAlchemy model for `tool_instances` table. |
| `apps/api/src/models/tool_type.py` | SQLAlchemy model for `tool_types`, includes `readiness_probe` JSON config. |
| `apps/api/src/api/health.py` | System health endpoint (DB + disk), **not** per-instance health. |
| `apps/web/src/components/instance-list.tsx` | Displays instances with status dots, polls health every 30s, inline tunnel error badges. |
| `apps/web/src/components/session-list.tsx` | Grouped list of sessions (active vs recent), receives `tunnelHealth` prop. |
| `apps/web/src/components/session-card.tsx` | Card UI with status badges, stop/delete confirm, tunnel error display. |
| `apps/web/src/api/sessions.ts` | API client for instance CRUD and `checkInstanceHealth()`. |

---

## 7. Risks and Unknowns

1. **Docker CLI dependency** — All container operations shell out to `docker` / `docker compose`. No Docker SDK or lib used. This is slow and brittle under load.
2. **Tunnel PID fragility** — `tunnel_id` is a process ID string. If the API restarts, PIDs are lost and tunnels may leak.
3. **No instance-level auto-restart** — If a container exits (crash, OOM), it stays `error` or `stopped` until a user manually restarts it.
4. **Probe result is a single JSON blob** — Overwritten on every start. No history.
5. **Polling load** — Frontend polls every 30s per running instance. With many users + many instances, this generates significant health-check load.
6. **No auth on WebSocket upgrade** — Terminal WS endpoint may not validate session ownership on connection (not verified in this scout).
7. **Cloudflared process leaks** — If `stop_cloudflared_tunnel` fails or the API crashes, tunnel processes may become orphaned.
8. **Log retention** — `docker logs` is the only source; no rotation or persistence strategy.
9. **Scaling limitation** — In-memory `TerminalManager` and any future in-memory event bus won't work across multiple API replicas.

---

## 8. Recommended Architecture Approach

### Short-Term (MVP): In-Memory Events + Server-Sent Events (SSE)

**Rationale:**
- The project already uses FastAPI. SSE is natively supported and simpler than WebSockets for one-way server→client push.
- No new infrastructure (message queue) needed.
- Matches the existing polling use case but eliminates 30s latency.

**Components:**
1. **InstanceEventBus** (in-memory singleton, similar to `TerminalManager`)
   - Publishes events: `instance.created`, `instance.started`, `instance.stopped`, `instance.health_changed`, `instance.error`.
2. **Background Monitor Task** (asyncio loop, like `TerminalManager._idle_check_loop`)
   - Every 10–30s, inspect running containers and tunnels.
   - On state change, update DB + publish event to bus.
3. **SSE Endpoint** (`GET /events`)
   - Stream JSON events to connected clients.
   - Frontend subscribes once, receives real-time updates.
4. **Frontend Toast Layer**
   - New lightweight toast component subscribed to SSE.
   - Shows notifications for errors, tunnel failures, successful starts.

### Medium-Term: Persistent Event Log + Health History

1. **`instance_events` table** — append-only audit log of all lifecycle transitions.
2. **`health_checks` table** — periodic snapshots of container + tunnel health for trend analysis.
3. **Structured logging** — Switch to JSON format; include `instance_id`, `event_type`, `correlation_id`.

### Long-Term: Message Queue (if multi-replica)

- If the API needs to scale horizontally, replace in-memory bus with **Redis Pub/Sub** or **NATS**.
- Background monitor becomes a separate worker process or scheduled task.

### Decision Summary

| Concern | Recommended Path |
|---|---|
| Real-time status updates | **SSE** from in-memory event bus |
| Health monitoring | Background asyncio task polling Docker + tunnels |
| User notifications | Lightweight toast component fed by SSE |
| Audit / history | New `instance_events` and `health_checks` tables |
| Log aggregation | JSON structured logs + optional log shipping |
| Multi-replica safety | Deferred to future; add Redis/NATS when needed |
