Arcezia / AI agent security / Agent governance
AI agent governance: a rule, a decision per action, a record you can check
AI agent governance is the set of rules an organisation sets for what its agents may do, and the evidence that the rules held. The term covers two jobs. One is inventory and posture: knowing which agents run, with which tools and settings. The other is governance of each action: every tool call an agent attempts is decided against your rules before it runs, and the decision is kept as evidence. Arcezia does the second, in four steps: you write a rule; each tool call gets ALLOW, REVIEW or BLOCK before it runs; each answer is kept as a signed record; an auditor checks the records offline, without trusting Arcezia. Below, the four steps run end to end on the live service.
The four steps
- Rule. A contract states what a tool does and your own rules for it, such as a rule that refunds above a set amount never run. A session’s scope, signed by the person in charge, says what one run may do at most.
- Decision. Each tool call is answered before it runs: ALLOW, REVIEW (held, with what would release it) or BLOCK (refused, with why). Your code runs the tool on ALLOW and on nothing else.
- Signed record. Each answer is kept as a record signed with Ed25519 and linked to the record before it, so a changed or missing record shows. When a person’s approval was attached, the record shows whether it counted and whether it was used up; for a plan of several steps, an approval for a step that never ran reads “not counted: step not run”.
- Offline check. An open-source checker verifies the records with the account’s key fingerprint, which the auditor gets from the account holder, not from the file.
Run it end to end
pip install -U arcezia cryptography, set ARCEZIA_API_KEY to an owner key from the Keys page at app.arcezia.com, and save the two files below. rule_call.py writes the rule and sends the calls:
import os
import arcezia
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
PATH = "principal_key.pem" # your signing key: keep it where the agent cannot read it
if not os.path.exists(PATH):
with open(PATH, "wb") as f:
f.write(Ed25519PrivateKey.generate().private_bytes(
serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8, serialization.NoEncryption()))
key = serialization.load_pem_private_key(open(PATH, "rb").read(), password=None)
# 1. The rule: refunds above 500 never run.
admin = arcezia.Arcezia(task="refund customer orders", signing_key=key)
admin.register_token_key(key.public_key()) # once per account; safe to repeat with the same key
for c in admin.contracts():
if c["name"] == "refund-policy":
admin.delete_contract("refund-policy")
contract = admin.register_contract({
"facts": {"large_refund": {"means": "the refund is above 500", "from": "call_amount > 500"}},
"tools": {"issue_refund": {
"pack": "agent_action",
"not_present": ["outbound", "trust_boundary_crossing", "sensitive_data", "mass_scope", "irreversible"],
"arguments": {"amount": ["amount"]},
"rules": [{"when": ["large_refund"], "never": True}],
}},
}, name="refund-policy")
REFUND = contract["domains"]["issue_refund"]
# 2. The calls: the same tool, two amounts, in one session you sign.
az = arcezia.Arcezia(task="refund customer orders", signing_key=key)
az.start_session(capability_envelope=contract["session_grants"])
for amount in (40, 2400):
cert = az.verify(action_type="issue_refund", action_description=f"refund order 42: {amount} EUR",
domain=REFUND, action_parameters={"order_id": "42", "amount": amount})
print("amount", amount, "|", cert.verdict, "| release", cert.release, "| reason", cert.reason,
"| record", cert.log_id)
govern.sh runs it, exports the two records and checks them offline:
API=https://api.arcezia.com
AUTH="Authorization: Bearer $ARCEZIA_API_KEY"
# 1-2. Declare the rule, send the calls (prints each answer and its record id).
python3 rule_call.py | tee answers.txt
FIRST=$(grep -o 'record [0-9]*' answers.txt | head -1 | cut -d' ' -f2)
# 3. Export those records, and get the account's record-signing key fingerprint.
curl -s "$API/v1/account/export?after_id=$((FIRST - 1))&limit=2" -H "$AUTH" > records.ndjson
FP=$(curl -s "$API/v1/account/record_keys" -H "$AUTH" | python3 -c 'import json,sys; print(json.load(sys.stdin)["fingerprint_sha256"])')
# 4. Check the records offline.
arcezia-verify-records records.ndjson --use-export-keys --partial --fingerprint "$FP"
Output of sh govern.sh:
amount 40 | REVIEW | release ['approval:user'] | reason [] | record 13567
amount 2400 | BLOCK | release [] | reason ['contract:large_refund'] | record 13568
account : 26
keys : 1 key(s) matched a fingerprint supplied out of band (keys read from the export)
key 4c5e576d5765a733 sha256 4c5e576d5765a7334c9d1a34bef62084627b66206d2018dff957987717c3c15c (compare with the account holder's)
records : 2
times shown : the signed time inside each record; the export's plain 'timestamp' column is not covered by the signature
record 13567 UNVERIFIED signed 2026-10-07T15:01:52.778285+00:00 verdict REVIEW
- the first record here follows records not in this export (partial export)
record 13568 PASS signed 2026-10-07T15:01:53.248097+00:00 verdict BLOCK
export headers:
records.ndjson PASS header signature matches and agrees with the file
checkpoints (carried inside the export):
checkpoint 25 PASS up to record 13542, 3301 record(s), as of 2026-10-07T11:04:47.712682+00:00: signature matches
checkpoint 24 PASS up to record 13500, 3259 record(s), as of 2026-10-07T10:51:43.072731+00:00: signature matches
checkpoint 23 PASS up to record 13474, 3236 record(s), as of 2026-10-07T10:47:13.058137+00:00: signature matches
checkpoint 22 PASS up to record 13102, 2864 record(s), as of 2026-10-07T10:19:50.461882+00:00: signature matches
checkpoint 21 PASS up to record 13001, 2774 record(s), as of 2026-10-07T09:53:20.289230+00:00: signature matches
checkpoint 20 PASS up to record 13000, 2773 record(s), as of 2026-10-07T09:53:18.369772+00:00: signature matches
checkpoint 19 PASS up to record 12941, 2714 record(s), as of 2026-10-07T03:13:25.916291+00:00: signature matches
checkpoint 18 PASS up to record 12500, 2467 record(s), as of 2026-09-20T19:05:05.696928+00:00: signature matches
checkpoint 17 PASS up to record 12000, 2052 record(s), as of 2026-09-16T20:45:49.349811+00:00: signature matches
checkpoint 16 PASS up to record 11500, 1636 record(s), as of 2026-09-16T19:16:26.565572+00:00: signature matches
checkpoint 14 PASS up to record 10863, 1273 record(s), as of 2026-09-12T11:28:35.306509+00:00: signature matches
checkpoint 13 PASS up to record 10863, 1273 record(s), as of 2026-09-12T11:28:27.678777+00:00: signature matches
checkpoint 12 PASS up to record 10769, 1179 record(s), as of 2026-09-12T11:17:11.490864+00:00: signature matches
checkpoint 11 PASS up to record 10769, 1179 record(s), as of 2026-09-12T11:17:05.970650+00:00: signature matches
checkpoint 10 PASS up to record 10717, 1127 record(s), as of 2026-09-12T11:15:47.888685+00:00: signature matches
checkpoint 9 PASS up to record 10717, 1127 record(s), as of 2026-09-12T11:15:42.562457+00:00: signature matches
checkpoint 8 PASS up to record 10614, 1059 record(s), as of 2026-09-11T10:10:54.502046+00:00: signature matches
checkpoint 7 PASS up to record 10614, 1059 record(s), as of 2026-09-11T10:10:47.853331+00:00: signature matches
checkpoint 6 PASS up to record 10500, 972 record(s), as of 2026-09-11T09:59:48.253208+00:00: signature matches
checkpoint 4 PASS up to record 9500, 694 record(s), as of 2026-09-07T17:04:14.502500+00:00: signature matches
checkpoint 3 PASS up to record 9065, 341 record(s), as of 2026-09-07T09:45:12.593718+00:00: signature matches
checkpoint 2 PASS up to record 9065, 341 record(s), as of 2026-09-07T09:45:05.375206+00:00: signature matches
checkpoint 1 PASS up to record 9000, 323 record(s), as of 2026-09-07T09:39:46.687967+00:00: signature matches
completeness : PASS the export reaches record 13568, at or beyond the strongest signed checkpoint (record 13542, carried inside the export)
(these checkpoints travelled inside the export; one kept outside Arcezia (--checkpoint) is stronger evidence)
PASS 1 PASS-ATTESTED 0 FAIL 0 UNVERIFIED 1
RESULT : UNVERIFIED
Measured 7 October 2026, 15:01:48 UTC to 15:01:57 UTC, with the arcezia Python SDK 1.0.7 from PyPI, against https://api.arcezia.com, on Arcezia’s self-test account. The contract was deleted afterwards. On your account the record ids and times will differ.
What the run shows
| Call | Answer | Why | Record |
|---|---|---|---|
issue_refund, amount 40 | REVIEW | release ['approval:user']: a refund is a lasting change, so a person approves it | 13567 |
issue_refund, amount 2400 | BLOCK | reason ['contract:large_refund']: your rule refuses it | 13568 |
- The rule decided. The same tool, in the same session, gets two answers. The larger refund is refused, with your rule’s name as the reason.
- The records are genuine. The refused call’s record is PASS: its signature matches, and its link to the held call’s record holds. The held call’s record is UNVERIFIED because the records before it are not in this two-record export, so its own link back cannot be checked. That is why the exit status is 2.
- Nothing is missing from the end. Completeness is PASS: the export reaches at or beyond the newest signed checkpoint it carries.
An audit uses the complete export, every page from the account’s first record, without --partial. A real sample, and what PASS, UNVERIFIED and FAIL mean: audit evidence. To remove the rule afterwards: az.delete_contract("refund-policy").
What this does not cover
- Inventory and posture. Arcezia does not find the agents an organisation runs or report their settings.
- Calls never sent for a check. A record exists for each call that was checked. Match your execution logs against the records to show that every executed action had one.
- Whether your rules are the right ones. They do what you wrote.
- Certification. The records are evidence for an auditor, not a compliance certificate. How they relate to specific rules: banks, healthcare.
Questions
What is the difference between AI governance and AI agent governance?
AI governance covers how an organisation builds and uses AI: risk assessment, documentation, oversight. Agent governance adds the actions. An agent changes real systems, so its rules have to apply to each action when it happens, and leave evidence that they did.
Can a person approve an action that a rule holds?
A held call (REVIEW) names what would release it, such as approval:user. A person’s approval is a token your backend signs, bound to that one call and used once; an approval the agent only claims is refused. A rule written as never refuses the call outright. Approvals in the developer docs.