Docs/Concepts/PolicyContext
Implemented

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 record
  • args — bound input arguments for the call
  • output — the function's return value (only available in after_capability policies)
  • runtime_metadata — metadata passed from the runtime to the policy engine

Convenience Properties

PropertyTypeDescription
capabilityCapabilityThe capability being attempted.
toolCapabilityAlias for capability. Convenience for tool-centric policies.
agent_idstr | NoneAgent identity from the action.
principal_idstr | NonePrincipal identity from the action.
tenant_idstr | NoneTenant from the action.
is_prodboolTrue if environment is 'prod'.
is_high_riskboolTrue if capability.risk is 'high' or 'critical'.
has_side_effectsboolTrue if capability has any side effects.
disableable_side_effectslist[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)   # False
ctx.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 False

Common 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().