Nous.Permissions.Policy (nous v0.17.2)

Copy Markdown View Source

Permission policy controlling tool access.

Defines which tools are denied, which require approval, and the overall permission mode.

Modes

  • :default — read/search tools are open, write/execute tools require approval
  • :permissive — all tools are open, none require approval
  • :strict — all tools require approval; at the filter layer, ONLY tools in :allow_names / :allow_prefixes are exposed (deny-by-default).

Examples

# Custom policy that blocks bash and requires approval for write tools
%Nous.Permissions.Policy{
  deny_names: MapSet.new(["bash"]),
  approval_required: MapSet.new(["file_write", "file_edit"]),
  mode: :default
}

# Strict + explicit allowlist
%Nous.Permissions.Policy{
  mode: :strict,
  allow_names: MapSet.new(["file_read", "search_web"])
}

Summary

Functions

Combine two policies so the result is at least as strict as both.

Types

mode()

@type mode() :: :default | :permissive | :strict

t()

@type t() :: %Nous.Permissions.Policy{
  allow_names: MapSet.t(),
  allow_prefixes: [String.t()],
  allow_unattended_execute: boolean(),
  approval_required: MapSet.t(),
  deny_names: MapSet.t(),
  deny_prefixes: [String.t()],
  mode: mode()
}

Functions

strictest(a, b)

@spec strictest(t() | nil, t() | nil) :: t() | nil

Combine two policies so the result is at least as strict as both.

Used when execution policy must inherit across delegation (e.g. Nous.Plugins.SubAgent): the parent's policy always applies, and a sub-agent template may only narrow it, never widen it. nil means "no policy" and yields the other argument unchanged.

Combination rules:

  • mode — the stricter of the two (:strict > :default > :permissive)
  • deny_names / deny_prefixes / approval_required — union
  • allow_unattended_execute — logical AND
  • allowlists — an allowlist means deny-by-default (see Nous.Permissions.blocked?/2), and a :strict policy's allowlist is its entire grant (empty = allow nothing, not "no opinion"). If only one side constrains the allow set, its list stands; if both do, the result allows only what BOTH sides allow. When that combination allows nothing, the result is pinned to mode: :strict with an empty allowlist — blocked?/2 reads that as deny-everything, whereas an empty allowlist on :default/:permissive would read as "no allowlist" and fail open.