arcezia 1.0.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.
arcezia-1.0.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+
4
+ Copyright (c) 2026 Arcezia
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
arcezia-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,345 @@
1
+ Metadata-Version: 2.4
2
+ Name: arcezia
3
+ Version: 1.0.0
4
+ Summary: Runtime safety enforcement for autonomous AI agents — official Python SDK
5
+ Author-email: Praveen Dante <praveen@arcezia.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://arcezia.com
8
+ Project-URL: Documentation, https://arcezia.com/developer-docs
9
+ Project-URL: Repository, https://github.com/Praveendante/arcezia-python
10
+ Project-URL: Bug Tracker, https://github.com/Praveendante/arcezia-python/issues
11
+ Keywords: ai-safety,agents,langchain,openai,anthropic,llm,guardrails,runtime-safety,ai-agents,safety-verification
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Classifier: Topic :: Security
16
+ Classifier: Development Status :: 4 - Beta
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Provides-Extra: httpx
21
+ Requires-Dist: httpx>=0.27; extra == "httpx"
22
+ Provides-Extra: langchain
23
+ Requires-Dist: langchain>=0.2; extra == "langchain"
24
+ Requires-Dist: langchain-core>=0.2; extra == "langchain"
25
+ Requires-Dist: httpx>=0.27; extra == "langchain"
26
+ Provides-Extra: langgraph
27
+ Requires-Dist: langgraph>=0.2; extra == "langgraph"
28
+ Requires-Dist: langchain-core>=0.2; extra == "langgraph"
29
+ Requires-Dist: httpx>=0.27; extra == "langgraph"
30
+ Provides-Extra: openai
31
+ Requires-Dist: openai>=1.30; extra == "openai"
32
+ Requires-Dist: httpx>=0.27; extra == "openai"
33
+ Provides-Extra: anthropic
34
+ Requires-Dist: anthropic>=0.28; extra == "anthropic"
35
+ Requires-Dist: httpx>=0.27; extra == "anthropic"
36
+ Provides-Extra: crewai
37
+ Requires-Dist: crewai>=0.30; extra == "crewai"
38
+ Requires-Dist: httpx>=0.27; extra == "crewai"
39
+ Provides-Extra: llamaindex
40
+ Requires-Dist: llama-index-core>=0.11; extra == "llamaindex"
41
+ Requires-Dist: httpx>=0.27; extra == "llamaindex"
42
+ Provides-Extra: autogen
43
+ Requires-Dist: autogen-agentchat>=0.4; extra == "autogen"
44
+ Requires-Dist: httpx>=0.27; extra == "autogen"
45
+ Provides-Extra: all
46
+ Requires-Dist: httpx>=0.27; extra == "all"
47
+ Requires-Dist: langchain>=0.2; extra == "all"
48
+ Requires-Dist: langchain-core>=0.2; extra == "all"
49
+ Requires-Dist: langgraph>=0.2; extra == "all"
50
+ Requires-Dist: openai>=1.30; extra == "all"
51
+ Requires-Dist: anthropic>=0.28; extra == "all"
52
+ Requires-Dist: crewai>=0.30; extra == "all"
53
+ Requires-Dist: llama-index-core>=0.11; extra == "all"
54
+ Requires-Dist: autogen-agentchat>=0.4; extra == "all"
55
+ Dynamic: license-file
56
+
57
+ # arcezia
58
+
59
+ Runtime safety verification for autonomous AI agents — official Python SDK.
60
+
61
+ All verification runs in Arcezia's secure cloud. The SDK makes HTTPS calls to
62
+ `api.arcezia.com` and returns typed result objects. Zero inference on the client.
63
+
64
+ ## Install
65
+
66
+ ```bash
67
+ pip install arcezia
68
+ ```
69
+
70
+ Framework extras:
71
+
72
+ ```bash
73
+ pip install "arcezia[langchain]" # LangChain + LangGraph
74
+ pip install "arcezia[openai]" # OpenAI Agents SDK
75
+ pip install "arcezia[anthropic]" # Anthropic (Claude) SDK
76
+ pip install "arcezia[autogen]" # AutoGen (legacy + modern)
77
+ pip install "arcezia[llamaindex]" # LlamaIndex
78
+ pip install "arcezia[all]" # everything
79
+ ```
80
+
81
+ ## Quick start
82
+
83
+ ```python
84
+ import arcezia
85
+
86
+ az = arcezia.Arcezia(api_key="ar_live_...", task="clean up test records")
87
+
88
+ cert = az.verify(
89
+ action_type="execute_sql",
90
+ action_description="DELETE FROM analytics_staging WHERE date < '2024-01-01'",
91
+ domain="database_ops",
92
+ )
93
+
94
+ if cert.degraded:
95
+ # Arcezia could not be reached, so nothing was actually verified.
96
+ # The default on_error="fail_closed" raises before you get here; check this
97
+ # explicitly if you set on_error="review" or "fail_open".
98
+ raise RuntimeError("Not verified — Arcezia unreachable")
99
+
100
+ # Gate on ALLOW positively — never on "not blocked". A verdict can be
101
+ # review (insufficient evidence, human confirmation required), which is
102
+ # neither allow nor block; treating it as runnable executes an action the
103
+ # engine explicitly declined to clear. cert.allow is True only for a
104
+ # grounded ALLOW.
105
+ if not cert.allow:
106
+ raise RuntimeError(f"Not allowed ({'review' if cert.review else 'blocked'}): {cert.summary}")
107
+
108
+ db.execute(sql) # only reached when the verdict is ALLOW
109
+ ```
110
+
111
+ The framework adapters below do this for you: a degraded certificate always
112
+ raises `ArceziaUnavailableError` and the tool never executes.
113
+
114
+ ## Framework integrations
115
+
116
+ **LangChain / LangGraph**
117
+ ```python
118
+ from arcezia.integrations.langchain import ArceziaToolkit
119
+
120
+ toolkit = ArceziaToolkit(az)
121
+ safe_tools = toolkit.wrap(tools) # classic AgentExecutor
122
+ safe_tools = toolkit.wrap_for_langgraph(tools) # LangGraph / tool-calling
123
+ ```
124
+
125
+ **OpenAI function calling**
126
+ ```python
127
+ from arcezia.integrations.openai import ArceziaGuard
128
+
129
+ guard = ArceziaGuard(az)
130
+ result = guard.execute_tool_call(
131
+ tool_call=response.choices[0].message.tool_calls[0],
132
+ tool_implementations={"execute_sql": db.execute},
133
+ )
134
+ # or wrap a single function:
135
+ safe_execute = guard.wrap_function("execute_sql", db.execute)
136
+ ```
137
+
138
+ **CrewAI**
139
+ ```python
140
+ from arcezia.integrations.openai import ArceziaCrewTool
141
+
142
+ class SafeSQLTool(ArceziaCrewTool):
143
+ az = your_arcezia_client
144
+ domain = "database_ops"
145
+ name = "execute_sql"
146
+ description = "Execute SQL"
147
+
148
+ def _run(self, sql: str) -> str:
149
+ return db.execute(sql)
150
+ ```
151
+
152
+ **Anthropic (Claude tool_use)**
153
+ ```python
154
+ from arcezia.integrations.anthropic import ArceziaAnthropicGuard
155
+
156
+ guard = ArceziaAnthropicGuard(az)
157
+ safe_uses, blocked = guard.filter_tool_uses(message.content)
158
+ ```
159
+
160
+ **AutoGen**
161
+ ```python
162
+ from arcezia.integrations.autogen import ArceziaAutoGenGuard
163
+
164
+ guard = ArceziaAutoGenGuard(az)
165
+ safe_fn = guard.wrap("execute_sql", db.execute, "database_ops") # name first
166
+ safe_map = guard.wrap_many([ # list of tuples
167
+ ("execute_sql", db.execute, "database_ops"),
168
+ ("send_data", exporter.send, "agent_action"),
169
+ ])
170
+ ```
171
+
172
+ **LlamaIndex**
173
+ ```python
174
+ from arcezia.integrations.llamaindex import ArceziaLlamaToolkit
175
+ safe_tools = ArceziaLlamaToolkit(az).wrap(tools)
176
+ ```
177
+
178
+ **Any framework** (Pydantic AI, smolagents, Google ADK, Strands, …)
179
+ ```python
180
+ from arcezia import guard_callable
181
+ safe_fn = guard_callable(run_sql, az)
182
+ ```
183
+
184
+ **Claude Code CLI hook** (gated at the harness level — every tool call)
185
+ ```bash
186
+ arcezia-hook install # writes PreToolUse hook to ~/.claude/settings.json
187
+ export ARCEZIA_API_KEY=ar_live_...
188
+ export TASK="refactor auth module"
189
+ ```
190
+
191
+ **Generic dispatch-loop agents (OpenCLAW, AutoAgent, …)**
192
+ ```python
193
+ from arcezia.integrations.openclaw import DispatchGuard
194
+ guard = DispatchGuard(api_key="ar_live_...", task="...")
195
+ result = guard.dispatch("write_file", {"path": "/etc/app.conf", "content": "..."})
196
+ ```
197
+
198
+ **n8n workflows**
199
+ ```python
200
+ from arcezia.integrations.n8n import workflow_template, save_template
201
+ save_template("arcezia_gate.json") # import into n8n
202
+ ```
203
+
204
+ ## Verdicts
205
+
206
+ | Verdict | Meaning |
207
+ |---|---|
208
+ | `cert.allow` | Safe to execute — all required evidence is grounded |
209
+ | `cert.block` | Execution blocked — violated constraint or fabrication detected |
210
+ | `cert.review` | Insufficient evidence — human confirmation required |
211
+
212
+ Two scores travel with every certificate — they measure different things:
213
+
214
+ | Field | Meaning |
215
+ |---|---|
216
+ | `cert.precondition_score` | `[0,1]` severity-weighted fraction of required preconditions **satisfied** |
217
+ | `cert.trust_score` | `[0,1]` fraction of evidence that is **externally grounded**, not agent-claimed |
218
+
219
+ ## The four levels
220
+
221
+ Each level is useful on its own and assumes the one below it. Every framework
222
+ adapter implements **Level 1** for you; Levels 2–4 are reached through the
223
+ adapter's `.az` property — the same client, no private access.
224
+
225
+ | Level | What you get | How |
226
+ |---|---|---|
227
+ | **1 — Drop-in gating** | Every tool call verified before it runs | `toolkit.wrap(tools)` |
228
+ | **2 — Chain verification** | Verify the whole *plan*, not just each step | `toolkit.az.verify_chain(...)` |
229
+ | **3 — Grounded evidence** | Arcezia asks *your* systems for facts instead of trusting the agent | register a probe webhook |
230
+ | **4 — Custom domains** | Your own constraint domains and compliance packs | `POST /v1/domains` |
231
+
232
+ **Level 2 — verify the plan before running any of it**
233
+ ```python
234
+ result = toolkit.az.verify_chain({
235
+ "steps": [
236
+ {"id": "s1", "action_type": "execute_sql", "domain": "database_ops",
237
+ "action_description": "SELECT ssn, name FROM customers"},
238
+ {"id": "s2", "action_type": "send_email", "domain": "email_ops",
239
+ "action_description": "email the list to external-analytics@gmail.com"},
240
+ ]
241
+ }, stop_on_block=True)
242
+ # → overall_verdict "SEMANTIC_BLOCK", blocked_at "s2",
243
+ # semantic_triggers [{"pattern_name": "structural_exfiltration", ...}]
244
+
245
+ # Response: {overall_verdict, blocked_at, steps[], semantic_triggers,
246
+ # final_state, session_state_updated}
247
+ # There is no top-level "verdict" — per-step verdicts live under steps[].
248
+ if result["overall_verdict"] != "SAFE":
249
+ # blocked_at names the step only when execution was actually stopped.
250
+ # On REVIEW_REQUIRED nothing was blocked, so it is null — find the step
251
+ # that needs attention in steps[] instead.
252
+ step = result["blocked_at"] or next(
253
+ (s["id"] for s in result["steps"] if s["verdict"] != "ALLOW"), None
254
+ )
255
+ abort(step)
256
+ ```
257
+
258
+ `overall_verdict` is one of:
259
+
260
+ | Value | Meaning | `blocked_at` |
261
+ |---|---|---|
262
+ | `SAFE` | every step cleared | `null` |
263
+ | `BLOCKED` | a single step was blocked on its own merits | the step id |
264
+ | `SEMANTIC_BLOCK` | the steps are individually fine but **compose** into harm — check `semantic_triggers` (e.g. `structural_exfiltration`, `credential_exfiltration`, `recon_then_exfil`) | the step id |
265
+ | `REVIEW_REQUIRED` | a step needs evidence or human approval | `null` — nothing was blocked |
266
+
267
+ > **Describe the artefact, not just the operation.** Arcezia grounds its
268
+ > verdicts on concrete referents in the description — file paths, URLs,
269
+ > recipient addresses, credential and PII field names. `"SELECT ssn, name FROM
270
+ > customers"` names PII, so the read grounds as sensitive access and the chain
271
+ > above composes into `SEMANTIC_BLOCK`. `"SELECT email, name FROM customers"`
272
+ > names only column identifiers, so the same chain returns `REVIEW_REQUIRED`
273
+ > instead: still not `SAFE`, still not executable, but held for a human rather
274
+ > than positively identified as exfiltration.
275
+ >
276
+ > The rule this reflects: **a vague description degrades a verdict toward
277
+ > review — never toward approval.** Arcezia never allows what it could not
278
+ > verify, so imprecision costs you review latency, not safety. The framework
279
+ > adapters get this right automatically because they pass the real tool
280
+ > arguments; it is worth attention only when you hand-build chain manifests.
281
+
282
+ Chain steps do **not** inherit the session's capability envelope, so
283
+ `action_within_task_scope` stays unresolved and a chain will not reach `SAFE`
284
+ on the envelope alone. Ground it per step with an `evidence` dict (the key is
285
+ `evidence` — `agent_evidence` is ignored on chain steps):
286
+
287
+ ```python
288
+ {"id": "s1", "action_type": "execute_sql", "domain": "database_ops",
289
+ "action_description": "SELECT ssn, name FROM customers",
290
+ "evidence": {"action_within_task_scope": True}}
291
+ ```
292
+
293
+ `id` and `step_id` are accepted interchangeably. Note that `state_mutations`
294
+ may only *add* danger, never remove it: asserting a danger flag `True` is
295
+ accepted, asserting it `False` is rejected, and flags the engine derives for
296
+ itself (the `g_*` world-state namespace) are not caller-writable at all.
297
+
298
+ Audit after execution — did reality match the prediction?
299
+ ```python
300
+ toolkit.az.verify_outcome(
301
+ action_type="execute_sql",
302
+ action_description="DELETE FROM orders WHERE test = true",
303
+ outcome={"rows_affected": 50000}, # what ACTUALLY happened
304
+ expected={"rows_affected": 1}, # what you intended
305
+ )
306
+ ```
307
+
308
+ **Level 3 — ground the evidence.** Register a probe webhook so evidence is
309
+ `GROUNDED` rather than `CLAIMED`. Human intent can never be produced by a model,
310
+ so ground it explicitly:
311
+ ```python
312
+ toolkit.az.authorize(user_token) # user_explicit_authorization
313
+ toolkit.az.authorize_production(prod_token) # production_explicit_authorization
314
+ ```
315
+
316
+ These ground **different** constraints. Actions touching production generally
317
+ need both — `authorize()` alone will leave `production_explicit_authorization`
318
+ unresolved and the action stays in `REVIEW`.
319
+
320
+ > **Integrating over raw HTTP** (n8n, curl, another language)? Two things the
321
+ > SDK handles for you: the API is behind a WAF that rejects the default
322
+ > library agent strings (e.g. `Python-urllib/*`), so send an explicit
323
+ > `User-Agent` of your own; and the verify response field on the wire is
324
+ > `dc_score` — the SDK exposes it as `cert.precondition_score`.
325
+
326
+ Full guide: [arcezia.com/docs](https://arcezia.com/developer-docs)
327
+
328
+ ## Development mode
329
+
330
+ Use an `ar_test_` key for local development — infrastructure constraints
331
+ (backup APIs, capability envelopes, CI gates) are relaxed so you are not blocked
332
+ by production infra that does not exist on your laptop:
333
+
334
+ ```python
335
+ az = arcezia.Arcezia(api_key="ar_test_...", task="...") # dev mode by default
336
+ ```
337
+
338
+ Development mode is only available on `ar_test_` keys and is re-checked
339
+ server-side. Live `ar_live_` keys are always pinned to production and cannot
340
+ point at `localhost`.
341
+
342
+ ## Links
343
+
344
+ - [Developer documentation](https://arcezia.com/developer-docs)
345
+ - [Dashboard](https://app.arcezia.com)
@@ -0,0 +1,289 @@
1
+ # arcezia
2
+
3
+ Runtime safety verification for autonomous AI agents — official Python SDK.
4
+
5
+ All verification runs in Arcezia's secure cloud. The SDK makes HTTPS calls to
6
+ `api.arcezia.com` and returns typed result objects. Zero inference on the client.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install arcezia
12
+ ```
13
+
14
+ Framework extras:
15
+
16
+ ```bash
17
+ pip install "arcezia[langchain]" # LangChain + LangGraph
18
+ pip install "arcezia[openai]" # OpenAI Agents SDK
19
+ pip install "arcezia[anthropic]" # Anthropic (Claude) SDK
20
+ pip install "arcezia[autogen]" # AutoGen (legacy + modern)
21
+ pip install "arcezia[llamaindex]" # LlamaIndex
22
+ pip install "arcezia[all]" # everything
23
+ ```
24
+
25
+ ## Quick start
26
+
27
+ ```python
28
+ import arcezia
29
+
30
+ az = arcezia.Arcezia(api_key="ar_live_...", task="clean up test records")
31
+
32
+ cert = az.verify(
33
+ action_type="execute_sql",
34
+ action_description="DELETE FROM analytics_staging WHERE date < '2024-01-01'",
35
+ domain="database_ops",
36
+ )
37
+
38
+ if cert.degraded:
39
+ # Arcezia could not be reached, so nothing was actually verified.
40
+ # The default on_error="fail_closed" raises before you get here; check this
41
+ # explicitly if you set on_error="review" or "fail_open".
42
+ raise RuntimeError("Not verified — Arcezia unreachable")
43
+
44
+ # Gate on ALLOW positively — never on "not blocked". A verdict can be
45
+ # review (insufficient evidence, human confirmation required), which is
46
+ # neither allow nor block; treating it as runnable executes an action the
47
+ # engine explicitly declined to clear. cert.allow is True only for a
48
+ # grounded ALLOW.
49
+ if not cert.allow:
50
+ raise RuntimeError(f"Not allowed ({'review' if cert.review else 'blocked'}): {cert.summary}")
51
+
52
+ db.execute(sql) # only reached when the verdict is ALLOW
53
+ ```
54
+
55
+ The framework adapters below do this for you: a degraded certificate always
56
+ raises `ArceziaUnavailableError` and the tool never executes.
57
+
58
+ ## Framework integrations
59
+
60
+ **LangChain / LangGraph**
61
+ ```python
62
+ from arcezia.integrations.langchain import ArceziaToolkit
63
+
64
+ toolkit = ArceziaToolkit(az)
65
+ safe_tools = toolkit.wrap(tools) # classic AgentExecutor
66
+ safe_tools = toolkit.wrap_for_langgraph(tools) # LangGraph / tool-calling
67
+ ```
68
+
69
+ **OpenAI function calling**
70
+ ```python
71
+ from arcezia.integrations.openai import ArceziaGuard
72
+
73
+ guard = ArceziaGuard(az)
74
+ result = guard.execute_tool_call(
75
+ tool_call=response.choices[0].message.tool_calls[0],
76
+ tool_implementations={"execute_sql": db.execute},
77
+ )
78
+ # or wrap a single function:
79
+ safe_execute = guard.wrap_function("execute_sql", db.execute)
80
+ ```
81
+
82
+ **CrewAI**
83
+ ```python
84
+ from arcezia.integrations.openai import ArceziaCrewTool
85
+
86
+ class SafeSQLTool(ArceziaCrewTool):
87
+ az = your_arcezia_client
88
+ domain = "database_ops"
89
+ name = "execute_sql"
90
+ description = "Execute SQL"
91
+
92
+ def _run(self, sql: str) -> str:
93
+ return db.execute(sql)
94
+ ```
95
+
96
+ **Anthropic (Claude tool_use)**
97
+ ```python
98
+ from arcezia.integrations.anthropic import ArceziaAnthropicGuard
99
+
100
+ guard = ArceziaAnthropicGuard(az)
101
+ safe_uses, blocked = guard.filter_tool_uses(message.content)
102
+ ```
103
+
104
+ **AutoGen**
105
+ ```python
106
+ from arcezia.integrations.autogen import ArceziaAutoGenGuard
107
+
108
+ guard = ArceziaAutoGenGuard(az)
109
+ safe_fn = guard.wrap("execute_sql", db.execute, "database_ops") # name first
110
+ safe_map = guard.wrap_many([ # list of tuples
111
+ ("execute_sql", db.execute, "database_ops"),
112
+ ("send_data", exporter.send, "agent_action"),
113
+ ])
114
+ ```
115
+
116
+ **LlamaIndex**
117
+ ```python
118
+ from arcezia.integrations.llamaindex import ArceziaLlamaToolkit
119
+ safe_tools = ArceziaLlamaToolkit(az).wrap(tools)
120
+ ```
121
+
122
+ **Any framework** (Pydantic AI, smolagents, Google ADK, Strands, …)
123
+ ```python
124
+ from arcezia import guard_callable
125
+ safe_fn = guard_callable(run_sql, az)
126
+ ```
127
+
128
+ **Claude Code CLI hook** (gated at the harness level — every tool call)
129
+ ```bash
130
+ arcezia-hook install # writes PreToolUse hook to ~/.claude/settings.json
131
+ export ARCEZIA_API_KEY=ar_live_...
132
+ export TASK="refactor auth module"
133
+ ```
134
+
135
+ **Generic dispatch-loop agents (OpenCLAW, AutoAgent, …)**
136
+ ```python
137
+ from arcezia.integrations.openclaw import DispatchGuard
138
+ guard = DispatchGuard(api_key="ar_live_...", task="...")
139
+ result = guard.dispatch("write_file", {"path": "/etc/app.conf", "content": "..."})
140
+ ```
141
+
142
+ **n8n workflows**
143
+ ```python
144
+ from arcezia.integrations.n8n import workflow_template, save_template
145
+ save_template("arcezia_gate.json") # import into n8n
146
+ ```
147
+
148
+ ## Verdicts
149
+
150
+ | Verdict | Meaning |
151
+ |---|---|
152
+ | `cert.allow` | Safe to execute — all required evidence is grounded |
153
+ | `cert.block` | Execution blocked — violated constraint or fabrication detected |
154
+ | `cert.review` | Insufficient evidence — human confirmation required |
155
+
156
+ Two scores travel with every certificate — they measure different things:
157
+
158
+ | Field | Meaning |
159
+ |---|---|
160
+ | `cert.precondition_score` | `[0,1]` severity-weighted fraction of required preconditions **satisfied** |
161
+ | `cert.trust_score` | `[0,1]` fraction of evidence that is **externally grounded**, not agent-claimed |
162
+
163
+ ## The four levels
164
+
165
+ Each level is useful on its own and assumes the one below it. Every framework
166
+ adapter implements **Level 1** for you; Levels 2–4 are reached through the
167
+ adapter's `.az` property — the same client, no private access.
168
+
169
+ | Level | What you get | How |
170
+ |---|---|---|
171
+ | **1 — Drop-in gating** | Every tool call verified before it runs | `toolkit.wrap(tools)` |
172
+ | **2 — Chain verification** | Verify the whole *plan*, not just each step | `toolkit.az.verify_chain(...)` |
173
+ | **3 — Grounded evidence** | Arcezia asks *your* systems for facts instead of trusting the agent | register a probe webhook |
174
+ | **4 — Custom domains** | Your own constraint domains and compliance packs | `POST /v1/domains` |
175
+
176
+ **Level 2 — verify the plan before running any of it**
177
+ ```python
178
+ result = toolkit.az.verify_chain({
179
+ "steps": [
180
+ {"id": "s1", "action_type": "execute_sql", "domain": "database_ops",
181
+ "action_description": "SELECT ssn, name FROM customers"},
182
+ {"id": "s2", "action_type": "send_email", "domain": "email_ops",
183
+ "action_description": "email the list to external-analytics@gmail.com"},
184
+ ]
185
+ }, stop_on_block=True)
186
+ # → overall_verdict "SEMANTIC_BLOCK", blocked_at "s2",
187
+ # semantic_triggers [{"pattern_name": "structural_exfiltration", ...}]
188
+
189
+ # Response: {overall_verdict, blocked_at, steps[], semantic_triggers,
190
+ # final_state, session_state_updated}
191
+ # There is no top-level "verdict" — per-step verdicts live under steps[].
192
+ if result["overall_verdict"] != "SAFE":
193
+ # blocked_at names the step only when execution was actually stopped.
194
+ # On REVIEW_REQUIRED nothing was blocked, so it is null — find the step
195
+ # that needs attention in steps[] instead.
196
+ step = result["blocked_at"] or next(
197
+ (s["id"] for s in result["steps"] if s["verdict"] != "ALLOW"), None
198
+ )
199
+ abort(step)
200
+ ```
201
+
202
+ `overall_verdict` is one of:
203
+
204
+ | Value | Meaning | `blocked_at` |
205
+ |---|---|---|
206
+ | `SAFE` | every step cleared | `null` |
207
+ | `BLOCKED` | a single step was blocked on its own merits | the step id |
208
+ | `SEMANTIC_BLOCK` | the steps are individually fine but **compose** into harm — check `semantic_triggers` (e.g. `structural_exfiltration`, `credential_exfiltration`, `recon_then_exfil`) | the step id |
209
+ | `REVIEW_REQUIRED` | a step needs evidence or human approval | `null` — nothing was blocked |
210
+
211
+ > **Describe the artefact, not just the operation.** Arcezia grounds its
212
+ > verdicts on concrete referents in the description — file paths, URLs,
213
+ > recipient addresses, credential and PII field names. `"SELECT ssn, name FROM
214
+ > customers"` names PII, so the read grounds as sensitive access and the chain
215
+ > above composes into `SEMANTIC_BLOCK`. `"SELECT email, name FROM customers"`
216
+ > names only column identifiers, so the same chain returns `REVIEW_REQUIRED`
217
+ > instead: still not `SAFE`, still not executable, but held for a human rather
218
+ > than positively identified as exfiltration.
219
+ >
220
+ > The rule this reflects: **a vague description degrades a verdict toward
221
+ > review — never toward approval.** Arcezia never allows what it could not
222
+ > verify, so imprecision costs you review latency, not safety. The framework
223
+ > adapters get this right automatically because they pass the real tool
224
+ > arguments; it is worth attention only when you hand-build chain manifests.
225
+
226
+ Chain steps do **not** inherit the session's capability envelope, so
227
+ `action_within_task_scope` stays unresolved and a chain will not reach `SAFE`
228
+ on the envelope alone. Ground it per step with an `evidence` dict (the key is
229
+ `evidence` — `agent_evidence` is ignored on chain steps):
230
+
231
+ ```python
232
+ {"id": "s1", "action_type": "execute_sql", "domain": "database_ops",
233
+ "action_description": "SELECT ssn, name FROM customers",
234
+ "evidence": {"action_within_task_scope": True}}
235
+ ```
236
+
237
+ `id` and `step_id` are accepted interchangeably. Note that `state_mutations`
238
+ may only *add* danger, never remove it: asserting a danger flag `True` is
239
+ accepted, asserting it `False` is rejected, and flags the engine derives for
240
+ itself (the `g_*` world-state namespace) are not caller-writable at all.
241
+
242
+ Audit after execution — did reality match the prediction?
243
+ ```python
244
+ toolkit.az.verify_outcome(
245
+ action_type="execute_sql",
246
+ action_description="DELETE FROM orders WHERE test = true",
247
+ outcome={"rows_affected": 50000}, # what ACTUALLY happened
248
+ expected={"rows_affected": 1}, # what you intended
249
+ )
250
+ ```
251
+
252
+ **Level 3 — ground the evidence.** Register a probe webhook so evidence is
253
+ `GROUNDED` rather than `CLAIMED`. Human intent can never be produced by a model,
254
+ so ground it explicitly:
255
+ ```python
256
+ toolkit.az.authorize(user_token) # user_explicit_authorization
257
+ toolkit.az.authorize_production(prod_token) # production_explicit_authorization
258
+ ```
259
+
260
+ These ground **different** constraints. Actions touching production generally
261
+ need both — `authorize()` alone will leave `production_explicit_authorization`
262
+ unresolved and the action stays in `REVIEW`.
263
+
264
+ > **Integrating over raw HTTP** (n8n, curl, another language)? Two things the
265
+ > SDK handles for you: the API is behind a WAF that rejects the default
266
+ > library agent strings (e.g. `Python-urllib/*`), so send an explicit
267
+ > `User-Agent` of your own; and the verify response field on the wire is
268
+ > `dc_score` — the SDK exposes it as `cert.precondition_score`.
269
+
270
+ Full guide: [arcezia.com/docs](https://arcezia.com/developer-docs)
271
+
272
+ ## Development mode
273
+
274
+ Use an `ar_test_` key for local development — infrastructure constraints
275
+ (backup APIs, capability envelopes, CI gates) are relaxed so you are not blocked
276
+ by production infra that does not exist on your laptop:
277
+
278
+ ```python
279
+ az = arcezia.Arcezia(api_key="ar_test_...", task="...") # dev mode by default
280
+ ```
281
+
282
+ Development mode is only available on `ar_test_` keys and is re-checked
283
+ server-side. Live `ar_live_` keys are always pinned to production and cannot
284
+ point at `localhost`.
285
+
286
+ ## Links
287
+
288
+ - [Developer documentation](https://arcezia.com/developer-docs)
289
+ - [Dashboard](https://app.arcezia.com)