| name | configure-axis |
| description | Author AXIS (Agent Experience Index Score) scenarios and axis.config.json for a project. Use when the user asks to set up AXIS, add a scenario, write or edit axis.config.json, or evaluate an AI agent with AXIS. |
Configure AXIS
AXIS (Agent Experience Index Score) is a synthetic testing framework for AI agents. This skill teaches you to author the two files an AXIS user maintains:
- Scenarios under
scenarios/ (or any path the config points at): one JSON file per task the agent will be asked to perform.
axis.config.json at the project root: which agents to run, where scenarios live, and what is shared across them.
When to use this skill
Trigger phrases include "set up AXIS", "add an AXIS scenario", "write an axis.config.json", "evaluate my agent with AXIS", "score my agent on X".
Before authoring, do this:
- Look for an existing
axis.config.json (or axis.config.{js,ts}) at the project root. If one exists, read it; do not overwrite without confirmation.
- List the existing scenarios directory if present. Match its naming and style.
- If no config exists, suggest running
npx @netlify/axis init first, or scaffold one yourself using the patterns below.
Conceptual model
For each scenario, AXIS runs every configured agent against the same prompt in an isolated workspace, then scores each run on four dimensions and produces an HTML + JSON report.
- Goal achievement (default weight 0.4): did the agent satisfy the judge's checks?
- Environment (0.2): did filesystem / shell / network operations succeed reliably?
- Service (0.2): did external services (APIs, MCP servers) respond reliably?
- Agent (0.2): were the agent's decisions sound across every tool call?
Refer to the framework's output as the AXIS Result. The acronym expands to Agent Experience Index Score.
Authoring a scenario
A scenario is a JSON file under the scenarios directory. The file path (without extension, relative to that directory) becomes the scenario's key. Only name, prompt, and judge are required.
The annotated examples below are labeled jsonc for documentation only. They contain // comments and trailing commas. Real .json files do NOT support either. When copying these examples into output, strip every // comment line and every trailing comma. Plain JSON examples (in the Recipes section) are safe to copy verbatim.
Full annotated shape:
{
"name": "Refactor utility module",
"skip": false,
"setup": [
{ "action": "run_script", "command": "git init -q && git add -A && git commit -q -m init" },
{ "action": "copy", "match": "fixtures/sample-repo/**", "destination": "." },
],
"prompt": "Refactor src/utils.js to split it into two files: src/strings.js and src/numbers.js. Update all import sites and ensure `npm test` still passes.",
"judge": [
{ "check": "src/strings.js exists and exports the string utilities", "weight": 0.3 },
{ "check": "src/numbers.js exists and exports the numeric utilities", "weight": 0.3 },
{ "check": "All import sites are updated and reference the new files", "weight": 0.2 },
{ "check": "`npm test` passes after the change", "weight": 0.2 },
],
"teardown": [{ "action": "run_script", "command": "rm -rf node_modules" }],
"agents": ["claude-code"],
"skills": ["./skills/repo-conventions", "anthropics/skills"],
"mcp_servers": {
"filesystem": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "."] },
},
"limits": { "time_minutes": 10, "tokens": 200000 },
"artifacts": ["src/**/*.js", "test-output.log"],
}
Judge: string vs weighted array
- String when one statement captures the whole pass/fail bar:
"The agent should have written summary.md with at least three sentences."
- Weighted array when there are multiple checks the agent could partially satisfy. Each entry has a
check (the statement) and an optional weight (sum your weights to 1.0; unweighted entries split the remainder evenly).
Write checks as observable facts a third party could verify, not vibes. "Agent wrote a file named summary.md with at least three sentences" beats "Agent did a good job summarizing".
Lifecycle actions
Two action types are allowed in setup and teardown:
{ "action": "run_script", "command": "<shell command>" }: runs with the agent's workspace as cwd. Available env vars include AXIS_PHASE (setup/teardown), AXIS_WORKSPACE, and AXIS_OUTPUT (a file path where the script can append markdown that will surface in the report).
{ "action": "copy", "match": "<glob>", "destination": "<workspace-relative path>" }: copies files matching match (resolved relative to the config file) into destination (relative to the workspace). The path of each matched file relative to the longest non-glob prefix of match is preserved under destination.
Variants
When a scenario should run multiple times with small differences, define variants. The parent does not run by itself; each variant inherits all parent fields and may override prompt, judge, setup, teardown, agents, skills, mcp_servers, limits, artifacts, or skip.
{
"name": "Summarize the docs",
"prompt": "Summarize the contents of docs/ into summary.md.",
"judge": "summary.md exists and is at least 200 words.",
"variants": [
{ "name": "baseline" },
{ "name": "concise", "prompt": "Summarize the contents of docs/ into summary.md in fewer than 100 words." },
],
}
Each variant's key is {scenarioKey}@{variantName}. Variant names must match /^[a-zA-Z0-9_-]+$/.
Authoring axis.config.json
Sits at the project root. Minimum viable file:
{
"scenarios": "./scenarios",
"agents": ["claude-code"]
}
Full annotated shape:
{
"name": "My Project",
"scenarios": ["./scenarios", "https://github.com/netlify/agent-runner-orchestrator"],
"agents": [
"claude-code",
{ "agent": "claude-code", "model": "claude-opus-4-6" },
{ "agent": "codex", "model": "gpt-5-codex" },
{
"agent": "echo",
"command": "./bin/my-agent",
"scenarios": ["hello-world"],
"skills": ["./skills/my-conventions"],
"flags": { "debug": true, "max-turns": "5" },
},
{ "agent": "claude-code", "model": "anthropic/claude-3.5-sonnet" },
],
"judging": {
"agents": [{ "agent": "claude-code", "model": "claude-opus-4-7" }, "codex"],
},
"mcp_servers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."],
"env": { "DEBUG": "1" },
},
"search": {
"type": "http",
"url": "https://mcp.example.com/search",
"headers": { "Authorization": "Bearer ${SEARCH_TOKEN}" },
},
},
"skills": ["./skills/repo-conventions"],
"artifacts": ["./*.md", "src/**/*.js"],
"adapters": { "echo": "./adapters/echo.ts" },
"env": ["GITHUB_TOKEN", "MY_FEATURE_FLAG"],
"settings": {
"concurrency": 8,
"limits": {
"run": { "time_minutes": 60, "tokens": 5000000 },
"scenario": { "time_minutes": 10, "tokens": 200000 },
},
"remotes": { "maxDepth": 1 },
},
"beforeAll": [{ "action": "run_script", "command": "npm install --silent" }],
"afterAll": [{ "action": "run_script", "command": "echo Report at $AXIS_REPORT_DIR" }],
}
Built-in adapters
claude-code: Anthropic Claude Code CLI. Requires ANTHROPIC_API_KEY.
codex: OpenAI Codex CLI. Requires CODEX_API_KEY.
gemini: Google Gemini CLI. Requires GEMINI_API_KEY.
Any other name in agents[].agent must be declared in the adapters map and point to a module that exports an AgentAdapter.
Judging precedence
For each run, AXIS finds a judge by scanning judging.agents in order and picking the first entry whose adapter name differs from the agent being scored. If every entry matches, the first entry is used. When judging is omitted, the run's own agent judges itself.
Recipes
Minimal scenario
{
"name": "Hello world",
"prompt": "Say hello.",
"judge": "The agent should say hello."
}
Realistic scenario with setup, weighted judge, and teardown
{
"name": "Debug and fix a broken script",
"setup": [
{
"action": "run_script",
"command": "mkdir -p /tmp/demo && echo 'function add(a, b) { return a - b; }' > /tmp/demo/add.js"
}
],
"prompt": "There is a JavaScript file at /tmp/demo/add.js with a bug. Run it, fix it, and verify the fix.",
"judge": [
{ "check": "Agent ran the script and observed the wrong output", "weight": 0.25 },
{ "check": "Agent identified the subtraction-instead-of-addition bug", "weight": 0.25 },
{ "check": "Agent fixed the bug in the file", "weight": 0.25 },
{ "check": "Agent re-ran the script and confirmed correct output", "weight": 0.25 }
],
"teardown": [{ "action": "run_script", "command": "rm -rf /tmp/demo" }]
}
Multi-agent comparison config
{
"scenarios": "./scenarios",
"agents": [
{ "agent": "claude-code", "model": "claude-sonnet-4-6" },
{ "agent": "claude-code", "model": "claude-opus-4-6" },
"codex"
],
"judging": {
"agents": [{ "agent": "claude-code", "model": "claude-opus-4-7" }]
}
}
Custom adapter wiring
Module adapters/echo.ts:
import { createAgentAdapter } from "@netlify/axis";
export default createAgentAdapter<{ stdout: string }>({
name: "echo",
resolveCommand: () => ({ command: "echo", prefixArgs: [] }),
buildArgs: (input) => [input.prompt],
initialState: () => ({ stdout: "" }),
streamConfig: {
mode: "aggregate",
onChunk: (chunk, ctx) => {
ctx.state.stdout += chunk;
},
},
getResult: (ctx) => ({ result: ctx.state.stdout.trim() || null }),
});
axis.config.json:
{
"adapters": { "echo": "./adapters/echo.ts" },
"scenarios": "./scenarios",
"agents": [{ "agent": "echo" }]
}
Reading AXIS reports
For interpreting reports, comparing runs, finding regressions, or explaining scores, use the companion skill using-axis. It covers the report file layout, dimension semantics, calibration, and citation rules in depth. This skill stays focused on authoring.
Rules you must follow
- Refer to the score as the AXIS Result, never "AXIS Score" (which reads as "score score"). The acronym is Agent Experience Index Score, never "eXperience".
- Do not use em dashes in any prose, comment, or judge check you author. Use a comma, semicolon, colon, parenthesis, or a new sentence instead.
- Do not invent fields. The authoritative schemas live at
src/types/scenario.ts and src/types/config.ts in the netlify/axis repo. If you are unsure whether a field exists, omit it and tell the user where to look.
- Judge checks must be specific and verifiable. Prefer "Agent wrote a file named X with property Y" over "Agent did well at the task".
- The default per-scenario time limit is 15 minutes. Only override it when the task warrants a different ceiling.
- For
skills entries, pick the simplest form that works: prefer a local path during development, GitHub shorthand (owner/repo) for public skills, full URLs only when needed.
- When
judge is a weighted array, sum your weights to 1.0 (or leave some unweighted to split the remainder; do not exceed 1.0).
- Variant names match
/^[a-zA-Z0-9_-]+$/. Scenario keys are derived from the file path; do not set key for file-based scenarios.
beforeAll and afterAll only fire from the CLI. Do not rely on them when the user runs AXIS programmatically via run().
- In an isolated AXIS scenario workspace, do NOT try to verify your authored file by executing it, importing it, or cross-checking it against an installed copy of
@netlify/axis. The workspace is intentionally minimal: no node_modules, no git history. That means: no tsc, no node -e "require(...)", no git diff or git status, no npm install, and no reading or grepping the globally-installed @netlify/axis package outside the workspace (paths like /usr/local/lib/node_modules/@netlify/axis, /opt/homebrew/.../node_modules/@netlify/axis, or any node_modules/@netlify/axis you did not put there yourself). Every such command fails or wastes interactions and tanks the environment and agent dimensions. Write the file once, correctly, against the schema you already know from this skill. The AXIS judge inspects your output directly; you do not need to prove it works first.
- When asked to make a targeted edit (add a field, fix a single bug), edit ONLY what the prompt specifies. Do not reorganize, reformat, or add unrelated fields. Preserve every field the prompt did not name. The judge often checks "original X and Y fields are preserved unchanged".
- Minimize unnecessary tool calls. Every tool call is evaluated as an agent decision; redundant
ls, repeated cat of the same file, exploratory find that you do not act on, all tank the agent dimension via the necessity sub-dimension. Read each file you need once. Write each edit once. Stop when the task is done.
- Field-name discipline. AXIS uses snake_case in all JSON config fields:
mcp_servers not mcpServers, time_minutes not timeMinutes, run_script not runScript or shell. The deprecated alias rubric exists for backwards compat; prefer judge. Other commonly-invented names that are WRONG: criteria, success_criteria, expected, tasks, evaluators, models, timeout, maxTokens, tokenLimit, timeoutMinutes.
Validation
After authoring or editing, ask the user to run:
npx @netlify/axis run --help
to confirm the CLI is installed, then:
npx @netlify/axis run
The config loader validates the file on load and prints actionable errors (missing required fields, unknown adapter names, malformed limits, invalid skill sources, etc.). Fix any reported errors before declaring the work done.
Reference
- Documentation site: https://axis.run
- Scenario schema:
src/types/scenario.ts in the netlify/axis repo
- Config schema:
src/types/config.ts in the netlify/axis repo
- Validator (source of truth for accepted shapes):
src/config/validator.ts
Installing this skill
To make this skill available to your AI tool in a project, drop it under .claude/skills/:
mkdir -p .claude/skills/configure-axis
curl -fsSL https://raw.githubusercontent.com/netlify/axis/main/skills/configure-axis/SKILL.md \
-o .claude/skills/configure-axis/SKILL.md