Writing a policy
A policy is a small declarative document attached to one agent. The engine evaluates every tool call against it and returns one of three outcomes.
The model
Section titled “The model”Each policy rule is shaped like:
permit(principal, action, resource) when { <condition> };forbid(principal, action, resource) when { <condition> };Where:
principalis the agent — usuallyAgentum::Agent::"<uuid>".actionis the tool name —Action::"call_tool"for tool calls,Action::"http_request"for raw HTTP, etc.resourceis what the call is touching — the tool name itself, a URL, a database table.
The engine evaluates all rules. If any forbid matches, the outcome is deny. If at
least one permit matches and no forbid matches, the outcome is allow. Otherwise
the outcome is deny (deny-by-default).
The simplest possible policy
Section titled “The simplest possible policy”Allow the agent to call any tool:
permit(principal == Agentum::Agent::"9da9afd6-7158-42b1-ada4-9c84dd4c4d67", action, resource);Useful for getting started. Not useful in production.
A real-world example
Section titled “A real-world example”support-bot should be able to read the knowledge base and create tickets, but
escalate to a human before deleting one:
// allow read-only KB queriespermit( principal == Agentum::Agent::"<bot-uuid>", action == Action::"call_tool", resource is Agentum::Tool) when { resource.name == "search_kb" };
// allow ticket creationpermit( principal == Agentum::Agent::"<bot-uuid>", action == Action::"call_tool", resource is Agentum::Tool) when { resource.name == "create_ticket" };
// require human approval on ticket deletion@advice("require_hitl:Ticket deletion is irreversible — verify customer intent")permit( principal == Agentum::Agent::"<bot-uuid>", action == Action::"call_tool", resource is Agentum::Tool) when { resource.name == "delete_ticket" };
// forbid everything else (deny-by-default already does this, but explicit is clearer)forbid( principal == Agentum::Agent::"<bot-uuid>", action == Action::"call_tool", resource is Agentum::Tool) when { !(resource.name in ["search_kb", "create_ticket", "delete_ticket"]) };Attaching a policy to an agent
Section titled “Attaching a policy to an agent”-
Save the policy to a
.cedarfile:Terminal window cat > support-bot.cedar <<'EOF'permit(principal == Agentum::Agent::"<bot-uuid>", action, resource);EOF -
Install it via the CLI or the dashboard:
Terminal window agentum policy set <bot-uuid> support-bot.cedarOr hit the API directly:
Terminal window curl -X PUT http://localhost:3000/api/v1/policies/<bot-uuid> \-H "X-API-Key: $LUPID_API_KEY" \-H "Content-Type: application/json" \-d "{\"cedar_source\":$(jq -Rs . support-bot.cedar)}" -
Reload so live decisions pick up the change. The API does this automatically when you
PUTa policy; if you edited a.cedarfile on disk underPOLICIES_DIR, you can force a reload:Terminal window agentum policy reload
Test before you ship
Section titled “Test before you ship”The simulate endpoint evaluates a (principal, action, resource) tuple without
side effects:
agentum policy simulate \ --agent-id <bot-uuid> \ --action call_tool \ --resource search_kbReturns allow, deny, or require_hitl along with the matched rule id. Run it for
every (action, resource) combination the agent will see during normal operation;
anything unexpected here will be a runtime denial in prod.
Default policy modes
Section titled “Default policy modes”When you register an agent without supplying a policy:
- In observe mode (
gateway_mode: "observe"inagentum.yaml), Lupid auto-applies a permit-all template scoped to the agent. Useful for staging where you want the audit trail but not the enforcement. - In enforce mode (the default), Lupid registers the agent with an empty policy set and logs a warning. Every call denies until you attach a policy.
@advice annotations
Section titled “@advice annotations”The engine recognizes a small set of advice annotations on permit rules. The most
useful is require_hitl:<reason>, which sends matching calls to the
human approval queue before they
go out. The reason string is shown to the approver.
Other annotations live in the policy module but the opensource build only wires up
require_hitl end-to-end.