Allela SDK
Tamper-evident attestation for AI decisions, timestamped with an RFC 3161 token from DigiCert's timestamp authority.
Quickstart
Create a project folder and a virtual environment, then install Allela.
macOS / Linux:
mkdir allela-quickstart && cd allela-quickstart
python3 -m venv .venv
source .venv/bin/activate
pip install allela
Windows (PowerShell):
mkdir allela-quickstart; cd allela-quickstart
py -m venv .venv
.venv\Scripts\Activate.ps1
pip install allela
Windows note: if
Activate.ps1is blocked ("running scripts is disabled on this system"), run this once, then activate again:Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
Save this as quickstart.py:
import allela
allela.configure(
tenant_id="quickstart",
model_provider="anthropic",
model_id="claude-sonnet-4-6",
model_version="20250929",
)
prompt = "Applicant: income $60,000, requesting $10,000 loan."
answer = "APPROVED: Low risk profile."
# 1. Sign the record and get an RFC 3161 timestamp from DigiCert.
event_id = allela.attest(prompt, answer)
# 2. Look up what was stored.
event = allela.get_event(event_id)
print("event: ", event_id)
print("status: ", event["status"]) # "success" = signed and timestamped
# 3. Verify the record against the original input and output.
result = allela.reconcile(event_id, raw_input=prompt, raw_output=answer)
print("verified: ", result["path"]) # FULL_INTEGRITY_VERIFICATION
print("timestamp:", result["timestamp_imprint_matches"]) # True
Run it:
python quickstart.py
Expected output (your event ID will differ):
event: evt_...
status: success
verified: FULL_INTEGRITY_VERIFICATION
timestamp: True
The first run creates three files in the current folder:
allela_private.key: your Ed25519 signing key. Keep it secret and never commit it.allela_public.key: the matching public key used to verify records.allela_ledger.db: the local, tamper-evident record ledger. Each decision record is signed and hashed. UPDATE and DELETE on itsdecision_eventstable are blocked by database triggers, which anyone with access to the file can remove; itsintent_gapstable has no such triggers.
Back up allela_private.key: records can only be verified with the key that
signed them. If the key file is missing while allela_ledger.db exists,
configure() raises SigningKeyMissingError instead of creating a new key.
If the key doesn't verify the ledger's newest record (a different key, or an
altered record), configure() raises LedgerKeyCheckError and changes nothing.
get_event, list_events and reconcile raise LedgerNotFoundError if the
ledger file doesn't exist, rather than creating an empty one.
Add *.key and *.db to your .gitignore. Timestamping needs outbound
HTTP access to timestamp.digicert.com; without it, records are still
signed and stored, with status signed_no_tsa.
Using it in your app
Wrap your existing AI call with @allela.trace to attest each call to the function you wrap (see "What is not captured" for when a record can't be written):
import allela
allela.configure(
tenant_id="acme_corp",
model_provider="anthropic",
model_id="claude-sonnet-4-6",
model_version="20250929",
)
@allela.trace(system_prompt="You are a credit risk assistant.")
def make_credit_decision(applicant_profile: str) -> str:
return call_your_llm(applicant_profile) # your existing AI call, unchanged
result = make_credit_decision("Applicant: income $60,000, requesting $10,000 loan.")
# `result` is returned immediately — attestation happens on a background
# thread and does not add latency to your function call.
# Before your process exits, flush any in-flight attestations:
allela.shutdown()
Verifying a decision later
Allela stores only hashes, never your raw inputs and outputs. Keeping the
original inputs and outputs is up to you. To verify a decision later, you
hand them back to reconcile(), which checks them against the signed record.
Save this as verify_example.py:
import allela
allela.configure(
tenant_id="acme_corp",
model_provider="anthropic",
model_id="claude-sonnet-4-6",
model_version="20250929",
)
@allela.trace(system_prompt="You are a credit risk assistant.")
def make_credit_decision(applicant_profile: str) -> str:
return "APPROVED: Low risk profile." # stand-in for your model call
applicant = "Applicant: income $60,000, requesting $10,000 loan."
decision = make_credit_decision(applicant)
# Store `applicant` and `decision` in your own records; Allela keeps only hashes.
# @trace writes its record in the background. shutdown() waits for that write;
# it does not stop Allela, and @trace keeps working afterwards.
allela.shutdown()
# @trace doesn't return the event ID, so look it up: newest first, per tenant.
event_id = allela.list_events("acme_corp", limit=1)[0]["event_id"]
result = allela.reconcile(
event_id,
raw_input=allela.trace_input(applicant), # same arguments as the original call
raw_output=allela.trace_output(decision),
)
print(result["path"]) # FULL_INTEGRITY_VERIFICATION
Finding the event ID. list_events(tenant_id, limit=20, offset=0) returns
event_id, timestamp_utc and status, newest first. Taking the newest
event only works if no other traced call ran in between; otherwise match on
timestamp_utc. If you need the ID at the moment of the decision, call
allela.attest(input_text, output_text) directly instead of using
@trace: it returns the event ID.
Rebuilding the input exactly. @trace records the call's arguments in a
fixed format, and trace_input() rebuilds it. It only matches if you pass
the same values in the same way:
- Pass plain values (strings, numbers, lists, dicts), or give your objects a
stable
__str__. An object with Python's defaultstr()includes its memory address, so it never matches. - Pass arguments the same way as the original call: if it was
make_credit_decision(applicant_profile=x), usetrace_input(applicant_profile=x), nottrace_input(x). If any keyword value can't be turned into JSON (aDecimal, for example), keyword order matters too. - A dict passed positionally is recorded with
str(), so its key order matters.
A mismatch from any of these returns TAMPER_DETECTED, the same result as
real tampering, so get the arguments right before treating it as an alert.
result["path"] is one of:
FULL_INTEGRITY_VERIFICATION: the stored record is intact and your data matches it.TAMPER_DETECTED: the stored record is intact, but your data doesn't match it (mismatched_fieldssays which).DATA_UNAVAILABLE_AT_SOURCE: the stored record is intact; no data was supplied to compare.LEDGER_RECORD_INVALID: the stored record failed its signature or root-hash check (failed_checkssays which), so it can't be trusted and nothing was compared.NOT_FOUND: no event with that ID.
result["timestamp_imprint_matches"] only confirms that the stored
timestamp token covers this record's hash. It does not yet verify the
timestamp authority's signature or certificate chain.
What gets stored
Only cryptographic hashes and metadata (model ID/version, timestamps,
a hash of the system prompt) — never your raw input/output text. See
the Decision Event Schema (v1/decision-event.json) for the exact shape.
What is not captured
If an attestation fails, the function still returns its result, the failure is logged to the application log, and no ledger record exists.
Important: call allela.shutdown() before exiting
Because attestation runs on a background worker so it never blocks your
application, an attestation that's still in flight when your process
exits abruptly could be lost. Call allela.shutdown() (which flushes
the queue) during your application's graceful shutdown sequence.
shutdown() only waits for pending records; it doesn't stop Allela, so
it is also safe to call mid-run, as in the verification example above.
shutdown() waits at most timeout seconds (default 30; pass None to
wait indefinitely) and returns True if every attestation was written,
or False if the timeout expired with some still pending — in which
case a warning is logged with the count.
License
Apache License 2.0 — see LICENSE.
Metadata
Release files for allela 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| allela-0.1.3.tar.gz | 20.2 kB | Without provenance | Details for allela-0.1.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| allela-0.1.3-py3-none-any.whl | Python 3 | none | any | Details for allela-0.1.3-py3-none-any.whl |
Total release size: 38.5 kB
Release files / allela-0.1.3.tar.gz
| Download URL | allela-0.1.3.tar.gz |
|---|---|
| Size | 20.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
23adc798556116d93ff6cbf6b2e44152e4c275535c653e623ee945d4d98a4dc3
|
|
BLAKE2b-256 checksum How to use checksums |
c9667a6e42ff87b7c7a76d1b7ea5e267655b5be1355956c12fe9733a0d729436
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / allela-0.1.3-py3-none-any.whl
| Download URL | allela-0.1.3-py3-none-any.whl |
|---|---|
| Size | 18.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
73dabebef5eac747e74432c6e64aa0c47b50a039dfd5dda7171770b318b74d94
|
|
BLAKE2b-256 checksum How to use checksums |
c59c00474426b3f6e0296628a6b2764b26faaabc6fa5b113270bcb9c347e00f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Security for allela 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.