Guardrails that forbid destructive tool calls#
You will add guardrails to a workspace so that no agent — whoever
wrote it, whatever its policy says — can delete, drop, pay, transfer or
send through its tools, merge a pull request, or write anything outside
office hours. Guardrails only forbid, and a forbid beats every
permit, so no agent's policy can lift them.
Before you begin#
- The workspace's admin role.
- Know your tools' names: guardrails can match MCP tools by name
(
resource.name), HTTP requests by method, host and path, and the provider's own tools.
1. Write the guardrails#
// MCP tools whose names say they destroy, send or pay.
@id("no-destructive-tools")
@reason("tools that delete, send or pay are not allowed")
forbid (principal, action == Action::"tools/call", resource)
when {
resource.name like "*delete*" || resource.name like "*remove*" || resource.name like "*drop*" ||
resource.name like "*destroy*" || resource.name like "*purge*" || resource.name like "*truncate*" ||
resource.name like "*send*" || resource.name like "*pay*" || resource.name like "*transfer*"
};
// No HTTP DELETE on any tool or page.
@id("no-http-delete")
@reason("deleting is for people")
forbid (principal, action == Action::"http", resource)
when { resource.method == "DELETE" };
// No merging, by MCP tool or REST.
@id("no-merges")
@reason("merging is for people")
forbid (principal, action == Action::"tools/call", resource)
when { resource.name like "*merge*" };
@id("no-rest-merges")
@reason("merging is for people")
forbid (principal, action == Action::"http", resource)
when { resource.host == "api.github.com" && resource.method == "PUT" && resource.path like "/repos/*/pulls/*/merge" };
// No code run on the model provider's side.
@id("no-provider-code-execution")
@reason("code runs in the sandbox, not at the provider")
forbid (principal, action == Action::"model/server_tool", resource)
when { resource.tool like "code_*" };
// Outside 07:00–19:00 UTC on weekdays, HTTP tools may only be read.
@id("office-hours")
@reason("writes wait for office hours")
forbid (principal, action == Action::"http", resource)
when { !(["GET", "HEAD"].contains(resource.method)) && (context.weekday > 5 || context.hour < 7 || context.hour >= 19) };
If one agent must merge with a person's approval — the
pull-request reviewer — leave out no-merges and use
that recipe's narrower guardrail, which exempts it.
Matching by name catches tools you add later whose names follow the same
words — and can catch an innocent one (list_removed_items). Test before
you rely on it (step 3).
2. Add them to the workspace#
- Anemoi → Guardrails → New guardrail.
- Name
no-destruction, a Description, and the first text — or press Examples and start from No deleting, No merging pull requests, No code run by the model's provider. - Create. Repeat for
office-hours(example Writes in office hours only).
$ for g in no-destruction office-hours; do
jq -n --arg n "$g" --rawfile t "$g.cedar" '{metadata: {name: $n}, spec: {text: $t}}' \
| curl -sS -X POST "$WS/agent-guardrails" -H "Authorization: Bearer $ASTRA_TOKEN" \
-H "Content-Type: application/json" -d @- | jq -r .metadata.name
done
no-destruction
office-hours
A text with a permit is refused (a guardrail may only forbid).
3. Test them against an agent's policy#
A guardrail applies when a run is made; to see the combined decision before that, test the agent's policy with the guardrails appended:
$ curl -sS "$WS/agents/pr-reviewer" -H "Authorization: Bearer $ASTRA_TOKEN" | jq -r .spec.policy.tools > agent.cedar
$ cat agent.cedar no-destruction.cedar office-hours.cedar > combined.cedar
$ jq -n --rawfile p combined.cedar '{tools: $p, tests: [
{server: "github", tool: "merge_pull_request"},
{server: "github", tool: "delete_file"},
{server: "github", tool: "pull_request_read"}]}' \
| curl -sS -X POST "$WS/agent-policy-checks" -H "Authorization: Bearer $ASTRA_TOKEN" \
-H "Content-Type: application/json" -d @- | jq -c '.tests[] | {decision, reason}'
{"decision":"deny","reason":"merging is for people (no-merges)"}
{"decision":"deny","reason":"tools that delete, send or pay are not allowed (no-destructive-tools)"}
{"decision":"allow","reason":"permitted by github-reads"}
The console's Test a call on an agent decides by the agent's own policy only.
4. See them work#
Runs made from now on carry the guardrails; runs under way keep the policy they started with. In a new run's Trace, a refused call shows the guardrail's rule and reason, and the agent is told merging is for people (no-merges). Each run's Receipt names the guardrails it ran under, and its policy digest covers them.
For every workspace#
To hold every workspace of the organisation, an organisation owner or admin
writes the same text as an organisation guardrail; it appears in each
workspace as org-<name>, read-only. See
Agent governance.