agenticrail
AgenticRail Python SDK — deterministic sequence enforcement for AI agents.
Gate every step of your agent workflow before it executes. Cryptographic receipts. Zero enforcement failures.
pip install agenticrail
What it does
AgenticRail sits beneath your agent and enforces that steps run in the right order, with no skips, no replays, and no re-entry after a sequence is sealed. Every decision — ALLOW, DENY, or HALT — produces a cryptographically signed receipt stored at the edge.
Three verdicts:
- ALLOW — step is clear, your agent code executes
- DENY — enforcement violation (wrong order, replay, sealed), execution blocked
- HALT — hard stop (injection attempt, poison payload detected), execution blocked
Install
# Core client only
pip install agenticrail
# With LangGraph support
pip install "agenticrail[langgraph]"
# With CrewAI support
pip install "agenticrail[crewai]"
# Everything
pip install "agenticrail[all]"
Quick start — any framework
You do not need a key. There is nothing to sign up for.
Since 0.3.0 the client sends no credential unless you give it one, and the gate serves unrecognised callers on its public demo lane: the call runs, the receipt is real and signed, and the response tells you which lane you are on via
lane,lane_reasonandlane_notice.The trade is that a demo-lane sequence is prefixed
demo-, is rate limited to 300 requests/minute, and its report is readable by anyone holding the sequence id at report.agenticrail.nz/report. So evaluate freely, and do not put anything private inattestationuntil you are on a key of your own.
DEMO-AGENTICRAIL-PUBLIC-2026is that lane's real, public key if you would rather name it explicitly. It is not a placeholder.Production keys look different —
prefix.secret, with a dot. Pass anything else, including an unset environment variable, and you are not turned away: the call runs on the demo lane and says so. Only a real key presented wrongly is refused. For a production key: hello@agenticrail.nz
What the demo key can do, and what is public
It is a real key, not a sandbox. Your own step names, your own order, your
own inputs, every action type, signed receipts, full chain verification. The
only differences from a production key are the demo- prefix on your sequence
ids, the 300 req/min limit, and a shared model_id.
inputs and attestation are treated differently. This is the one thing to
read before pointing the demo key at real work.
| stays private | published in full |
|---|---|
inputs values — never in the receipt at all; it carries payload_hash, a SHA-256 of the whole request body |
attestation — it is evidence, and evidence is meant to be readable |
step names, order, decisions, timings, pack_ids, signatures |
|
| the generated compliance narrative |
inputs is for the payload. A third party can prove your sequence ran in order
and was not tampered with, and still learn nothing about what was in it.
attestation is for the evidence you want a reader to see — {"aml_check": "passed", "approvals": ["alice@co.com"]} — so it appears verbatim in the
report. That is the design, not a leak, but it means anything you put in
attestation on a demo sequence is world-readable.
And any demo- sequence can be read by anyone who knows or guesses its id,
with no key at all. So kyc_check → credit_decision → disburse_funds tells a
reader your process even though it tells them nothing about the customer.
Use a random sequence id per run. If the step names or the attestation contents
are sensitive, that is the point to move to a production key —
hello@agenticrail.nz.
Give every run a fresh
sequence_id. Sealing is permanent and by design: once a sequence reaches its last step it can never be reopened. A fixed id therefore works exactly once — and on the shared demo key that id is shared with every other user, so the second person to run it getsRailDenied: SEALED_SEQUENCE. That is the gate working, not a bug.
import uuid
from agenticrail import RailClient
# No key needed. With no argument the client uses $AGENTICRAIL_API_KEY if
# set, and otherwise sends no credential at all -- the gate answers on its
# public demo lane and every decision carries `lane` so you know where you are.
client = RailClient()
# Silences the startup warning by naming the same lane explicitly:
# client = RailClient(api_key=DEMO_API_KEY)
# RailSequence sends step_order on every call — the gate reads it from each payload
seq = client.sequence(
sequence_id=f"my-agent-run-{uuid.uuid4().hex[:8]}",
step_order=["verify_identity", "assess_risk", "execute_transfer", "audit_ledger"],
)
seq.next("verify_identity") # → ALLOW
seq.next("assess_risk") # → ALLOW
seq.next("execute_transfer") # → ALLOW
seq.next("audit_ledger") # → ALLOW (seals sequence)
# Any out-of-order, replay, or post-seal call raises RailDenied
seq.next("verify_identity") # → raises RailDenied: SEALED_SEQUENCE
Get a production API key at agenticrail.nz.
LangGraph integration
Wrap each node at add_node time. Uses thread_id from LangGraph config as the sequence_id — each graph invocation is isolated.
from langgraph.graph import StateGraph, END
from agenticrail import RailClient
from agenticrail.integrations.langgraph import LangGraphRail
client = RailClient() # $AGENTICRAIL_API_KEY, else the public demo lane
rail = LangGraphRail(
client,
step_order=["research", "analyze", "write_report", "review"],
)
builder = StateGraph(AgentState)
# Wrap each node — gate fires before node execution
builder.add_node("research", rail.wrap("research", research_fn))
builder.add_node("analyze", rail.wrap("analyze", analyze_fn))
builder.add_node("write_report", rail.wrap("write_report", write_report_fn))
builder.add_node("review", rail.wrap("review", review_fn))
builder.set_entry_point("research")
builder.add_edge("research", "analyze")
builder.add_edge("analyze", "write_report")
builder.add_edge("write_report", "review")
builder.add_edge("review", END)
graph = builder.compile()
# thread_id → sequence_id: each invocation is independently tracked and verifiable
result = graph.invoke(
{"query": "EU AI Act compliance checklist"},
config={"configurable": {"thread_id": "run-2026-001"}},
)
What happens on DENY or HALT:
rail.wrap() raises RailDenied inside the node. LangGraph catches unhandled node exceptions and halts graph execution. No subsequent nodes run.
Concurrent runs:
Each thread_id is a separate sequence. Concurrent graph invocations with different thread IDs do not interfere — the gate tracks them independently via Durable Objects.
CrewAI integration
Use guard.kickoff() instead of crew.kickoff(). Step[0] is gated before the crew starts; subsequent steps are gated via a task callback injected between tasks.
from crewai import Crew
from agenticrail import RailClient
from agenticrail.integrations.crewai import CrewRailGuard
client = RailClient() # $AGENTICRAIL_API_KEY, else the public demo lane
guard = CrewRailGuard(
client,
step_order=["research_task", "analysis_task", "write_task"],
)
crew = Crew(
agents=[researcher, analyst, writer],
tasks=[research_task, analysis_task, write_task],
)
# Drop-in replacement for crew.kickoff()
# Each call generates a fresh sequence_id — each run is independently tracked
result = guard.kickoff(crew, inputs={"topic": "AI governance"})
Note on blocking: CrewAI's task_callback fires synchronously between tasks, so the gate can block task N before task N starts. However, if CrewAI swallows exceptions in callbacks, a denied task may still execute. In that case, guard.kickoff() raises RailDenied after the crew finishes, preserving the denial in the audit trail.
For hard blocking (guaranteed pre-execution), use guard.gate() in a custom orchestration loop:
guard.reset()
for step, task_fn in zip(STEP_ORDER, task_functions):
guard.gate(step) # raises RailDenied immediately on DENY
task_fn()
API reference
RailClient
client = RailClient(
api_key=None, # Optional. Omit it and the gate answers on the
# public demo lane, labelled in every response.
model_id="agent", # Optional. Identifies your agent in receipts.
base_url=None, # Optional. Defaults to api.agenticrail.nz/v1/evaluate.
timeout=10, # Optional. Request timeout in seconds.
)
client.evaluate(sequence_id, step, *, ...)
decision = client.evaluate(
sequence_id="my-run-001",
step="verify_identity",
action_type="CHECK_STATE", # Optional. Default: CHECK_STATE.
action="verify user identity", # Optional. Human-readable label.
inputs={"user_id": "u123"}, # Optional. Metadata attached to receipt.
step_order=[...], # Required on every call — gate reads it from each payload.
raise_on_deny=True, # Optional. Default True.
)
# decision.decision → "ALLOW" | "DENY" | "HALT"
# decision.allowed → True if ALLOW
# decision.pack_id → SHA-256 receipt hash
# decision.receipt → full signed receipt dict
# decision.reasons → list of reason codes on DENY/HALT
client.sequence(sequence_id, step_order)
Returns a RailSequence that tracks step_order state — call .next(step) for each step without managing step_order yourself.
RailDenied
try:
seq.next("execute_transfer")
except RailDenied as e:
print(e.decision) # "DENY" or "HALT"
print(e.reasons) # ["SEQUENCE_VIOLATION"]
print(e.pack_id) # receipt hash for audit
print(e.step) # "execute_transfer"
LangGraphRail
rail = LangGraphRail(
client,
step_order=["step_a", "step_b", "step_c"],
fallback_sequence_id=None, # Used when thread_id not in config
)
wrapped_fn = rail.wrap("step_a", original_fn, action_type="CHECK_STATE")
CrewRailGuard
guard = CrewRailGuard(
client,
step_order=["task_1", "task_2", "task_3"],
sequence_id=None, # Optional. Auto-generated per kickoff() call if None.
action_type="CHECK_STATE",
)
result = guard.kickoff(crew, inputs={...})
guard.gate("task_1") # Explicit gate for custom loops
guard.reset() # Reset state for a new run
Compliance reports
Every pack_id in a RailDecision is a verifiable receipt. Paste your sequence ID into the report generator or call it programmatically:
curl -X POST https://api.agenticrail.nz/v1/report \
-H "Authorization: Bearer DEMO-AGENTICRAIL-PUBLIC-2026" \
-H "Content-Type: application/json" \
-d '{"sequence_id": "my-run-001", "format": "json"}'
Returns: chain proof (Ed25519 signature verification, offline-reproducible; legacy HMAC k1 verified server-side for pre-cutover receipts), enforcement log, AI-written compliance narrative.
Demo sequences (using DEMO-AGENTICRAIL-PUBLIC-2026) are verifiable at report.agenticrail.nz — no auth needed.
Action types
| Type | Use when |
|---|---|
CHECK_STATE |
Reading or observing state (default) |
VALIDATE_INPUT |
Verifying inputs before acting |
RECORD_RESULT |
Writing or committing an outcome |
CLARIFY_NEXT_STEP |
Asking for clarification |
SELECT_NEXT_STEP |
Choosing a path |
WAIT_FOR_SIGNAL |
Pausing for an external trigger |
PAUSE_CYCLE |
Deliberate suspension |
REDUCE_STIMULUS |
Backing off |
Links
- API docs: agenticrail.nz/docs
- Interactive demo: agenticrail.nz/demo
- Compliance matrix: agenticrail.nz/compliance — what this actually provides, and what it does not
- Verify a sequence: report.agenticrail.nz
TUARA KURI LIMITED — trading as AgenticRail. Hokianga, New Zealand.
Release files for agenticrail 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agenticrail-0.3.1.tar.gz | 22.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agenticrail-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.6 kB
Release files / agenticrail-0.3.1.tar.gz
| Download URL | agenticrail-0.3.1.tar.gz |
|---|---|
| Size | 22.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
44e83648ebc16dfc3e8ee420e47f7bdeaf33983d3f22398509ca08f647b03866
|
|
BLAKE2b-256 checksum How to use checksums |
8b406d96cc68cc9d276c5a335367dd21eac140a332886f682a92a71307fcbcba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / agenticrail-0.3.1-py3-none-any.whl
| Download URL | agenticrail-0.3.1-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1b6b433907b4d0796a3fb1c732a328e4cb9251c7c7661cb216e1a98cd553e4c7
|
|
BLAKE2b-256 checksum How to use checksums |
44b0f0b9474458b9734f68e9e58f8a568c5599ac07458f024348acb64094792a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log