Ctrl K
ComunidadGuías…

agent_docs/evals.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# MCP Eval Framework

Read this before running evals, comparing MCP branches, or investigating eval
results. For the full CLI reference, config schema, scenario details, and
scoring formula, see [`packages/hdx-eval/README.md`](../packages/hdx-eval/README.md).

## What It Does

The eval framework spawns Claude Code as an SRE agent, gives it access to an
MCP server, and asks it to solve observability scenarios (find a root cause,
build a dashboard, etc.) against synthetic telemetry seeded into ClickHouse.
Answers are graded with programmatic regex checks (40%) + an LLM judge (60%),
minus a penalty for tool-call errors. The framework is MCP-agnostic — you can
compare any two (or more) MCP servers side by side.

### Available scenarios

| Scenario | Type | What It Tests |
|---|---|---|
| `error-root-cause` | Investigation | Find a cascading DB timeout causing checkout failures |
| `latency-spike` | Investigation | Identify a segmented p99 spike for enterprise tenants |
| `noisy-signals` | Investigation | Find load-bearing log patterns among noise |
| `segmented-regression` | Investigation | Detect a regression hidden by single-axis aggregation |
| `service-health-check` | Investigation | Generate a peace-time health report |
| `dashboard-build` | Dashboard creation | Build monitoring dashboards with diverse tile types |

## Prerequisites

| Requirement | Notes |
|---|---|
| `yarn dev` running | Each worktree needs `yarn dev` running with a fixed slot. This handles `yarn install`, `yarn build:common-utils`, Docker containers (ClickHouse, MongoDB), and the API server. See [Development Setup](development.md). |
| `ANTHROPIC_API_KEY` or `AI_API_KEY` | Auto-loaded from `.env.local` at the monorepo root. Check `.env.local` in other worktrees for existing keys if missing. |
| `claude` CLI | Installed globally (`which claude`). The harness spawns it in streaming JSON mode. |
| `uv` | Only needed for `stdio`-type MCPs (e.g. raw ClickHouse MCP). Install via `curl -LsSf https://astral.sh/uv/install.sh \| sh`. |
| Two git worktrees | One checked out to the branch under test, one to `main`. Use `git worktree list` to verify. |

## Dual-Slot A/B Comparison (Primary Workflow)

This is the standard workflow for comparing a feature branch against main.

### Architecture

Evals run from **one worktree** (the branch being tested). The eval harness
talks to **two separate ClickStack API instances** running on different slots:

```
                        ┌──────────────────────┐
                        │   Eval Harness       │
                        │   (this worktree)    │
                        │                      │
                        │  eval.config.json    │
                        │  ┌────────┬────────┐ │
                        │  │branch  │ main   │ │
                        │  │MCP     │ MCP    │ │
                        │  └───┬────┴───┬────┘ │
                        └──────┼────────┼──────┘
                               │        │
              ┌────────────────┘        └────────────────┐
              ▼                                          ▼
   ┌─────────────────────┐                  ┌─────────────────────┐
   │  Slot 98 (main)     │                  │  Slot 99 (branch)   │
   │  API  :30198        │                  │  API  :30199        │
   │  CH   :30598        │                  │  CH   :30599        │
   │  Mongo:30498        │                  │  Mongo:30499        │
   │  MCP  :30198/mcp    │                  │  MCP  :30199/mcp    │
   └─────────────────────┘                  └─────────────────────┘
       main worktree                            branch worktree
```

Each slot runs its own ClickHouse instance. `setup-hyperdx` (step 2) creates
the ClickStack account, Connection, and Sources on each instance, but does **not**
seed telemetry data. The `run` command auto-seeds on first run, but only into
the single ClickHouse specified in the top-level `clickhouse` config — so for
dual-slot comparisons you must seed each instance explicitly (step 4b).

### 1. Start both dev stacks

`yarn dev` starts Docker infra (ClickHouse, MongoDB, OTel Collector) **and**
the API + App servers in one command. Run it from each worktree with a fixed
slot:

```bash
# From the main worktree (slot 98)
HDX_DEV_SLOT=98 yarn dev

# From the branch worktree (slot 99)
HDX_DEV_SLOT=99 yarn dev
```

Each command prints a banner with its port assignments. Wait for both to
finish starting, then verify:

```bash
curl -s -o /dev/null -w "Slot 98 API: %{http_code}\n" http://localhost:30198/api/v1/me
curl -s -o /dev/null -w "Slot 99 API: %{http_code}\n" http://localhost:30199/api/v1/me
# Expected: 404 (no auth token — but the server is responding)

curl -s -o /dev/null -w "Slot 98 CH: %{http_code}\n" http://localhost:30598/
curl -s -o /dev/null -w "Slot 99 CH: %{http_code}\n" http://localhost:30599/
# Expected: 200
```

### 2. Run `setup-hyperdx` against each instance (if needed)

> Run these commands from the worktree that has the eval framework code (the
> branch under test).

This registers an eval account, creates a ClickHouse Connection, and creates
per-scenario Sources on each ClickStack instance. The command is **idempotent** —
it skips anything that already exists, so it's safe to run every time.

**Skip this step** if `eval.config.branch.json` and `eval.config.main.json`
already exist and pass the check:

```bash
# Quick check: do saved configs exist and are the MCPs reachable?
# NOTE: These cp commands temporarily overwrite eval.config.json with a
# single-instance config. If you already have a combined two-MCP config,
# you must re-run step 3 (jq merge) afterward.
cp packages/hdx-eval/eval.config.branch.json packages/hdx-eval/eval.config.json 2>/dev/null && \
  yarn workspace @hyperdx/hdx-eval dev setup-hyperdx --check && \
  echo "Branch config OK" || echo "Branch config needs setup"

cp packages/hdx-eval/eval.config.main.json packages/hdx-eval/eval.config.json 2>/dev/null && \
  yarn workspace @hyperdx/hdx-eval dev setup-hyperdx --check && \
  echo "Main config OK" || echo "Main config needs setup"
```

If either needs setup (or this is the first time):

```bash
# Setup main instance (slot 98)
yarn workspace @hyperdx/hdx-eval dev setup-hyperdx --api-url http://localhost:30198
cp packages/hdx-eval/eval.config.json packages/hdx-eval/eval.config.main.json

# Setup branch instance (slot 99)
yarn workspace @hyperdx/hdx-eval dev setup-hyperdx --api-url http://localhost:30199
cp packages/hdx-eval/eval.config.json packages/hdx-eval/eval.config.branch.json
```

### 3. Build the combined eval config

The eval runner needs a single `eval.config.json` with both MCPs so it can
compare them in one batch. Use `jq` to merge:

```bash
cat packages/hdx-eval/eval.config.branch.json | jq \
  --slurpfile main packages/hdx-eval/eval.config.main.json '
  # Helper: rewrite mcp__hyperdx__ prefixes in toolPattern and deniedTools
  def reprefix($new):
    .toolPattern = ("mcp__" + $new + "__*")
    | if .deniedTools then
        .deniedTools = [.deniedTools[] | gsub("^mcp__hyperdx__"; "mcp__" + $new + "__")]
      else . end;

  . as $branch |
  .mcps = {
    "hyperdx-branch": ($branch.mcps.hyperdx + {"label": "HyperDX Branch", "enabled": true} | reprefix("hyperdx-branch")),
    "hyperdx-main": ($main[0].mcps.hyperdx + {"label": "HyperDX Main", "enabled": true} | reprefix("hyperdx-main"))
  } |
  .hyperdxApi = $branch.hyperdxApi
' > packages/hdx-eval/eval.config.json
```

> **Why the `reprefix` step?** The harness uses the config key (e.g.
> `hyperdx-branch`) as the MCP server name, so Claude Code registers tools as
> `mcp__hyperdx-branch__*`. The `toolPattern` and `deniedTools` from the
> original single-MCP config still say `mcp__hyperdx__*`, which won't match.
> The `reprefix` helper rewrites both to use the new key.

Verify the config has both MCPs:

```bash
jq '.mcps | keys' packages/hdx-eval/eval.config.json
# Expected: ["hyperdx-branch", "hyperdx-main"]
```

### 4. Verify MCP connectivity

```bash
BRANCH_KEY=$(jq -r '.hyperdxApi.accessKey' packages/hdx-eval/eval.config.branch.json)
MAIN_KEY=$(jq -r '.mcps["hyperdx-main"].headers.Authorization' packages/hdx-eval/eval.config.json | sed 's/Bearer //')
curl -s -o /dev/null -w "Branch MCP: %{http_code}\n" \
  -H "Authorization: Bearer $BRANCH_KEY" http://localhost:30199/mcp
curl -s -o /dev/null -w "Main MCP: %{http_code}\n" \
  -H "Authorization: Bearer $MAIN_KEY" http://localhost:30198/mcp
# Expected: 406 (SSE endpoint, wrong HTTP method — but proves auth works)
```

### 5. Seed both ClickHouse instances

`setup-hyperdx` creates the ClickStack account and Sources but does **not** seed
telemetry data. The `run` command's auto-seed only writes to the single
ClickHouse in the top-level `clickhouse` config, so you must seed each
instance explicitly:

```bash
# Seed the main instance (slot 98, CH on port 30598)
HDX_DEV_SLOT=98 yarn workspace @hyperdx/hdx-eval dev seed <scenario>

# Seed the branch instance (slot 99, CH on port 30599)
HDX_DEV_SLOT=99 yarn workspace @hyperdx/hdx-eval dev seed <scenario>
```

The seed command is idempotent — it skips if data already exists. Replace
`<scenario>` with the scenario name (e.g. `error-root-cause`). You can seed
multiple scenarios by running the command once per scenario.

### 6. Run the comparison

```bash
yarn workspace @hyperdx/hdx-eval dev run <scenario> \
  --mcp hyperdx-branch,hyperdx-main \
  --baseline hyperdx-main \
  --runs 3 \
  --max-turns 15 \
  --timeout 600000 \
  --judge-model claude-opus-4-7
```

The eval harness automatically loads `ANTHROPIC_API_KEY` (or `AI_API_KEY`)
from `.env.local` at the monorepo root via `dotenvx`. If the key isn't there,
either add it to `.env.local` or export it in your shell. Check `.env.local`
files in other worktrees or `~/sites/hyperdx/.env.local` for existing keys.

For single-MCP runs, the harness auto-seeds on first run if the scenario
tables are empty. For dual-slot A/B runs, seed both instances explicitly
(step 5 above). Subsequent runs reuse existing data and the saved anchor time.

### Recommended flags

| Flag | Recommended Value | Why |
|---|---|---|
| `--timeout` | `600000` (10 min) | The default 5 min causes false timeouts on complex scenarios like `dashboard-build`. |
| `--concurrency` | omit (default 1) | Concurrent runs cause resource contention and false timeouts. Run sequentially. |
| `--runs` | `3` minimum | Eval results have high variance. 3 runs is the minimum for meaningful comparison. |
| `--baseline` | `hyperdx-main` | Sets main as the reference for delta computation in reports. |
| `--judge-model` | `claude-opus-4-7` | Default. Use a strong model for judging. |
| `--no-judge` | use for fast iteration | Skips the LLM judge (saves cost/time). Programmatic scores only. |

## Other Use Cases

The dual-slot A/B comparison is the primary workflow, but the eval framework
supports other configurations with a single dev stack.

### Quick score check (single MCP)

Run one MCP against a scenario to get a baseline score without comparison.
Useful for validating that a scenario works or checking score after a change.

```bash
# Single slot — just one `yarn dev` needed
export HDX_DEV_SLOT=98
yarn workspace @hyperdx/hdx-eval dev setup-hyperdx
yarn workspace @hyperdx/hdx-eval dev run error-root-cause --mcp hyperdx --runs 1 --no-judge
```

### Model comparison (same MCP, different models)

Compare how different models perform on the same MCP and data. Useful for
evaluating model upgrades or cost/quality tradeoffs.

```bash
yarn workspace @hyperdx/hdx-eval dev run error-root-cause \
  --mcp hyperdx --model claude-sonnet-4-20250514 --runs 3
yarn workspace @hyperdx/hdx-eval dev run error-root-cause \
  --mcp hyperdx --model claude-opus-4-6 --runs 3
```

### Non-ClickStack MCP (e.g. raw ClickHouse)

The framework works with any MCP. The `clickhouse` stdio MCP is included by
default in `eval.config.json` after setup. Use it to compare ClickStack's MCP
against querying ClickHouse directly.

```bash
yarn workspace @hyperdx/hdx-eval dev run error-root-cause \
  --mcp hyperdx,clickhouse --baseline clickhouse --runs 3
```

## Reading Results

Results are written to `packages/hdx-eval/runs/<batch-timestamp>/`. Each batch
contains:

```
_summary.md              # Markdown comparison report
_summary.json            # Structured data for programmatic use
<scenario>/
  <mcp>/
    <model>/
      0.json             # Full run trajectory (tool calls, tokens, timing)
      0.grade.json       # Grade record (programmatic + judge scores)
```

Useful inspection commands:

```bash
# List all batches
yarn workspace @hyperdx/hdx-eval dev runs-list

# Show a specific run's tool calls and queries
yarn workspace @hyperdx/hdx-eval dev runs-show <path> --queries

# Show the agent's final answer
yarn workspace @hyperdx/hdx-eval dev runs-show <path> --final-answer

# Re-grade with only programmatic checks (fast, no API key needed)
yarn workspace @hyperdx/hdx-eval dev grade <batch> --no-judge

# Re-generate the comparison report with a different baseline
yarn workspace @hyperdx/hdx-eval dev report <batch> --baseline hyperdx-main

# Browse results in the web viewer
yarn workspace @hyperdx/hdx-eval viewer
```

## Common Pitfalls

### Containers bind to wrong ports

If you run `docker compose up` without sourcing `scripts/dev-env.sh` first,
containers bind to default ports (8123, 27017) instead of slot-specific ones.
Always use the `bash -c 'export HDX_DEV_SLOT=... && source scripts/dev-env.sh
&& docker compose ...'` pattern (see [Cleanup](#cleanup) below).

### Access keys go stale after container restarts

When MongoDB is recreated, the eval account's access key changes. Re-run
`setup-hyperdx` against the affected instance and rebuild the combined config.

### False timeouts

Two common causes:
1. **Default 5-minute timeout is too short** for complex scenarios. Use
   `--timeout 600000` (10 minutes).
2. **`--concurrency > 1`** causes resource contention between parallel Claude
   processes. Run sequentially (omit `--concurrency`).

### `eval.config.json` is gitignored

The eval config files (`eval.config.json`, `eval.config.*.json`) are
gitignored because they contain instance-specific access keys and Source IDs.
They must be regenerated per environment using `setup-hyperdx`.

### ClickHouse data missing after container recreation

If Docker volumes are removed (e.g. `docker compose down -v`), seeded data is
lost. The eval harness auto-seeds on the next run if it detects empty tables,
but this adds time. Use `--reseed` explicitly to force a fresh seed.

### Dev stack not starting

If `yarn dev` fails:
- Port already in use — another stack on the same slot is running. Check with
  `lsof -i :301XX` and kill stale processes.
- Docker not running — start Docker Desktop or OrbStack first.
- Missing dependencies — run `yarn setup` from the repo root.

## Cleanup

```bash
# If you started with `yarn dev`, Ctrl-C in each terminal stops everything.
# If you started Docker separately:
bash -c 'export HDX_DEV_SLOT=98 && source scripts/dev-env.sh 2>/dev/null && \
  docker compose -p hdx-dev-98 -f docker-compose.dev.yml down'
bash -c 'export HDX_DEV_SLOT=99 && source scripts/dev-env.sh 2>/dev/null && \
  docker compose -p hdx-dev-99 -f docker-compose.dev.yml down'

# Drop eval data from ClickHouse (optional)
yarn workspace @hyperdx/hdx-eval dev drop <scenario>
```