Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/microsoft/agent-governance-toolkit/llms.txt

Use this file to discover all available pages before exploring further.

The policy engine is the single enforcement point through which every governed agent action flows. Before any tool is called, any response is returned, or any resource is accessed, the engine evaluates declarative rules against the agent’s runtime context and returns a structured decision — typically in sub-millisecond time. It operates at two layers: a declarative layer of YAML/JSON PolicyDocument files evaluated by the PolicyEvaluator, and an integration layer of GovernancePolicy objects applied by framework adapters to intercept tool calls, enforce token limits, and check blocked patterns at runtime.

Policy YAML Syntax

Every policy file follows the same schema. The version, name, and description fields provide metadata; rules lists the enforcement logic; and defaults defines the fallback when no rule matches.
version: "1.0"
name: my-policy
description: What this policy enforces

rules:
  - name: rule-name
    condition:
      field: <context-key>     # dot-path into the execution context
      operator: <operator>
      value: <comparison-value>
    action: allow | deny | audit | block
    priority: 100              # higher = evaluated first
    message: Human-readable explanation

defaults:
  action: allow                # fallback when no rule matches
  max_tokens: 4096
  max_tool_calls: 10
  confidence_threshold: 0.8
The defaults.action field defaults to deny in the Python SDK so that a policy file with no matching rules does not silently permit actions. Set it to allow explicitly if your intent is a permissive baseline.

PolicyDocument fields

FieldTypeRequiredDefaultDescription
versionstringNo"1.0"Schema version identifier
namestringNo"unnamed"Human-readable name for audit logs
descriptionstringNo""Free-form description
rulesarrayNo[]Ordered list of PolicyRule objects
defaultsobjectNosee belowFallback settings when no rule matches
inheritbooleanNotrueWhether parent policies are loaded during folder-level discovery
scopestring or nullNonullGlob pattern restricting which action paths this policy applies to

PolicyRule fields

FieldTypeRequiredDefaultDescription
namestringYesUnique rule identifier within the document
conditionPolicyConditionYesThe matching condition (field/operator/value triple)
actionPolicyActionYesAction to take when the condition matches
priorityintegerNo0Higher values are evaluated first
messagestringNo""Explanation included in decisions and audit entries
overridebooleanNofalseIf true, replaces a parent rule with the same name during folder-level merge

PolicyDefaults fields

FieldTypeDefaultDescription
actionPolicyAction"deny"Default action when no rule matches
max_tokensinteger4096Maximum tokens per request
max_tool_callsinteger10Maximum tool invocations per request
confidence_thresholdfloat0.8Minimum confidence score (0.0–1.0)

Condition Operators

A PolicyCondition contains exactly three fields: field (a dot-path into the execution context), operator, and value.
OperatorSemanticsExample
eqContext value equals target valuetool_name eq "execute_code"
neContext value does not equal target valueagent_id ne "admin"
gtContext value is greater than targettoken_count gt 4096
ltContext value is less than targetconfidence lt 0.8
gteContext value is greater than or equal to targetconfidence gte 0.8
lteContext value is less than or equal to targetretries lte 3
inContext value is a member of target collectiontool_name in ["read", "write"]
not_inContext value is NOT a member of target collectiontool_name not_in ["execute_code", "run_shell"]
containsTarget value is contained within context valuearguments contains "password"
matchesContext value matches target regex (search semantics)tool_name matches "^exec_.*"
If the condition references a context field that does not exist (returns null), the condition evaluates to false. A missing field is never an error — it simply does not match.

Policy Actions

ActionAllowed?Semantics
allowYesThe action is permitted. decision.allowed = True.
denyNoThe action is blocked. The agent must not proceed.
auditYesThe action is permitted but must be logged for review. decision.allowed = True, entry written to audit trail.
blockNoAlias for deny. Hard block with the rule message surfaced to the caller.
Actions allow and audit are considered allowing. Actions deny and block are considered denying.

Conflict Resolution

When multiple policies produce competing decisions for the same agent action, the PolicyConflictResolver in agentmesh.governance.conflict_resolution applies one of four strategies.

deny_overrides

Any deny wins. Among multiple denies, highest priority wins. This is the safest strategy and aligns with XACML deny-overrides semantics. Use for enterprise deployments with hard deny guardrails.

allow_overrides

Any allow wins. Among multiple allows, highest priority wins. Use for zero-trust baseline with explicit grants — exception-based governance where allows should override default-deny policies.

priority_first_match

Candidates are sorted by priority descending; the highest-priority candidate wins regardless of action type. This is the default strategy and mirrors how PolicyEvaluator resolves rules within a single policy.

most_specific_wins

Candidates are ranked by scope specificity: Agent > Organization > Tenant > Global. Within the same scope, priority breaks ties. Use for multi-tenant setups with org → team → agent layering.
from agentmesh.governance.conflict_resolution import (
    PolicyConflictResolver,
    ConflictResolutionStrategy,
    CandidateDecision,
    PolicyScope,
)

resolver = PolicyConflictResolver(ConflictResolutionStrategy.DENY_OVERRIDES)

candidates = [
    CandidateDecision(
        action="allow", priority=50,
        scope=PolicyScope.GLOBAL, rule_name="allow_web_search",
    ),
    CandidateDecision(
        action="deny", priority=10,
        scope=PolicyScope.AGENT, rule_name="block_internal_access",
    ),
]

result = resolver.resolve(candidates)
assert result.winning_decision.action == "deny"   # deny always wins
assert result.conflict_detected is True

for line in result.resolution_trace:
    print(line)
# "Evaluating 2 candidates with deny_overrides strategy"
# "Found 1 deny candidate(s) — deny overrides"
# "Winner: block_internal_access (deny, priority=10, scope=agent)"
The ResolutionResult fields:
FieldTypeDescription
winning_decisionCandidateDecisionThe decision that prevailed
strategy_usedConflictResolutionStrategyWhich strategy was applied
candidates_evaluatedintNumber of candidates considered
conflict_detectedboolTrue if there was a mix of allow and deny candidates
resolution_tracelist[str]Step-by-step log of the resolution logic

PolicyEvaluator API

PolicyEvaluator is the engine that evaluates PolicyDocument objects against execution contexts.
from agent_os.policies import PolicyEvaluator

# Constructor: optionally supply pre-built policies and/or a root_dir for
# folder-scoped discovery
evaluator = PolicyEvaluator(
    policies=None,       # list[PolicyDocument] | None
    root_dir=None,       # str | Path | None — enables folder-scoped evaluation
)

load_policies(directory)

Loads all .yaml and .yml files from a directory. Can be called multiple times; rules from all loaded documents are merged and sorted by priority.
evaluator = PolicyEvaluator()
evaluator.load_policies("./policies/global/")
evaluator.load_policies("./policies/team-specific/")
evaluator.load_policies("./policies/agent-overrides/")

evaluate(context, dynamic_context=None)

Evaluates all loaded policy rules against the given context dictionary. Returns a PolicyDecision.
decision = evaluator.evaluate({
    "tool_name": "execute_code",
    "token_count": 500,
    "agent_id": "research-agent",
})
print(decision.allowed, decision.reason)
# False  "Code execution is not permitted in this environment"
  • Rules are sorted by priority descending and evaluated in order.
  • The first matching rule determines the decision.
  • If no YAML rule matches, external backends are consulted in registration order.
  • If no backend produces a result, the default action from the first loaded policy applies.

add_backend(backend)

Registers an external policy backend. Backends are consulted in registration order when no YAML rule matches.
evaluator.add_backend(my_opa_backend)

Convenience backend loaders

# OPA/Rego backend
opa = evaluator.load_rego(
    rego_path="./policies/authz.rego",
    package="agentos",
    mode="local",       # "local" | "remote" | "builtin"
)

# Cedar backend
cedar = evaluator.load_cedar(
    policy_path="./policies/authz.cedar",
    entities=[{"uid": {"type": "Agent", "id": "agent-1"}, ...}],
    mode="auto",        # "auto" | "cedarpy" | "cli" | "builtin"
)

PolicyDecision Fields

Every call to evaluator.evaluate() returns a PolicyDecision:
allowed
bool
Whether the action is permitted. True for allow and audit actions; False for deny and block.
matched_rule
str | None
Name of the rule that fired. None if defaults applied or a backend was used.
action
str
The action taken: "allow", "deny", "audit", or "block".
reason
str
Human-readable explanation — the rule’s message field, or a default description.
audit_entry
dict
Structured audit metadata including policy name, rule name, action, context snapshot, and UTC timestamp.
metadata
dict
Structured adaptation hints from dynamic-context rules (e.g., backoff_seconds, blocked_tools, retry_after).
The audit_entry structure for a matched rule:
{
    "policy": "production_safety",
    "rule": "block_code_execution",
    "action": "deny",
    "context_snapshot": {"tool_name": "execute_code", ...},
    "timestamp": "2025-01-15T10:30:00Z",
}
For folder-scoped evaluations, audit_entry also includes policy_chain — the ordered list of policy names in the merge chain. For backend evaluations, it includes backend and evaluation_ms.

External Policy Backends

AGT ships with OPABackend (OPA/Rego) and CedarBackend. Both implement the ExternalPolicyBackend protocol: a name property and an evaluate(context) method returning a BackendDecision.
1

Register an OPA backend

from agent_os.policies.backends import OPABackend

backend = OPABackend(
    rego_content="""
package agentos
default allow = false
allow { input.tool_name == "web_search" }
""",
    package="agentos",
    mode="builtin",
)
evaluator.add_backend(backend)
2

Register a Cedar backend

from agent_os.policies.backends import CedarBackend

backend = CedarBackend(
    policy_content="""
permit(principal, action == Action::"invoke", resource)
when { resource.name == "web_search" };
""",
    mode="cedarpy",
)
evaluator.add_backend(backend)
3

Evaluate — YAML rules run first

# YAML rules are evaluated first.
# If no YAML rule matches, OPA is consulted; then Cedar.
# The first backend returning error=None determines the decision.
decision = evaluator.evaluate({"tool_name": "web_search"})
print(decision.audit_entry["backend"])  # "opa" or "cedar"
A BackendDecision carries: allowed (bool), action (str), reason (str), backend (str), evaluation_ms (float | None), and error (str | None).

Fail-Closed Semantics

The fail-closed behavior is a deliberate security boundary. Never change the default to fail-open.
The policy engine enforces fail-closed semantics on all evaluation errors:
  • If the engine encounters an unhandled exception during evaluation, it denies the action.
  • If a registered backend returns error != null, the engine immediately denies and does not fall through to the next backend or the default action.
  • If no policies are loaded at all, the default decision is deny (matching the TypeScript and .NET SDKs).
A fail-closed decision always returns:
PolicyDecision(
    allowed=False,
    action="deny",
    reason="Policy evaluation error — access denied (fail closed)",
    audit_entry={
        "policy": None,
        "rule": None,
        "action": "deny",
        "context_snapshot": { ... },
        "timestamp": "...",
        "error": True,
    },
)
All fail-closed events are logged at ERROR level with full exception context.

Code Examples

from agent_os.policies import PolicyEvaluator

evaluator = PolicyEvaluator()
evaluator.load_policies("./policies/")   # loads every .yaml/.yml in the dir
decision = evaluator.evaluate({"tool_name": "execute_code", "token_count": 500})
print(decision.allowed, decision.reason) # False, "Code execution is blocked …"

Folder-Level Policy Hierarchy

When root_dir is configured on PolicyEvaluator, governance files are discovered by walking the directory tree from the action path up to the root. At each level, governance.yaml is loaded. The chain is assembled root-first and merged into a single flat rule list. The deny immutability invariant ensures that a parent deny rule can never be overridden by a child rule — even when the child sets override: true and a higher priority. This prevents more specific policies from defeating security-critical denials set at higher levels, matching Azure Policy semantics.
# /root/governance.yaml — deny is immutable
rules:
  - name: no-delete
    condition:
      field: tool_name
      operator: eq
      value: delete_resource
    action: deny
    priority: 200

# /root/dev/governance.yaml — this override attempt is DROPPED
rules:
  - name: no-delete
    condition:
      field: tool_name
      operator: eq
      value: delete_resource
    action: allow
    priority: 300
    override: true      # dropped: parent rule is deny

Build docs developers (and LLMs) love