Metadata-Version: 2.4
Name: agenticrail
Version: 0.2.7
Summary: AgenticRail SDK — deterministic sequence enforcement for AI agents
Project-URL: Homepage, https://agenticrail.nz
Project-URL: Documentation, https://agenticrail.nz/docs
Project-URL: Verify a sequence, https://report.agenticrail.nz/report
Project-URL: API reference, https://agenticrail.nz/openapi.json
License: MIT
Keywords: ai-agents,compliance,crewai,enforcement,langgraph,sequence
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Requires-Dist: requests>=2.28
Provides-Extra: all
Requires-Dist: crewai>=0.60; extra == 'all'
Requires-Dist: langchain-core>=0.1; extra == 'all'
Requires-Dist: langgraph>=0.1; extra == 'all'
Provides-Extra: crewai
Requires-Dist: crewai>=0.60; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: pytest-mock; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: responses; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langchain-core>=0.1; extra == 'langgraph'
Requires-Dist: langgraph>=0.1; extra == 'langgraph'
Description-Content-Type: text/markdown

# 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

```bash
# 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

> **The key below is real. Copy it as it is.**
>
> `DEMO-AGENTICRAIL-PUBLIC-2026` is a live, public evaluation key. It is not a
> placeholder and there is nothing to sign up for — no account, no email, no
> waiting. It is rate limited to 300 requests/minute, prefixes your sequence
> ids with `demo-`, and every sequence you run with it is verifiable by anyone
> at [report.agenticrail.nz/report](https://report.agenticrail.nz/report).
>
> Production keys look different — `prefix.secret`, with a dot. If you pass
> anything else, including an unset environment variable, the gate replies
> `Malformed API key`. 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 — hashed into the receipt (`payload_hash`), never in the report | `attestation` — it is evidence, and evidence is meant to be readable |
| | step names, order, decisions, timings, `pack_id`s, 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 gets
> `RailDenied: SEALED_SEQUENCE`. That is the gate working, not a bug.

```python
import uuid

from agenticrail import RailClient

client = RailClient(api_key="DEMO-AGENTICRAIL-PUBLIC-2026")

# 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](https://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.

```python
from langgraph.graph import StateGraph, END
from agenticrail import RailClient
from agenticrail.integrations.langgraph import LangGraphRail

client = RailClient(api_key="YOUR_KEY")
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.

```python
from crewai import Crew
from agenticrail import RailClient
from agenticrail.integrations.crewai import CrewRailGuard

client = RailClient(api_key="YOUR_KEY")
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:

```python
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`

```python
client = RailClient(
    api_key="...",       # Required. Bearer token for the wrapper API.
    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, *, ...)`

```python
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`

```python
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`

```python
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`

```python
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:

```bash
curl -X POST https://api.agenticrail.nz/v1/report \
  -H "Authorization: Bearer YOUR_KEY" \
  -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](https://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](https://agenticrail.nz/docs)
- **Interactive demo:** [agenticrail.nz/demo](https://agenticrail.nz/demo)
- **Compliance matrix:** [agenticrail.nz/compliance](https://agenticrail.nz/compliance) — what this actually provides, and what it does not
- **Verify a sequence:** [report.agenticrail.nz](https://report.agenticrail.nz)

---

*TUARA KURI LIMITED — trading as AgenticRail. Hokianga, New Zealand.*
