Policy
Every action an agent takes is a request for a capability, with a target (the tool, the path, the host, the server). The policy decides each request: allow it, deny it, or ask a person. An ask pauses the run until someone decides (durable runs); the security model explains the design.How a request is decided
- A deny rule that matches wins.
- Otherwise an ask rule that matches: a person decides. An ask outranks an allow, so adding an allow rule to a profile does not lift that profile’s asks; remove the ask rule, or write your own policy.
- Otherwise an allow rule that matches.
- Otherwise the policy’s mode decides:
permissiveallows,interactiveasks,strictdenies, each with reasonunknown_capabilityand no rule named, so the evidence tells it apart from a rule’s decision.
effect and reason_code
(matched_allow, matched_ask, matched_deny, unknown_capability,
approved when a person allowed an ask, follows_command for the runtime’s own
listing after a sandbox command, and others).
Two capabilities are decided when the agent connects, not during a run:
mcp.server.start and mcp.server.connect. There is no run to pause then, so
an ask on them fails the connection with ApprovalRequiredError; allow the
servers you trust.
The served agent’s policy also decides what an operator asks of OmniServe’s
background API: background.task.* and background.run.*. A refusal there is
an HTTP 403.
Writing a rule
A policy can carry its budgets under a
budgets key: in a dict policy, in a
JSON policy file (policy_path), or as policy.budgets on a policy object.
It takes the same shape as
governance_config["budgets"] (budgets), and the
budgets are hashed with the policy. Set budgets in the policy or in
governance_config, not both: both is refused.
Rules on shell commands
Every command an agent runs reaches the policy asprocess.exec on sh (the
execute tool runs sh -c <command>). A command matcher names a command by
its first words:
How a command is read: only a plain chain (simple commands of literal
words joined by
&&, ||, ;, | or a newline) is split into its commands.
Anything else makes the whole command unreadable: a redirect, $(...) or
backticks, a variable or ~, a glob, a brace, an escape, a leading
NAME=value, a subshell, a group, a heredoc, a function or loop, a command
in the background, a program that runs text as a command (eval,
source, exec, sh -c, trap, alias), or a shell reading
commands from its input (... | sh).
How a command is decided:
- A deny or ask rule applies when any command of a readable line
begins with its prefix:
ls && rm -rf buildis denied by the rule above. - An allow rule applies only when every command is matched by some
allow rule and is named without a path (not
./gitor/tmp/git):git status | headneedsheadallowed too. - An unreadable command is never allowed by a rule. Where the policy has
any command rule for it, it is asked about, in every mode (refused in
strict), with the reason codecommand_opaque: a deny rule onrmis not passed byeval "rm -rf x". - A policy without
commandrules decides exactly as before.
["-r", "-rf", "-fr"]) rather than
patterns. A person asked to approve a command sees each command of a plain
chain on its own line, or the whole text when it is unreadable, with control
and invisible characters shown; the approval is bound to that exact text.
Capabilities
Built-in profiles
Each profile below isbuild_default_policy(name). Start from one and add
rules, or write your own policy.
permissive-dev
Mode permissive.
interactive-dev
Mode interactive.
strict-production
Mode strict.