agenticrail 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agenticrail-0.1.0/.gitignore +29 -0
- agenticrail-0.1.0/PKG-INFO +304 -0
- agenticrail-0.1.0/README.md +268 -0
- agenticrail-0.1.0/agenticrail/__init__.py +6 -0
- agenticrail-0.1.0/agenticrail/client.py +184 -0
- agenticrail-0.1.0/agenticrail/integrations/__init__.py +3 -0
- agenticrail-0.1.0/agenticrail/integrations/crewai.py +141 -0
- agenticrail-0.1.0/agenticrail/integrations/langgraph.py +115 -0
- agenticrail-0.1.0/examples/crewai_example.py +100 -0
- agenticrail-0.1.0/examples/langgraph_example.py +90 -0
- agenticrail-0.1.0/pyproject.toml +52 -0
- agenticrail-0.1.0/tests/test_live.py +139 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
.env
|
|
2
|
+
.env.*
|
|
3
|
+
*.env
|
|
4
|
+
|
|
5
|
+
node_modules/
|
|
6
|
+
.wrangler/
|
|
7
|
+
*.log
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.pyc
|
|
10
|
+
*.pyo
|
|
11
|
+
sdks/python/dist/
|
|
12
|
+
sdks/js/core/dist/
|
|
13
|
+
.DS_Store
|
|
14
|
+
Thumbs.db
|
|
15
|
+
|
|
16
|
+
.claude/
|
|
17
|
+
|
|
18
|
+
# Personal/internal — local only, never push
|
|
19
|
+
CLAUDE.md
|
|
20
|
+
CODEBASE_OVERVIEW.md
|
|
21
|
+
start-claudeV2.ps1
|
|
22
|
+
*.ps1
|
|
23
|
+
|
|
24
|
+
# Sub-repos — tracked separately
|
|
25
|
+
agenticrail-v1/
|
|
26
|
+
agenticrail-wrapper/
|
|
27
|
+
clients/
|
|
28
|
+
website/
|
|
29
|
+
archive/
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agenticrail
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AgenticRail SDK — deterministic sequence enforcement for AI agents
|
|
5
|
+
Project-URL: Homepage, https://agenticrail.nz
|
|
6
|
+
Project-URL: Documentation, https://agenticrail.nz/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/MSMD-RUA/agenticrail-sdk
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/MSMD-RUA/agenticrail-sdk/issues
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: ai-agents,compliance,crewai,enforcement,langgraph,sequence
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Requires-Dist: requests>=2.28
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: crewai>=0.60; extra == 'all'
|
|
24
|
+
Requires-Dist: langchain-core>=0.1; extra == 'all'
|
|
25
|
+
Requires-Dist: langgraph>=0.1; extra == 'all'
|
|
26
|
+
Provides-Extra: crewai
|
|
27
|
+
Requires-Dist: crewai>=0.60; extra == 'crewai'
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest-mock; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
31
|
+
Requires-Dist: responses; extra == 'dev'
|
|
32
|
+
Provides-Extra: langgraph
|
|
33
|
+
Requires-Dist: langchain-core>=0.1; extra == 'langgraph'
|
|
34
|
+
Requires-Dist: langgraph>=0.1; extra == 'langgraph'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# agenticrail
|
|
38
|
+
|
|
39
|
+
AgenticRail Python SDK — deterministic sequence enforcement for AI agents.
|
|
40
|
+
|
|
41
|
+
Gate every step of your agent workflow before it executes. Cryptographic receipts. Zero enforcement failures.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install agenticrail
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## What it does
|
|
50
|
+
|
|
51
|
+
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.
|
|
52
|
+
|
|
53
|
+
Three verdicts:
|
|
54
|
+
- **ALLOW** — step is clear, your agent code executes
|
|
55
|
+
- **DENY** — enforcement violation (wrong order, replay, sealed), execution blocked
|
|
56
|
+
- **HALT** — hard stop (injection attempt, poison payload detected), execution blocked
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Core client only
|
|
64
|
+
pip install agenticrail
|
|
65
|
+
|
|
66
|
+
# With LangGraph support
|
|
67
|
+
pip install "agenticrail[langgraph]"
|
|
68
|
+
|
|
69
|
+
# With CrewAI support
|
|
70
|
+
pip install "agenticrail[crewai]"
|
|
71
|
+
|
|
72
|
+
# Everything
|
|
73
|
+
pip install "agenticrail[all]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Quick start — any framework
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from agenticrail import RailClient
|
|
82
|
+
|
|
83
|
+
client = RailClient(api_key="DEMO-AGENTICRAIL-PUBLIC-2026")
|
|
84
|
+
|
|
85
|
+
# RailSequence sends step_order on every call — the gate reads it from each payload
|
|
86
|
+
seq = client.sequence(
|
|
87
|
+
sequence_id="my-agent-run-001",
|
|
88
|
+
step_order=["verify_identity", "assess_risk", "execute_transfer", "audit_ledger"],
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
seq.next("verify_identity") # → ALLOW
|
|
92
|
+
seq.next("assess_risk") # → ALLOW
|
|
93
|
+
seq.next("execute_transfer") # → ALLOW
|
|
94
|
+
seq.next("audit_ledger") # → ALLOW (seals sequence)
|
|
95
|
+
|
|
96
|
+
# Any out-of-order, replay, or post-seal call raises RailDenied
|
|
97
|
+
seq.next("verify_identity") # → raises RailDenied: SEALED_SEQUENCE
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Get a production API key at [agenticrail.nz](https://agenticrail.nz).
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## LangGraph integration
|
|
105
|
+
|
|
106
|
+
Wrap each node at `add_node` time. Uses `thread_id` from LangGraph config as the sequence_id — each graph invocation is isolated.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from langgraph.graph import StateGraph, END
|
|
110
|
+
from agenticrail import RailClient
|
|
111
|
+
from agenticrail.integrations.langgraph import LangGraphRail
|
|
112
|
+
|
|
113
|
+
client = RailClient(api_key="YOUR_KEY")
|
|
114
|
+
rail = LangGraphRail(
|
|
115
|
+
client,
|
|
116
|
+
step_order=["research", "analyze", "write_report", "review"],
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
builder = StateGraph(AgentState)
|
|
120
|
+
|
|
121
|
+
# Wrap each node — gate fires before node execution
|
|
122
|
+
builder.add_node("research", rail.wrap("research", research_fn))
|
|
123
|
+
builder.add_node("analyze", rail.wrap("analyze", analyze_fn))
|
|
124
|
+
builder.add_node("write_report", rail.wrap("write_report", write_report_fn))
|
|
125
|
+
builder.add_node("review", rail.wrap("review", review_fn))
|
|
126
|
+
|
|
127
|
+
builder.set_entry_point("research")
|
|
128
|
+
builder.add_edge("research", "analyze")
|
|
129
|
+
builder.add_edge("analyze", "write_report")
|
|
130
|
+
builder.add_edge("write_report", "review")
|
|
131
|
+
builder.add_edge("review", END)
|
|
132
|
+
|
|
133
|
+
graph = builder.compile()
|
|
134
|
+
|
|
135
|
+
# thread_id → sequence_id: each invocation is independently tracked and verifiable
|
|
136
|
+
result = graph.invoke(
|
|
137
|
+
{"query": "EU AI Act compliance checklist"},
|
|
138
|
+
config={"configurable": {"thread_id": "run-2026-001"}},
|
|
139
|
+
)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**What happens on DENY or HALT:**
|
|
143
|
+
`rail.wrap()` raises `RailDenied` inside the node. LangGraph catches unhandled node exceptions and halts graph execution. No subsequent nodes run.
|
|
144
|
+
|
|
145
|
+
**Concurrent runs:**
|
|
146
|
+
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.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## CrewAI integration
|
|
151
|
+
|
|
152
|
+
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.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from crewai import Crew
|
|
156
|
+
from agenticrail import RailClient
|
|
157
|
+
from agenticrail.integrations.crewai import CrewRailGuard
|
|
158
|
+
|
|
159
|
+
client = RailClient(api_key="YOUR_KEY")
|
|
160
|
+
guard = CrewRailGuard(
|
|
161
|
+
client,
|
|
162
|
+
step_order=["research_task", "analysis_task", "write_task"],
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
crew = Crew(
|
|
166
|
+
agents=[researcher, analyst, writer],
|
|
167
|
+
tasks=[research_task, analysis_task, write_task],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Drop-in replacement for crew.kickoff()
|
|
171
|
+
# Each call generates a fresh sequence_id — each run is independently tracked
|
|
172
|
+
result = guard.kickoff(crew, inputs={"topic": "AI governance"})
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**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.
|
|
176
|
+
|
|
177
|
+
For hard blocking (guaranteed pre-execution), use `guard.gate()` in a custom orchestration loop:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
guard.reset()
|
|
181
|
+
for step, task_fn in zip(STEP_ORDER, task_functions):
|
|
182
|
+
guard.gate(step) # raises RailDenied immediately on DENY
|
|
183
|
+
task_fn()
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## API reference
|
|
189
|
+
|
|
190
|
+
### `RailClient`
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
client = RailClient(
|
|
194
|
+
api_key="...", # Required. Bearer token for the wrapper API.
|
|
195
|
+
model_id="agent", # Optional. Identifies your agent in receipts.
|
|
196
|
+
base_url=None, # Optional. Defaults to api.agenticrail.nz/v1/evaluate.
|
|
197
|
+
timeout=10, # Optional. Request timeout in seconds.
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `client.evaluate(sequence_id, step, *, ...)`
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
decision = client.evaluate(
|
|
205
|
+
sequence_id="my-run-001",
|
|
206
|
+
step="verify_identity",
|
|
207
|
+
action_type="CHECK_STATE", # Optional. Default: CHECK_STATE.
|
|
208
|
+
action="verify user identity", # Optional. Human-readable label.
|
|
209
|
+
inputs={"user_id": "u123"}, # Optional. Metadata attached to receipt.
|
|
210
|
+
step_order=[...], # Required on every call — gate reads it from each payload.
|
|
211
|
+
raise_on_deny=True, # Optional. Default True.
|
|
212
|
+
)
|
|
213
|
+
# decision.decision → "ALLOW" | "DENY" | "HALT"
|
|
214
|
+
# decision.allowed → True if ALLOW
|
|
215
|
+
# decision.pack_id → SHA-256 receipt hash
|
|
216
|
+
# decision.receipt → full signed receipt dict
|
|
217
|
+
# decision.reasons → list of reason codes on DENY/HALT
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### `client.sequence(sequence_id, step_order)`
|
|
221
|
+
|
|
222
|
+
Returns a `RailSequence` that tracks `step_order` state — call `.next(step)` for each step without managing `step_order` yourself.
|
|
223
|
+
|
|
224
|
+
### `RailDenied`
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
try:
|
|
228
|
+
seq.next("execute_transfer")
|
|
229
|
+
except RailDenied as e:
|
|
230
|
+
print(e.decision) # "DENY" or "HALT"
|
|
231
|
+
print(e.reasons) # ["SEQUENCE_VIOLATION"]
|
|
232
|
+
print(e.pack_id) # receipt hash for audit
|
|
233
|
+
print(e.step) # "execute_transfer"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### `LangGraphRail`
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
rail = LangGraphRail(
|
|
240
|
+
client,
|
|
241
|
+
step_order=["step_a", "step_b", "step_c"],
|
|
242
|
+
fallback_sequence_id=None, # Used when thread_id not in config
|
|
243
|
+
)
|
|
244
|
+
wrapped_fn = rail.wrap("step_a", original_fn, action_type="CHECK_STATE")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### `CrewRailGuard`
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
guard = CrewRailGuard(
|
|
251
|
+
client,
|
|
252
|
+
step_order=["task_1", "task_2", "task_3"],
|
|
253
|
+
sequence_id=None, # Optional. Auto-generated per kickoff() call if None.
|
|
254
|
+
action_type="CHECK_STATE",
|
|
255
|
+
)
|
|
256
|
+
result = guard.kickoff(crew, inputs={...})
|
|
257
|
+
guard.gate("task_1") # Explicit gate for custom loops
|
|
258
|
+
guard.reset() # Reset state for a new run
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Compliance reports
|
|
264
|
+
|
|
265
|
+
Every `pack_id` in a `RailDecision` is a verifiable receipt. Paste your sequence ID into the report generator or call it programmatically:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
curl -X POST https://api.agenticrail.nz/v1/report \
|
|
269
|
+
-H "Authorization: Bearer YOUR_KEY" \
|
|
270
|
+
-H "Content-Type: application/json" \
|
|
271
|
+
-d '{"sequence_id": "my-run-001", "format": "json"}'
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Returns: chain proof, HMAC verification, enforcement log, AI-written compliance narrative (EU AI Act Article 11).
|
|
275
|
+
|
|
276
|
+
Demo sequences (using `DEMO-AGENTICRAIL-PUBLIC-2026`) are verifiable at [report.agenticrail.nz](https://report.agenticrail.nz) — no auth needed.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Action types
|
|
281
|
+
|
|
282
|
+
| Type | Use when |
|
|
283
|
+
|------|----------|
|
|
284
|
+
| `CHECK_STATE` | Reading or observing state (default) |
|
|
285
|
+
| `VALIDATE_INPUT` | Verifying inputs before acting |
|
|
286
|
+
| `RECORD_RESULT` | Writing or committing an outcome |
|
|
287
|
+
| `CLARIFY_NEXT_STEP` | Asking for clarification |
|
|
288
|
+
| `SELECT_NEXT_STEP` | Choosing a path |
|
|
289
|
+
| `WAIT_FOR_SIGNAL` | Pausing for an external trigger |
|
|
290
|
+
| `PAUSE_CYCLE` | Deliberate suspension |
|
|
291
|
+
| `REDUCE_STIMULUS` | Backing off |
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Links
|
|
296
|
+
|
|
297
|
+
- **API docs:** [agenticrail.nz/docs](https://agenticrail.nz/docs)
|
|
298
|
+
- **Interactive demo:** [agenticrail.nz/demo](https://agenticrail.nz/demo)
|
|
299
|
+
- **Compliance matrix (60+ frameworks):** [agenticrail.nz/compliance](https://agenticrail.nz/compliance)
|
|
300
|
+
- **Verify a sequence:** [report.agenticrail.nz](https://report.agenticrail.nz)
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
*TUARA KURI LIMITED — trading as AgenticRail. Hokianga, New Zealand.*
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# agenticrail
|
|
2
|
+
|
|
3
|
+
AgenticRail Python SDK — deterministic sequence enforcement for AI agents.
|
|
4
|
+
|
|
5
|
+
Gate every step of your agent workflow before it executes. Cryptographic receipts. Zero enforcement failures.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pip install agenticrail
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What it does
|
|
14
|
+
|
|
15
|
+
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.
|
|
16
|
+
|
|
17
|
+
Three verdicts:
|
|
18
|
+
- **ALLOW** — step is clear, your agent code executes
|
|
19
|
+
- **DENY** — enforcement violation (wrong order, replay, sealed), execution blocked
|
|
20
|
+
- **HALT** — hard stop (injection attempt, poison payload detected), execution blocked
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Core client only
|
|
28
|
+
pip install agenticrail
|
|
29
|
+
|
|
30
|
+
# With LangGraph support
|
|
31
|
+
pip install "agenticrail[langgraph]"
|
|
32
|
+
|
|
33
|
+
# With CrewAI support
|
|
34
|
+
pip install "agenticrail[crewai]"
|
|
35
|
+
|
|
36
|
+
# Everything
|
|
37
|
+
pip install "agenticrail[all]"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Quick start — any framework
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from agenticrail import RailClient
|
|
46
|
+
|
|
47
|
+
client = RailClient(api_key="DEMO-AGENTICRAIL-PUBLIC-2026")
|
|
48
|
+
|
|
49
|
+
# RailSequence sends step_order on every call — the gate reads it from each payload
|
|
50
|
+
seq = client.sequence(
|
|
51
|
+
sequence_id="my-agent-run-001",
|
|
52
|
+
step_order=["verify_identity", "assess_risk", "execute_transfer", "audit_ledger"],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
seq.next("verify_identity") # → ALLOW
|
|
56
|
+
seq.next("assess_risk") # → ALLOW
|
|
57
|
+
seq.next("execute_transfer") # → ALLOW
|
|
58
|
+
seq.next("audit_ledger") # → ALLOW (seals sequence)
|
|
59
|
+
|
|
60
|
+
# Any out-of-order, replay, or post-seal call raises RailDenied
|
|
61
|
+
seq.next("verify_identity") # → raises RailDenied: SEALED_SEQUENCE
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Get a production API key at [agenticrail.nz](https://agenticrail.nz).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## LangGraph integration
|
|
69
|
+
|
|
70
|
+
Wrap each node at `add_node` time. Uses `thread_id` from LangGraph config as the sequence_id — each graph invocation is isolated.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from langgraph.graph import StateGraph, END
|
|
74
|
+
from agenticrail import RailClient
|
|
75
|
+
from agenticrail.integrations.langgraph import LangGraphRail
|
|
76
|
+
|
|
77
|
+
client = RailClient(api_key="YOUR_KEY")
|
|
78
|
+
rail = LangGraphRail(
|
|
79
|
+
client,
|
|
80
|
+
step_order=["research", "analyze", "write_report", "review"],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
builder = StateGraph(AgentState)
|
|
84
|
+
|
|
85
|
+
# Wrap each node — gate fires before node execution
|
|
86
|
+
builder.add_node("research", rail.wrap("research", research_fn))
|
|
87
|
+
builder.add_node("analyze", rail.wrap("analyze", analyze_fn))
|
|
88
|
+
builder.add_node("write_report", rail.wrap("write_report", write_report_fn))
|
|
89
|
+
builder.add_node("review", rail.wrap("review", review_fn))
|
|
90
|
+
|
|
91
|
+
builder.set_entry_point("research")
|
|
92
|
+
builder.add_edge("research", "analyze")
|
|
93
|
+
builder.add_edge("analyze", "write_report")
|
|
94
|
+
builder.add_edge("write_report", "review")
|
|
95
|
+
builder.add_edge("review", END)
|
|
96
|
+
|
|
97
|
+
graph = builder.compile()
|
|
98
|
+
|
|
99
|
+
# thread_id → sequence_id: each invocation is independently tracked and verifiable
|
|
100
|
+
result = graph.invoke(
|
|
101
|
+
{"query": "EU AI Act compliance checklist"},
|
|
102
|
+
config={"configurable": {"thread_id": "run-2026-001"}},
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**What happens on DENY or HALT:**
|
|
107
|
+
`rail.wrap()` raises `RailDenied` inside the node. LangGraph catches unhandled node exceptions and halts graph execution. No subsequent nodes run.
|
|
108
|
+
|
|
109
|
+
**Concurrent runs:**
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## CrewAI integration
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from crewai import Crew
|
|
120
|
+
from agenticrail import RailClient
|
|
121
|
+
from agenticrail.integrations.crewai import CrewRailGuard
|
|
122
|
+
|
|
123
|
+
client = RailClient(api_key="YOUR_KEY")
|
|
124
|
+
guard = CrewRailGuard(
|
|
125
|
+
client,
|
|
126
|
+
step_order=["research_task", "analysis_task", "write_task"],
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
crew = Crew(
|
|
130
|
+
agents=[researcher, analyst, writer],
|
|
131
|
+
tasks=[research_task, analysis_task, write_task],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Drop-in replacement for crew.kickoff()
|
|
135
|
+
# Each call generates a fresh sequence_id — each run is independently tracked
|
|
136
|
+
result = guard.kickoff(crew, inputs={"topic": "AI governance"})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**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.
|
|
140
|
+
|
|
141
|
+
For hard blocking (guaranteed pre-execution), use `guard.gate()` in a custom orchestration loop:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
guard.reset()
|
|
145
|
+
for step, task_fn in zip(STEP_ORDER, task_functions):
|
|
146
|
+
guard.gate(step) # raises RailDenied immediately on DENY
|
|
147
|
+
task_fn()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## API reference
|
|
153
|
+
|
|
154
|
+
### `RailClient`
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
client = RailClient(
|
|
158
|
+
api_key="...", # Required. Bearer token for the wrapper API.
|
|
159
|
+
model_id="agent", # Optional. Identifies your agent in receipts.
|
|
160
|
+
base_url=None, # Optional. Defaults to api.agenticrail.nz/v1/evaluate.
|
|
161
|
+
timeout=10, # Optional. Request timeout in seconds.
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### `client.evaluate(sequence_id, step, *, ...)`
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
decision = client.evaluate(
|
|
169
|
+
sequence_id="my-run-001",
|
|
170
|
+
step="verify_identity",
|
|
171
|
+
action_type="CHECK_STATE", # Optional. Default: CHECK_STATE.
|
|
172
|
+
action="verify user identity", # Optional. Human-readable label.
|
|
173
|
+
inputs={"user_id": "u123"}, # Optional. Metadata attached to receipt.
|
|
174
|
+
step_order=[...], # Required on every call — gate reads it from each payload.
|
|
175
|
+
raise_on_deny=True, # Optional. Default True.
|
|
176
|
+
)
|
|
177
|
+
# decision.decision → "ALLOW" | "DENY" | "HALT"
|
|
178
|
+
# decision.allowed → True if ALLOW
|
|
179
|
+
# decision.pack_id → SHA-256 receipt hash
|
|
180
|
+
# decision.receipt → full signed receipt dict
|
|
181
|
+
# decision.reasons → list of reason codes on DENY/HALT
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `client.sequence(sequence_id, step_order)`
|
|
185
|
+
|
|
186
|
+
Returns a `RailSequence` that tracks `step_order` state — call `.next(step)` for each step without managing `step_order` yourself.
|
|
187
|
+
|
|
188
|
+
### `RailDenied`
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
try:
|
|
192
|
+
seq.next("execute_transfer")
|
|
193
|
+
except RailDenied as e:
|
|
194
|
+
print(e.decision) # "DENY" or "HALT"
|
|
195
|
+
print(e.reasons) # ["SEQUENCE_VIOLATION"]
|
|
196
|
+
print(e.pack_id) # receipt hash for audit
|
|
197
|
+
print(e.step) # "execute_transfer"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `LangGraphRail`
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
rail = LangGraphRail(
|
|
204
|
+
client,
|
|
205
|
+
step_order=["step_a", "step_b", "step_c"],
|
|
206
|
+
fallback_sequence_id=None, # Used when thread_id not in config
|
|
207
|
+
)
|
|
208
|
+
wrapped_fn = rail.wrap("step_a", original_fn, action_type="CHECK_STATE")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `CrewRailGuard`
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
guard = CrewRailGuard(
|
|
215
|
+
client,
|
|
216
|
+
step_order=["task_1", "task_2", "task_3"],
|
|
217
|
+
sequence_id=None, # Optional. Auto-generated per kickoff() call if None.
|
|
218
|
+
action_type="CHECK_STATE",
|
|
219
|
+
)
|
|
220
|
+
result = guard.kickoff(crew, inputs={...})
|
|
221
|
+
guard.gate("task_1") # Explicit gate for custom loops
|
|
222
|
+
guard.reset() # Reset state for a new run
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Compliance reports
|
|
228
|
+
|
|
229
|
+
Every `pack_id` in a `RailDecision` is a verifiable receipt. Paste your sequence ID into the report generator or call it programmatically:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
curl -X POST https://api.agenticrail.nz/v1/report \
|
|
233
|
+
-H "Authorization: Bearer YOUR_KEY" \
|
|
234
|
+
-H "Content-Type: application/json" \
|
|
235
|
+
-d '{"sequence_id": "my-run-001", "format": "json"}'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Returns: chain proof, HMAC verification, enforcement log, AI-written compliance narrative (EU AI Act Article 11).
|
|
239
|
+
|
|
240
|
+
Demo sequences (using `DEMO-AGENTICRAIL-PUBLIC-2026`) are verifiable at [report.agenticrail.nz](https://report.agenticrail.nz) — no auth needed.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Action types
|
|
245
|
+
|
|
246
|
+
| Type | Use when |
|
|
247
|
+
|------|----------|
|
|
248
|
+
| `CHECK_STATE` | Reading or observing state (default) |
|
|
249
|
+
| `VALIDATE_INPUT` | Verifying inputs before acting |
|
|
250
|
+
| `RECORD_RESULT` | Writing or committing an outcome |
|
|
251
|
+
| `CLARIFY_NEXT_STEP` | Asking for clarification |
|
|
252
|
+
| `SELECT_NEXT_STEP` | Choosing a path |
|
|
253
|
+
| `WAIT_FOR_SIGNAL` | Pausing for an external trigger |
|
|
254
|
+
| `PAUSE_CYCLE` | Deliberate suspension |
|
|
255
|
+
| `REDUCE_STIMULUS` | Backing off |
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Links
|
|
260
|
+
|
|
261
|
+
- **API docs:** [agenticrail.nz/docs](https://agenticrail.nz/docs)
|
|
262
|
+
- **Interactive demo:** [agenticrail.nz/demo](https://agenticrail.nz/demo)
|
|
263
|
+
- **Compliance matrix (60+ frameworks):** [agenticrail.nz/compliance](https://agenticrail.nz/compliance)
|
|
264
|
+
- **Verify a sequence:** [report.agenticrail.nz](https://report.agenticrail.nz)
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
*TUARA KURI LIMITED — trading as AgenticRail. Hokianga, New Zealand.*
|