ExAgent.Permissions (ExAgent v1.1.0)

Copy Markdown View Source

Per-tool admission control: :allow, :ask or :deny, matched against tool names with glob patterns — the same model opencode uses for tool safety.

Rules are evaluated in order and the last matching rule wins (so put a catch-all * first, then more specific rules after it). Anything that matches no rule falls back to :default (:allow by default).

  • :allow — run the tool.
  • :deny — never run it; the model receives a "permission denied" tool return so it can adapt.
  • :ask — require human approval. In a one-shot ExAgent.run/3 you pass an :approve callback (tool_call -> :approve | :deny); the run calls it (it may block on a PubSub round-trip in a LiveView). With no callback, :ask is treated as :deny (fail closed).

Example

perms =
  ExAgent.Permissions.new!(
    rules: [{"*", :deny}, {"read", :allow}, {"search_*", :allow}, {"bash", :ask}],
    default: :deny
  )

ExAgent.Permissions.decide(perms, "bash")        #=> :ask
ExAgent.Permissions.decide(perms, "search_web")  #=> :allow
ExAgent.Permissions.decide(perms, "write")       #=> :deny

Summary

Functions

Decide the action for a tool name (last matching rule wins, else default).

Build a permissions set from rules ([{glob_string, action}]) and a :default action. Globs support * (any run of chars) and ? (single char).

Resolve an :ask decision through an optional approve callback.

Types

action()

@type action() :: :allow | :ask | :deny

t()

@type t() :: %ExAgent.Permissions{default: action(), rules: [{Regex.t(), action()}]}

Functions

decide(permissions, tool_name)

@spec decide(t(), String.t()) :: action()

Decide the action for a tool name (last matching rule wins, else default).

new!(opts)

@spec new!(keyword()) :: t()

Build a permissions set from rules ([{glob_string, action}]) and a :default action. Globs support * (any run of chars) and ? (single char).

resolve(action, tool_call, approve)

@spec resolve(action(), term(), (term() -> :approve | term()) | nil) :: action()

Resolve an :ask decision through an optional approve callback.

  • :allow:allow.
  • :deny:deny.
  • :ask with a callback → calls it with tool_call and maps :approve to :allow, anything else to :deny.
  • :ask without a callback → :deny (fail closed).