Policy

The policy block in axon.config.ts controls what the agent's capsule subprocess is allowed to do. Enforcement is structural — not advisory, not a prompt hint. If a call violates the policy, it does not execute.

export default defineAgent({
    policy: {
        fs: {
            read:  ["./src/**", "./package.json"],
            write: ["./src/**", "./CHANGELOG.md"],
            deny:  ["**/.env*", "**/node_modules/**"],
        },
        network: {
            allow: ["api.github.com"],
        },
        process: {
            allow: ["git *", "bun *"],
            deny:  ["git push --force*"],
        },
    },
})

No policy block means unrestricted access, not denied access. A missing fs block gives the agent full filesystem access. Development agents often run without any policy — that is intentional. For anything deployed or published, declare explicit rules.

Enforcement layers

Policy is applied at two independent layers. You write it once. The runtime applies it at every layer available on the host.

Mediator gates run inside the capsule on every platform. Every tool call is evaluated against the policy rules before the function executes. A denied call never reaches the function body.

OS-level enforcement runs on Linux when user is set. The capsule subprocess runs as a different system user. Filesystem ACLs derived from the fs policy are applied at boot. Sensitive env vars are stripped before the process starts. This layer is independent of the mediator — it closes gaps the mediator can't address.

Writing policy

Mediator-only (all platforms)

The default. Declare fs, network, and process rules. The mediator enforces them.

export default defineAgent({
    policy: {
        fs: {
            read:  ["./src/**", "./tests/**", "./package.json"],
            write: ["./src/**"],
            deny:  ["**/.env*", "**/*.key", "**/*.pem", "**/node_modules/**"],
        },
        network: {
            allow: ["api.github.com"],
        },
        process: {
            allow: ["git status", "git diff *", "git add *", "git commit *", "bun test"],
            deny:  ["git push --force*", "rm -rf *"],
        },
    },
})

With OS-level enforcement (Linux)

Add user to run the capsule subprocess as a dedicated system user. The runtime handles everything else automatically.

export default defineAgent({
    policy: {
        fs: {
            read:  ["./src/**", "./tests/**", "./package.json"],
            write: ["./src/**"],
            deny:  ["**/.env*", "**/*.key", "**/node_modules/**"],
        },
        network: {
            allow: ["api.github.com"],
        },
        process: {
            allow: ["git *", "bun test"],
            deny:  ["git push --force*"],
        },
        user: "axon-agent",
        keepEnv: ["GITHUB_TOKEN"],
    },
})

At boot, the runtime applies setfacl ACLs for axon-agent derived from the fs policy, strips sensitive env vars (preserving GITHUB_TOKEN from keepEnv), and launches the capsule as axon-agent. The subprocess and everything it spawns runs as that user.

On macOS and Windows, user has no effect. OS enforcement is Linux only.

Escalation

For calls where a static allow/deny isn't expressive enough, escalation pauses execution and surfaces the call for human review.

Escalation can be declared per-domain with pattern lists:

export default defineAgent({
    policy: {
        process: {
            allow:    ["git *"],
            escalate: ["git push*"],
        },
        fs: {
            read:     ["./**"],
            escalate: ["./secrets/**"],
        },
    },
})

Or programmatically with a function when the condition depends on the arguments:

export default defineAgent({
    policy: {
        escalate: call => {
            // call.fn   — e.g. "fs.write", "process.spawn"
            // call.args — the arguments passed to the operation
            return call.fn === "process.spawn" && call.args[0]?.toString().includes("--force")
        },
    },
})

When a call escalates, the TUI shows the function, module, and arguments and waits for approval or denial. In headless execution with no TUI attached, escalations fail closed after the timeout (default 30 seconds, configurable via resources.escalationTimeoutMs).

Per-invocation narrowing

The base policy applies to every invocation. Scripts and routes can narrow it for a single call:

// server/api/support.post.ts
export default defineEventHandler(async event => {
    const ticket = await readBody(event)
    const prompt = await axon.prompt("support-triage", { ticketId: ticket.id })

    return axon.request({
        prompt,
        policy: {
            fs:      { read: false, write: false },
            network: { allow: ["api.stripe.com"] },
            process: { allow: [] },
        },
    })
})

Narrowing can only restrict. A call cannot grant access to something the base policy already denies. The narrowed policy is cleared when the invocation completes.

Resource limits

Alongside access control, policy accepts resource limits that cap what the agent can consume across a session:

export default defineAgent({
    policy: {
        fs: { read: ["./**"] },
        resources: {
            maxMemoryMb:        512,
            maxCommandTimeMs:   30_000,
            maxFileReadBytes:   10_000_000,
            maxNetworkRequests: 50,
        },
    },
})

For the complete field reference — all options, types, and defaults — see Policy reference.