Configuration Reference
Complete reference for axis.config.{json|js|mjs|ts}.
Full Example
AXIS is configured via an axis.config.* file in your project root. JSON is the
default; JavaScript and TypeScript configs are also supported and
let you compose your config programmatically. Here is a JSON example showing all available
fields:
{
"scenarios": "./scenarios",
"agents": [
"claude-code",
{
"agent": "gemini",
"model": "gemini-2.5-pro",
"scenarios": ["cms/*"],
"flags": { "yolo": true }
}
],
"settings": {
"concurrency": 4,
"scoring_weights": {
"goal_achievement": 0.4,
"environment": 0.2,
"service": 0.2,
"agent": 0.2
},
"limits": {
"run": { "time_minutes": 60, "tokens": 2000000 },
"scenario": { "time_minutes": 10, "tokens": 200000 }
}
},
"env": ["ANTHROPIC_API_KEY", "GEMINI_API_KEY"],
"mcp_servers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
},
"skills": ["./skills/deploy"],
"adapters": {
"my-agent": "./adapters/my-agent.ts"
}
} Config File Formats
AXIS resolves the config file by extension, in priority order:
axis.config.ts → axis.config.js →
axis.config.mjs → axis.config.json. Use
--config <path> to point at a specific file.
| Extension | Loader | Notes |
|---|---|---|
.json | Native JSON parse | Static config, no executable logic. |
.js / .mjs / .cjs | Native dynamic import | ESM module. Default export is the config object or a function returning one. |
.ts / .mts / .cts | Loaded via jiti | No build step needed. Type-only imports are stripped at runtime. |
JavaScript / TypeScript configs
JS and TS configs let you build the config programmatically. Useful for sharing logic across scenarios, deriving values from environment variables, or generating large numbers of scenarios from a fixture set. The module's default export must be either the config object directly or a (sync or async) function that returns one:
// axis.config.ts
import type { AxisConfig, ScenarioInput } from "@netlify/axis";
import applyLimits from "./scenarios/apply-limits.js";
import authorScenario from "./scenarios/author-scenario.js";
const dynamicScenarios: ScenarioInput[] = ["alpha", "beta", "gamma"].map((id) => ({
key: "smoke-" + id,
name: "Smoke test " + id,
prompt: "Do thing for " + id,
rubric: [{ check: "Did the thing" }],
}));
export default {
scenarios: [
"./scenarios",
applyLimits,
authorScenario,
...dynamicScenarios,
],
agents: ["claude-code"],
settings: {
limits: { run: { time_minutes: 60 } },
},
} satisfies AxisConfig; Or as a function (sync or async):
// axis.config.ts
import type { AxisConfig } from "@netlify/axis";
export default async () => {
const fixtures = await loadFixtures();
const config: AxisConfig = {
scenarios: fixtures.map(buildScenario),
agents: ["claude-code"],
};
return config;
}; axis init --format ts (or --format js) scaffolds a typed config
file alongside a sample JSON scenario. Without --format, AXIS produces a
.json config to preserve back-compat.
Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
scenarios | string | (string | ScenarioInput)[] | No |
A path to the scenarios directory, or an array of paths and/or inline scenario objects.
Defaults to "./scenarios" when omitted. See
Authoring scenarios for the full schema.
|
agents | (string | AgentConfig)[] | Yes | Agent names or full agent configurations. |
settings | object | No | Concurrency, scoring weight, and limit overrides. |
env | string[] | No | Additional environment variables to pass through to agent processes. |
mcp_servers | object | No | MCP servers available to all agents. |
skills | string[] | No | Skills available to all agents. |
adapters | object | No | Custom agent module paths, keyed by agent name. |
artifacts | string[] | No | Glob patterns of files to capture from each scenario's workspace after teardown. Merged with per-scenario artifacts. |
Agent Configuration
Each entry in the agents array can be a simple string (agent name with defaults)
or a full configuration object.
| Field | Type | Required | Description |
|---|---|---|---|
agent | string | Yes | Agent name: claude-code, codex, gemini, goose, etc. |
model | string | No | Model override passed to the agent CLI. |
scenarios | string[] | No | Subset of scenarios to run. Supports glob patterns like cms/*. |
skills | string[] | No | Agent-specific skills (merged with top-level skills). |
flags | object | No | CLI flags passed to the agent, e.g. {"full-auto": true}. |
command | string | No | Custom CLI command (for custom agents). |
Scoring Weights
Override the default dimension weights under settings.scoring_weights. Values
must sum to 1.0. See Scoring Framework for what each dimension measures.
| Field | Type | Required | Description |
|---|---|---|---|
goal_achievement | number | No | Goal Achievement weight. Default: 0.4. |
environment | number | No | Environment weight. Default: 0.2. |
service | number | No | Service weight. Default: 0.2. |
agent | number | No | Agent weight. Default: 0.2. |
{
"settings": {
"scoring_weights": {
"goal_achievement": 0.5,
"environment": 0.2,
"service": 0.2,
"agent": 0.1
}
}
} Limits
Limits control how much time and tokens a run or individual scenario can consume. This prevents runaway agents from consuming unbounded resources. Limits can be configured at three levels:
- Overall run limits (
settings.limits.run) — when hit, all remaining and in-progress jobs are aborted. - Default scenario limits (
settings.limits.scenario) — default budget for each individual job. Only that job fails when exceeded. - Per-scenario limits (
limitsin the scenario JSON) — override the defaults for a specific scenario. Only that job fails when exceeded.
Default behavior
Even without any limits configured, each scenario has a default time limit of 15 minutes.
You can override this by setting settings.limits.scenario.time_minutes or by adding
limits.time_minutes to individual scenarios.
Limit fields
| Field | Type | Description |
|---|---|---|
time_minutes | number | Maximum wall-clock time in minutes. Accepts fractional values (e.g. 0.5 for 30 seconds). Default: 15 per scenario. |
tokens | number | Maximum total tokens (input + output + cache). Must be a positive integer. No default. |
Overall run limits
Set settings.limits.run to cap the total time or tokens across the entire run.
When an overall limit is reached, all remaining and currently running jobs are immediately
terminated and marked as failed.
{
"settings": {
"limits": {
"run": { "time_minutes": 60, "tokens": 2000000 }
}
}
} Per-scenario limits
Set settings.limits.scenario to define default per-job budgets. These can be
overridden by adding a limits field directly in a scenario file.
// axis.config.json — default for all scenarios
{
"settings": {
"limits": {
"scenario": { "time_minutes": 10, "tokens": 200000 }
}
}
}
// scenarios/expensive-task.json — override for one scenario
{
"name": "Expensive task",
"prompt": "...",
"rubric": "...",
"limits": { "time_minutes": 30, "tokens": 500000 }
} Token limits are enforced using a conservative estimate during execution (based on streamed assistant text). The actual token count may slightly exceed the limit before the job is terminated. The authoritative token count from the agent's API is used for overall run limit tracking.
MCP Servers
Configure Model Context Protocol servers that are automatically wired into each agent environment. AXIS supports both stdio (local process) and HTTP (remote endpoint) servers.
| Field | Type | Required | Description |
|---|---|---|---|
type | "stdio" | "http" | Yes | Server transport type. |
command | string | Yes | Command to start the server process (stdio only). |
args | string[] | No | Arguments passed to the command. |
env | object | No | Environment variables for the server process. |
url | string | Yes | Remote server endpoint URL (http only). |
headers | object | No | HTTP headers (supports ${VAR} env interpolation). |
{
"mcp_servers": {
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
"env": { "LOG_LEVEL": "info" }
},
"remote-api": {
"type": "http",
"url": "https://mcp.example.com/tools",
"headers": { "Authorization": "Bearer ${TOKEN}" }
}
}
} Each agent writes MCP configuration in its native format before spawning:
| Agent | Config File | Location |
|---|---|---|
claude-code | .mcp.json | Workspace root |
codex | config.toml | CODEX_HOME |
gemini | settings.json | GEMINI_CLI_HOME |
Skills
Skills extend agent capabilities with reusable instruction sets. Specify them at the top level (shared across all agents), per agent, or per scenario.
| Format | Example | Description |
|---|---|---|
| Local path | ./skills/deploy | Relative to the config file. |
| GitHub shorthand | netlify/axis-skill-deploy | owner/repo format, cloned automatically. |
| Full URL | https://github.com/owner/repo | GitHub repository URL, cloned automatically. |
{
"skills": [
"./skills/deploy",
"netlify/axis-skill-deploy",
"https://github.com/owner/repo"
]
}
Remote skills are cached in .axis/skills-cache/. Use --refresh-skills
to force re-clone.
Environment Variables
The env field lists additional environment variables to pass through to agent
processes. The following are always passed through by default:
| Category | Variables |
|---|---|
| API keys | ANTHROPIC_API_KEY, CODEX_API_KEY, GEMINI_API_KEY |
| System | PATH, USER, SHELL, LANG, TERM, TMPDIR |
{
"env": ["MY_CUSTOM_TOKEN", "DATABASE_URL"]
} Scenarios
Scenarios live in the configured scenarios directory as .json,
.js, or .ts files, or are listed inline in
axis.config.{js,ts}. The filename (without extension) becomes the scenario
key, and nested directories create namespaced keys: scenarios/cms/create-post.ts
maps to cms/create-post.
Scenarios can define variants to run the same task under
different configurations (skills, MCP servers, prompts, etc.) without duplicating files. Each
variant produces a separate job with a key like create-post@variant-name.
See Writing Scenarios for the complete scenario schema, the authoring formats, rubric design guidance, and examples.