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.