Writing Policies
Policies are YAML files that define rules for agent actions. TameFlare uses a deny-wins precedence model: if multiple policies match an action and any one returns deny, the action is denied regardless of other policies.
Policy model overview
TameFlare has two enforcement systems that work together:
| System | Mode | How it works | Best for | |---|---|---|---| | Access rules (gateway permissions) | Mode A (proxy) | Per-agent, per-connector allow/deny rules set via CLI or dashboard wizard | Simple setups: "Agent X can use GitHub but not Stripe" | | Policies (JSON) | Mode A + Mode B | Condition-based rules evaluated against the full action spec | Complex logic: "Allow PR creates, require approval for merges to main, deny branch deletes" |
Which takes precedence?
Evaluation order:
- Kill switch — if active (scoped or global), the action is immediately denied
- Access rules — gateway checks if the agent has permission for this connector. If no permission exists, the request is blocked (deny-all default). If the permission is
require_approval, the proxy holds the connection. - Policies — if the action passes access rules, policies are evaluated for fine-grained decisions
Access rules are the first gate. Policies provide additional refinement. An action must pass both to be allowed.
tf permissions set) are sufficient. Policies are for teams that need condition-based logic, risk-based routing, or policy-as-code workflows.When to use each
| Scenario | Use access rules | Use policies |
|---|---|---|
| "Agent X can use GitHub" | Yes | Not needed |
| "Agent X can read GitHub but not write" | Partial (action pattern: github.*.get) | Better with policies for complex patterns |
| "Block merges to main unless approved" | No | Yes — condition on parameters.branch_name |
| "Require approval for transfers over $10k" | No | Yes — condition on parameters.amount |
| "Block all actions during maintenance windows" | No | Yes — time-window policy pack |
Decision algorithm
When an action is evaluated, TameFlare follows this algorithm:
1. Collect all enabled policies whose scope matches the action
2. Sort by priority (highest first)
3. For each policy, evaluate rules in order:
a. First rule whose conditions match → record its decision
b. If no rules match → policy has no effect (skip)
4. Aggregate all decisions using deny-wins:
- If ANY policy returned "deny" → final decision = DENY
- Else if ANY policy returned "requires_approval" → final decision = REQUIRES_APPROVAL
- Else if ANY policy returned "allow" → final decision = ALLOW
- Else (no policies matched) → final decision = DENY (default)
Deny-wins precedence
The precedence order is: deny > requires_approval > allow
| Policy A says | Policy B says | Final decision | Why | |---|---|---|---| | allow | allow | allow | Both agree | | allow | requires_approval | requires_approval | Stricter wins | | allow | deny | deny | Deny always wins | | requires_approval | deny | deny | Deny always wins | | requires_approval | requires_approval | requires_approval | Both agree |
Worked example
Given these policies:
- Policy 1 (priority 900, GitHub Safety): PR merges to
main→requires_approval - Policy 2 (priority 800, Payment Controls): All GitHub actions →
allow - Policy 3 (priority 50, Risk Safety Net): Irreversible + production →
requires_approval
Action: github.pr.merge to main in production, marked irreversible.
- Policy 1 matches →
requires_approval - Policy 2 matches →
allow - Policy 3 matches →
requires_approval - Aggregate:
requires_approvalwins overallow(stricter) - Final: requires_approval
If Policy 1 had said deny instead, the final would be deny regardless of the others.
Default behavior
When no policies match an action (no scope matches, or no rule conditions match):
- Final decision: deny — TameFlare uses a fail-closed model
- Hint provided: the API response includes a
hintfield explaining which action type wasn't covered and suggesting you create a catch-all policy - The audit log records the denial with reason
no_matching_policy
Org-level default
There is no "default allow" setting. The deny-all default is intentional and cannot be overridden globally. To allow actions without specific policies, create a low-priority catch-all policy:
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_catch_all
name: Allow All (development only)
priority: 1
enabled: true
spec:
scope: {}
rules:
- name: allow-everything
conditions:
all:
- field: type
operator: exists
decision: allow
reason: Catch-all allow for developmentFull YAML schema
Every policy follows this structure:
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_unique_id
name: Human-readable name
description: What this policy does
tags: [production, safety]
priority: 100
enabled: true
spec:
scope:
action_types: [github.pr.merge, github.branch.delete]
resources:
providers: [github]
environments: [production, staging]
targets: [my-org/api-service]
agents:
ids: [agent_deploy_bot]
environments: [production]
rules:
- name: rule-name
conditions:
all:
- field: type
operator: eq
value: github.pr.merge
decision: allow | deny | requires_approval
reason: Human-readable explanation
approver_groups: [engineering-leads]
evidence_required: [ticket_url]metadata
| Field | Required | Default | Description |
|---|---|---|---|
| id | Yes | — | Unique identifier (e.g., pol_github_safety) |
| name | Yes | — | Display name shown in dashboard and audit trail |
| description | No | — | Longer explanation of the policy's purpose |
| tags | No | [] | Labels for filtering and organization |
| priority | No | 100 | Higher priority policies are evaluated first |
| enabled | No | true | Set to false to disable without deleting |
scope
The scope determines which actions this policy applies to. If an action doesn't match the scope, the policy is skipped entirely. All scope fields are optional — an empty scope matches everything.
- action_types — List of action types this policy applies to. If omitted, matches all types.
- resources.providers — Only match actions targeting these providers (e.g.,
github,stripe,aws). - resources.environments — Only match actions in these environments (e.g.,
production). - resources.targets — Only match actions targeting these specific resources.
- agents.ids — Only apply to these specific agents.
- agents.environments — Only apply to agents in these environments.
providers: [github] won't slow down evaluation of Stripe actions.rules
Rules are evaluated in order within a policy. The first rule whose conditions match determines the decision. If no rules match, the policy has no effect on the action.
Each rule has:
- conditions — An
all(AND) orany(OR) group of conditions. Can be nested. - decision —
allow,deny, orrequires_approval. - reason — Shown to the agent and logged in the audit trail.
- approver_groups — Required when decision is
requires_approval. Specifies who can approve.
Conditions and operators
Conditions match fields from the action spec using dot-path notation. For example, parameters.amount accesses the amount field inside parameters.
Available fields
| Field path | Example value | Description |
|---|---|---|
| type | github.pr.merge | The action type |
| resource.provider | github | Service provider |
| resource.environment | production | Target environment |
| resource.target | my-org/api-service | Specific resource |
| parameters.* | Any value | Action-specific parameters |
| risk_hints.production_target | true | Risk hint flags |
| risk_hints.financial_impact | true | Risk hint flags |
| risk_hints.irreversible | true | Risk hint flags |
| risk_hints.estimated_blast_radius | high | Risk hint level |
| agent_id | agent_deploy_bot | The requesting agent |
| agent_environment | production | Agent's environment |
Operators
| Operator | Description | Example |
|---|---|---|
| eq | Equals | field: type, operator: eq, value: github.pr.merge |
| neq | Not equals | field: resource.environment, operator: neq, value: development |
| in | Value is in list | field: parameters.currency, operator: in, value: [USD, EUR, GBP] |
| not_in | Value is not in list | field: resource.environment, operator: not_in, value: [production] |
| contains | String contains substring | field: parameters.body, operator: contains, value: BREAKING |
| matches | Glob pattern match | field: resource.target, operator: matches, value: "my-org/*" |
| not_matches | Does not match glob | field: type, operator: not_matches, value: "*.delete" |
| regex | Regular expression | field: parameters.email, operator: regex, value: ".*@company\\.com$" |
| gt | Greater than (numbers) | field: parameters.amount, operator: gt, value: 10000 |
| gte | Greater than or equal | field: risk_hints.estimated_blast_radius, operator: gte, value: 5 |
| lt | Less than | field: parameters.instance_count, operator: lt, value: 100 |
| lte | Less than or equal | field: parameters.amount, operator: lte, value: 500 |
| exists | Field is present | field: parameters.ticket_url, operator: exists |
| not_exists | Field is absent | field: parameters.approval_override, operator: not_exists |
Nested conditions
You can nest all and any groups to build complex logic:
conditions:
all:
- field: type
operator: eq
value: github.pr.merge
- any:
- field: resource.target
operator: matches
value: "my-org/api-*"
- field: resource.target
operator: matches
value: "my-org/web-*"This matches: action is a PR merge AND (target starts with api- OR starts with web-).
Decisions
| Decision | Behavior | Token issued? |
|---|---|---|
| allow | Action proceeds immediately | Yes |
| deny | Action is blocked with a reason | No |
| requires_approval | Slack notification sent, agent waits | Yes, after approval |
deny, the action is denied — even if higher-priority policies say allow. This is a fail-closed security model.Real-world examples
1. GitHub: Protect production branches
Block deletion of protected branches and require approval for production merges.
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_github_branch_safety
name: GitHub Branch Safety
tags: [github, production]
priority: 900
spec:
scope:
resources:
providers: [github]
rules:
- name: block-protected-branch-deletion
conditions:
all:
- field: type
operator: eq
value: github.branch.delete
- field: parameters.branch_name
operator: in
value: [main, master, develop, staging, release]
decision: deny
reason: Cannot delete protected branches
- name: require-approval-for-production-merges
conditions:
all:
- field: type
operator: eq
value: github.pr.merge
- field: resource.environment
operator: eq
value: production
decision: requires_approval
reason: Production merges require engineering lead approval
approver_groups:
- engineering-leads
- name: allow-dev-actions
conditions:
all:
- field: resource.environment
operator: eq
value: development
decision: allow
reason: Development actions are auto-approved2. Payments: Control financial transactions
Require approval for large transfers, block transfers to unknown accounts, and deny high-risk currencies.
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_payment_controls
name: Payment Controls
tags: [payments, financial, compliance]
priority: 800
spec:
scope:
action_types:
- payment.transfer.initiate
- payment.refund.create
- payment.subscription.cancel
rules:
- name: block-sanctioned-currencies
conditions:
all:
- field: type
operator: eq
value: payment.transfer.initiate
- field: parameters.currency
operator: in
value: [KPW, SYP, IRR]
decision: deny
reason: Transfers in sanctioned currencies are prohibited
- name: require-approval-large-transfers
conditions:
all:
- field: type
operator: eq
value: payment.transfer.initiate
- field: parameters.amount
operator: gt
value: 10000
decision: requires_approval
reason: "Transfers over $10,000 require finance team approval"
approver_groups:
- finance-team
- name: require-approval-large-refunds
conditions:
all:
- field: type
operator: eq
value: payment.refund.create
- field: parameters.amount
operator: gt
value: 500
decision: requires_approval
reason: "Refunds over $500 require manager approval"
approver_groups:
- support-managers
- name: allow-small-transactions
conditions:
all:
- field: parameters.amount
operator: lte
value: 500
decision: allow
reason: Small transactions are auto-approvedpayment.transfer.initiate, payment.refund.create) are examples. You define your own action types to match your domain. TameFlare doesn't enforce a naming scheme.3. Infrastructure: Guard production deployments
Prevent destructive infrastructure changes and require approval for production modifications.
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_infra_safety
name: Infrastructure Safety
tags: [infrastructure, aws, production]
priority: 850
spec:
scope:
action_types:
- infra.server.terminate
- infra.server.provision
- infra.dns.modify
- infra.database.drop
- infra.cluster.scale
rules:
- name: block-database-drops
conditions:
all:
- field: type
operator: eq
value: infra.database.drop
- field: resource.environment
operator: eq
value: production
decision: deny
reason: Production database drops are permanently blocked
- name: require-approval-server-termination
conditions:
all:
- field: type
operator: eq
value: infra.server.terminate
- field: resource.environment
operator: in
value: [production, staging]
decision: requires_approval
reason: Server termination in production/staging requires SRE approval
approver_groups:
- sre-team
- name: limit-cluster-scaling
conditions:
all:
- field: type
operator: eq
value: infra.cluster.scale
- field: parameters.instance_count
operator: gt
value: 50
decision: requires_approval
reason: "Scaling beyond 50 instances requires infrastructure lead approval"
approver_groups:
- infra-leads
- name: allow-dev-infra
conditions:
all:
- field: resource.environment
operator: eq
value: development
decision: allow
reason: Development infrastructure changes are auto-approved4. Communications: Control outbound messages
Prevent agents from sending emails to external recipients without approval, and block mass communications.
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_comms_safety
name: Communications Safety
tags: [email, slack, communications]
priority: 700
spec:
scope:
action_types:
- email.send
- slack.message.post
- notification.broadcast
rules:
- name: block-mass-emails
conditions:
all:
- field: type
operator: eq
value: email.send
- field: parameters.recipient_count
operator: gt
value: 100
decision: deny
reason: Mass emails (100+ recipients) are blocked. Use the marketing platform instead.
- name: require-approval-external-emails
conditions:
all:
- field: type
operator: eq
value: email.send
- field: risk_hints.external_recipient
operator: eq
value: true
decision: requires_approval
reason: Emails to external recipients require compliance review
approver_groups:
- compliance-team
- name: require-approval-announcements
conditions:
all:
- field: type
operator: eq
value: notification.broadcast
decision: requires_approval
reason: Broadcast notifications require comms team approval
approver_groups:
- comms-team
- name: allow-internal-messages
conditions:
all:
- field: risk_hints.external_recipient
operator: eq
value: false
decision: allow
reason: Internal messages are auto-approved5. Data access: Protect sensitive information
Control access to PII, prevent bulk data exports, and require approval for cross-environment data movement.
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_data_access
name: Data Access Controls
tags: [data, privacy, compliance]
priority: 900
spec:
scope:
action_types:
- data.query.execute
- data.export.create
- data.record.delete
rules:
- name: block-bulk-deletes
conditions:
all:
- field: type
operator: eq
value: data.record.delete
- field: parameters.record_count
operator: gt
value: 1000
decision: deny
reason: Bulk deletes (1000+ records) are blocked. Use the admin console.
- name: require-approval-pii-access
conditions:
all:
- field: risk_hints.contains_pii
operator: eq
value: true
decision: requires_approval
reason: Actions involving PII require privacy team approval
approver_groups:
- privacy-team
- name: require-approval-large-exports
conditions:
all:
- field: type
operator: eq
value: data.export.create
- field: parameters.row_count
operator: gt
value: 10000
decision: requires_approval
reason: "Large data exports (10k+ rows) require data team approval"
approver_groups:
- data-team
- name: allow-small-queries
conditions:
all:
- field: type
operator: eq
value: data.query.execute
- field: risk_hints.contains_pii
operator: eq
value: false
decision: allow
reason: Non-PII queries are auto-approved6. Risk-based: Universal safety net
A catch-all policy that uses risk hints to require approval for any high-risk action, regardless of domain.
apiVersion: TameFlare/v1
kind: Policy
metadata:
id: pol_risk_safety_net
name: Risk-Based Safety Net
tags: [universal, risk, safety]
priority: 50
spec:
scope: {}
rules:
- name: require-approval-irreversible-production
conditions:
all:
- field: risk_hints.irreversible
operator: eq
value: true
- field: risk_hints.production_target
operator: eq
value: true
decision: requires_approval
reason: Irreversible production actions require team lead approval
approver_groups:
- team-leads
- name: require-approval-high-blast-radius
conditions:
all:
- field: risk_hints.estimated_blast_radius
operator: eq
value: high
- field: risk_hints.financial_impact
operator: eq
value: true
decision: requires_approval
reason: High blast radius actions with financial impact require approval
approver_groups:
- team-leads50) so domain-specific policies take precedence. It acts as a safety net for any action that slips through without a dedicated policy.Policy scoping guide
Scopes control which actions a policy applies to. Use scopes to keep policies focused and efficient.
Scope levels
| Scope | Field | Effect |
|---|---|---|
| Global | scope: {} | Matches every action. Use for safety nets. |
| Per-connector | scope.resources.providers: [github] | Only evaluates for actions targeting this provider |
| Per-action-type | scope.action_types: [github.pr.merge] | Only evaluates for specific action types |
| Per-agent | scope.agents.ids: [agent_deploy_bot] | Only applies to a specific agent |
| Per-environment | scope.resources.environments: [production] | Only applies in specific environments |
| Combined | Multiple fields | All scope fields must match (AND logic) |
Priority ordering
Policies are evaluated in priority order (highest number first). Use priority to control which policies are checked first:
| Priority range | Recommended use | |---|---| | 900-1000 | Hard blocks (deny rules that should never be overridden) | | 700-899 | Domain-specific policies (GitHub safety, payment controls) | | 400-699 | Team-level policies (per-agent restrictions) | | 100-399 | Default/general policies | | 1-99 | Safety nets and catch-all rules |
Priority does not override deny-wins. A priority-50 deny still beats a priority-900 allow. Priority only controls evaluation order, which matters when you want a specific policy's requires_approval to be recorded first (for audit trail clarity).
Policy-as-code workflow
Policies are YAML files that can be stored in git, reviewed in PRs, and deployed via CI.
Recommended workflow
1. Create a policy via the dashboard Policy Builder (or POST JSON to the API)
2. Test with the dry-run sandbox against sample actions
3. Enable the policy and monitor via the audit log
4. Iterate: adjust scope, conditions, and decisions as needed
5. Dashboard shows version history for each policy
Deploying policies via API
# Create a new policy
curl -X POST http://localhost:3000/api/v1/policies \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Branch Safety",
"yaml": "apiVersion: TameFlare/v1\nkind: Policy\nmetadata:\n id: pol_github_branch_safety\n name: GitHub Branch Safety\n priority: 900\nspec:\n scope:\n resources:\n providers: [github]\n rules:\n - name: block-branch-delete\n conditions:\n all:\n - field: type\n operator: eq\n value: github.branch.delete\n decision: deny\n reason: Branch deletion is blocked"
}'CI pipeline example (GitHub Actions)
- name: Deploy policies
run: |
for policy in policies/*.yaml; do
# Dry-run first
curl -sf -X POST http://localhost:3000/api/v1/actions/dry-run \
-H "Authorization: Bearer $AAF_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"action_spec\": {\"type\": \"github.pr.merge\"}}" \
|| echo "Warning: dry-run failed for $policy"
# Deploy
YAML_CONTENT=$(cat "$policy" | jq -Rs .)
curl -X POST http://localhost:3000/api/v1/policies \
-H "Authorization: Bearer $AAF_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"name\": \"$(basename $policy .yaml)\", \"yaml\": $YAML_CONTENT}"
doneVersion history
Every policy change is recorded in the policy_versions table:
- Version number incremented on each update
- Changed by — user who made the change
- Change type —
created,updated,enabled,disabled - Full YAML snapshot — the complete policy at that version
View version history in the dashboard: Policies → (select policy) → Version History
Risk model
Connectors assign risk hints to parsed actions based on the action type and parameters. Risk hints are metadata that policies can use for condition matching.
Built-in risk hints
| Hint | Type | Set by | Example |
|---|---|---|---|
| production_target | boolean | Connector (from environment field) | true if resource.environment == "production" |
| financial_impact | boolean | Connector (from action type) | true for payment.transfer.*, stripe.charge.* |
| irreversible | boolean | Connector (from action type) | true for *.delete, *.terminate, *.drop |
| external_recipient | boolean | Agent (in action spec) | true if action sends data outside the organization |
| contains_pii | boolean | Agent (in action spec) | true if action involves personally identifiable information |
| estimated_blast_radius | string | Connector (heuristic) | low, medium, or high based on action scope |
How risk hints affect evaluation
Risk hints do not automatically change the decision. They are fields that policies can match on:
# This policy uses risk hints for condition matching
rules:
- name: require-approval-high-risk
conditions:
all:
- field: risk_hints.irreversible
operator: eq
value: true
- field: risk_hints.production_target
operator: eq
value: true
decision: requires_approvalAgent-provided vs connector-provided hints
- Connector-provided:
production_target,financial_impact,irreversible,estimated_blast_radius— set automatically by the connector based on the parsed action - Agent-provided:
external_recipient,contains_pii, and any custom hints — set by the agent in therisk_hintsfield of the action spec
Agents can add custom risk hint fields. Policies can match on any field path under risk_hints.* using the standard operators.
Risk scoring
TameFlare calculates a risk score (0-100) for each action based on the combination of risk hints:
| Factor | Score contribution |
|---|---|
| production_target: true | +20 |
| financial_impact: true | +25 |
| irreversible: true | +30 |
| external_recipient: true | +15 |
| estimated_blast_radius: high | +20 |
| estimated_blast_radius: medium | +10 |
The risk score is logged in the audit trail and shown in the dashboard action detail page. It does not directly affect the decision — only policies determine allow/deny/require_approval.
Policy packs
Three built-in packs ship in policies/ and can be installed from the dashboard Policies page with one click:
- github-safe-defaults — Branch protection, PR approval requirements, and safe defaults for all GitHub actions
- rate-limits — Action frequency limits to prevent runaway agents
- time-windows — Restrict destructive actions to business hours
Testing policies (dry-run and simulation)
Dry-run endpoint
Test policies without affecting live traffic or counting toward action limits:
curl -X POST http://localhost:3000/api/v1/actions/dry-run \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{
"action_spec": {
"type": "payment.transfer.initiate",
"resource": {
"provider": "stripe",
"account": "acct_123",
"target": "transfers",
"environment": "production"
},
"parameters": {
"amount": 25000,
"currency": "USD",
"recipient": "acct_external"
},
"risk_hints": {
"financial_impact": true,
"external_recipient": true,
"production_target": true,
"irreversible": true
}
}
}'Dry-run response
The response includes the full evaluation trace:
{
"decision": "requires_approval",
"risk_score": 90,
"matched_policies": [
{
"id": "pol_payment_controls",
"name": "Payment Controls",
"matched_rule": "require-approval-large-transfers",
"decision": "requires_approval",
"reason": "Transfers over $10,000 require finance team approval"
},
{
"id": "pol_risk_safety_net",
"name": "Risk-Based Safety Net",
"matched_rule": "require-approval-irreversible-production",
"decision": "requires_approval",
"reason": "Irreversible production actions require team lead approval"
}
],
"evaluated_policies": 5,
"skipped_policies": 2,
"hint": null
}Dashboard dry-run UI
The Policies page includes an interactive dry-run panel:
- Navigate to Policies in the dashboard
- Click Test Policy (or use the dry-run panel)
- Enter an action spec (JSON) or select from templates
- Click Evaluate — see the decision, matched rules, and full trace
- Edit the action spec and re-evaluate to test edge cases
The dashboard shows live YAML validation with inline errors as you type.
Simulation endpoint
For batch testing, use the simulation endpoint to evaluate an action against a specific set of policies:
curl -X POST http://localhost:3000/api/v1/policies/simulate \
-H "Authorization: Bearer <api_key>" \
-H "Content-Type: application/json" \
-d '{
"action_spec": { "type": "github.pr.merge" },
"policy_ids": ["pol_github_branch_safety", "pol_risk_safety_net"]
}'This evaluates only the specified policies, useful for testing a new policy before enabling it.
Policy hit analytics
Identify which policies and rules are triggered most frequently to optimize your policy set.
Finding noisy rules
Query the audit log for the most frequently matched policies:
# Top 10 most-hit policies (last 7 days)
sqlite3 apps/web/local.db "
SELECT json_extract(details, '$.matched_policy') as policy,
json_extract(details, '$.matched_rule') as rule,
json_extract(details, '$.decision') as decision,
COUNT(*) as hits
FROM audit_events
WHERE event_type IN ('action.allowed', 'action.denied', 'action.pending_approval')
AND created_at > date('now', '-7 days')
GROUP BY policy, rule, decision
ORDER BY hits DESC
LIMIT 10;
"What to look for
| Pattern | Meaning | Action | |---|---|---| | One rule has 90%+ of all hits | Catch-all rule is too broad | Narrow the scope or add more specific rules above it | | A deny rule fires constantly | Agents keep attempting blocked actions | Check if the agent is in a retry loop, or if the policy is too strict | | An approval rule fires frequently | Too many actions need human approval | Consider allowing low-risk variants automatically | | A policy has zero hits | Policy scope doesn't match any real traffic | Review scope — it may be misconfigured or unnecessary |
Dashboard analytics
The dashboard overview page shows:
- Actions over time — stacked area chart by decision (allow/deny/require_approval)
- Top agents — which agents generate the most traffic
- Top denied action types — which actions are most frequently blocked
Use these charts to identify patterns and tune policies accordingly.
Prometheus metrics
If you have Prometheus configured, use the aaf_requests_total metric:
# Top 5 most-denied action types
topk(5, sum(rate(aaf_requests_denied_total[7d])) by (action_type))See Observability for the full metrics reference.
Next steps
- Your First Policy — step-by-step tutorial
- Action Types — reference for all connector action types
- Security — how the policy engine is sandboxed
- Audit Log — audit architecture and compliance
- API Reference — full API documentation