Allem Python SDK
Check AI agent actions before execution. Log actions for compliance.
This is the governance SDK — Tier 1 in Allem's connection-tier model: check_action() before
the action happens, log_action() after, a durable spool so nothing is lost, and local enforcement
of hard gates when Allem is unreachable. It is a different package from allem-api (a convenience
client for the REST admin surface) and from allem-cli (the Tier 2 coding-agent hooks). Only this
one blocks locally during an outage.
Install
pip install allem
Quick start
from allem import AllemClient
allem = AllemClient(
api_key="alm_sk_test_...",
endpoint="http://localhost:8000",
)
verdict = allem.check_action(
agent_id="my_agent",
action_type="send_email",
parameters={"to": "client@example.com"},
affects_external=True,
)
if verdict:
send_email(...)
else:
print(f"Blocked: {verdict.explanation}")
Verdict is truthy when the action is allowed, so if verdict: works
directly. Inspect .severity, .recommended_action, .event_id, and
.freeze for richer detail.
Fire-and-forget logging
For events that already happened (webhooks, retroactive logs):
allem.log_action(
agent_id="my_agent",
action_type="send_email",
parameters={"to": "client@example.com"},
)
log_action never raises — logging must never break the agent.
Fail-open design
If the Allem API is unreachable, check_action returns
Verdict(allowed=True, ...). An Allem outage will not block the customer's
agent. The condition is recorded in verdict.explanation so calling code can
log it.
Only two things ever produce allowed=False: a verdict Allem actually returned, and the local hard
gate below. An auth failure, a 500, or a response body the client cannot parse is an absence of a
verdict, not a denial, and fails open like any other outage.
Hard gates: the one thing that fails closed (Tier 1 only)
A scope rule the customer marks hard_gate: true is their fail-closed exception to the rule
above, and setting it means Allem's availability becomes your availability for those actions. On
this package the gate holds even when we are unreachable — a gate that only holds while Allem is up
opens during exactly the incident it was bought for.
The SDK keeps a cached list of which action types are hard-gated for each agent
(GET /agents/{id}/scopes/hard-gates, resolved through GET /agents/me), refreshed off the latency
path after a successful check. When the platform does not answer:
| Situation | Outcome |
|---|---|
| Action is not hard-gated | allowed — fail-open, unchanged |
| Action is on the cached list | denied locally |
| Cached list is stale | hard-gated entries still block |
| No list has ever been fetched | allowed, and warned on every check |
A stale cache does not relax the gate. Age makes the list less complete — a rule added during the outage is unknown to the SDK — not less binding on what it already holds.
The never-fetched row is the honest limit: the SDK cannot enforce a list it has never seen, so it
proceeds and says so through the allem.client logger, every time, naming the endpoint and the
reason. Watch for that warning; it means hard gates are not being enforced locally.
A locally-denied action still reaches the chain. It is spooled like any other event, annotated with
which gate blocked it, the scope_version the list came from, and how stale the cache was — so an
auditor can tell a gate that worked from a gate that guessed.
Configuration. The cache lives under ALLEM_HOME (default ~/.allem/state/, owner-only), with
a 300s staleness bound overridable by ALLEM_HARD_GATE_TTL_S. On a read-only filesystem — a
container or a Lambda — it falls back to memory for the process lifetime and logs once; the gate
still blocks, it just has to be refetched on each cold start. check_action is never failed because
a cache file could not be written.
Tier 2 is different, deliberately. The coding-agent hooks in allem-cli do not block during
an outage: a hook that broke a developer's session would be uninstalled within the hour, and the
agent could reach the same result through another tool path anyway. If you read "hard gates fail
closed", it is true of this package and not of that one.
Authentication
The SDK sends the API key in the X-API-Key header. Use a test-mode key
(alm_sk_test_...) when developing against a non-production endpoint.
Completeness: sequences and the durable spool
Every event the SDK emits carries a client-generated sequence block —
{run_id, client_seq, signature, signature_method} — numbered 1, 2, 3, …
per agent per process. The server can therefore prove not only that stored
records were never altered (the hash chain), but that no record is
missing: a sequence number that never arrives is a detectable gap.
Before any network attempt, each event is written to a local SQLite spool
(default ~/.allem/spool.db, override with ALLEM_SPOOL_DIR or the
spool_path argument). A background thread delivers spooled events and
retries forever — backoff 1s → 32s, then every 60s. If the process exits
with events undelivered, the next process replays them. Nothing is ever
silently lost, and fail-open behaviour is unchanged: an Allem outage never
blocks your agent.
Signing the sequence (optional, recommended)
The sequence number is a claim your agent makes. Signing it lets Allem check that claim — and, because your agent holds the private key and Allem holds only the public half, Allem cannot manufacture a claim on your behalf.
pip install 'allem[signing]'
python -m allem.sequence --out ~/.allem/agent.key # private key, written 0600
# register the PUBLIC key printed by that command:
# POST /v1/agents/{agent_id}/signing-keys
export ALLEM_SIGNING_KEY_PATH=~/.allem/agent.key
Or AllemClient(api_key=..., signing_key_path="~/.allem/agent.key").
The signature covers a digest of the event body, not just its position, so a signature cannot be lifted from one event onto another. Your private key is never sent to Allem, and the registration endpoint refuses anything that looks like one.
If a signature fails to verify, Allem records the failure and raises a critical finding — it still accepts the event. Enforcement fails open; that does not change.
Full details: docs/completeness-signing.md.
ALLEM_SIGNING_SECRET (HMAC-SHA256) is still accepted so existing deployments
keep running, but Allem has never held that secret and therefore cannot verify
those signatures: they are recorded as unverified. The SDK warns once at
construction. Use a key path instead.
Run lifecycle and heartbeats
client.start_run("billing-bot") # run_start (client_seq 1), then a beat every 60s
...
client.close() # run_end
Heartbeats make silence mean something specific. Without them, "the agent did nothing" and "the agent stopped reporting" are the same observation. With them, the server opens a blackout window when an agent goes quiet past three missed beats, and closes it when your spool replays — so an outage becomes a bounded, recorded fact instead of a hole in the log.
A heartbeat consumes a client_seq like any other event, which is the point: a
missing heartbeat is itself a detectable gap.
start_run is an explicit call because run_start must take client_seq = 1 —
any log_action for that agent would consume that number first. run_end is
also emitted on atexit and SIGTERM (chained onto your own handler, never
replacing it).
Call client.flush(timeout=...) before process exit to drain the spool if
you want delivery confirmation; it is never required for correctness.
v1 limitations (stated openly)
- Spool encryption: the spool file is not encrypted at rest in v1. It
is created with restrictive permissions (
0700directory,0600file), but it may contain sensitive action parameters. If you operate under regulatory requirements (healthcare, financial services), placeALLEM_SPOOL_DIRon an encrypted volume until spool encryption ships. - One run per process: sequence state lives in memory, so serverless / one-shot agents produce many short runs (seq 1–2 each). Completeness still holds within each run, but gap detection is nearly vacuous for such workloads. Cross-process sequence persistence is deliberately not attempted in v1.
- Completeness covers instrumented call sites only. Code paths that never call the SDK reserve no sequence numbers and are outside the guarantee.
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 allem-0.1.0.tar.gz.
File metadata
- Download URL: allem-0.1.0.tar.gz
- Upload date:
- Size: 53.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
991c3b1ccfa0e9e9d101997f00582efa5db69e80b782be7b460775dbd09fe9d3
|
|
| MD5 |
fa3a4aba66ae4718b039aeff5c9c6ff2
|
|
| BLAKE2b-256 |
9da7ac654385b4f387e90c038079a7c7e86c94a254baa8c7b7299d6defd26f84
|
Provenance
The following attestation bundles were made for allem-0.1.0.tar.gz:
Publisher:
publish.yml on Allemai/allem-platform
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
allem-0.1.0.tar.gz -
Subject digest:
991c3b1ccfa0e9e9d101997f00582efa5db69e80b782be7b460775dbd09fe9d3 - Sigstore transparency entry: 2764800456
- Sigstore integration time:
-
Permalink:
Allemai/allem-platform@ccc2f5729a4884bc824f892d8fdc39e91ac4a37a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Allemai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ccc2f5729a4884bc824f892d8fdc39e91ac4a37a -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file allem-0.1.0-py3-none-any.whl.
File metadata
- Download URL: allem-0.1.0-py3-none-any.whl
- Upload date:
- Size: 36.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a31508fbabbc931371334536c33f26a3c9479bc7585828f7b58a9a3304595e69
|
|
| MD5 |
a05095c7a72935e409fe8c55c2de3ba2
|
|
| BLAKE2b-256 |
2b3b9cdff7f7de290b5afbc237f14348c88d13be0bffa43109c8528e0e66d7c7
|
Provenance
The following attestation bundles were made for allem-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Allemai/allem-platform
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
allem-0.1.0-py3-none-any.whl -
Subject digest:
a31508fbabbc931371334536c33f26a3c9479bc7585828f7b58a9a3304595e69 - Sigstore transparency entry: 2764800459
- Sigstore integration time:
-
Permalink:
Allemai/allem-platform@ccc2f5729a4884bc824f892d8fdc39e91ac4a37a -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Allemai
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ccc2f5729a4884bc824f892d8fdc39e91ac4a37a -
Trigger Event:
workflow_dispatch
-
Statement type: