Skip to main content

Custom Action Builder Example

This example demonstrates how to use a custom action builder when application inputs do not map directly to policy fields.

This is one of the most important advanced integration patterns because it gives you full control over how runtime inputs become Actra actions.

Perfect for:

  • framework request objects
  • nested payloads
  • legacy APIs
  • MCP tool inputs
  • queue envelopes
  • AI tool normalization

Core Mental Model

Application input -> custom builder -> policy action -> decision

Instead of relying on default field extraction, you explicitly decide which fields become part of policy evaluation.


Example

"""
Custom Action Builder Example
"""

from actra import Actra, ActraPolicyError
from actra.runtime import ActraRuntime


schema_yaml = """
version: 1

actions:
refund:
fields:
amount: number

actor:
fields:
role: string

snapshot:
fields:
fraud_flag: boolean
"""

policy_yaml = """
version: 1

rules:
- id: block_large_refund
scope:
action: refund
when:
subject:
domain: action
field: amount
operator: greater_than
value:
literal: 1000
effect: block
"""

policy = Actra.from_strings(schema_yaml, policy_yaml)
runtime = ActraRuntime(policy)

runtime.set_actor_resolver(lambda ctx: {"role": "support"})
runtime.set_snapshot_resolver(lambda ctx: {"fraud_flag": False})


def build_refund_action(action_type, args, kwargs, ctx):
return {
"type": action_type,
"amount": kwargs["amount"],
}


@runtime.admit(action_builder=build_refund_action)
def refund(amount: int, currency: str):
print(f"Refund executed: {amount} {currency}")


print("\nAllowed call")
refund(amount=200, currency="USD")

print("\nBlocked call")
try:
refund(amount=1500, currency="USD")
except ActraPolicyError as e:
print("Refund blocked by policy")
print("Rule:", e.matched_rule)

Why This Example Matters

Custom builders are the highest-control action mapping surface in both SDKs.

Use them when:

  • request payloads are nested
  • framework objects include extra metadata
  • legacy parameters need remapping
  • queue messages use envelopes
  • tool schemas differ from policy schemas

This is especially valuable for AI tool governance and MCP integrations.


Python Advantage

The Python builder receives:

(action_type, args, kwargs, ctx)

This makes it excellent for:

  • FastAPI request extraction
  • Celery task envelopes
  • typed function remapping
  • selective field exposure

JavaScript Advantage

The JavaScript builder is ideal for:

  • payload-first frameworks
  • object normalization
  • request body mapping
  • edge function adapters

Best Practice

Only include fields that are intentionally part of the policy schema.

This prevents:

  • sensitive transport metadata
  • framework internals
  • request wrappers
  • hidden agent state

from leaking into governance decisions.