EPI (Evidence Packaged Infrastructure) — Evidence for AI agents
Record. Seal. Verify offline. The answer is a file.
pip install epi-recorder
epi demo --no-browser # record → seal → verify (no API key)
60-second path · What a .epi is · CLI · Docs & pilot · Standards
How it works: ARCHITECTURE.md — packages, integrity model, and where to read next.
When someone asks what your agent did six months ago,
the answer should be a.epifile — not a dashboard login and a shrug.
epi-recorder turns an AI agent's run into evidence a third party can verify. Not a log you have to trust, but a portable, signed artifact that leaves your platform and declares exactly what it captured — verifiable offline with open tooling.
No account and no EPI server: verification is fully offline. When sealing, EPI asks public
RFC 3161 timestamp services for a trusted time (only a hash is sent; switch off with EPI_NOTARIZE=0
or use your own with EPI_TSA_URLS).
Open a sealed .epi offline — epi view run.epi
Forensic case view of a sealed run. Sample artifact: docs/assets/readme-demo.epi
60-second path
Works without any LLM API key:
# demo.py
from epi_recorder import record, get_current_session
with record("demo.epi", goal="show the golden path"):
s = get_current_session()
s.log("tool.call", tool="lookup", id="A-1")
s.log("tool.response", ok=True, balance=250)
s.log("decision", action="approve", reason="within limit")
python demo.py
epi verify demo.epi
epi-register demo.epi
epi view demo.epi
record("demo.epi") writes epi-recordings/demo.epi: a bare file name goes
into ./epi-recordings/. A path with a folder, such as ./demo.epi or
out/demo.epi, is written exactly there. Run the commands above from the same
folder as demo.py; epi verify demo.epi finds the file in ./epi-recordings/.
Running python demo.py again never replaces earlier evidence: the second run
seals to epi-recordings/demo-2.epi (then demo-3.epi, …) and says so. Set
EPI_OVERWRITE=1 to replace demo.epi in place instead.
$ python demo.py
[EPI] 3 steps → epi-recordings/demo.epi Signed ✓
| Step | Command | What you get |
|---|---|---|
| Record + seal | python demo.py |
Signed epi-recordings/demo.epi (secrets redacted by default) |
| Verify | epi verify demo.epi |
Integrity + signature checks offline |
| Register | epi-register demo.epi |
Without --service: a local, self-issued SCITT receipt embedded in the .epi (not an independent ledger entry) |
| View | epi view demo.epi |
Self-contained browser viewer (screenshot above) |
Typical first-run verify:
| Check | Result |
|---|---|
| Integrity (SHA-256) | Valid |
| Signature (Ed25519) | Valid |
| Identity | Often LOCAL / UNKNOWN until you pin trust |
| Secrets | Redacted by default (redact=True) |
First-run WARN / LOCAL identity is normal — seal integrity and signature can still pass.
Identity is separate from seal. A customer pins the sender's key, obtained out of band, withepi keys trust <sender-key-file> --name <name>(the sender's.pub,did.jsonor savedepi keys exportline — never the.epiyou are checking) orepi keys trust --from-did did:web:vendor.com --name <name>.
Policy / “did the run break our rules?” is separate again:epi analyze— see docs/POLICY.md.
epi-register is a standalone alias for epi scitt register (same options, same result).
Without --service <url> it creates a LOCAL self-issued receipt signed by your own key, not an
entry in an independent transparency ledger; pass --service for that.
That’s the product. Everything below is optional depth.
With OpenAI
from openai import OpenAI
from epi_recorder import record, wrap_openai
client = wrap_openai(OpenAI()) # needs OPENAI_API_KEY
with record("agent.epi", goal="Answer a user question"):
client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hello"}],
)
python agent.py
epi verify agent.epi
epi view agent.epi
API keys in prompts/headers are redacted automatically before they land in the file.
What a .epi file is
Every .epi uses the Envelope v2 container format — a polyglot HTML+ZIP
binary that opens natively in any browser and can be extracted programmatically.
demo.epi
├── manifest.json # Ed25519 signature + SHA-256 file hashes
├── steps.jsonl # Timeline (hash-linked steps)
├── environment.json # Runtime snapshot (sensitive env redacted)
├── analysis.json # Fault / policy analysis (when generated at seal)
├── viewer.html # Offline forensic UI
└── VERIFY.txt # Plain-text auditor instructions
| Guarantee | How |
|---|---|
| Integrity | SHA-256 over every sealed member |
| Authenticity | Ed25519 signature on the manifest |
| Chain | Each step’s prev_hash links the timeline |
| Privacy | Default secret redaction (API keys, tokens, PII) |
Samples: docs/assets/SAMPLES.md · try docs/assets/readme-demo.epi.
Integrations
| Stack | How |
|---|---|
| OpenAI | wrap_openai(OpenAI()) |
| Anthropic | wrap_anthropic(Anthropic()) |
| LangChain | EpiCallbackHandler(session) inside with record(...) as session |
| LiteLLM | EPICallback |
| pytest | pytest --epi |
| Microsoft AGT | epi import agt <file> |
| TRACE Trust Record | epi export trace <file.epi> — log-import Level 0; see caveats below |
# LangChain (canonical adapter)
from langchain_openai import ChatOpenAI
from epi_recorder import record
from epi_recorder.adapters.langchain import EpiCallbackHandler
with record("run.epi") as session:
handler = EpiCallbackHandler(session)
llm = ChatOpenAI(model="gpt-4o-mini", callbacks=[handler])
llm.invoke("…")
# pytest — attach evidence to failing tests
pytest --epi
pytest --epi writes one .epi per failing test to ./test-evidence/
(--epi-dir DIR to change it, --epi-on-pass to keep passing tests too).
Files are named after the test (test_x.py_test_bad.epi); unlike record(),
a re-run replaces the file for the same test in place.
TRACE export (honest claims)
epi export trace run.epi --out run.trace.json --transcript-uri https://host/run.epi
Signing key order: --key → private key that sealed the .epi → TRACE_PRIVATE_KEY_PEM → ephemeral (warning). allow_embedded_key=True when verifying only proves internal consistency of the record against its own cnf.jwk. It does not prove the record came from a trusted issuer. What we claim: the JSON is self-consistent and bound to the key that signed it (the sealing key when that key is available).
policy.bundle_hash in TRACE is specified as a Cedar policy hash. We hash policy.json (the sealed EPI policy document), not a Cedar bundle. policy.enforcement_mode is "declared" — we did not run a Cedar engine. appraisal.status is "none".
More: docs/FRAMEWORK-INTEGRATIONS-5-MINUTES.md
CLI
| Command | Purpose |
|---|---|
epi demo |
Guided demo: record → seal → verify |
epi verify <file.epi> |
Offline integrity + signature check; customers and auditors run epi verify FILE --policy strict --witness-log received.jsonl (rely on the exit code only with --policy strict; WARN exits 0) |
epi-register <file.epi> |
Alias of epi scitt register: embed a SCITT receipt (local self-issued unless --service <url>) |
epi view <file.epi> |
Open offline viewer (screenshot above) |
epi analyze <file.epi> |
Fault / policy summary from sealed analysis |
epi policy init |
Create epi_policy.json rulebook |
epi run [script.py] |
Record a Python workflow that already emits EPI steps (prompts for a script if omitted) |
epi keys generate / list / trust / revoke / unrevoke |
Signing keys and pinned (trusted) keys; generate and list print fingerprints |
epi enterprise setup / pack |
Org kit + auditor pack |
epi scitt register <file.epi> |
SCITT receipt; --service <url> for an independent transparency service |
epi import agt <path> |
Import Microsoft AGT evidence |
epi export trace <file.epi> |
TRACE v0.2 log-import record (self-consistency, not issuer attestation) |
Policy + fault analyzer guide: docs/POLICY.md
Security defaults
- Redaction is on (
redact=True). Keys/tokens/PII become placeholders. - Prefer not using
redact=Falsein production (it warns). - Verification is local — no network required for integrity/signature.
- First-run identity WARN / LOCAL is expected until you trust a key.
- Seal ≠ identity ≠ policy. Verify proves the file; analyze grades the run against rules/heuristics.
Docs & pilot
| Topic | Link |
|---|---|
| Docs map | docs/README.md |
| How it works | ARCHITECTURE.md |
| Archive (historical guides, may be out of date) | Pilot pack · Enterprise in 15 minutes · Enterprise capability · Policy + fault analyzer |
| Known limitations | docs/KNOWN_LIMITATIONS.md |
| CLI deep dive | docs/CLI.md |
| Auditors guide | docs/AUDITORS-GUIDE.md |
Standards & compliance
EPI produces evidence files that help with audit trails. It is not a compliance guarantee and does not provide legal advice. Whether evidence satisfies a specific regulatory threshold is for the auditor or notified body to determine.
| Topic | Docs |
|---|---|
| EU AI Act Annex IV | docs/ANNEX-IV.md |
| AIUC-1 domains | docs/standards/aiuc-1-evidence.md |
| SCITT | docs/standards/scitt-predicate.md |
epi verify agent.epi --aiuc1 # evidence grouped by AIUC-1 domain (not a certification)
Troubleshooting
| Symptom | Fix |
|---|---|
epi: command not found |
Same venv as pip install, or python -m epi_cli |
| First verify WARN / LOCAL identity | Normal if seal OK — the receiver pins the sender's key from the sender (epi keys trust <sender-key-file> --name <name> or --from-did <did>), never from the file being checked |
Integrity: FAILED |
File changed after seal — re-record |
epi analyze says heuristic only |
Add epi_policy.json via epi policy init, re-run from that folder |
| Share / portal fails | Hosted needs backend; local record/verify never depends on it |
Trust-model note: Integrity checks whether the sealed record was altered since sealing — not that every real-world action was captured.
Project layout (contributors)
Core (actively developed, covered by the release gate):
| Path | Role |
|---|---|
epi_recorder/ |
Python SDK (record, wrappers) |
epi_core/ |
Container, crypto, redaction, verify, fault analyzer |
epi_cli/ |
epi command |
epi_gateway/ |
Self-hosted capture/review runtime (epi gateway serve) |
web_viewer/ |
Canonical browser UI (source for epi view and the embedded viewer) |
epi_viewer_static/ |
Browser crypto baked into every sealed file at pack time |
pytest_epi/ |
Pytest plugin |
website/ |
Public site source of truth (epilabs.org) |
scripts/ |
Build, sync, release and verification helper scripts |
docs/ |
Start at docs/README.md |
tests/ |
~190 test files, including the test_core_loop_golden.py regression path |
Experimental (frozen — works as documented, bug fixes allowed, no new features until a paying user asks; not on the critical path):
| Path | Note |
|---|---|
epi_mcp/ |
ChatGPT/Claude connector |
verify_portal/ |
Hosted portal, auth, Paddle billing, tier gating |
epi_recorder/integrations/trace_exporter.py |
TRACE export |
epi_recorder/integrations/agt* |
Microsoft AGT import/export |
epi_guardrails/ |
Guardrails |
website-v2/ |
Sandbox redesign (not deployed) |
| remote SCITT service | Local SCITT receipt is core; the hosted service is not |
Website edits: only under website/ (the source of truth). npm run build copies it to site/ for Cloudflare Pages; python scripts/sync_website.py mirrors it to verify_portal/static/, epi-official/ and site/. Details: ARCHITECTURE.md, docs/archive/SITE.md.
License
MIT — see LICENSE.
Site: epilabs.org · Issues: GitHub Issues
Metadata
Release files for epi-recorder 4.6.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| epi_recorder-4.6.1.tar.gz | 1.2 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| epi_recorder-4.6.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 2.1 MB
Release files / epi_recorder-4.6.1.tar.gz
| Download URL | epi_recorder-4.6.1.tar.gz |
|---|---|
| Size | 1.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
64a9ea33862f0ff7f8946d82d240b0528e7c3ef9b09560a611bcb0c21222a678
|
|
BLAKE2b-256 checksum How to use checksums |
f708399c3c9bc20f9d42e3e0fdfda38c1a7f353b24f29fa7852c5bc943e3de5c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / epi_recorder-4.6.1-py3-none-any.whl
| Download URL | epi_recorder-4.6.1-py3-none-any.whl |
|---|---|
| Size | 844.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
28c68230aadc11d4daf42163401d88bf6bee86b056f54b986cad7cd1561e5c03
|
|
BLAKE2b-256 checksum How to use checksums |
32bb2d46d296c517b2d00db647d2404c8ffa027ddf69b5736660f27efadab441
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|