Observed since 20 Oct, 18:40 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Runtime policies for agents — the open-source edge SDK (capture + check).
Govern what your agents do, not just what they say. Actrail captures every agent action's full tool-call arguments as telemetry and enforces deterministic policies before a risky action runs — a human-readable rule, never an LLM, in the binding decision.
AI agents don't just generate text — they take actions: refund a customer, delete a row, deploy to prod, send data to an external LLM. Content guardrails don't see any of that. Actrail governs the actions:
5000 → deny`) evaluated on a bounded request, fail-open by default. No model participates in the verdict.
pip install actrail
Requirements
Verify:
actrail --help # bundled CLI on PATH
python -c "import actrail; print(actrail.__version__)"
not a supported wheel on this platform? Almost always your Python isn't 3.11/3.12 (macOS ships 3.9). Create a 3.11/3.12 env — e.g.uv venv --python 3.12— and reinstall.
actrail init --key ak_live_... # registers the SDK, wires hooks, starts the daemon (shadow)
actrail doctor # verify config · hooks · daemon
init also registers this SDK release with the backend (metadata only —
integration/version/contract, no customer data), so Actrail can install the managed
policies this release is certified to enforce before the first trail arrives.
init wires the pre-tool policy check immediately. Policies still begin in shadow,
so nothing blocks until an administrator graduates one to enforce in the Actrail
console. actrail enforce off is an explicit local emergency opt-out; enforce on
restores the hook if it was removed.
Two managed catalog rules intentionally need organization-specific named sets before
they can govern: production_destructive_action needs prod_hosts, and
customer_data_unapproved_destination needs approved_destinations. Configure those
in the console during onboarding; the SDK never guesses tenant trust boundaries.
import actrail
actrail.init(key="ak_live_...", agent="support-bot")
# Before a risky action — get a deterministic, bounded verdict.
verdict = actrail.check(
trail_id="sess_42",
tool="pay.api",
action="refund",
tool_input={"order": {"amount": 2500, "currency": "USD"}, "table": "prod.customers"},
)
if not verdict.allow:
raise PermissionError(verdict.reason) # require_approval / deny
# After it runs — capture the action as telemetry (fire-and-forget).
actrail.capture(
trail_id="sess_42",
tool="pay.api",
action="refund",
tool_input={"order": {"amount": 2500}, "customer": {"email": "alice@example.com"}},
)
tool_input is the full tool-call argument JSON, exactly as your integration (or
Claude Code) produced it. Actrail sends it on the wire, bounded only by size (see
What leaves the machine) — the backend is where it's
governed: bounded and redacted server-side, and turned into the policy-queryable
fields a custom rule like "deny refund_payment when payment.transaction_amount >
5000" is written against.
{
"tool": "mcp__razorpay__refund_payment", "action": "call", "destination": "razorpay",
"tool_use_id": "toolu_01AbC…",
"tool_input": { // the FULL argument JSON, as produced
"payment": {"transaction_amount": 6000, "currency": "INR"}
},
"signals": {},
"sdk_version": "2.0.0", "contract_version": "v3"
}
No SDK-side redaction, bucketing, or type-only reduction — that governance moved to
the backend (Ingest::ToolInput): it bounds an oversized payload, redacts a
credential-shaped value or a value under a secret-named key (api_key, password,
token, …) in place, and derives the field.<path> facts a policy can match on. A
credential passed as an ordinary tool argument (e.g. a curl command with an
Authorization header, or an MCP tool whose input includes an API key) is redacted
there, not on the SDK side — the SDK does no content scanning at all.
tool call ──► capture()/check() ──► classify (action/destination) ──► bound (size only) ──► backend
structural parsers only src/actrail/bound.py /spans · /check
/check, fail-safe at ingest.action and destination from the
tool name (and, for Bash, the command text) and bounds tool_input for size;
everything else is the backend's job.After upgrading, run:
actrail init --key ak_live_... # idempotently upgrades owned hook matchers
actrail restart # loads the new SDK into the warm daemon
actrail doctor
See SECURITY.md for the full picture. Short version: the SDK sends the
full tool_input — bounded for size, nothing more — and the backend is the boundary
that bounds, redacts, and validates it before anything is persisted or evaluated.
uv venv && uv pip install -e ".[dev]"
uv run pytest # tests
uv run ruff check . # lint
uv run mypy # strict types
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 2.0.0 | 23 Sept 2026 | 13 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 18:40 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:bd8fb7ab538829f4519402c0bf172b492e1419ec4c8b8452690562e8e99cb74b