Policy

What agents on your machine may do. A top-level block in profile.config.ts, beside extensions and settings — never inside them.

// profile.config.ts
export default defineProfile({
    policy: {
        shell: {
            allow: ["*"],                // any program, but...
            args:  { "*": "escalate" },  // ...every invocation asks
            raw:   false,                // and never through sh -c
            spawn: false,                // long-lived children: off entirely
        },
        env: { allow: [] },              // no host variables reach any agent
        fs:  { read: ["~/work"], write: ["~/work/output"] },
    },
})

Declaring nothing means no opinion, not denied — every capability falls through to the agent's own policy. A machine with no profile policy runs agents with your privileges, which is the right default for your own tools and the wrong one for anything you installed.

Why this is yours and not the agent's

Two configs can declare policy, and they answer different questions.

axon.config.ts is a statement about the agent: "this agent only reads ./src". The author knows what their agent needs, and says so.

profile.config.ts is a statement about your machine: "nothing on my laptop touches ~/.ssh". That cannot depend on every agent author agreeing with you forever — which is exactly why it lives here, where you can write it once and have it hold for an agent you installed and never read.

A ceiling, not a default

An agent narrows within your policy and can never widen past it. The stricter of the two layers wins on every call.

Your profile saysThe agent saysResult
nothinganythingthe agent's rule
trueanythingthe agent's rule
falseanythingdenied
"escalate"anything but falseescalate
a globa globboth carried, evaluated per call

That last row is what makes the ceiling hold. Two glob rules are never merged into one, because allow: ["git status"] unioned with allow: ["git push"] would permit a command neither layer allowed on its own. Both are kept and evaluated against the real subject at call time, so widening is impossible by construction — and a denial can name which layer decided it.

List surfaces intersect rather than union: net.allow and env.allow keep only what both layers admit, denylists union, and shell.raw is an AND. A profile that turned the raw shell off cannot have it re-enabled by an agent.

A blanket "*": "escalate" therefore reaches every capability an agent names — a ceiling a specific grant cannot punch through.

It applies whether or not the terminal is running

Your policy is read from profile.config.ts on every agent loadaxon dev, axon run inside a script, an agent spawned by another agent. It is parsed directly rather than imported and evaluated, so it never depends on the terminal having loaded your config.

That is deliberate, and it is the difference between a ceiling and a suggestion: a rule that only applied when the TUI happened to be open would be advisory, and advisory is not a ceiling.

What each capability accepts

The block is the same shape an agent declaresfs, net, shell, env, tools, limits, isolation — read as a ceiling instead of a grant. One grammar, documented once:

policy → — every field, what it accepts, and what each of the two enforcement layers can express.

When a rule pauses a call

An "escalate" rule does not deny — it asks you. The call pauses, you answer, and answering "allow always" writes a grant that stops it asking again.

A grant can only ever satisfy an escalation. It can never overturn a deny from either layer: "stop asking me this" and "override the ceiling" are different acts, and one keypress must not undo a machine-wide rule you set deliberately.

Escalations → — the prompt, the three outcomes, and where grants are written.

See also

profile.config.ts — the file this lives in. Settings — the other top-level block. Kernel & Policy — why the two-layer split exists.