Metadata-Version: 2.4
Name: agenticrail
Version: 0.1.1
Summary: AgenticRail SDK — deterministic sequence enforcement for AI agents
Project-URL: Homepage, https://agenticrail.nz
Project-URL: Documentation, https://agenticrail.nz/docs
Project-URL: Repository, https://github.com/MSMD-RUA/agenticrail-sdk
Project-URL: Bug Tracker, https://github.com/MSMD-RUA/agenticrail-sdk/issues
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

```python
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="my-agent-run-001",
    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, HMAC verification, enforcement log, AI-written compliance narrative (EU AI Act Article 11).

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 (60+ frameworks):** [agenticrail.nz/compliance](https://agenticrail.nz/compliance)
- **Verify a sequence:** [report.agenticrail.nz](https://report.agenticrail.nz)

---

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