Domain Governance Policy
The domain-governance policy controls which domain actions an agent can invoke, validates payloads, and gates sensitive operations behind approval. This governs the communication between the Waxell runtime and external business applications (e.g., Firecrawl scraping, database writes, payment processing).
Fires at the before_domain_call hook (E5 -- the Connect domain-endpoint guard, currently shadow-mode in many tenants until fully ratcheted on).
Rules
| Rule | Type | Default | Description |
|---|---|---|---|
allowed_domains | string[] | [] | Domain allowlist. Empty = all domains allowed |
blocked_domains | string[] | [] | Domain blocklist. Always blocks even if in allowed_domains |
allowed_actions | object | {} | Per-domain action allowlist, e.g. {"vendor_research": ["search_web"]} |
blocked_actions | object | {} | Per-domain action blocklist. Use "*" to block all actions in a domain |
require_approval_for | string[] | [] | "domain/action" strings that require approval (emits WARN) |
max_payload_size_kb | integer | 0 | Max payload size per call. 0 = unlimited |
max_calls_per_run | integer | 0 | Max domain calls per agent run. 0 = unlimited |
log_all_calls | boolean | true | Log every domain call for audit |
action_on_violation | string | "block" | Either block or warn |
How It Works
The domain-governance handler fires at before_domain_call (per-call enforcement) and runs audit at after_workflow.
| Phase | What It Checks | Actions |
|---|---|---|
before_workflow | Stores rules and call-counter on context for downstream phases | ALLOW |
before_domain_call | Increments call counter; checks max_calls_per_run, blocked_domains, allowed_domains, blocked_actions, allowed_actions, max_payload_size_kb, require_approval_for | BLOCK / WARN per action_on_violation (approval always WARN) |
after_workflow | Audits context.domain_intents for any blocked-domain calls that slipped through | WARN if violations, ALLOW otherwise |
Context Attributes Read
| Attribute | Phase | Purpose |
|---|---|---|
context._domain_governance_rules | before_domain_call | Rules stamped by before_workflow |
context._domain_call_count | before_domain_call | Running call count (incremented by handler) |
context.domain_intents | after_workflow | List of {domain_name, action_name} dicts the run produced |
Enforcement Order
For each call the handler evaluates in this order, returning the first violation:
max_calls_per_runexceeded- Domain in
blocked_domains - Domain not in
allowed_domains(if allowlist set) - Action in
blocked_actions[domain](or"*"blocks all) - Action not in
allowed_actions[domain](if allowlist set for that domain) - Payload size >
max_payload_size_kb "domain/action"inrequire_approval_for(emits WARN, not BLOCK)
Example Policy
{
"name": "Vendor Research Agent Guardrails",
"category": "domain-governance",
"rules": {
"allowed_domains": ["vendor_research", "contract_analysis"],
"blocked_domains": ["payment"],
"allowed_actions": {
"vendor_research": ["get_vendor_profile", "search_web", "scrape_website"],
"contract_analysis": ["get_contracts", "spend_analysis"]
},
"blocked_actions": {
"payment": ["*"]
},
"require_approval_for": [
"vendor_research/save_vendor_research",
"contract_analysis/save_contract_intelligence"
],
"max_payload_size_kb": 1024,
"max_calls_per_run": 50,
"log_all_calls": true,
"action_on_violation": "block"
},
"scope": {"agents": ["procurement-agent"]},
"enabled": true
}
SDK Integration
import waxell_observe as waxell
waxell.init()
@waxell.observe(agent_name="procurement-agent", enforce_policy=True)
async def research_vendor(vendor: str) -> dict:
# Every ctx.call_domain("vendor_research", "search_web", {...}) goes
# through before_domain_call. The handler checks allow/block lists,
# increments _domain_call_count, and enforces payload size.
return await ctx.call_domain("vendor_research", "search_web", {"q": vendor})
Observability
| Field | Example (block) |
|---|---|
| Category | domain-governance |
| Action | block |
| Reason | "Action 'payment/charge' is blocked by policy" |
| Metadata | {"domain": "payment", "action": "charge"} |
| Field | Example (payload size) |
|---|---|
| Reason | "Domain call payload exceeds limit (1547.2KB > 1024KB)" |
| Metadata | {"domain": "vendor_research", "action": "bulk_import", "payload_size_kb": 1547.2} |
| Field | Example (approval) |
|---|---|
| Action | warn |
| Reason | "Action 'vendor_research/save_vendor_research' requires approval (proceeding with warning)" |
| Metadata | {"requires_approval": true} |
Common Gotchas
require_approval_forALWAYS emits WARN, never BLOCK. It is a soft gate -- the call proceeds but is flagged in observability. To actually halt, put the action inblocked_actionsinstead and use a separate approval workflow.allowed_domainsis the most restrictive switch. A non-emptyallowed_domainsbecomes a strict allowlist: any domain not in the list is blocked. To permit everything except a few, leaveallowed_domainsempty and useblocked_domains.blocked_actions["*"]is wildcard for that domain, not all domains. Setting"blocked_actions": {"payment": ["*"]}blocks allpaymentactions. There is no global"*"key -- useblocked_domains: ["payment"]for whole-domain blocking.- Payload-size check requires JSON-serializable inputs. The handler calls
json.dumps(inputs, default=str). If inputs contain a non-serializable object that evendefault=strcan't handle, the size check is silently skipped (caughtTypeError/ValueError). max_calls_per_runis checked BEFORE the other rules. A run hitting the call cap will be blocked with "Domain call limit exceeded" even if the next call would have been blocked for a more specific reason.before_workflowis required to stampcontext._domain_call_count = 0. If your runtime path skipsbefore_workflowand goes straight tobefore_domain_call, the counter starts atgetattr(context, "_domain_call_count", 0)which is 0 -- still safe, but the rule schema assumes the stamp happened.- Audit in
after_workflowonly warns, it cannot block. The run is over. Use it for compliance evidence; rely onbefore_domain_callBLOCK for prevention.
Next Steps
- Tool Allowlist (Safety) --
blocked_tools/approval_toolsfor tool-level control, complementary to domain-level - Network Policy -- Lower-level HTTP allow/block; domain governance is the semantic layer above it
- Approval Policy -- General human-in-the-loop gates
- Policy Categories