Skip to content

Argument & Semantic Policies

Guardrails look at content. Policies look at behavior: what a tool call is actually about to do. Brutor has two policy engines:

Argument policies Semantic policies
Mechanism Deterministic analyzers (SQL parser, URL/shell/path/JSON inspection) LLM judge evaluating a natural-language constraint
Latency / cost Microseconds, free One judge-model call per evaluated request
Determinism 100% reproducible Model judgment — use shadow mode first
Best for “No writes to the sales DB”, “no requests to private IPs” “Agents must never commit to pricing or discounts”

Use argument policies wherever the rule can be expressed structurally. Reach for semantic policies when the rule lives in meaning, not syntax.

An argument policy targets one argument of one tool (or all tools) on a surface, runs a deterministic analyzer over the value, and denies or warns based on rules.

Fields: name, description, surface (llm_output_tool_calls | mcp_input | skill_input), target (tool/skill name, or "*"), argument_key (which JSON argument to analyze), analyzer (sql | url | shell | path | json), rules, severity (deny | warn), enabled, group_ids (resource-group binding).

llm_output_tool_calls inspects tool calls the LLM proposes in its output (before your agent executes them); mcp_input and skill_input inspect calls arriving at the MCP/skill gateway.

Argument policy rule editor

Terminal window
curl -X POST http://localhost:5050/v1/admin/argument-policies \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "sales-db-read-only",
"description": "The sales DB tool may only run single read statements",
"surface": "llm_output_tool_calls",
"target": "query_sales_db",
"argument_key": "sql",
"analyzer": "sql",
"severity": "deny",
"enabled": true,
"rules": [
{ "deny_if": "writes", "message": "Write statements are not allowed on the sales DB" },
{ "deny_if": "multi_statement", "message": "One statement per call" },
{ "deny_if": "touches_any", "values": ["salaries", "employees_pii"], "message": "Restricted tables" }
],
"group_ids": ["9f2c..."]
}'

A denied call is rejected before execution and logged with argument_policy_denied = true in the proxy logs. With severity: "warn" the call proceeds but the violation is recorded.

Rules are {deny_if | allow_only_if, values?, message?}. Predicates marked * require values.

Analyzer Predicates
sql writes, multi_statement, statement_type_in, statement_type_not_in, touches_any, touches_none_of, subquery_depth_gt*
url host_in_private_range, scheme_in, scheme_not_in, host_in, host_not_in
shell command_in, command_not_in, flag_present, flag_not_present
path traverses_parent, absolute_required, inside_root, outside_root
json schema_invalid, field_present, field_not_present

Policies can be exported and versioned as YAML: GET /v1/admin/argument-policies/export.yaml / POST /v1/admin/argument-policies/import.yaml. Group bindings are set with PATCH /v1/admin/argument-policies/{id}/groups.

A semantic policy is a natural-language constraint enforced by an LLM judge. The gateway sends the request content plus your constraint_text to a judge model and blocks or warns based on the verdict.

Fields: name, description, constraint_text (the natural-language rule), action (block | warn), mode (shadow | enforce), surfaces (any of chat_input, mcp_input, a2a_inbound, a2a_outbound, skill_input), judge_model_id (a specific model to judge with; null = tenant default), enabled, resource_group_ids.

Semantic policy editor — the constraint in plain language

Scope: when resource_group_ids is empty or omitted, the policy is tenant-wide; otherwise it applies only to the listed resource groups.

Terminal window
curl -X POST http://localhost:5050/v1/admin/semantic-policies \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "no-pricing-commitments",
"description": "Customer-facing agents must never commit to pricing",
"constraint_text": "The request must not ask the assistant to quote, promise, or negotiate prices, discounts, or contractual terms on behalf of the company.",
"action": "block",
"mode": "shadow",
"surfaces": ["chat_input", "a2a_inbound"],
"enabled": true,
"resource_group_ids": []
}'
{
"id": "smp-...",
"name": "no-pricing-commitments",
"action": "block",
"mode": "shadow",
"surfaces": ["chat_input", "a2a_inbound"],
"enabled": true
}

Never launch a semantic policy directly in enforce mode. The judge is a model; you want evidence of its judgment on your traffic first.

  1. Create in mode: "shadow". The judge runs on live traffic and records would-block decisions (proxy_subtype = semantic_policy_shadow in the proxy logs) — nothing is actually blocked.

  2. Review shadow hits in the Governance view and the proxy logs for a few days. Tighten constraint_text until false positives are acceptably rare. A more capable judge_model_id often beats a longer constraint.

  3. Flip to mode: "enforce" with PATCH /v1/admin/semantic-policies/{id} {"mode": "enforce"}. Blocks now return errors to callers and log as semantic_policy_block.

  4. Keep action: "warn" as a middle ground — enforce mode, but violations only log and alert rather than block.

  • Rule mentions a table, host, path, flag, or field → argument policy. It’s free, instant, and never hallucinates.
  • Rule mentions intent, topic, or tone (“no legal advice”, “no competitor disparagement”) → semantic policy, rolled out through shadow mode.
  • Many real controls use both: an argument policy guarantees the SQL is read-only, while a semantic policy watches for the agent being talked into exfiltration-shaped requests.

Policy denials and shadow hits are first-class governance events: they appear in Mission Control → Governance, in the audit trail, and count toward compliance tagging where a profile is enabled.