cendor-acttrace
A tamper-evident, append-only record of every AI decision — what model, what context, what it cost, which tools, and who signed off — mapped to control templates and exportable as an evidence pack. No database, no infra: integrity comes from a hash chain, not a server.
Detect and prove: 20 categories of secrets, PII & special-category data — block / warn / redact in one line, offline — every decision in a hash chain you can verify without a server.
·
pip install cendor-acttrace
Using an AI coding assistant? npx @cendor/init (TS) / uvx cendor-init (Python) wires it up — or point it at cendor.ai/docs/for-ai-assistants.
from cendor.core import instrument
from cendor.acttrace import AuditLog
client = instrument(OpenAI())
audit = AuditLog(system="loan_triage", risk_tier="high", signing_key="…") # auto-subscribes
with audit.decision(input=application, actor="agent") as d:
resp = client.chat.completions.create(model="gpt-4o", messages=msgs) # auto-logged
d.record(model="gpt-4o", prompt_id="triage@v3") # cost/context captured for free
d.human_oversight(reviewer="ops@bank", action="approved")
audit.export("evidence_q3.jsonl", framework="eu_ai_act") # evidence pack (also nist_rmf)
acttrace verify evidence_q3.jsonl --key "…" # re-walks the chain + checks signatures; non-zero if broken
One live AuditLog per chain file (1.13.1)
Reopening a chain path is supported and always has been: a process restarts, you point a new
AuditLog at the same path=, and the chain resumes from the last on-disk entry — no second
audit_open, and verify() spans the whole file. What silently broke the evidence was two logs
writing one file at the same time: each keeps its own head and sequence, both auto-capture the same
bus event, and the two chains interleave until verify() reports broken link at seq N: prev_hash mismatch — discovered whenever someone finally audits. Since 1.13.1 the second one raises at
construction and tells you what to do: detach() the log whose life ended, or give the new one its own
(dated) file. Two separate processes on one file can't be detected from inside either — one writer
per file.
Every mirrored entry names its agent (1.13.0)
OTelMirror used to stamp cendor.audit.agent only on a guardrail_decision — the one entry type whose
payload carries an agent. Measured against Cendor Monitor: 13 of 386 governance rows named their
agent, so "which agent was blocked" was answerable only by inferring it from step ordering. Since
1.13.0 the mirror reads the acting agent (and its id) from cendor-core's ambient registry and
stamps cendor.audit.agent / cendor.audit.agent_id on every entry — including a budget block, a
decision record, an llm_call. The entry's own payload always wins, and the hash-chained evidence file
is untouched: this is the operational copy.
The mirror attaches itself (1.11.0)
With OpenTelemetry installed and a provider configured by your app, AuditLog(system="support", path="audit.jsonl") also streams every
chained entry to your backend as an audit.<type> span — no mirror= line. Pass mirror=False to never mirror
a log, your own sink to use exactly that one, or set CENDOR_TELEMETRY=off to stop all Cendor
telemetry. The mirror is an operational copy: the hash-chained file (or a signed export() pack)
stays the only artifact verify() checks, and a failing mirror is swallowed rather than breaking the
chain.
Highlights
- Offline detection engine + policy — a validator-gated
Detectorregistry spanning secrets, PII, financial, government-ID, free-text credentials, and GDPR special-category data (20 categories), plus aPolicythat maps each toallow·flag·redact·block. Regex + local checksums (Luhn / IBAN mod-97 / Verhoeff / ABA) — no model, no network, no account. Presets:Policy.default()/gdpr()/pci()/strict().
from cendor.acttrace import scan, redact, Policy
scan("card 4111 1111 1111 1111 for alice@example.com") # -> [Finding(credit_card…), Finding(email…)]
cleaned, findings = redact({"note": "ping alice@example.com"}, Policy.default())
# cleaned == {"note": "ping <redacted>"} — findings report counts, never the raw value
audit = AuditLog(system="triage", policy=Policy.gdpr()) # special-category → block, PII → redact
- Enforce + record in one line —
guard(policy, audit=…)returns a dual-shape interceptor (1.5.0): install it oncore's seam yourself, or use it as a context manager (with guard(...):) that installs/removes itself around the block. It enforces the policy (block a disallowed call before it runs, redact-before-send, or flag) andacttracerecords the decision as a tamper-evidentpolicy_flag. Recorder and enforcer stay separate —coreis what stops the call.resolve_findings()exports the per-category action resolution for composers.
from cendor.core.instrument import add_interceptor
from cendor.acttrace import AuditLog, Policy, guard, PolicyViolation
audit = AuditLog(system="support_bot", risk_tier="high", signing_key="ops-key")
add_interceptor(guard(Policy.gdpr(), audit=audit)) # a special-category call is blocked + recorded
- Auto-populating — construct an
AuditLogand it subscribes to the bus: every LLM/tool call, plus cost (tokenguard) and context decisions (contextkit) on the same stream, becomes an entry — no per-call wiring. - Bounded memory for long-running logs —
AuditLog(path="audit.jsonl", max_entries=N)caps the in-memory entry ring so a multi-day agent doesn't grow memory per event. The file stays the complete, verifiable chain (verify()/export()read it),evicted_from_memorycounts what left memory, and the default (None) is unbounded. Bound together withpath=. - Tamper-evident hash chain —
verify()catches edits, reordering, and tail-truncation. The pack's_metahead+count catch truncation, but that header is only authenticated when the log is HMAC-signed and youverify(key=…)— the header itself is signed, so a rewritten_metafails. Without a key it's an unauthenticated in-file check, so pass an out-of-bandexpected_head=(captured fromlog.headat write time) for an authoritative completeness guarantee. Each entry is optionally HMAC-signed too. - Decisions & oversight —
decision()groups a unit of work;d.record(...)andd.human_oversight(reviewer, action)capture Art. 14-style sign-off. - Compliance evidence packs —
export(framework=…)annotates control IDs for EU AI Act, ISO/IEC 42001, GDPR, and NIST AI RMF (starting templates, not certified mappings), and a_meta.summary(counts of decisions, oversight, flags by action/severity) gives a reviewer the at-a-glance read first. PII redaction on by default (swap inredactor=). - Opt-in extras, defaults unchanged —
enable_locale_pack("uk", "in")(UK NINO, India Aadhaar — Verhoeff-checked),enable_entropy_detector()(high-entropy generic secrets — noisy), and NER-backed name/address redaction viapip install "cendor-acttrace[ner]"(Microsoft Presidio, still offline). All strictly opt-in — the zero-extra install stays pure-regex. - Auto-flag by policy — every auto-captured entry is scanned against the full registry, and each detection appends a
policy_flagwith its resolved action (redacted/flagged/blocked), severity, and category — so "we removed / flagged / blocked this" is in the hash chain, not silent (flag_on_redact=Trueby default; a customredactor=owns its own flagging). Detection scrubs the record; block oncore's interceptor seam is the pre-send control. - Policy flags (validation) —
audit.flag(reason, action="blocked", …)records a tamper-evidentpolicy_flag(and returns the chained entry) when your pre-flight guard refuses input that shouldn't be processed — so the refusal is auditable, not just the calls that ran:
from cendor.core.instrument import add_interceptor, MISS
def guard(call): # your pre-flight policy guard
if my_policy_disallows(call): # YOUR rule
audit.flag("special-category data", action="blocked") # acttrace records the refusal
raise PolicyViolation("blocked") # your guard enforces it
return MISS
add_interceptor(guard) # the blocked call never reaches the bus — flag() is its only record
Produces evidence to support compliance — not legal advice, not a guarantee. Control mappings are starting templates for your compliance team.
See docs/acttrace.md · CHANGELOG. Part of the Cendor stack — github.com/cendorhq/cendor-libs. Powered by PowerAI Labs. Apache-2.0; provided "as is", without warranty — use at your own risk (LICENSE §7–8).
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 cendor_acttrace-1.13.1.tar.gz.
File metadata
- Download URL: cendor_acttrace-1.13.1.tar.gz
- Upload date:
- Size: 83.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a99bc8ed98085e8b81cf21226d15dcbf9be8ea1078f33b021d5a2396de0ea420
|
|
| MD5 |
281b25534daaec5e978b7d481887679a
|
|
| BLAKE2b-256 |
0eb2cefbbf8783a66efcba1442c69fa6555b2711edfab9630aa7af3802b8d2af
|
Provenance
The following attestation bundles were made for cendor_acttrace-1.13.1.tar.gz:
Publisher:
release.yml on cendorhq/cendor-libs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cendor_acttrace-1.13.1.tar.gz -
Subject digest:
a99bc8ed98085e8b81cf21226d15dcbf9be8ea1078f33b021d5a2396de0ea420 - Sigstore transparency entry: 2256628403
- Sigstore integration time:
-
Permalink:
cendorhq/cendor-libs@e10e85fd4a2b3fe072403d17ed9569c9fff4e79a -
Branch / Tag:
refs/tags/acttrace-v1.13.1 - Owner: https://github.com/cendorhq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e10e85fd4a2b3fe072403d17ed9569c9fff4e79a -
Trigger Event:
push
-
Statement type:
File details
Details for the file cendor_acttrace-1.13.1-py3-none-any.whl.
File metadata
- Download URL: cendor_acttrace-1.13.1-py3-none-any.whl
- Upload date:
- Size: 54.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea3e326e07945b1d883d076ceeb6de416c913cb605e26dd7fc7827b292c433de
|
|
| MD5 |
90a67cdf916c17bdf0e7619f17dbc391
|
|
| BLAKE2b-256 |
a0f56076f3517df21f74a8a9d4ef1a68c3295795dd0c7677a6d027bcea79b510
|
Provenance
The following attestation bundles were made for cendor_acttrace-1.13.1-py3-none-any.whl:
Publisher:
release.yml on cendorhq/cendor-libs
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cendor_acttrace-1.13.1-py3-none-any.whl -
Subject digest:
ea3e326e07945b1d883d076ceeb6de416c913cb605e26dd7fc7827b292c433de - Sigstore transparency entry: 2256628408
- Sigstore integration time:
-
Permalink:
cendorhq/cendor-libs@e10e85fd4a2b3fe072403d17ed9569c9fff4e79a -
Branch / Tag:
refs/tags/acttrace-v1.13.1 - Owner: https://github.com/cendorhq
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e10e85fd4a2b3fe072403d17ed9569c9fff4e79a -
Trigger Event:
push
-
Statement type: