# AI Agent

The AI Agent turns natural language into ClickHouse insights. Ask a question and the agent plans a series of read-only tool calls — querying system tables, inspecting schema, comparing hosts, spotting anomalies — then streams back a concise answer with optional inline charts.

The agent connects through the **same ClickHouse host and user** as the rest of the dashboard. Its visibility is bounded by the grants on that user. By default it only runs read-only queries. Keep control tools off and use a restricted ClickHouse user for safe exposure.

## What it can do

| Category | What the agent covers |
|---|---|
| Schema exploration | List databases, tables, columns; explore tables relevant to a topic |
| Query analysis | Running, slow, failed, and expensive queries; EXPLAIN plans; optimization advice |
| System health | Server metrics, disks, errors, anomaly detection |
| Storage, merges & mutations | Part sizes, active merges, stuck mutations, merge throughput |
| Replication & cluster | Replication lag, queue, ZooKeeper/Keeper status, cluster topology |
| Schema & data-type advice | ORDER BY/keys, codecs, skip indexes, column type right-sizing |
| Tuning & upgrades | Hardware-based settings, version-upgrade guidance |
| Charts & visualization | Run SQL and render an interactive chart inline |

A small set of tools delivers this: dedicated primitives for the common reads, and the `query` tool plus an expert **skill** recipe for everything else. See [Capabilities](/ai-agent/capabilities) for the full list.

**Skills** — when a question needs domain depth, the agent loads a bundled expert guide (a skill) with copy-pasteable SQL recipes before answering. Eighteen skills cover data analysis, anomaly detection, query tuning, schema & data-type design, hardware tuning, version upgrades, concept explanations, incident response, plan-and-verify, and the core domains (replication, cluster, storage, migration, security, best practices, troubleshooting, system tables). Available skills: `GET /api/v1/agent/skills`.

**Plan and verify** — for multi-step tasks (incident triage, investigations, find-and-fix) the agent authors a live checklist with `update_plan`, keeps one step in progress, and verifies each result before stating it. Simple one-step questions skip the plan. See [Capabilities](/ai-agent/capabilities) for details.

## Quick start

Set at minimum one environment variable:

```bash
LLM_API_KEY=your-provider-key
```

Then open `/agents` in the dashboard, pick a host, and start asking questions. The agent uses OpenRouter by default (`https://openrouter.ai/api/v1`) with the free model tier.

For full configuration options see [Configuration](/ai-agent/configuration).

## Child pages

- [Configuration](/ai-agent/configuration) — all env vars, providers, access control
- [Capabilities](/ai-agent/capabilities) — tools, skills, plan & verify, examples
- [Conversation History](/ai-agent/conversation-history) — how history works, how to enable server persistence
  - [Store Backends](/ai-agent/conversation-history/backends) — per-backend setup (D1, ClickHouse, Postgres, …)
  - [AgentState](#conversation-history-backends) — managed/self-hosted persistence with AI enrichment (auto-titles, follow-ups)

## Conversation history backends

Agent chat history is persisted by a server-side `ConversationStore` chosen at **deploy time** by environment variables. Four backends are available:

| Backend | Selected when | Notes |
|---|---|---|
| **Browser** | conversation-DB feature flag off | `localStorage` only; history is per-browser, no server setup |
| **AgentState** | `AGENTSTATE_API_KEY` is set | Managed or self-hosted conversation-history service; optional AI enrichment |
| **D1** | a Cloudflare D1 binding is present | Cloudflare-native SQLite |
| **Postgres** | `DATABASE_URL` is set | Any PostgreSQL-compatible database |

Selection priority when persistence is enabled: **AgentState** (if `AGENTSTATE_API_KEY`) → **D1** (Cloudflare binding) → **Postgres** (`DATABASE_URL`) → **Memory** (last-resort, non-persistent). With the feature flag off, history stays in the **Browser**.

Server persistence requires `VITE_FEATURE_CONVERSATION_DB=true` and an authenticated user — configure Clerk (`VITE_AUTH_PROVIDER=clerk` + `CLERK_SECRET_KEY`) so history can be scoped per user. Unauthenticated sessions always fall back to browser history. See [Authentication](/authentication).

### AgentState

[AgentState](https://agentstate.app) is a conversation-history database-as-a-service for AI agents. Use it when you want managed (or self-hosted) persistence that survives browser clears and is shared across devices, plus optional **AI enrichment** — auto-generated conversation titles and follow-up question suggestions — and per-project analytics. It is self-hostable as a single Cloudflare Worker (see [duyet/agentstate](https://github.com/duyet/agentstate)) and ships an npm SDK (`@agentstate/sdk`).

Per-user isolation is built in: each thread is namespaced by AgentState `external_id` as `<userId>:<conversationId>` and tagged `user:<userId>`, so one project key safely holds every user's history without cross-talk.

**Environment variables** (all server-side):

| Variable | Required | Default | Purpose |
|---|---|---|---|
| `AGENTSTATE_API_KEY` | to enable | — | `as_live_...` project key; its presence activates the AgentState backend |
| `AGENTSTATE_BASE_URL` | no | `https://agentstate.app/api` | endpoint; override to point at a self-hosted instance |
| `AGENTSTATE_AI_ENRICH` | no | `false` | enable auto-title + follow-up suggestions |
| `CONVERSATION_STORE_BACKEND` | no | `auto` | force `agentstate` when a Cloudflare D1 binding is also present |

Still requires `VITE_FEATURE_CONVERSATION_DB=true` and a Clerk auth provider (`VITE_AUTH_PROVIDER=clerk` + `CLERK_SECRET_KEY`).

#### Setup (hosted)

1. Sign in at [agentstate.app](https://agentstate.app) and create a project.
2. Create an API key — it is prefixed `as_live_` and shown **once**, so copy it immediately.
3. Set it server-side:

```bash
VITE_FEATURE_CONVERSATION_DB=true
AGENTSTATE_API_KEY=as_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
## Optional: turn on auto-titles + follow-up suggestions
AGENTSTATE_AI_ENRICH=true
```

#### Setup (self-host)

Run your own AgentState Worker per [duyet/agentstate](https://github.com/duyet/agentstate), then point the dashboard at it:

```bash
VITE_FEATURE_CONVERSATION_DB=true
AGENTSTATE_API_KEY=as_live_your_self_hosted_key
AGENTSTATE_BASE_URL=https://your-agentstate-host/api
```

#### Local dev

Against a locally running AgentState instance, use the seed test key:

```bash
VITE_FEATURE_CONVERSATION_DB=true
AGENTSTATE_BASE_URL=http://localhost:8787
AGENTSTATE_API_KEY=as_live_TEST_KEY_FOR_LOCAL_DEV_ONLY_1234567890ab
```

#### Verify it's active

- **Settings sidebar** — the agent settings sidebar shows a read-only **Conversation History** section naming the active backend (e.g. `AgentState`).
- **Endpoint** — `GET /api/v1/conversations/backend` reports the active backend and whether enrichment is available:

```bash
curl https://your-host/api/v1/conversations/backend
## => { "backend": "agentstate", "supportsAiEnrichment": true }
```

#### AI enrichment

When AgentState is active **and** `AGENTSTATE_AI_ENRICH=true`:

- **Auto-title** — new conversations get a concise generated title instead of a placeholder.
- **Follow-ups** — the chat suggests follow-up questions after a turn. The dashboard reads them from `GET /api/v1/conversations/$id/follow-ups`.

With enrichment off, conversations persist normally but titles and follow-up suggestions are not generated.

## AI Insights persistence

Separate from chat history, the AI Insights panel on `/overview` persists the
short observations it generates through its own pluggable store, mirroring the
conversation backends above. It is **additive opt-in** via one env var and
defaults to ClickHouse, so existing deployments are unaffected.

```bash
INSIGHTS_STORE_BACKEND=auto   # auto | clickhouse | d1 | postgres | agentstate | memory
```

| Value | Backend | Prerequisite |
|---|---|---|
| `auto` (default) | ClickHouse `monitoring_findings` table | writable monitoring connection |
| `clickhouse` | same as `auto` | writable monitoring connection |
| `d1` | Cloudflare D1 `insights_findings` table | `INSIGHTS_D1` binding, else `CONVERSATIONS_D1` |
| `postgres` | Postgres `insights_findings` table | `DATABASE_URL` |
| `agentstate` | AgentState State store | `AGENTSTATE_API_KEY` (+ optional `AGENTSTATE_BASE_URL`) |
| `memory` | in-process (ephemeral) | — |

The D1, Postgres, and AgentState backends reuse the same env / bindings as the
conversation store. `auto` resolves to ClickHouse and never silently follows
other env; if an explicitly selected backend is missing its prerequisite, the
engine logs a warning and falls back to ClickHouse. `GET /api/v1/insights/backend`
reports the active backend (`{ "backend": "d1" }`), and the overview panel shows
a read-only "Stored in `<backend>`" footer.

## HTTP API

```bash
curl -X POST https://your-host/api/v1/agent \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $AGENT_API_TOKEN" \
  -d '{"message": "Which queries are running right now?", "hostId": 0}'
```

`hostId` is a zero-based numeric index; defaults to `0`.

## MCP Server

The dashboard exposes an MCP (Model Context Protocol) endpoint at `/api/mcp`. The same implementation runs in the standalone Cloudflare MCP Worker (`apps/mcp`). All tools are read-only by design — write and DDL statements are rejected.

### Agent settings sidebar

The **MCP Servers** section of the agent settings sidebar shows the built-in `clickhouse-monitor` server with its live connection status, tool count, resource count, and version — fetched from `GET /api/v1/mcp/info`. Click a server row to see the full tool and resource list. The built-in server is always enabled and cannot be toggled off. Custom server registration ("Connect new server") is not yet available.

### MCP tools

| Tool | Purpose |
|---|---|
| `query` | Execute a read-only SQL query (SELECT / WITH / DESCRIBE / EXPLAIN only) |
| `list_databases` | List all databases with their engines and comments |
| `list_tables` | List tables in a database with row counts and sizes |
| `get_table_schema` | Get column definitions for a table including types, defaults, and comments |
| `get_metrics` | Key server metrics: version, uptime, active connections, memory usage |
| `get_running_queries` | Currently running queries ordered by elapsed time |
| `get_slow_queries` | Slowest completed queries from the query log |
| `get_merge_status` | Currently running merge operations with progress and elapsed time |
| `explore_table_schema` | Three-mode schema exploration: databases → tables → full schema with relationships |

### Read-only enforcement

The `query` tool — the only tool that accepts arbitrary SQL — validates every statement before execution via `validateSqlQuery` (`@chm/sql-builder`). It **throws** on any write or DDL statement and on dangerous ClickHouse commands. Allowed prefixes: `SELECT`, `WITH` (CTE), `DESCRIBE`, `EXPLAIN`. Rejected: `INSERT`, `ALTER`, `DROP`, `CREATE`, `TRUNCATE`, `RENAME`, `DELETE`, `UPDATE`, `SYSTEM RELOAD/FLUSH/KILL/SHUTDOWN`, `GRANT`, `REVOKE`, `ATTACH`, `DETACH`, `KILL`, `SET`, multi-statement payloads (`;` chaining), and dangerous table functions (`remote()`, `url()`, `s3()`, etc.).

All other MCP tools run fixed SELECT queries and pass `readonly: '1'` to the ClickHouse connection — they cannot issue writes regardless of the validator.

## Security

- Use a **read-only ClickHouse user** scoped to the system tables the dashboard needs.
- Keep **control tools off** (`AGENT_ENABLE_CONTROL_TOOLS=false`, the default) unless you trust the user and need kill/optimize.
- On public deployments, **require authentication**: `CHM_FEATURE_AGENT_ACCESS=authenticated`.
- Keep LLM keys **server-side** — never in `VITE_*` variables (those are baked into browser JS at build time).
