axon.config.ts

Every agent has one. It declares the agent's identity, which engine it uses, what the capsule is allowed to do, and what environment the capsule sees. Everything else in the folder is discovered automatically — this is the one file Axon explicitly reads at boot.

import { defineAgent } from "@axon/sdk"
import { Axon } from "@axon/engines"

export default defineAgent({
    identity: {
        name: "barry",
        description: "spec-driven automation agent for the arclabs monorepo",
    },

    engine: Axon(),

    policy: {
        fs: {
            read:  ["./**"],
            write: ["./**"],
            deny:  [".env", "**/node_modules/**"],
        },
        network: {
            allow: ["api.github.com", "api.linear.app"],
        },
        procs: {
            allow: ["git *", "bun *"],
            deny:  ["git push --force*"],
        },
    },

    env: {
        passthrough: ["PATH", "HOME"],
        needs: ["GITHUB_TOKEN", "LINEAR_API_KEY"],
        describe: {
            GITHUB_TOKEN:   "Open PRs and read repo state",
            LINEAR_API_KEY: "Read and update Linear issues",
        },
    },

    workspace: true,
})

identity

identity: {
    name:        "barry",
    description: "spec-driven automation agent for the arclabs monorepo",
}

name is how the agent appears in the TUI, the CLI, and the registry. It matches the folder name. description surfaces in the command palette and registry listings — write it for someone scanning a list.

engine

import { Axon, Cerebras, Codex, Ollama, OpenRouter } from "@axon/engines"

engine: Axon()                                      // Axon Cloud (default)
// engine: Codex()                                  // ChatGPT subscription via OAuth
// engine: Cerebras({ model: "gpt-oss-120b" })      // Cerebras Inference
// engine: Ollama({ model: "qwen2.5:7b" })          // local inference
// engine: OpenRouter({ model: "openai/gpt-4o" }) // BYOK, 100+ models

Engine functions come from @axon/engines. The connected provider in the TUI (* to switch) overrides this when running locally — the config value is the default for headless and deployed runs. See Get Connected for setup.

policy

Controls what the capsule subprocess is allowed to do. Three rule groups plus an optional escalate callback for decisions that need context.

policy: {
    fs: {
        read:  ["./src/**"],
        write: ["./src/**"],
        deny:  [".env", "**/node_modules/**"],
    },
    network: {
        allow: ["api.github.com"],
    },
    procs: {
        allow: ["git *", "bun *"],
        deny:  ["git push --force*"],
    },
    escalate: call =>
        call.fn === "proc.spawn" && call.args[0]?.includes("push --force"),
}

A missing block means unrestricted access in that domain — no fs block means the capsule can read and write anywhere. For production agents, always declare explicit rules.

See Policy for the full reference.

env

Controls what environment the capsule subprocess sees. The capsule is isolated by default — it only sees what's explicitly given to it.

env: {
    passthrough: ["PATH", "HOME"],   // pull these from the host shell
    cwdEnv: true,                    // also load .env from the working directory
    needs: ["GITHUB_TOKEN"],         // advisory — shown at install time
    describe: {
        GITHUB_TOKEN: "Open PRs and read repo state",
    },
}

Secrets go in .env. Keys from your shell environment that the agent needs go in passthrough. See Environment for the full reference.

workspace

workspace: true

Opts the agent into the nearest .agents/ folder in the directory tree. When true, Axon walks upward from the agent's working directory, finds the workspace layer, and merges its tools, prompts, scripts, and modules into the agent's runtime.

See Workspace Agents for the full guide.

Why one file

Everything declared in one TypeScript file means the agent is readable at a glance. One file changed is one diff to review. The escalate callback can contain real logic — conditions, pattern matching, context-sensitive decisions — which a JSON config file can't.