# Best Practices

Well-designed policies help AI agents operate safely without slowing down development.

## Start with sandbox mode

Develop and test workflows in sandbox mode before enabling production execution:

```bash
swy init --mode=sandbox
```

Sandbox mode lets you exercise the full pipeline - trusted tools, policy evaluation, execution - against non-production resources, so a policy mistake never touches real data.

---

## Block destructive operations

Prevent accidental changes by blocking operations such as `DELETE`-style calls, database resets, and infrastructure removal. In practice this means a policy that always fires for the target method:

```json
{
  "id": "block-repo-delete",
  "target": ["github.repo.delete"],
  "when": { "field": "name", "operator": "exists" },
  "action": {
    "type": "POLICY_BLOCKED",
    "message": "Repository deletion is disabled in this environment."
  }
}
```

`name` is a field every `github.repo.delete` request has, so this condition always matches and the method is effectively disabled - the request never reaches the GitHub API.

---

## Require a human for sensitive actions

For actions that should run only after a person confirms them, add a [human approval](/policies/human-approval/) policy. Swytchcode holds the command and asks in Slack. The terminal exits while the request is pending.

Good candidates for approval:

- Payments and refunds above a threshold
- Billing and subscription changes
- Production deployments
- User and permission management

If the action should never run in this environment, use a hard block instead of approval:

```json
{
  "id": "block-refunds",
  "target": ["stripe.payment.refund"],
  "when": { "field": "id", "operator": "exists" },
  "action": {
    "type": "POLICY_BLOCKED",
    "message": "Refunds are disabled in this environment."
  }
}
```

---

## Keep policies simple

Create small, focused rules instead of a few large, complex ones - one `id` per business rule, not one rule with a dozen nested `all`/`any`/`not` groups.

Simple policies are easier to review and maintain, and easier to identify from `swy audit policy` output when they fire.

---

## Test changes before deployment

Write down what each policy should do as [tests](/configuration/policy-json/#tests) in the file, next to the policy:

```json
"tests": [
  { "args": { "to": "a@gmail.com" }, "expect": "block" },
  { "args": { "to": "a@acme.com" }, "expect": "allow" }
]
```

`swy policy validate` runs them against the whole file and exits with an error if one fails, so run it in CI. Tests catch the surprises reviews miss, such as another policy blocking a call you meant to allow. Keep real customer data out of test arguments: `policies.json` is usually committed.

Then check a real request with a dry run, which runs every policy without calling the live API:

```bash
swy policy validate
swy exec <canonical_id> --dry-run
```

Deploy the change, then confirm it behaves as expected by checking `swy audit policy`.

---

## Try new rules in monitor mode

A new rule can block more than you meant. Add `"mode": "monitor"` first: the rule records its matches in `swy audit policy` as `monitored` but never blocks. Once it catches exactly the calls you intended, remove `mode` to enforce it. The same works for a whole allowlist with `"on_no_match": "monitor"` before `"deny"`.

---

## Tell the agent what to do instead

A bare "blocked" leaves an agent guessing, and it may retry or look for a way around. Give each blocking policy a clear `message` and, in a version-2 file, a `hint`:

```json
"action": {
  "type": "POLICY_BLOCKED",
  "message": "Emails to {{$email_domains}} are not allowed",
  "hint": "Use the customer's business email address, or ask the user"
}
```

---

## Prefer changing a call to failing it

When a request is almost right, a `MODIFY` policy can fix it instead of blocking - cap a page size, add a compliance BCC, or turn an agent's payment into a dry run. The agent is told what changed. See [MODIFY](/configuration/policy-json/#modify).

---

## Put time limits on temporary rules

For a change freeze or an incident, set `expires_at` so the rule stops on its own. `swy policy validate` warns about expired policies so they can be removed.

---

## Review policies regularly

As your project grows, review policy rules with `swy policy list` to ensure they still reflect your application's security requirements, and check `swy audit policy` periodically for violations that suggest a rule is too strict (or too loose).

Provider updates can rename or remove fields. After `swy sync`, `swy get`, or `swy add`, read the policy warnings they print: a policy on a field that no longer exists never matches.

## Next Steps

- [Policy recipes](https://docs.swytchcode.com/policies/recipes/) - Ready-to-use policies for email, payments, data safety, and more.
- [Human approval](https://docs.swytchcode.com/policies/human-approval/) - Hold high-impact commands until someone approves in Slack.
- [Policy Rules](https://docs.swytchcode.com/policies/policy-rules/) - Learn how to define custom policy rules using conditions, operators, and actions to control tool execution.
- [Production Guardrails](https://docs.swytchcode.com/policies/production-guardrails/) - Discover best practices for safely deploying AI agents and protecting production systems.
- [CLI Overview](https://docs.swytchcode.com/cli/overview/) - Understand how the Swytchcode CLI manages projects, integrations, authentication, and policy enforcement.
