PolicyContext
The policy author's view of an AgentAction. The clean interface for writing real application policy.
Definition
PolicyContext is not the same thing as raw action data. It is what the policy author sees.
The runtime has a large internal record — trace IDs, capability metadata, tenant metadata, workflow state, risk scores, side effect metadata. The policy author should not have to navigate all of that directly. PolicyContext is the clean, stable interface that makes policy feel like natural application code.
def my_policy(ctx: PolicyContext) -> Decision:
if ctx.is_prod and ctx.is_high_risk:
return Decision(type="deny", reason="Requires approval in prod")
return Decision(type="allow")The goal is to let developers express real application policy without writing a rule engine.
Raw Fields
These are the underlying fields on the context object:
action— the full AgentAction recordargs— bound input arguments for the calloutput— the function's return value (only available in after_capability policies)runtime_metadata— metadata passed from the runtime to the policy engine
Convenience Properties
| Property | Type | Description |
|---|---|---|
| capability | Capability | The capability being attempted. |
| tool | Capability | Alias for capability. Convenience for tool-centric policies. |
| agent_id | str | None | Agent identity from the action. |
| principal_id | str | None | Principal identity from the action. |
| tenant_id | str | None | Tenant from the action. |
| is_prod | bool | True if environment is 'prod'. |
| is_high_risk | bool | True if capability.risk is 'high' or 'critical'. |
| has_side_effects | bool | True if capability has any side effects. |
| disableable_side_effects | list[SideEffect] | Side effects that can be disabled via input transform. Planned. |
Methods
ctx.arg(name, default=None)Read an input argument by name. Returns the bound argument value, or default if not present.
ctx.arg("amount_usd") # 249.0
ctx.arg("dry_run", False) # Falsectx.agent_has_scope(scope)Check whether the capability requires a given scope name. Returns True if the capability.scopes list contains a Scope with that name. Note: this is a name-match check against capability metadata, not a full agent grant check (planned).
ctx.agent_has_scope("refunds:create") # True or FalseCommon Policy Patterns
Check environment:
if ctx.is_prod:
# extra scrutiny in production
...Read an input argument:
amount = ctx.arg("amount_usd", 0)
if amount > 100:
return Decision(type="deny", reason="Amount exceeds limit")Check risk level:
if ctx.is_high_risk and ctx.is_prod:
return Decision(type="deny", reason="High-risk tool blocked in prod")Check tenant:
ALLOWED_TENANTS = {"tenant_acme", "tenant_globex"}
if ctx.tenant_id not in ALLOWED_TENANTS:
return Decision(type="deny", reason="Tenant not authorized")Check scope (basic name match):
if not ctx.agent_has_scope("refunds:create"):
return Decision(type="deny", reason="Missing refunds:create scope")Inspect output (after policy only):
@runtime.after_capability("call_model")
def check_model_output(ctx):
if ctx.output and "SECRET_KEY" in str(ctx.output):
return Decision(type="deny", reason="Possible secret in model output")
return Decision(type="allow")Combine multiple checks:
@runtime.before_capability("refund_customer")
def refund_policy(ctx):
amount = ctx.arg("amount_usd", 0)
tenant_limit = get_tenant_limit(ctx.tenant_id)
if ctx.is_prod and amount > tenant_limit:
return Decision(
type="deny",
reason=f"Refund ${amount} exceeds tenant limit of ${tenant_limit}",
)
return Decision(type="allow")What PolicyContext Does Not Have
PolicyContext surfaces a curated interface. It does not expose the full runtime internals. This is intentional — policy authors should not need to know about framework-specific metadata, internal trace systems, or deployment details. If you need additional context for your policies, use capability.metadata or action.metadata to pass custom data through.
Future versions will add: principal_has_scope(), tenant_has_feature(), has_grant(), and require_metadata().