Policy & Governance
Waxell Observe provides a policy enforcement layer that checks whether an agent is allowed to execute before it runs. Policies are configured on the control plane and evaluated server-side, giving administrators centralized control over agent behavior without modifying agent code.
New to policies? Start with Policy Categories & Templates to see what's available, then use the Platform Assistant to create your first policy through chat.
How It Works
- Before an agent executes, the client sends a policy check request to the control plane
- The control plane evaluates all applicable policies (sorted by priority)
- The response contains an action that determines what happens next
Policies are defined across 26 categories -- from cost budgets and content scanning to data access boundaries, cognitive governance, and regulatory compliance. The platform also generates automated recommendations based on your agents' actual behavior.
Policy Actions
Every policy check returns one of these actions:
| Action | Meaning | Client Behavior |
|---|---|---|
allow | Execution is permitted | Agent runs normally |
warn | Execution is permitted with a warning | Agent runs; warning recorded in trace |
redact | Sensitive content masked | Content replaced with ##TYPE## placeholders |
throttle | Rate limited | PolicyViolationError raised |
block | Execution denied | PolicyViolationError raised |
skip | Execution skipped silently | Run not started, no error raised |
retry | Execution retried | Automatic retry with backoff |
The PolicyCheckResult object provides convenience properties:
result.action # "allow", "block", "warn", "throttle", "redact", "skip", "retry"
result.reason # Human-readable explanation
result.metadata # Additional data from the policy engine
result.allowed # True if action is "allow" or "warn"
result.blocked # True if action is "block", "throttle", or "skip"
Pre-Execution Checks
The recommended pattern: call waxell.init() before importing your LLM SDK, then wrap your agent with @observe(enforce_policy=True). Policies are checked server-side before your function runs, and every LLM call inside is auto-captured.
import waxell_observe as waxell
from waxell_observe.errors import PolicyViolationError
waxell.init() # BEFORE importing the LLM SDK
import openai
client = openai.OpenAI()
@waxell.observe(agent_name="support-bot", enforce_policy=True)
async def handle_query(query: str) -> str:
# Policies checked automatically before this runs
response = client.chat.completions.create( # auto-captured
model="gpt-4o",
messages=[{"role": "user", "content": query}],
)
return response.choices[0].message.content
try:
result = await handle_query("test")
except PolicyViolationError as e:
print(f"Blocked: {e.policy_result.action}")
print(f"Reason: {e.policy_result.reason}")
print(f"Details: {e.policy_result.metadata}")
With LangChain
from waxell_observe.integrations.langchain import WaxellLangChainHandler
from waxell_observe.errors import PolicyViolationError
handler = WaxellLangChainHandler(
agent_name="langchain-bot",
enforce_policy=True,
)
try:
result = chain.invoke(input, config={"callbacks": [handler]})
handler.flush_sync(result={"output": result})
except PolicyViolationError as e:
print(f"Blocked: {e}")
Mid-Execution Policy Checks
For long-running agents, check policies between steps to catch budget exhaustion or policy changes during execution:
import waxell_observe as waxell
waxell.init()
@waxell.observe(
agent_name="pipeline-agent",
enforce_policy=True,
mid_execution_governance=True, # Check on each step
)
async def run_pipeline(query: str) -> str:
# Step 1
data = await retrieve(query)
waxell.step("retrieve", output={"sources": len(data)})
# Step 2 -- mid-execution check happens automatically
summary = await summarize(data)
waxell.step("summarize", output={"length": len(summary)})
return summary
Advanced: Manual Mid-Execution Checks with WaxellContext
For full control over when policy checks happen (e.g., conditional checks based on intermediate state), use WaxellContext directly:
from waxell_observe import WaxellContext
async with WaxellContext(agent_name="pipeline-agent") as ctx:
await step_one()
ctx.record_step("step_one")
# Manual mid-execution check
policy = await ctx.check_policy()
if policy.blocked:
ctx.set_result({"stopped_at": "step_one", "reason": policy.reason})
return
await step_two()
ctx.record_step("step_two")
For most agents, prefer @observe(mid_execution_governance=True) above -- the decorator runs the check automatically on each waxell.step() call.
Approval Workflows
When a policy blocks execution, you can handle it with human approval instead of failing. Pass on_policy_block to define what happens:
@waxell.observe(
agent_name="data-manager",
workflow_name="delete",
enforce_policy=True,
on_policy_block=waxell.prompt_approval, # terminal Y/N prompt
)
async def delete_records(table: str) -> dict:
return {"deleted": 500, "table": table}
If the human approves, the function executes normally. If denied or timed out, PolicyViolationError propagates to the caller.
Built-in handlers: prompt_approval (terminal), auto_approve (testing), auto_deny (testing). You can also write custom handlers for Slack, webhooks, or any approval channel.
See Approval Workflows for the full guide — custom handlers, ApprovalDecision, and what gets traced.
Disabling Policy Checks
Skip enforcement in development or testing:
# Decorator
@waxell.observe(agent_name="my-agent", enforce_policy=False)
async def my_function():
...
# Or disable all observability
# export WAXELL_OBSERVE=false
Configuring Policies
Policies are configured on the Waxell control plane, not in agent code. This separation means:
- Administrators define what agents are allowed to do
- Developers write agents that respect those policies automatically
- Policy changes take effect immediately without redeploying agents
Three ways to configure policies:
- Dashboard -- Governance > Policies in the UI
- API --
POST /waxell/v1/policies/(see API Reference) - Platform Assistant -- Ask the AI to create policies through chat (see Platform Assistant)
See Policy Categories & Templates for the full list of available policy types and pre-built templates.
When the control plane is unreachable
The SDK is fail-open by default: if a policy check cannot complete, the operation proceeds and the run records that governance was skipped. An agent does not stop working because telemetry is down.
Set WAXELL_SURFACE_GATE=closed (or WAXELL_GATE_FAIL_CLOSED=1) to invert
that. In fail-closed mode the preventive surfaces — run.start,
llm.before, tool.before, network.before, mcp.before, spawn.before,
memory.write, memory.delete, code.exec — block instead of allowing, so
governance cannot be bypassed by taking the control plane offline. Surfaces
that only observe are unaffected: there is nothing to prevent once the call
has happened.
export WAXELL_SURFACE_GATE=closed # compliance-critical deployments
Choose per environment. Fail-closed means a control-plane outage stops your agents; fail-open means an outage leaves them ungoverned. Both are defensible and neither is a safe default for everyone, which is why it is explicit.
Runs Waxell executes for you
Experiments, the model arena, the prompt playground and version replay run on Waxell's servers rather than in your process, so there is no SDK in the path. They are governed too: the tenant's Content policies are evaluated on the prompt before it reaches the provider and on the response before it reaches you, with the same block / redact / warn actions.
Two differences from the SDK path, both deliberate:
- Identity. These runs have no agent of yours to attribute to, so they
carry a synthetic one —
system:model-arena,system:experiment-runner,system:prompt-playground,system:prompt-replay. A policy scoped to a named agent will not apply to them; a policy with no agent scope is global and will. If you want a policy to cover hosted runs specifically, scope it to one of those names. - Failure posture. Hosted evaluation runs in the same process as the policy engine, with no network between them, so there is nothing to partition. An evaluation error there is a defect rather than an outage and fails closed — unlike the SDK, which fails open by default for the reason described above.
Evaluators and LLM-as-judge passes are exempt. A judge reads the output it is scoring, which may contain exactly the content a policy targets; redacting underneath it would score text the judge never saw and report a pass.
What an outage costs
Governance is never the reason an agent is slow. Each in-flight policy check
is a single attempt with a short timeout (WAXELL_GATE_TIMEOUT, default 2s).
Telemetry calls bound the connect stage separately
(WAXELL_CONNECT_TIMEOUT, default 2s) and a refused connection is not
retried — nothing is listening, so the ladder would only cost the caller
time. After two consecutive connection failures against a host, further calls
to it short-circuit for 30 seconds and the first request after that window is
allowed through as a probe.
Scoping a policy to sub-agents
Policies resolve by agent name, and a sub-agent runs under its own name — so a policy scoped to the agent that spawned it does not follow it. Scope by kind instead:
scope_agent_types: ["subagent"]
One policy then applies to every sub-agent in the tenant, regardless of what it is called or which framework spawned it. Policies with no scope continue to apply to everything, parents and sub-agents alike.
Delegation itself is evaluated at the spawn.before surface before the
sub-agent starts, which is where spawn-limit, concurrent-spawn,
delegation-depth and cross-agent-isolation policies decide. Set
inherit_policies: true on a delegation policy to be told when a delegate
runs under narrower scope than its parent.
See Multi-Agent for how this applies to LangGraph and deep agents.
Next Steps
- Approval Workflows -- Handle policy blocks with human approval
- Human-in-the-Loop -- Capture any interactive human input as observable spans
- Policy Categories & Templates -- All 26 categories and pre-built templates
- Code Execution Policy -- Govern generated code: blocked commands, paths, sandbox requirements, and human review
- Communication Policy -- Govern output channels: allowed/blocked channels, disclaimers, and message limits
- Context Management Policy -- Control conversation length, context window usage, and session boundaries
- Compliance Policy -- Meta-validator that checks required sibling policies are active for HIPAA, SOC 2, and other regulatory frameworks
- Safety Policy -- Step limits, tool call limits, and execution depth guardrails
- Content Policy -- PII detection, credential scanning, injection patterns, and blocked phrases
- Rate Limit Policy -- Request rates and concurrency limits per agent, user, or team
- Kill Switch Policy -- Emergency stop based on error rates or anomaly thresholds
- LLM Policy -- Model allowlists/blocklists and provider restrictions
- Quality Policy -- Output quality thresholds and stability checks
- Operations Policy -- Timeouts, retry limits, and concurrency settings
- Input Validation Policy -- Schema validation, size limits, and input sanitization
- Data Access Policy -- Data source allowlists/blocklists, read-only enforcement, record limits
- Network Policy -- Outbound domain allowlists/blocklists and protocol restrictions
- Scope Policy -- Blast radius limits for records, files, transactions, and API writes
- Grounding Policy -- Source grounding requirements, citation minimums, abstention thresholds, and LLM-based grounding evaluation
- Retrieval Policy -- RAG quality governance with relevance scores, source age, and diversity
- Reasoning Policy -- Decision explainability, bias detection, and confidence requirements
- Delegation Policy -- Multi-agent trust, delegation depth, and policy inheritance
- Privacy Policy -- Consent requirements, data residency, and purpose limitations
- Identity Policy -- AI disclosure requirements and impersonation prevention
- Memory Policy -- Session isolation, cross-session memory, and retention limits
- Recommendations -- Automated policy suggestions from runtime data
- Platform Assistant -- Create and manage policies via chat
- REST API Reference -- Direct API usage