Skip to main content

Integrate any Python application

ActionRuntime is ActionRail's framework-neutral execution boundary. Give it an action name, arguments, and the operation to call. It runs the same policy, live grounding, review, availability, and audit pipeline as the LangGraph adapter, then invokes the operation only after an allow decision.

Use this API for custom agent loops, background workers, API handlers, other orchestration frameworks, or any consequential Python operation.

Automatic discovery versus the direct API

LangGraph is ActionRail's current automatic adapter: it discovers the graph's tools and wraps them for you. With ActionRuntime, you define the action surface in the Console or in code and place the guard at the operation boundary.

1. Install the lightweight runtime

python -m pip install actionrail actionrail-console

The base SDK does not install LangGraph or LangChain. Install actionrail[agents] only when using the LangGraph adapter or packaged LangGraph quickstart.

2. Define the action surface

Start the Console, create an agent, and open its detail page:

actionrail-console --no-open

Choose Add action and enter the operation name and argument names—for example, issue_refund with order_id, amount. Then configure its policy and grounding rules in the Console. This manual surface remains in place when the direct runtime connects; ActionRail does not require framework introspection.

After creating the action, expand Integration code on its detail page. The recommended Guard and execute snippet places ActionRuntime.execute() at the operation boundary. A secondary Decision only snippet uses evaluate() when the application must enforce the returned verdict itself. LangGraph users do not need either snippet because enforce() discovers and wraps their tools automatically.

The generated code is a starter for that specific action and expects the agent key in ACTIONRAIL_AGENT_KEY; it does not embed the credential. Long-running services should create and reuse one runtime during application startup and close it during shutdown, as described in Runtime lifecycle.

Keep the one-time agent key shown at registration. The agent ID identifies the configuration; the agent key authenticates this runtime process.

3. Guard the real operation

import os

from actionrail import ActionBlocked, ActionHeld, ActionRuntime


def issue_refund(order_id: str, amount: int):
return billing.refund(order_id=order_id, amount=amount)


with ActionRuntime(
agent_id=os.environ["ACTIONRAIL_AGENT_ID"],
api_key=os.environ["ACTIONRAIL_AGENT_KEY"],
endpoint=os.environ.get(
"ACTIONRAIL_ENDPOINT",
"http://127.0.0.1:8020",
),
) as runtime:
try:
result = runtime.execute(
"issue_refund",
{"order_id": proposed_order, "amount": proposed_amount},
issue_refund,
context={"customer_id": authenticated_customer_id},
)
except ActionBlocked:
return "The refund did not pass live verification."
except ActionHeld:
return "The refund was not approved for execution."

execute() calls issue_refund(**arguments) only after authorization. Prefer it over a separate evaluate() followed by an operation: keeping the decision and effect in one call makes accidental bypasses easier to prevent.

The context mapping must come from authenticated host-application state. Do not copy model-generated values into trusted context.

Own the complete execution boundary

The direct API can protect only the code paths that call it. Route every invocation of a consequential operation through execute() or aexecute(), and keep the unguarded implementation private to that boundary.

Define actions in code instead

For code-first action inventories, register privacy-safe metadata when the runtime starts:

from actionrail import ActionDefinition, ActionRuntime

runtime = ActionRuntime(
agent_id=os.environ["ACTIONRAIL_AGENT_ID"],
api_key=os.environ["ACTIONRAIL_AGENT_KEY"],
actions=[
ActionDefinition(
name="issue_refund",
description="Refund a delivered order",
arguments=("order_id", "amount"),
critical_arguments=("order_id", "amount"),
)
],
)

Definitions contain names and classifications only; they never contain runtime argument values. A code-defined action takes precedence over a manual action with the same name; other manual actions remain registered. If you prefer to manage the inventory entirely in the Console, omit actions=.

Inspect a decision without executing

decision = runtime.evaluate(
"issue_refund",
{"order_id": proposed_order, "amount": proposed_amount},
context={"customer_id": authenticated_customer_id},
)

if decision.allowed:
...
elif decision.held:
...
elif decision.blocked:
...

Use evaluate() for previews and diagnostics. For production effects, execute() is the safer integration surface.

ActionBlocked and ActionHeld both inherit from ActionNotAllowed and carry the local decision. Detailed reasons may contain sensitive runtime data; do not return them to a model or export them without review.

Async operations

result = await runtime.aexecute(
"send_payment",
{"account_id": account_id, "amount": amount},
payments.send,
context={"customer_id": authenticated_customer_id},
)

aexecute() supports sync and async callables without blocking the event loop during verification or review waits. aevaluate() is the decision-only async equivalent.

Local configuration without the Console

Pass a rule mapping, a GroundConfig, or an actionrail.yaml path as config= when the application must run without a control plane:

runtime = ActionRuntime(
{
"tools": {
"issue_refund": {
"kind": "consequential",
"args": {"amount": {"policy": "amount <= 200"}},
}
}
}
)

Local mode has no remote Activity or review queue. The repository's runnable framework_neutral_runtime.py example demonstrates an allowed operation and proves that a held operation is never invoked.

Lifecycle and health

Managed runtimes refresh configuration and deliver audit reports in the background. Use the context manager, or call runtime.close(timeout=7) during shutdown. runtime.flush() waits for current audit reports without closing, and runtime.health exposes configuration and delivery status for application health checks.

See Runtime lifecycle and Runtime availability for production failure behavior.