Skip to main content

Scope Policy

The scope policy category enforces blast-radius limits — it controls the maximum impact a single agent execution can have on data, files, transactions, and external systems.

Use it when agents perform write operations (database updates, file modifications, financial transactions, external API calls) and you need guardrails to prevent runaway executions from causing disproportionate damage.

Rules

RuleTypeDefaultDescription
max_records_modifiedinteger100Maximum number of records that can be modified in a single execution
max_records_deletedinteger0Maximum number of records that can be deleted (default 0 means deletions require explicit allowance)
max_files_changedinteger10Maximum number of files that can be created or modified
max_transaction_amountnumber1000.00Maximum total dollar amount of financial transactions
max_api_writesinteger50Maximum number of external API write calls (POST, PUT, PATCH, DELETE)
require_rollback_capabilitybooleanfalseWarn if the agent context does not declare rollback support
dry_run_firstbooleanfalseWhen true, sets context._dry_run_mode = true at before_workflow
action_on_violationstring"block""block" raises PolicyViolationError; "warn" logs and continues

How It Works

The scope handler runs at all three enforcement phases:

before_workflow

Stores the scope rules into context._scope_rules for mid-execution access. Optionally enables dry-run mode (context._dry_run_mode = true) and warns if rollback capability is required but not declared.

mid_execution

Reads the running totals from the context and checks each limit:

  1. context.records_modified vs max_records_modified
  2. context.records_deleted vs max_records_deleted
  3. context.files_changed vs max_files_changed
  4. context.transaction_total vs max_transaction_amount
  5. context.api_writes vs max_api_writes

The first exceeded limit produces an immediate result (BLOCK or WARN based on action_on_violation). Limits are checked in this order — only one violation is reported per mid_execution check.

after_workflow

Audits all limits again against the final totals. Collects all violations (not just the first) and returns them as a warning list in the metadata, regardless of action_on_violation. This is an audit record — the after_workflow phase does not block on scope.

When mid_execution Fires

mid_execution runs every time the agent calls ctx.record_scope_impact(). This means violations are caught as soon as the agent reports impact, not just at the end of execution. The agent is blocked before additional writes can occur.

SDK Integration

Recording Scope Impact

import waxell_observe as waxell
from waxell_observe.errors import PolicyViolationError

async with waxell.WaxellContext(
agent_name="data-agent",
enforce_policy=True,
) as ctx:
# Perform your data operation
result = await update_database(query)

# Report the blast radius — triggers mid_execution governance check
ctx.record_scope_impact(
records_modified=result.rows_updated,
records_deleted=result.rows_deleted,
files_changed=result.files_written,
transaction_total=result.payment_amount,
api_writes=result.external_calls,
)

Additive Totals

record_scope_impact() is additive — each call increments the running totals. Call it once per operation to build up an accurate picture of cumulative impact:

async with waxell.WaxellContext(...) as ctx:
# First batch operation
ctx.record_scope_impact(records_modified=30, api_writes=5)

# Second batch operation — totals are now 80 records, 12 writes
ctx.record_scope_impact(records_modified=50, api_writes=7)

# Third batch — totals now 105 records, 12 writes
# If max_records_modified=100, this call triggers a mid_execution BLOCK
ctx.record_scope_impact(records_modified=25)

Handling Violations

try:
async with waxell.WaxellContext(
agent_name="data-agent",
enforce_policy=True,
) as ctx:
result = await process_records(batch)
ctx.record_scope_impact(
records_modified=len(result.modified),
records_deleted=len(result.deleted),
transaction_total=result.total_amount,
)
ctx.set_result(result)

except PolicyViolationError as e:
# e.g. "Records modified (250) exceeds limit (100)"
print(f"Scope limit exceeded: {e}")
await notify_operator(str(e))

Example Policies

Conservative Data Agent

Strict limits for agents with broad database access:

{
"max_records_modified": 100,
"max_records_deleted": 0,
"max_files_changed": 10,
"max_transaction_amount": 1000.00,
"max_api_writes": 50,
"require_rollback_capability": false,
"action_on_violation": "block"
}

Financial Operations

Zero tolerance for large transactions; allow more record modifications:

{
"max_records_modified": 500,
"max_records_deleted": 10,
"max_files_changed": 20,
"max_transaction_amount": 5000.00,
"max_api_writes": 100,
"require_rollback_capability": true,
"action_on_violation": "block"
}

Bulk ETL Pipeline

High limits for intentional bulk operations — use warn mode to audit without blocking:

{
"max_records_modified": 10000,
"max_records_deleted": 1000,
"max_files_changed": 50,
"max_transaction_amount": 0,
"max_api_writes": 0,
"action_on_violation": "warn"
}

Read-Only Enforcement

Prevent any writes — useful for analytics agents that should only read:

{
"max_records_modified": 0,
"max_records_deleted": 0,
"max_files_changed": 0,
"max_transaction_amount": 0,
"max_api_writes": 0,
"action_on_violation": "block"
}

Enforcement Flow

Agent starts (WaxellContext.__aenter__)

└── before_workflow governance runs
├── Stores scope rules into context._scope_rules
├── require_rollback_capability? → WARN if context.supports_rollback is False
└── dry_run_first? → sets context._dry_run_mode = True

Agent runs — calls ctx.record_scope_impact(...)

└── mid_execution governance runs (each call)
├── records_modified > max_records_modified? → BLOCK/WARN
├── records_deleted > max_records_deleted? → BLOCK/WARN
├── files_changed > max_files_changed? → BLOCK/WARN
├── transaction_total > max_transaction_amount? → BLOCK/WARN
├── api_writes > max_api_writes? → BLOCK/WARN
└── All within limits → ALLOW

Agent completes (WaxellContext.__aexit__)

└── after_workflow governance runs
├── Re-checks all limits against final totals
├── Collects all violations (not just first)
├── Violations → WARN with impact_summary metadata
└── No violations → ALLOW with impact_summary metadata

Creating via Dashboard

  1. Navigate to Governance > Policies
  2. Click New Policy
  3. Select category Scope
  4. Set your limit values
  5. Set action_on_violation to block or warn
  6. Set scope to target specific agents (e.g., data-agent)
  7. Enable

Creating via API

curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
https://acme.waxell.dev/waxell/v1/policies/ \
-d '{
"name": "Conservative Data Agent Limits",
"category": "scope",
"rules": {
"max_records_modified": 100,
"max_records_deleted": 0,
"max_files_changed": 10,
"max_transaction_amount": 1000.00,
"max_api_writes": 50,
"action_on_violation": "block"
},
"scope": {
"agents": ["data-agent"]
},
"enabled": true
}'

Observability

Governance Tab

Scope evaluations appear at each enforcement phase:

before_workflow (always ALLOW unless rollback required):

FieldExample
Policy nameConservative Data Agent Limits
Actionallow
Phasebefore_workflow
Reason"Scope limits stored for enforcement"

mid_execution (per record_scope_impact call):

FieldExample
Actionblock
Phasemid_execution
Reason"Records modified (250) exceeds limit (100)"
Metadata{"records_modified": 250, "limit": 100}

after_workflow (final audit):

FieldExample
Actionallow
Phaseafter_workflow
Reason"Scope audit passed (modified=8, deleted=0, files=2, tx=$450.00)"
Metadata{"impact_summary": {"records_modified": 8, "records_deleted": 0, ...}}

Impact Summary Metadata

Every after_workflow evaluation includes a full impact_summary:

{
"impact_summary": {
"records_modified": 8,
"records_deleted": 0,
"files_changed": 2,
"transaction_total": 450.0,
"api_writes": 3
}
}

This is available even on successful runs, making it useful for auditing the actual impact of every execution.

Common Gotchas

  1. max_records_deleted defaults to 0. This means the default policy blocks any record deletion. If your agent deletes records legitimately, set an explicit max_records_deleted value.

  2. record_scope_impact() is additive, not absolute. Each call adds to the running total. If you call it three times with records_modified=50 each, the total is 150 — not 50.

  3. mid_execution only reports the first violation. If records_modified and transaction_total both exceed limits in the same call, only the first exceeded limit (by check order) is reported. The after_workflow phase reports all violations.

  4. after_workflow always warns, never blocks. Even if action_on_violation=block, the after_workflow phase issues warnings in its result metadata. Use mid_execution for blocking enforcement.

  5. Rollback checks always warn, never block. Setting require_rollback_capability=true when the context does not support rollback produces a WARN at before_workflow, not a BLOCK. The agent still runs.

  6. Zero values are valid limits. max_transaction_amount=0 blocks any financial transaction. max_api_writes=0 blocks all external API writes. Use this for read-only enforcement.

  7. Limits are per-execution, not per-day. The scope policy resets with each new WaxellContext. For rate-limiting across executions, use the rate-limit policy category.

Combining with Other Policies

  • cost: Use cost to limit LLM spend per execution; use scope to limit data impact. They operate on different dimensions.
  • audit: Enable audit alongside scope to get a permanent record of every impact summary. The after_workflow metadata from scope is captured in the audit trail.
  • approval: Combine scope with approval to require human sign-off before executions that would approach scope limits.
  • operations: Use operations to limit execution time and retries; use scope to limit data blast radius. Both protect against runaway executions in complementary ways.

Next Steps