> ## Documentation Index
> Fetch the complete documentation index at: https://docs.swarmd.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Policy Configuration

> Create policy groups using OPA Rego to control, monitor, and restrict agent communication with regex detection, PII scanning, skill restrictions, rate limiting, and human-in-the-loop review.

# Policy Configuration

Policy groups control what flows through the relay. Each policy group is a **Rego file** (Open Policy Agent's policy language) that defines which middleware rules to apply. Rego gives you conditional logic, time-based gating, and the ability to combine multiple rules in a single group.

Policy groups are applied through a two-step process:

1. **Create a policy group** with Rego source that defines the middleware rules
2. **Create a policy binding** that attaches the group to a tenant, agent, or subscription

***

## How the Middleware Pipeline Works

```
Source Agent -> [REQUEST_FROM_SOURCE] -> Relay -> [REQUEST_TO_SINK] -> Sink Agent
                                                                        |
Source Agent <- [RESPONSE_TO_SOURCE] <- Relay <- [RESPONSE_FROM_SINK] <- Sink Agent
```

Every middleware rule specifies which **legs** of this flow it applies to via `enabledLegs`:

| Leg                 | Direction       | Example use                                              |
| ------------------- | --------------- | -------------------------------------------------------- |
| `requestFromSource` | Source -> Relay | Scan outgoing requests for PII before they leave         |
| `requestToSink`     | Relay -> Sink   | Restrict which skills can be invoked on the target agent |
| `responseFromSink`  | Sink -> Relay   | Detect sensitive data in agent responses                 |
| `responseToSource`  | Relay -> Source | Mask data before returning to the caller                 |

<Warning>
  At least one leg must be enabled. The API will reject a policy where all four legs are `false`.
</Warning>

***

## How Evaluation Works

When a message flows through the relay:

1. The system finds all **active bindings** that match the current tenant, agent, and subscription
2. For each binding, the associated **policy group's Rego file** is evaluated with the request context as input
3. The Rego file returns a list of **middleware configs** to execute
4. Each middleware config is instantiated and executed in the specified **order**
5. If any middleware returns `BLOCK`, the message is rejected

Bindings are evaluated in **priority order** (lower number = higher priority). Within a group, middlewares are ordered by their `order` field.

***

## Rego File Structure

Every policy group's Rego file must follow this structure:

```rego theme={null}
package swarmd.policy

import rego.v1

middlewares contains {
    "name": "Human-readable name for this rule",
    "type": "MIDDLEWARE_TYPE",
    "order": 0,
    "config": {
        "type": "MIDDLEWARE_TYPE",
        ...type-specific fields...
    }
}
```

| Field                | Required | Description                                                                                                        |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `name`               | Yes      | A human-readable name for this rule (shown in audit logs)                                                          |
| `type`               | Yes      | One of: `SKILL_RESTRICTION`, `REGEX_DETECTION`, `PRESIDIO_DETECTION`, `COMPREHEND_DETECTION`, `RATE_LIMIT`, `HITL` |
| `order`              | Yes      | Evaluation order within the group (lower = first)                                                                  |
| `config`             | Yes      | Type-specific configuration (see sections below)                                                                   |
| `config.type`        | Yes      | Must match the outer `type` field                                                                                  |
| `config.enabledLegs` | Yes      | Which relay legs this rule applies to                                                                              |

***

## Middleware Types

### Skill Restriction

Control which skills an agent is allowed to use. You can allowlist or blocklist skills by ID, name, or tag.

```rego theme={null}
package swarmd.policy

import rego.v1

middlewares contains {
    "name": "Block filesystem access",
    "type": "SKILL_RESTRICTION",
    "order": 0,
    "config": {
        "type": "SKILL_RESTRICTION",
        "mode": "BLOCKLIST",
        "skillNames": ["read_file", "write_file", "delete_file"],
        "skillIds": [],
        "skillTags": ["filesystem"],
        "action": "BLOCK",
        "enabledLegs": {
            "requestFromSource": true,
            "requestToSink": true,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
}
```

| Field        | Required | Description                                                                    |
| ------------ | -------- | ------------------------------------------------------------------------------ |
| `mode`       | Yes      | `ALLOWLIST` (only these skills permitted) or `BLOCKLIST` (these skills denied) |
| `action`     | Yes      | `LOG`, `WARN`, or `BLOCK`                                                      |
| `skillIds`   | No       | Set of skill IDs to match                                                      |
| `skillNames` | No       | Set of skill names to match                                                    |
| `skillTags`  | No       | Set of skill tags to match                                                     |

<Tip>
  At least one of `skillIds`, `skillNames`, or `skillTags` must be non-empty. Use `ALLOWLIST` mode for high-security environments where agents should only use explicitly approved skills.
</Tip>

### Regex Detection

Match message content against a regular expression.

```rego theme={null}
package swarmd.policy

import rego.v1

middlewares contains {
    "name": "Block AWS access keys",
    "type": "REGEX_DETECTION",
    "order": 0,
    "config": {
        "type": "REGEX_DETECTION",
        "pattern": {
            "regex": "AKIA[0-9A-Z]{16}",
            "action": "BLOCK"
        },
        "enabledLegs": {
            "requestFromSource": true,
            "requestToSink": true,
            "responseFromSink": true,
            "responseToSource": true
        }
    }
}
```

| Field            | Required | Description                                                     |
| ---------------- | -------- | --------------------------------------------------------------- |
| `pattern.regex`  | Yes      | Java-compatible regular expression (validated at creation time) |
| `pattern.action` | Yes      | `LOG`, `MASK`, `BLOCK`, `WARN`, or `HUMAN_REVIEW_REQUIRED`      |

### PII Detection (Presidio)

Detect personally identifiable information using Microsoft Presidio.

```rego theme={null}
package swarmd.policy

import rego.v1

middlewares contains {
    "name": "Mask credit card numbers",
    "type": "PRESIDIO_DETECTION",
    "order": 0,
    "config": {
        "type": "PRESIDIO_DETECTION",
        "entity": {
            "type": "CREDIT_CARD",
            "scoreThreshold": 0.8,
            "action": "MASK"
        },
        "enabledLegs": {
            "requestFromSource": true,
            "requestToSink": true,
            "responseFromSink": true,
            "responseToSource": true
        }
    }
}
```

| Field                   | Required | Description                                                                                    |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `entity.type`           | Yes      | Presidio entity type (e.g. `CREDIT_CARD`, `US_SSN`, `EMAIL_ADDRESS`, `PERSON`, `PHONE_NUMBER`) |
| `entity.scoreThreshold` | Yes      | Confidence threshold between `0.0` and `1.0`                                                   |
| `entity.action`         | Yes      | `LOG`, `MASK`, `BLOCK`, `WARN`, or `HUMAN_REVIEW_REQUIRED`                                     |

<Accordion title="Supported Presidio Entity Types">
  | Entity Type               | Description                                   |
  | ------------------------- | --------------------------------------------- |
  | `CREDIT_CARD`             | Credit card numbers                           |
  | `CRYPTO`                  | Cryptocurrency wallet addresses               |
  | `EMAIL_ADDRESS`           | Email addresses                               |
  | `IBAN_CODE`               | International bank account numbers            |
  | `IP_ADDRESS`              | IP addresses                                  |
  | `LOCATION`                | Physical locations                            |
  | `PERSON`                  | Person names                                  |
  | `PHONE_NUMBER`            | Phone numbers                                 |
  | `MEDICAL_LICENSE`         | Medical licence numbers                       |
  | `US_BANK_NUMBER`          | US bank account numbers                       |
  | `US_DRIVER_LICENSE`       | US driver licence numbers                     |
  | `US_ITIN`                 | US Individual Taxpayer Identification Numbers |
  | `US_PASSPORT`             | US passport numbers                           |
  | `US_SSN`                  | US Social Security Numbers                    |
  | `UK_NHS`                  | UK National Health Service numbers            |
  | `ES_NIF`                  | Spanish tax identification numbers            |
  | `IT_FISCAL_CODE`          | Italian fiscal codes                          |
  | `IT_DRIVER_LICENSE`       | Italian driver licence numbers                |
  | `IT_VAT_CODE`             | Italian VAT codes                             |
  | `IT_PASSPORT`             | Italian passport numbers                      |
  | `IT_IDENTITY_CARD`        | Italian identity card numbers                 |
  | `SG_NRIC_FIN`             | Singapore NRIC/FIN numbers                    |
  | `AU_ABN`                  | Australian Business Numbers                   |
  | `AU_ACN`                  | Australian Company Numbers                    |
  | `AU_TFN`                  | Australian Tax File Numbers                   |
  | `AU_MEDICARE`             | Australian Medicare numbers                   |
  | `IN_PAN`                  | Indian PAN card numbers                       |
  | `IN_AADHAAR`              | Indian Aadhaar numbers                        |
  | `IN_VEHICLE_REGISTRATION` | Indian vehicle registration numbers           |
</Accordion>

### PII Detection (AWS Comprehend)

Detect PII using [AWS Comprehend](https://docs.aws.amazon.com/comprehend/latest/dg/how-pii.html).

```rego theme={null}
package swarmd.policy

import rego.v1

middlewares contains {
    "name": "Block SSN exposure",
    "type": "COMPREHEND_DETECTION",
    "order": 0,
    "config": {
        "type": "COMPREHEND_DETECTION",
        "entity": {
            "type": "SSN",
            "scoreThreshold": 0.9,
            "action": "BLOCK"
        },
        "enabledLegs": {
            "requestFromSource": false,
            "requestToSink": false,
            "responseFromSink": true,
            "responseToSource": true
        }
    }
}
```

| Field                   | Required | Description                                                                            |
| ----------------------- | -------- | -------------------------------------------------------------------------------------- |
| `entity.type`           | Yes      | Comprehend entity type (e.g. `CREDIT_DEBIT_NUMBER`, `NAME`, `SSN`, `EMAIL`, `ADDRESS`) |
| `entity.scoreThreshold` | Yes      | Confidence threshold between `0.0` and `1.0`                                           |
| `entity.action`         | Yes      | `LOG`, `MASK`, `BLOCK`, `WARN`, or `HUMAN_REVIEW_REQUIRED`                             |

### Rate Limiting

Throttle request volume. Three strategies are available:

**Sliding Window:**

```rego theme={null}
middlewares contains {
    "name": "100 requests per minute",
    "type": "RATE_LIMIT",
    "order": 0,
    "config": {
        "type": "RATE_LIMIT",
        "strategy": {
            "strategyType": "SLIDING_WINDOW",
            "maxRequests": 100,
            "windowSizeSeconds": 60
        },
        "enabledLegs": {
            "requestFromSource": true,
            "requestToSink": false,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
}
```

**Token Bucket:**

```json theme={null}
"strategy": {
    "strategyType": "TOKEN_BUCKET",
    "bucketCapacity": 100,
    "refillRatePerSecond": 10.0
}
```

**Leaky Bucket:**

```json theme={null}
"strategy": {
    "strategyType": "LEAKY_BUCKET",
    "bucketCapacity": 50,
    "leakRatePerSecond": 5.0
}
```

### Human-in-the-Loop (HITL)

Pause messages for manual human approval before they reach the target agent.

```rego theme={null}
middlewares contains {
    "name": "Require human approval",
    "type": "HITL",
    "order": 0,
    "config": {
        "type": "HITL",
        "enabledLegs": {
            "requestFromSource": false,
            "requestToSink": true,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
}
```

See the [Human-in-the-Loop tutorial](/tutorials/human-in-the-loop) for the complete approval workflow.

***

## Conditional Logic with Rego

This is where OPA's power comes in. Instead of static rules, you can use Rego's `if` statements to activate rules conditionally based on the request context, time of day, day of week, and more.

### Available Input Context

Every Rego evaluation receives this input:

```json theme={null}
{
    "tenantId": "uuid",
    "sourceAgentId": "uuid",
    "sourceUserId": "uuid",
    "sinkAgentId": "uuid",
    "timestamp": "2026-04-03T14:30:00Z",
    "timestampNanos": 1775225400000000000
}
```

| Field            | Type            | Description                                                                               |
| ---------------- | --------------- | ----------------------------------------------------------------------------------------- |
| `tenantId`       | UUID string     | The tenant executing the policy                                                           |
| `sourceAgentId`  | UUID string     | The agent sending the message (null for user sources)                                     |
| `sourceUserId`   | UUID string     | The user sending the message (null for agent sources)                                     |
| `sinkAgentId`    | UUID string     | The agent receiving the message                                                           |
| `timestamp`      | RFC 3339 string | Evaluation time (usable with `time.parse_rfc3339_ns()`)                                   |
| `timestampNanos` | integer         | Evaluation time as epoch nanoseconds (usable with `time.clock()`, `time.weekday()`, etc.) |

### Time-Based Activation

Activate rules only during specific time windows using OPA's built-in `time.*` functions with `input.timestampNanos`:

```rego theme={null}
package swarmd.policy

import rego.v1

# HITL review only during business hours (Mon-Fri 09:00-17:00 UTC)
middlewares contains {
    "name": "Business hours HITL",
    "type": "HITL",
    "order": 0,
    "config": {
        "type": "HITL",
        "enabledLegs": {
            "requestFromSource": false,
            "requestToSink": true,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
} if {
    hour := time.clock(input.timestampNanos)[0]
    hour >= 9
    hour < 17
    day := time.weekday(input.timestampNanos)
    day != "Saturday"
    day != "Sunday"
}
```

### Combining Always-On and Conditional Rules

```rego theme={null}
package swarmd.policy

import rego.v1

# Always block dangerous tools (no condition)
middlewares contains {
    "name": "Block shell commands",
    "type": "SKILL_RESTRICTION",
    "order": 0,
    "config": {
        "type": "SKILL_RESTRICTION",
        "mode": "BLOCKLIST",
        "skillNames": ["shell_command", "execute_code"],
        "skillIds": [],
        "skillTags": [],
        "action": "BLOCK",
        "enabledLegs": {
            "requestFromSource": true,
            "requestToSink": true,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
}

# Stricter rate limiting outside business hours
middlewares contains {
    "name": "After-hours rate limit",
    "type": "RATE_LIMIT",
    "order": 1,
    "config": {
        "type": "RATE_LIMIT",
        "strategy": {
            "strategyType": "SLIDING_WINDOW",
            "maxRequests": 10,
            "windowSizeSeconds": 60
        },
        "enabledLegs": {
            "requestFromSource": true,
            "requestToSink": false,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
} if {
    hour := time.clock(input.timestampNanos)[0]
    hour < 9
}

middlewares contains {
    "name": "After-hours rate limit",
    "type": "RATE_LIMIT",
    "order": 1,
    "config": {
        "type": "RATE_LIMIT",
        "strategy": {
            "strategyType": "SLIDING_WINDOW",
            "maxRequests": 10,
            "windowSizeSeconds": 60
        },
        "enabledLegs": {
            "requestFromSource": true,
            "requestToSink": false,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
} if {
    hour := time.clock(input.timestampNanos)[0]
    hour >= 17
}

# HITL for all weekend requests
middlewares contains {
    "name": "Weekend HITL",
    "type": "HITL",
    "order": 2,
    "config": {
        "type": "HITL",
        "enabledLegs": {
            "requestFromSource": false,
            "requestToSink": true,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
} if {
    day := time.weekday(input.timestampNanos)
    day == "Saturday"
}

middlewares contains {
    "name": "Weekend HITL",
    "type": "HITL",
    "order": 2,
    "config": {
        "type": "HITL",
        "enabledLegs": {
            "requestFromSource": false,
            "requestToSink": true,
            "responseFromSink": false,
            "responseToSource": false
        }
    }
} if {
    day := time.weekday(input.timestampNanos)
    day == "Sunday"
}
```

<Tip>
  Rego uses AND logic within a single `if` block (all conditions must be true). For OR logic, define multiple `middlewares contains` blocks with the same middleware config but different conditions - if **any** block matches, the middleware is included.
</Tip>

### Negation

Apply rules when a condition is NOT met:

```rego theme={null}
# Strict PII scanning outside business hours
middlewares contains {
    "name": "After-hours PII scan",
    "type": "PRESIDIO_DETECTION",
    "order": 0,
    "config": { ... }
} if {
    hour := time.clock(input.timestampNanos)[0]
    not hour >= 9
}
```

### Common Time-Gating Patterns

**Business hours only (Mon-Fri 09:00-17:00 UTC):**

```rego theme={null}
} if {
    hour := time.clock(input.timestampNanos)[0]
    hour >= 9
    hour < 17
    day := time.weekday(input.timestampNanos)
    day != "Saturday"
    day != "Sunday"
}
```

**Weekdays only:**

```rego theme={null}
} if {
    day := time.weekday(input.timestampNanos)
    day != "Saturday"
    day != "Sunday"
}
```

**Date-range / maintenance window:**

```rego theme={null}
} if {
    # Active from 1 May 2026 onwards
    input.timestampNanos >= time.parse_rfc3339_ns("2026-05-01T00:00:00Z")
}
```

```rego theme={null}
} if {
    # Active until end of Q2 2026
    input.timestampNanos < time.parse_rfc3339_ns("2026-07-01T00:00:00Z")
}
```

**Agent-specific conditions:**

```rego theme={null}
} if {
    input.sinkAgentId == "550e8400-e29b-41d4-a716-446655440000"
}
```

### Future Possibilities

Rego supports many more patterns that can be used as we enrich the input context:

* **Tenant-specific overrides** - different rules per tenant ID
* **Agent metadata matching** - rules based on agent properties like name and group
* **Dynamic config values** - threshold values computed from input data

***

## Policy Actions

Every detection rule requires an `action` that determines what happens when a match is found:

| Action                  | Behaviour                                                          |
| ----------------------- | ------------------------------------------------------------------ |
| `LOG`                   | Record the detection in the audit log. Message continues normally. |
| `WARN`                  | Log an advisory warning. Message continues normally.               |
| `MASK`                  | Redact the matched content before forwarding the message.          |
| `BLOCK`                 | Reject the message entirely. The sender receives an error.         |
| `HUMAN_REVIEW_REQUIRED` | Pause the message and create a HITL approval request.              |

***

## Creating a Policy Group

```bash theme={null}
curl -X POST https://api.swarmd.ai/relay/v1/policy-groups \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "name": "PII Protection Suite",
    "description": "Detect and mask common PII types",
    "regoSource": "package swarmd.policy\n\nimport rego.v1\n\nmiddlewares contains {\n    \"name\": \"Mask credit cards\",\n    \"type\": \"PRESIDIO_DETECTION\",\n    \"order\": 0,\n    \"config\": {\n        \"type\": \"PRESIDIO_DETECTION\",\n        \"entity\": {\n            \"type\": \"CREDIT_CARD\",\n            \"scoreThreshold\": 0.8,\n            \"action\": \"MASK\"\n        },\n        \"enabledLegs\": {\n            \"requestFromSource\": true,\n            \"requestToSink\": true,\n            \"responseFromSink\": true,\n            \"responseToSource\": true\n        }\n    }\n}\n\nmiddlewares contains {\n    \"name\": \"Mask SSNs\",\n    \"type\": \"PRESIDIO_DETECTION\",\n    \"order\": 1,\n    \"config\": {\n        \"type\": \"PRESIDIO_DETECTION\",\n        \"entity\": {\n            \"type\": \"US_SSN\",\n            \"scoreThreshold\": 0.7,\n            \"action\": \"MASK\"\n        },\n        \"enabledLegs\": {\n            \"requestFromSource\": true,\n            \"requestToSink\": true,\n            \"responseFromSink\": true,\n            \"responseToSource\": true\n        }\n    }\n}",
    "primaryUserId": "YOUR_USER_ID",
    "secondaryUserId": "BACKUP_USER_ID"
  }'
```

The Rego source is validated before saving:

* Rego syntax is checked
* Each middleware config is validated against its type-specific schema
* Invalid configs are rejected with detailed error messages

### Testing Before Saving

Use the evaluate endpoint to dry-run your Rego without creating a group:

```bash theme={null}
curl -X POST https://api.swarmd.ai/relay/v1/policy-groups/evaluate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "regoSource": "YOUR_REGO_SOURCE_HERE"
  }'
```

For time-gated policies, pass a `timestamp` to test at a specific point in time. When omitted, the current server time is used:

```bash theme={null}
curl -X POST https://api.swarmd.ai/relay/v1/policy-groups/evaluate \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "regoSource": "YOUR_REGO_SOURCE_HERE",
    "timestamp": "2026-04-07T10:30:00Z"
  }'
```

***

## Policy Bindings

Bindings attach a policy group to a scope. Three levels are available:

| Level          | Scope                          | Use case                                                |
| -------------- | ------------------------------ | ------------------------------------------------------- |
| `TENANT`       | All agents in your tenant      | Organisation-wide compliance rules                      |
| `AGENT`        | A specific agent               | Rules for a particular agent regardless of who calls it |
| `SUBSCRIPTION` | A specific source -> sink pair | Fine-grained control over a single communication path   |

<Info>
  When multiple bindings apply to a message, they are evaluated in `priority` order (lower number = higher priority). Within each binding's policy group, middlewares execute in `order` field sequence. All matching middlewares from all applicable bindings are applied.
</Info>

### Evaluation Order

```
Binding Priority 0 (TENANT)     -> Group A Rego -> [middleware order 0, 1, 2]
Binding Priority 1 (AGENT)      -> Group B Rego -> [middleware order 0, 1]
Binding Priority 2 (SUBSCRIPTION) -> Group C Rego -> [middleware order 0]
```

All middlewares from all matching bindings execute in this sequence. If any middleware returns `BLOCK`, the chain short-circuits and the message is rejected.

### Create Bindings

**Tenant-level** (applies to all agents):

```bash theme={null}
curl -X POST https://api.swarmd.ai/relay/v1/policy-bindings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "groupId": "GROUP_ID",
    "groupVersion": "1.0.0",
    "bindingLevel": "TENANT",
    "priority": 0
  }'
```

**Agent-level** (applies to a specific agent):

```bash theme={null}
curl -X POST https://api.swarmd.ai/relay/v1/policy-bindings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "groupId": "GROUP_ID",
    "groupVersion": "1.0.0",
    "bindingLevel": "AGENT",
    "agentId": "AGENT_ID",
    "priority": 10
  }'
```

**Subscription-level** (applies to a specific source -> sink path):

```bash theme={null}
curl -X POST https://api.swarmd.ai/relay/v1/policy-bindings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "groupId": "GROUP_ID",
    "groupVersion": "1.0.0",
    "bindingLevel": "SUBSCRIPTION",
    "sourceAgentId": "SOURCE_AGENT_ID",
    "sinkAgentId": "SINK_AGENT_ID",
    "priority": 20
  }'
```

***

## Versioning

Both policy groups and bindings are **versioned** using semantic versioning (e.g. `1.0.0`, `2.0.0`).

* **Updating a group** creates a new version. The old version is disabled automatically.
* **Upgrading a binding** points it to the latest group version via `POST /v1/policy-bindings/{id}/upgrade`.
* **Version history** is available via `GET /v1/policy-groups/{id}/history`.

***

## Ownership

Every policy group has a **primary owner** and an optional **secondary owner** (escalation contact). Ownership can be transferred via `POST /v1/policy-groups/{id}/transfer-ownership`.

***

## Example: Full Setup

```bash theme={null}
# 1. Create a policy group with Rego
GROUP_ID=$(curl -s -X POST https://api.swarmd.ai/relay/v1/policy-groups \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d '{
    "name": "Security Baseline",
    "description": "Block dangerous tools and mask PII",
    "regoSource": "package swarmd.policy\n\nimport rego.v1\n\nmiddlewares contains {\n    \"name\": \"Block shell commands\",\n    \"type\": \"SKILL_RESTRICTION\",\n    \"order\": 0,\n    \"config\": {\n        \"type\": \"SKILL_RESTRICTION\",\n        \"mode\": \"BLOCKLIST\",\n        \"skillNames\": [\"shell_command\", \"execute_code\"],\n        \"skillIds\": [],\n        \"skillTags\": [],\n        \"action\": \"BLOCK\",\n        \"enabledLegs\": {\"requestFromSource\": true, \"requestToSink\": true, \"responseFromSink\": false, \"responseToSource\": false}\n    }\n}\n\nmiddlewares contains {\n    \"name\": \"Mask email addresses\",\n    \"type\": \"PRESIDIO_DETECTION\",\n    \"order\": 1,\n    \"config\": {\n        \"type\": \"PRESIDIO_DETECTION\",\n        \"entity\": {\"type\": \"EMAIL_ADDRESS\", \"scoreThreshold\": 0.7, \"action\": \"MASK\"},\n        \"enabledLegs\": {\"requestFromSource\": true, \"requestToSink\": true, \"responseFromSink\": true, \"responseToSource\": true}\n    }\n}",
    "primaryUserId": "YOUR_USER_ID"
  }' | jq -r '.groupId')

echo "Created group: $GROUP_ID"

# 2. Bind to your tenant
curl -X POST https://api.swarmd.ai/relay/v1/policy-bindings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $SWARMD_TOKEN" \
  -d "{
    \"groupId\": \"$GROUP_ID\",
    \"groupVersion\": \"1.0.0\",
    \"bindingLevel\": \"TENANT\",
    \"priority\": 0
  }"
```

That's it - two API calls instead of four. The Rego file contains all your rules.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Human-in-the-Loop" icon="user-check" href="/tutorials/human-in-the-loop">
    Set up manual approval workflows for sensitive operations.
  </Card>

  <Card title="Monitoring & Audit" icon="chart-line" href="/tutorials/monitoring-and-audit">
    View policy enforcement events and audit trails.
  </Card>
</CardGroup>
