Policy

The policy block in axon.config.ts controls what the capsule subprocess is allowed to do. It is enforced at the execution layer — not advisory, not a prompt hint, not a system message asking the model to be careful. If a call violates the policy, it does not execute.

The capsule is a separate Bun subprocess. Every tool call the agent makes is an IPC call into that process. The policy check runs as a gate inside the capsule, before the function executes. The agent process never touches the filesystem, network, or shell directly — it only has the IPC channel. There is no path around the policy.

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

Policy controls access, not existence

The capsule always has filesystem, network, and process capabilities — they are built in. Policy determines what part of those capabilities the agent can reach. A missing fs block means unrestricted filesystem access, not no filesystem access.

The default agent ships without an fs block because development agents need broad filesystem access to be useful. Production agents and published modules should always declare explicit rules.

A locked-down production policy:

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

This agent can read and write its own source, make GitHub API calls, run git and tests — and nothing else.

fs — filesystem rules

fs: {
    read:  string[]   // glob patterns the agent may read
    write: string[]   // glob patterns the agent may write
    deny:  string[]   // deny overrides read and write — checked first
}

Patterns follow standard glob syntax. Paths are relative to the capsule's working directory.

fs: {
    read:  ["./**"],                         // read anything in cwd
    write: ["./src/**", "./CHANGELOG.md"],   // write only to src/ and changelog
    deny:  [".env", "**/*.key", "**/node_modules/**"],  // never these
}

deny is checked before read and write. A path that matches deny is blocked even if it also matches read or write.

network — outbound network rules

network: {
    allow: string[]   // hostname allowlist
    deny:  string[]   // hostname blocklist (deny overrides allow)
}

Matches against the hostname of the request. Subdomains must be listed explicitly or use a wildcard:

network: {
    allow: ["api.github.com", "*.linear.app"],
}

No network block means unrestricted outbound access. An empty allow array with no deny means all network access is blocked.

procs — process spawning

procs: {
    allow: string[]   // command patterns — glob-style matching against the full command string
    deny:  string[]   // deny overrides allow
}
procs: {
    allow: ["git *", "bun *", "npm *"],
    deny:  ["git push --force*", "rm -rf *"],
}

Patterns match against the full command string passed to axon.proc.spawn(). Use * for wildcards. deny is checked before allow.

No procs block means unrestricted process spawning. For production agents, always declare a procs policy.

escalate — programmatic decisions

For cases where a static rule isn't expressive enough, escalate lets you write a function that inspects a call and decides whether to surface a TUI prompt before allowing it:

escalate: call => {
    // call.fn   — the capability being called: "fs.write", "proc.spawn", etc.
    // call.args — the arguments passed to the call
    // return true  → pause execution, show TUI prompt, wait for approval
    // return false → allow (assuming it passes the static rules above)
    return call.fn === "proc.spawn" && call.args[0]?.toString().includes("push --force")
}

When escalate returns true, the TUI shows the call details and waits. You approve or deny. If approved, the call proceeds. If denied, it throws and the agent is told the call was blocked.

escalate is for edge cases your static rules can't cleanly express — sensitive operations that are sometimes fine and sometimes not, depending on context.

Request-scoped narrowing

The policy in axon.config.ts is the base policy for 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 })

    const result = await axon.request({
        prompt,
        policy: {
            fs: { read: false, write: false },
            procs: { allow: [] },
            network: { allow: ["api.stripe.com"] },
        },
    })

    return result
})

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

What happens when a call is blocked

A blocked call throws in the capsule subprocess. The agent receives a structured error indicating the call was denied by policy. It will typically surface this to you or adjust its approach.

A call that triggers escalate: true pauses and waits for your input in the TUI. The agent's execution is suspended until you respond.

Both paths are logged to the JSONL session trace.

Policy is published with the agent

When you deploy an agent to the registry, axon.config.ts is included in the published package — which means your policy block is visible to anyone installing the agent. This is intentional: users can see what the agent is allowed to do before they install it.

Keep the policy tight. An agent that requests unrestricted filesystem access will be less trusted and less installed than one with a clear, minimal policy.

For the full capsule model, see Capsule & Policy.