brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
agent-healthEvaluate, benchmark, and improve an AI agent with the Agent Health CLI and server APIs. Use when the user wants to run evaluations/benchmarks, interpret judge results (matcherResults / improvementStrategies), diagnose eval-vs-reality gaps, or raise an agent's pass rate. Mirror of docs/skills/AGENT_HEALTH.md.
Independent GitHub snapshot · Automated classification · Original documentation governs
Use these instructions to evaluate and improve your agent using the agent-health CLI and server APIs.
Copy this file into your project so your AI coding assistant auto-loads it — either from a local checkout or straight from GitHub.
mkdir -p .claude/skills
curl -o .claude/skills/agent-health.md \
https://raw.githubusercontent.com/opensearch-project/agent-health/main/docs/skills/AGENT_HEALTH.md
# or, from a checkout: cp docs/skills/AGENT_HEALTH.md .claude/skills/
Auto-discovered as a skill.
mkdir -p .kiro/steering
curl -o .kiro/steering/agent-health.md \
https://raw.githubusercontent.com/opensearch-project/agent-health/main/docs/skills/AGENT_HEALTH.md
Auto-loaded as a steering file.
Copy to project root, then tell your assistant: "Read AGENT_HEALTH.md and follow it"
Once this file is loaded, just ask:
"Evaluate my agent using agent-health and fix any failures"
Or step by step:
"Run an agent-health benchmark against my agent"
"What failed and why?"
"Fix the high-priority issues"
"Re-run to verify"
What happens under the hood:
1. Assistant runs: npx agent-health benchmark ...
↓
2. Gets JSON with failures + judge feedback (matcherResults + improvementStrategies)
↓
3. Reads your agent code
↓
4. Makes fixes based on the improvement strategies
↓
5. Re-runs the benchmark to verify
Agent Health uses file-based storage by default — results are written to a
local .agent-health/data/ directory and run / benchmark work with no
OpenSearch cluster required. Point at OpenSearch only when you want shared,
production-grade persistence (multiple machines / a team dashboard).
To use OpenSearch instead of file storage, set:
| Variable | Description |
|---|---|
OPENSEARCH_STORAGE_ENDPOINT | OpenSearch cluster URL (e.g. https://search-my-cluster.us-west-2.es.amazonaws.com) |
OPENSEARCH_STORAGE_USERNAME | OpenSearch username (basic auth) |
OPENSEARCH_STORAGE_PASSWORD | OpenSearch password (basic auth) |
SigV4 auth (AWS profile or the default credential chain; es domains or
aoss Serverless collections), none (no auth), and basic auth are all
supported, configurable via env vars, agent-health.config.json, or the
Settings UI — see docs/CONFIGURATION.md.
The Bedrock LLM judge scores evaluation results and needs AWS credentials to call Bedrock.
| Variable | Description |
|---|---|
AWS_PROFILE | AWS profile with Bedrock access |
AWS_REGION | AWS region for Bedrock (e.g. us-west-2) |
BEDROCK_MODEL_ID | (Optional) Default judge model ID. Default: us.anthropic.claude-sonnet-4-5-20250929-v1:0 |
.env FileThe CLI automatically loads a .env file from the current working directory (the folder where commands are run). Create a .env file with all required variables:
# OpenSearch Storage
OPENSEARCH_STORAGE_ENDPOINT=https://search-my-cluster.us-west-2.es.amazonaws.com
OPENSEARCH_STORAGE_USERNAME=admin
OPENSEARCH_STORAGE_PASSWORD=your-password
# AWS / Bedrock
AWS_PROFILE=your-aws-profile
AWS_REGION=us-west-2
# BEDROCK_MODEL_ID=us.anthropic.claude-sonnet-4-5-20250929-v1:0 # optional, this is the default
Alternatively, pass --env-file <path> to load a .env file from a different location.
After configuring your environment, verify everything is set up correctly:
npx @opensearch-project/agent-health doctor
This checks OpenSearch connectivity, AWS credentials, and Bedrock access. Fix any reported issues before proceeding.
# Verify setup
npx @opensearch-project/agent-health doctor --output json
# Generate config files
npx @opensearch-project/agent-health init
# List available resources
npx @opensearch-project/agent-health list agents --output json
npx @opensearch-project/agent-health list test-cases --output json
npx @opensearch-project/agent-health list benchmarks --output json
# Run single test case
npx @opensearch-project/agent-health run -t <test-case-id> -a <agent-key> --output json
# Run full benchmark with export
npx @opensearch-project/agent-health benchmark -n <benchmark-name> -a <agent-key> --export results.json
# Import test cases from JSON file and run benchmark
npx @opensearch-project/agent-health benchmark -f ./test-cases.json -a <agent-key>
# Import with a custom benchmark name
npx @opensearch-project/agent-health benchmark -f ./test-cases.json -n "My Benchmark" -a <agent-key>
# Export test cases from a benchmark (produces import-compatible JSON)
npx @opensearch-project/agent-health export -b <benchmark-name> -o test-cases.json
Besides JSON test cases, Agent Health has an experimental code-based SDK for
writing tests as .eval.js / .eval.ts files (Playwright-style test() /
expect() / fixtures / judge() / lifecycle hooks). Run them the same way you
run a JSON file:
# `-f` accepts BOTH JSON test-case files and code SDK (.eval.js / .eval.ts) files
npx @opensearch-project/agent-health benchmark -f ./examples/eval-files/demo.eval.js -a my-agent
They produce per-matcher results (matcherResults[]) instead of a single
pass/fail. Full guide: docs/SDK.md.
npx @opensearch-project/agent-health doctor --output json
Confirm all checks pass before proceeding. Fix any issues reported.
npx @opensearch-project/agent-health benchmark -n "My Benchmark" -a my-agent --export baseline.json
Read baseline.json and find entries where passFailStatus: "failed".
Key fields to examine:
matcherResults[*].reasoning - the canonical judge verdict + reasoning per matcher (llmJudgeReasoning is a legacy, deprecated shim carrying the same text)improvementStrategies - Specific recommendations with prioritytrajectory - Step-by-step agent executionDistinguish
failed(the agent answered, judge scored it below threshold) from errored (metricsStatus: "error"— the evaluator itself could not run, e.g. a judge validation error). Errored runs are excluded from pass-rate aggregation; don't treat them as agent misses.
Focus on priority: "high" issues first:
{
"category": "Tool Usage",
"issue": "Agent called search without time filter",
"recommendation": "Always include start_time and end_time parameters",
"priority": "high"
}
Read the agent's code and implement the recommendation.
npx @opensearch-project/agent-health benchmark -n "My Benchmark" -a my-agent --export after-fix.json
Compare passRate between baseline and after-fix.
Repeat until all high-priority issues are resolved.
{
"benchmark": { "id": "...", "name": "...", "testCaseCount": 10 },
"runs": [{
"agent": { "key": "my-agent", "name": "My Agent" },
"passed": 7,
"failed": 3,
"passRate": 70,
"reports": [{
"testCaseId": "tc-001",
"passFailStatus": "failed",
"metricsStatus": "ready",
"metrics": { "accuracy": 45 },
"matcherResults": [
{ "method": "llm-judge", "description": "identifies the root cause", "pass": false, "score": 0.45, "reasoning": "The agent failed because..." }
],
"llmJudgeReasoning": "The agent failed because...",
"improvementStrategies": [{
"category": "Tool Usage | Reasoning | Completeness",
"issue": "What went wrong",
"recommendation": "How to fix it",
"priority": "high | medium | low"
}],
"trajectory": [
{ "type": "thinking", "content": "Agent's reasoning..." },
{ "type": "action", "toolName": "search", "toolArgs": {} },
{ "type": "tool_result", "content": "...", "status": "SUCCESS" },
{ "type": "response", "content": "Final answer..." }
]
}]
}]
}
accuracy. Other evaluators emit their own
metric names (tool_selection_accuracy, reasoning_coherence,
bias_detection, …) and custom evaluators emit whatever their
scoringConfig defines. Don't assume metrics.accuracy exists.null/— when none), not a single "accuracy".matcherResults[] is the canonical judge surface. Read the judge verdict
matcherResults entries (method: 'llm-judge'). Each entry
is a MatcherResult: { description, pass (boolean), score? (0–1), method, reasoning?, durationMs? }. llmJudgeReasoning is still populated as a
backward-compatible shim but is deprecated.passed / failed come from passFailStatus; a separate
errored state (metricsStatus: 'error') means the evaluator could not
run and is excluded from pass-rate denominators.--output json for reliable parsing--export to get full reports with improvement strategies| Symptom | Fix |
|---|---|
npx agent-health not found | npm install -g @opensearch-project/agent-health |
| "No agents configured" | npx agent-health init (creates config with sample agents) |
| Server / config issues | npx agent-health doctor (checks config + connectivity) |
| "OpenSearch storage not configured" | Fine for local use — file-based storage is the default. Set OPENSEARCH_STORAGE_* only for shared / production persistence. |
The Agent Health server runs on port 4001 and exposes the following REST APIs. All endpoints return JSON unless noted (SSE endpoints return text/event-stream).
| Method | Path | Description |
|---|---|---|
| GET | /health | Health check → { status: 'ok', version, service: 'agent-health' } |
| GET | /api/agents | List all agents → { agents: AgentConfig[], total } |
| POST | /api/agents/custom | Add custom agent → { name, endpoint, connectorType?, useTraces? } |
| DELETE | /api/agents/custom/:id | Remove custom agent |
| GET | /api/models | List all models → { models: ModelConfig[], total } |
| GET | /api/debug | Debug status → { enabled: boolean } |
| POST | /api/debug | Toggle debug → { enabled: boolean } |
| Method | Path | Description |
|---|---|---|
| POST | /api/agent | Proxy agent request (SSE) → { endpoint, payload, headers?, agentKey? } |
| POST | /api/evaluate | Run evaluation (SSE) → { testCaseId?, testCase?, agentKey, modelId } |
| Method | Path | Description |
|---|---|---|
| POST | /api/judge | Evaluate a captured trajectory → { trajectory, expectedOutcomes? | expectedTrajectory?, modelId?, evaluatorId?, runId?, logs?, agents? } → JudgeResponse { passFailStatus, metrics, llmJudgeReasoning, improvementStrategies } |
| GET | /api/judge/bedrock-models | Discover Bedrock judge models (ListInferenceProfiles) |
| GET | /api/judge/openai-compatible-models | List OpenAI-compatible models → { models, endpoint, configured } (renamed from /api/judge/litellm-models) |
| GET | /api/judge/anthropic-models | Discover Anthropic-direct models (needs ANTHROPIC_API_KEY) |
| GET | /api/judge/github-models | Discover GitHub Models / Copilot models (needs GITHUB_TOKEN) |
On /api/judge, modelId is the judge model — the trajectory is already
captured, so there is no agent invocation here, and /api/judge does not
take a judgeModelId. The agent-vs-judge model distinction (where modelId
is the agent's LLM and judgeModelId is the judge's) applies to the run
endpoints — POST /api/evaluate and POST /api/storage/evaluation-runs — and
the CLI (-m / --judge-model). Agentic-provider judges (pi / agent /
agentic / claude-code) pick their own model and ignore the judge model id.
The run-level matcherResults[] are assembled by the runner from the judge's
response (see Output Reference); the raw /api/judge
primitive returns llmJudgeReasoning.
| Method | Path | Description |
|---|---|---|
| POST | /api/traces | Fetch traces → { traceId?, runIds?, startTime?, endTime?, size? } → { spans, total, hasMore } |
| GET | /api/traces/health | Traces health → { status: 'ok' | 'error' } |
| GET | /api/metrics/:runId | Run metrics → { totalTokens, costUsd, durationMs, llmCalls, toolCalls } |
| POST | /api/metrics/batch | Batch metrics → { runIds: string[] } → { metrics[], aggregate } |
| Method | Path | Description |
|---|---|---|
| POST | /api/logs | Fetch logs → { runId?, query?, startTime?, endTime?, size? } → { logs[], total } |
| Method | Path | Description |
|---|---|---|
| GET | /api/observability/health | Check observability data source health |
| POST | /api/observability/test-connection | Test connection to observability cluster |
| GET | /api/observability/defaults | Get default OTEL index patterns |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/test-cases | List test cases (latest versions). Query: ids?, fields?, size?, after? |
| GET | /api/storage/test-cases/:id | Get latest version of test case |
| GET | /api/storage/test-cases/:id/versions | Get all versions |
| GET | /api/storage/test-cases/:id/versions/:version | Get specific version |
| POST | /api/storage/test-cases | Create test case (v1) |
| PUT | /api/storage/test-cases/:id | Update (creates new version) |
| DELETE | /api/storage/test-cases/:id | Delete all versions |
| POST | /api/storage/test-cases/bulk | Bulk create |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/benchmarks | List benchmarks. Query: fields?, size? |
| GET | /api/storage/benchmarks/:id | Get by ID. Query: fields?, runsSize?, runsOffset? |
| GET | /api/storage/benchmarks/:id/export | Export test cases as JSON |
| POST | /api/storage/benchmarks | Create → { name, description?, testCaseIds } |
| PUT | /api/storage/benchmarks/:id | Update |
| PATCH | /api/storage/benchmarks/:id/metadata | Update metadata |
| DELETE | /api/storage/benchmarks/:id | Delete |
| POST | /api/storage/benchmarks/:id/execute | Execute benchmark (SSE) → { runConfig: RunConfigInput } |
| DELETE | /api/storage/benchmarks/:id/runs/:runId | Delete specific run |
| POST | /api/storage/benchmarks/:id/cancel | Cancel execution |
| POST | /api/storage/benchmarks/:id/refresh-all-stats | Recompute all run stats |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/runs | List runs. Query: size?, from?, fields? |
| GET | /api/storage/runs/:id | Get run by ID |
| POST | /api/storage/runs | Create run |
| PATCH | /api/storage/runs/:id | Update run |
| DELETE | /api/storage/runs/:id | Delete run |
| POST | /api/storage/runs/search | Search with filters |
| GET | /api/storage/runs/by-test-case/:testCaseId | Runs for test case |
| GET | /api/storage/runs/by-benchmark/:benchmarkId | Runs for benchmark |
| GET | /api/storage/runs/by-benchmark-run/:benchmarkId/:runId | Results for benchmark run |
| GET | /api/storage/runs/iterations/:benchmarkId/:testCaseId | Iterations for test case in benchmark |
| POST | /api/storage/runs/:id/annotations | Add annotation |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/analytics | Query analytics. Filters: experimentId?, testCaseId?, agentId?, modelId? |
| GET | /api/storage/analytics/aggregations | Aggregated metrics |
| POST | /api/storage/analytics/search | Complex search with aggregations |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/benchmarks/:id/report | Download report. Query: format? ('json'|'html'|'pdf') |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/evaluators | List evaluators (5 built-in system + custom) |
| GET | /api/storage/evaluators/:id | Get evaluator (latest version) |
| GET | /api/storage/evaluators/:id/versions | List all versions (newest first) |
| GET | /api/storage/evaluators/:id/versions/:version | Get a specific version snapshot |
| POST | /api/storage/evaluators | Create custom evaluator |
| PUT | /api/storage/evaluators/:id | Update (creates a new immutable version) |
| DELETE | /api/storage/evaluators/:id | Delete custom evaluator (system evaluators are protected) |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/evaluation-runs | List evaluation runs |
| GET | /api/storage/evaluation-runs/:id | Get evaluation run |
| POST | /api/storage/evaluation-runs | Start a run (SSE). Accepts sources: [{ type: 'code-import', filenames }] for .eval.js / .eval.ts SDK files, plus agentKey, modelId, judgeModelId?, evaluatorId? |
| POST | /api/storage/evaluation-runs/:id/cancel | Cancel a run |
| POST | /api/storage/evaluation-runs/:id/promote | Promote a run |
| Method | Path | Description |
|---|---|---|
| POST | /api/comparison/cluster-failures | Cluster regressed cases into named failure patterns (knowledge / tool_gap / reasoning / other) |
| GET | /api/comparison/clusters/:clusterId | Fetch a previously computed cluster by id |
| Method | Path | Description |
|---|---|---|
| GET | /api/storage/health | Storage backend health |
| POST | /api/storage/test-connection | Test storage connection |
| POST | /api/storage/init | Initialize indexes |
| GET | /api/storage/config/status | Config status |
| POST | /api/storage/config/storage | Update storage config |
| POST | /api/storage/config/observability | Update observability config |
| Method | Path | Description |
|---|---|---|
| POST | /api/assistant/chat | Chat with AI assistant (SSE) → { sessionId, message, context } → { type: 'delta'|'done', content } |
| DELETE | /api/assistant/session/:sessionId | Clear session |
| GET | /api/assistant/health | Check assistant availability |
The evaluation surface lives under /evaluations/*. The app uses
BrowserRouter — plain paths, no # prefix.
| Page | Route | Description |
|---|---|---|
| Dashboard | / | Overview with agent stats, recent runs, system health |
| Benchmarks | /evaluations/benchmarks | List benchmarks with pass rates, run counts, version badges |
| Benchmark Runs | /evaluations/benchmarks/:id/runs | Runs for a benchmark (split / tabs layout) |
| Run Inspector | /evaluations/benchmarks/:id/runs/:runId/inspect | Per-test-case results, trajectory, judge, traces |
| Test Cases | /evaluations/test-cases | List / create / edit / version test cases |
| Test Case Detail | /evaluations/test-cases/:id | Definition + runs with an inline live-run panel |
| Eval Runs | /evaluations/runs · /evaluations/runs/:id | Code-import (SDK) and ad-hoc run results |
| Evaluators | /evaluators · /evaluators/:id | Manage evaluators; version history + Git-style diff |
| Compare | /compare/:benchmarkId?runs=a,b | Diagnosis surface: verdict strip, first-divergence, failure clusters |
| Agent Traces | /agent-traces | OpenTelemetry trace explorer (timeline / flow) |
| Coding Agents | /coding-agents | Coding Agent Analytics (Claude Code / Kiro / Codex) |
| Settings | /settings | Configure agents, models, storage, observability connections |
| Assistant | /assistant | Full-page AI chat interface for help and analysis |
"What's the pass rate for benchmark bench-xxx? Which test cases are failing and why?"
→ The assistant will query /api/storage/benchmarks/:id and /api/storage/runs/by-benchmark/:id
"Why did test case tc-xxx fail in run run-xxx? What should I fix?"
→ The assistant reads llmJudgeReasoning and improvementStrategies from the run
"Help me write a test case for testing log search with time filters" → The assistant creates a test case with prompt, context, expectedOutcomes, and labels
"What are the most expensive LLM calls in run run-xxx?"
→ The assistant queries /api/traces and /api/metrics/:runId to find token-heavy spans
"Compare the results of run A vs run B in benchmark bench-xxx" → The assistant fetches both runs and diffs pass/fail status, accuracy, and strategies
"My agent passes 8/9 in evals but fails in real life — why?" → The assistant investigates: fetches the runs and test cases, examines judge reasoning and expectedOutcomes, checks whether the evaluator is appropriate for the domain, and recommends tightening outcomes or creating a custom evaluator
accuracy = (sum of scores / total outcomes) × 100passThreshold + critical failure checks| ID | Name | Threshold | Key Metrics |
|---|---|---|---|
system-rca-default | RCA Default | 70% | accuracy |
system-factuality | Factuality | 80% | factual_accuracy, hallucination_rate, source_grounding |
system-tool-usage | Tool Usage | 80% | tool_selection_accuracy, redundant_calls, tool_ordering |
system-reasoning-depth | Reasoning Depth | 75% | reasoning_coherence, step_completeness, logical_validity |
system-safety | Safety | 90% | safety_score, bias_detection, guardrail_adherence |
Create custom evaluators via the UI or API for domain-specific scoring:
{
"name": "My Domain Evaluator",
"systemPrompt": "You are evaluating an agent for [domain]. CRITICAL CRITERIA: ...",
"scoringConfig": {
"metrics": [
{ "name": "routing_accuracy", "weight": 0.4, "scale": 100 },
{ "name": "tool_correctness", "weight": 0.4, "scale": 100 },
{ "name": "diagnostic_completeness", "weight": 0.2, "scale": 100 }
],
"passThreshold": 85,
"scale": 100
}
}
Assign to a benchmark run via evaluatorId in the run config.
system-tool-usage as secondary evaluator: Catches redundant/wrong tool calls