Skip to main content

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.ps1 is 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": see below

# 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

success means the record is signed and DigiCert returned a timestamp token that covers it. DigiCert's own signature on that token is not yet verified. signed_no_tsa means the record is signed and stored but not timestamped. tsa_response_mismatch means DigiCert returned a token that doesn't match the record we sent; the record is signed but not timestamped.

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 its decision_events table are blocked by database triggers, which anyone with access to the file can remove; its intent_gaps table 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 (port 80) to timestamp.digicert.com. The request currently uses unencrypted HTTP. Without it, records are still signed and stored, with status signed_no_tsa. If the timestamp server returns a token that doesn't cover the hash and nonce Allela sent, the token is discarded and the signed record is stored with status tsa_response_mismatch.

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 never stores your raw inputs and outputs, only hashes of them. 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 default str() 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), use trace_input(applicant_profile=x), not trace_input(x). If any keyword value can't be turned into JSON (a Decimal, 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_fields says 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_checks says 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

Allela stores cryptographic hashes of your input, output and system prompt, plus metadata (model ID and version, timestamps, tenant ID) and any execution_params you pass. Your raw input and output text is never stored. execution_params is stored in readable form in the ledger file and is part of the signed record, so don't put personal data or secrets in it. 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, a failure is logged (the error type and the event ID or function name only), and no ledger record exists. If the ledger file is locked by another process, a background write waits about 7 seconds and is then dropped, with an error logged. If a timestamp request hangs, it holds up the attestations queued behind it for about 20 seconds per network step and sometimes longer, and shutdown() may return False with records still pending. The SDK does not retry failed timestamp requests or failed writes. Don't open the ledger with other tools, or copy the file, while your application is running.

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.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for allela 0.1.4
File Size Uploaded Provenance Actions
allela-0.1.4.tar.gz 21.7 kB Without provenance Details for allela-0.1.4.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for allela 0.1.4
File Interpreter ABI Platform Actions
allela-0.1.4-py3-none-any.whl Python 3 none any Details for allela-0.1.4-py3-none-any.whl

Total release size: 41.0 kB

Release files / allela-0.1.4.tar.gz

Download URL allela-0.1.4.tar.gz
Size 21.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e1d46fbe44736fe0ceee40e4dfe6c5eadba3ac1889785231f5e453643ab5432c
BLAKE2b-256 checksum
How to use checksums
79f8324b25c967d39a284273ce446825a9aed92becf34d8170f7b995067e875b
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.4-py3-none-any.whl

Download URL allela-0.1.4-py3-none-any.whl
Size 19.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b45181786e898b0d6c1a3de393db4148f180afa0d6cbe26e4411c75b2d273835
BLAKE2b-256 checksum
How to use checksums
26abfbbdc0cf001c14f0d4d1fa80cedd98c22d5b73934da84eaac60eed9db86c
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.4

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.

Release history Release notifications | RSS feed

This release

0.1.4 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page