archytan-lite (Python)
Fail-closed Python client for the Archytan Lite authorization gate.
pip install archytan-lite
The rule this client enforces
Only an HTTP 200 whose body has decision == "ALLOW", and a receipt whose
Ed25519 signature verifies against the gate's public key, and a receipt that
describes this request counts as authorized.
Everything else is denied: a non-200, a timeout, a refused connection, a malformed body, a signature that doesn't verify, a receipt for some other action, the gate process being dead.
Nothing here raises for a denial. A denial arrives as allowed=False, never
as an exception — an exception is something a caller can forget to catch, and a
forgotten except around an authorization check fails open.
Use
from uuid import uuid4
from archytan_lite import authorize, redeem_capability
result = authorize(
url="http://archytan-lite:8421",
token=CALLER_TOKEN,
gate_public_key_hex=GATE_PUBLIC_KEY, # the hex `keygen` printed
action="invoice.delete",
actor={"uid": "user_1"}, # see "Roles" below
resource={"type": "invoice", "id": "inv_7"},
idempotency_key=str(uuid4()),
)
if not result.allowed:
raise PermissionError(result.reason)
authorize_async and redeem_capability_async are the async forms. They share
their decision logic with the sync versions rather than reimplementing it — two
copies is how a sync path and an async path end up disagreeing about what
"authorized" means, with only one of them audited.
Roles
Omit actor["role"] when the gate runs credential-bound roles. In that mode
your role is a property of your credential, resolved server-side, and a role
asserted in the request that disagrees with it is treated as an escalation
attempt and refused. The receipt comes back carrying the role the gate
resolved.
Send actor["role"] only against a gate configured with a single shared token,
which has no other way to learn one. When you do send it, this client checks the
receipt agrees with it.
Capabilities
When the gate has capabilities enabled, an ALLOW carries a single-use grant scoped to exactly one action on one resource, expiring in seconds. Holding it is not permission to act — spend it immediately before acting:
spent = redeem_capability(
url="http://archytan-lite:8421",
token=CALLER_TOKEN,
capability_token=result.capability.token,
action="invoice.delete",
resource={"type": "invoice", "id": "inv_7"},
)
if not spent.redeemed:
raise PermissionError(spent.reason) # expired, out of scope, or already spent
delete_invoice("inv_7")
A second redemption of the same capability is refused, so an actuator cannot be driven to act twice on one authorization.
AI agent frameworks
pip install "archytan-lite[langchain]"
ArchytanGuardedTool wraps a function so an agent cannot reach it without the
gate authorizing the call and the capability being spent first:
from archytan_lite.integrations.langchain import ArchytanGuardedTool
delete_invoice_tool = ArchytanGuardedTool(
name="delete_invoice",
description="Permanently delete an invoice by id.",
action="invoice.delete",
resource_type="invoice",
func=really_delete_invoice, # reached only after ALLOW + redemption
gate_url="http://archytan-lite:8421",
caller_token=AGENT_TOKEN,
gate_public_key_hex=GATE_PUBLIC_KEY,
actor_uid="agent-invoices",
)
It is a LangChain BaseTool, which LangGraph and CrewAI both accept, so one
integration covers all three.
A denial returns an explanation to the agent rather than raising, because a raised exception inside an agent loop is usually swallowed and retried — the agent should be told it was refused, in text it can reason about, so it stops rather than loops.
What this cannot do for you: nothing stops your code from importing the underlying function and calling it directly. The wrapper makes the guarded path the easy one and the unguarded path a deliberate act; it is not a sandbox.
Contract siblings
This client, clients/node/index.js, and the authorize()
helper in testing/failclosed/failclosed_test.go are the same contract in three
languages. If you change the decision logic in one, change it in the others.
What it does not do
It verifies the signature over the intent_hash the gate returned. It does not
recompute that hash from the receipt's fields — doing so would require
byte-for-byte replication of Go's encoding/json output, including its
HTML-escaping of <, > and &, and getting that subtly wrong would silently
reject legitimate receipts. Confirming a stored receipt still matches its hash
is --verify-chain's job, server-side, where the canonical encoding lives.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file archytan_lite-2.0.0.tar.gz.
File metadata
- Download URL: archytan_lite-2.0.0.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30c5567164839f6dd2dce7983ad533506b13ee1655572e1bb67ffc0866f3c683
|
|
| MD5 |
c93068004f9fac4ecb2b4022b0a64ed8
|
|
| BLAKE2b-256 |
a8e427fcf1b3cf350d3ce965b21a54c2862941f0f44cc57999953d2fecc044ff
|
File details
Details for the file archytan_lite-2.0.0-py3-none-any.whl.
File metadata
- Download URL: archytan_lite-2.0.0-py3-none-any.whl
- Upload date:
- Size: 13.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
047a1d7a920319e6cd5ca4af243726bd73d545bf433c837d2106d904f9224ae9
|
|
| MD5 |
7395309b9ec41984c250eed80fdea980
|
|
| BLAKE2b-256 |
63c73a129a313730f55d4fa40f2f5d4614f67a1482155b21dd9536f63483f57d
|