Metadata-Version: 2.4
Name: epi-recorder
Version: 4.6.1
Summary: Tamper-evident evidence infrastructure for AI agents. Capture, seal, verify, and audit AI decisions in a single portable file.
Author-email: Mohd Ibrahim Afridi <mohdibrahim@epilabs.org>
Maintainer-email: Mohd Ibrahim Afridi <mohdibrahim@epilabs.org>
Project-URL: Homepage, https://epilabs.org
Project-URL: Documentation, https://epilabs.org/docs
Project-URL: Repository, https://github.com/mohdibrahimaiml/epi-recorder
Project-URL: Issues, https://github.com/mohdibrahimaiml/epi-recorder/issues
Keywords: evidence,forensics,audit,compliance,cryptography,ai,llm,verification,artifact,debugging,bug-report,incident-response,pytest,ci,execution-trace,reproducibility,tamper-evident,aiuc-1,aiuc
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Legal Industry
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: System :: Logging
Classifier: Typing :: Typed
Classifier: Framework :: Pydantic
Classifier: Framework :: Pydantic :: 2
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3.0.0,>=2.0.0
Requires-Dist: cryptography<51,>=41.0.0
Requires-Dist: cbor2>=5.6.0
Requires-Dist: rfc8785>=0.1.4
Requires-Dist: asn1crypto<2,>=1.5
Requires-Dist: typer<0.28.0,>=0.16.0
Requires-Dist: click<9.0.0,>=8.1.0
Requires-Dist: rich<16,>=13.0.0
Requires-Dist: httpx>=0.27.0
Provides-Extra: ai
Requires-Dist: google-generativeai>=0.4.0; extra == "ai"
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == "openai"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.25.0; extra == "anthropic"
Provides-Extra: litellm
Requires-Dist: litellm>=1.0.0; extra == "litellm"
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2.0; extra == "langchain"
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2.0; extra == "langgraph"
Provides-Extra: opentelemetry
Requires-Dist: opentelemetry-api>=1.25.0; extra == "opentelemetry"
Requires-Dist: opentelemetry-sdk>=1.25.0; extra == "opentelemetry"
Provides-Extra: integrations
Requires-Dist: openai>=1.0.0; extra == "integrations"
Requires-Dist: anthropic>=0.25.0; extra == "integrations"
Requires-Dist: litellm>=1.0.0; extra == "integrations"
Requires-Dist: langchain-core>=0.2.0; extra == "integrations"
Requires-Dist: langgraph>=0.2.0; extra == "integrations"
Requires-Dist: opentelemetry-api>=1.25.0; extra == "integrations"
Requires-Dist: opentelemetry-sdk>=1.25.0; extra == "integrations"
Requires-Dist: wrapt>=1.16.0; extra == "integrations"
Provides-Extra: analytics
Requires-Dist: pandas>=1.5.0; extra == "analytics"
Requires-Dist: matplotlib>=3.5.0; extra == "analytics"
Provides-Extra: pdf
Requires-Dist: fpdf2>=2.8.0; extra == "pdf"
Provides-Extra: gateway
Requires-Dist: fastapi<1,>=0.111.0; extra == "gateway"
Requires-Dist: uvicorn<1,>=0.30.0; extra == "gateway"
Requires-Dist: python-multipart>=0.0.20; extra == "gateway"
Requires-Dist: boto3>=1.34.0; extra == "gateway"
Requires-Dist: httpx>=0.27.0; extra == "gateway"
Provides-Extra: mcp
Requires-Dist: mcp>=2.0; extra == "mcp"
Provides-Extra: all
Requires-Dist: google-generativeai>=0.4.0; extra == "all"
Requires-Dist: openai>=1.0.0; extra == "all"
Requires-Dist: anthropic>=0.25.0; extra == "all"
Requires-Dist: litellm>=1.0.0; extra == "all"
Requires-Dist: langchain-core>=0.2.0; extra == "all"
Requires-Dist: langgraph>=0.2.0; extra == "all"
Requires-Dist: opentelemetry-api>=1.25.0; extra == "all"
Requires-Dist: opentelemetry-sdk>=1.25.0; extra == "all"
Requires-Dist: pandas>=1.5.0; extra == "all"
Requires-Dist: matplotlib>=3.5.0; extra == "all"
Requires-Dist: fastapi<1,>=0.111.0; extra == "all"
Requires-Dist: uvicorn<1,>=0.30.0; extra == "all"
Requires-Dist: boto3>=1.34.0; extra == "all"
Requires-Dist: wrapt>=1.16.0; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
Requires-Dist: httpx>=0.27.0; extra == "dev"
Requires-Dist: black>=24.0.0; extra == "dev"
Requires-Dist: ruff>=0.3.0; extra == "dev"
Requires-Dist: pandas>=1.5.0; extra == "dev"
Requires-Dist: fastapi<1,>=0.111.0; extra == "dev"
Requires-Dist: uvicorn<1,>=0.30.0; extra == "dev"
Requires-Dist: python-multipart>=0.0.20; extra == "dev"
Requires-Dist: wrapt>=1.16.0; extra == "dev"
Requires-Dist: mcp>=2.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=8.0.0; extra == "test"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "test"
Requires-Dist: pytest-cov>=4.1.0; extra == "test"
Requires-Dist: pytest-timeout>=2.3.1; extra == "test"
Requires-Dist: pytest-playwright>=0.5.0; extra == "test"
Requires-Dist: playwright>=1.45.0; extra == "test"
Requires-Dist: httpx>=0.27.0; extra == "test"
Requires-Dist: fastapi<1,>=0.111.0; extra == "test"
Requires-Dist: uvicorn<1,>=0.30.0; extra == "test"
Requires-Dist: python-multipart>=0.0.20; extra == "test"
Requires-Dist: pandas>=1.5.0; extra == "test"
Requires-Dist: litellm>=1.0.0; extra == "test"
Requires-Dist: langchain-core>=0.2.0; extra == "test"
Requires-Dist: opentelemetry-api>=1.25.0; extra == "test"
Requires-Dist: opentelemetry-sdk>=1.25.0; extra == "test"
Requires-Dist: wrapt>=1.16.0; extra == "test"
Requires-Dist: anyio; extra == "test"
Requires-Dist: agentrust-trace>=0.9.0; extra == "test"
Requires-Dist: agentrust-trace-tests>=0.5.1; extra == "test"
Requires-Dist: mcp>=2.0; extra == "test"
Dynamic: license-file

<div align="center">
  <img src="https://raw.githubusercontent.com/mohdibrahimaiml/epi-recorder/main/docs/assets/logo.png" alt="EPI Logo" width="180"/>

# EPI (Evidence Packaged Infrastructure) — Evidence for AI agents

### Record. Seal. Verify offline. The answer is a **file**.

[![PyPI](https://img.shields.io/pypi/v/epi-recorder?color=blue&label=PyPI)](https://pypi.org/project/epi-recorder/)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://python.org)
[![Version v4.6.1](https://img.shields.io/badge/version-v4.6.1-purple)](https://github.com/mohdibrahimaiml/epi-recorder/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/LICENSE)
[![Release Gate](https://github.com/mohdibrahimaiml/epi-recorder/actions/workflows/release-gate.yml/badge.svg?branch=main)](https://github.com/mohdibrahimaiml/epi-recorder/actions/workflows/release-gate.yml)

```bash
pip install epi-recorder
epi demo --no-browser    # record → seal → verify (no API key)
```

[60-second path](#60-second-path) ·
[What a .epi is](#what-a-epi-file-is) ·
[CLI](#cli) ·
[Docs & pilot](#docs--pilot) ·
[Standards](#standards--compliance)

How it works: [ARCHITECTURE.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/ARCHITECTURE.md) — packages, integrity model, and where to read next.

</div>

---

> When someone asks what your agent did six months ago,  
> the answer should be a **`.epi` file** — 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`).

<div align="center">
  <p><strong>Open a sealed <code>.epi</code> offline</strong> — <code>epi view run.epi</code></p>
  <img
    src="https://raw.githubusercontent.com/mohdibrahimaiml/epi-recorder/main/docs/assets/epi-file-viewer-full.png"
    alt="EPI offline viewer showing a sealed .epi evidence file — timeline, integrity, and decision context"
    width="900"
  />
  <p><em>Forensic case view of a sealed run. Sample artifact: <a href="https://github.com/mohdibrahimaiml/epi-recorder/blob/main/docs/assets/readme-demo.epi"><code>docs/assets/readme-demo.epi</code></a></em></p>
</div>

---

## 60-second path

Works **without** any LLM API key:

```python
# 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")
```

```bash
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.

```text
$ 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, with
> `epi keys trust <sender-key-file> --name <name>` (the sender's `.pub`, `did.json` or saved
> `epi keys export` line — never the `.epi` you are checking) or
> `epi 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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/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

```python
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"}],
    )
```

```bash
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.

```text
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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/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 |

```python
# 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("…")
```

```bash
# 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)

```bash
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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/POLICY.md)

---

## Security defaults

- **Redaction is on** (`redact=True`). Keys/tokens/PII become placeholders.
- Prefer **not** using `redact=False` in 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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/README.md) |
| **How it works** | [ARCHITECTURE.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/ARCHITECTURE.md) |
| **Archive** (historical guides, may be out of date) | [Pilot pack](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/archive/PILOT.md) · [Enterprise in 15 minutes](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/archive/ENTERPRISE-15-MINUTES.md) · [Enterprise capability](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/archive/ENTERPRISE-CAPABILITY.md) · [Policy + fault analyzer](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/archive/POLICY-AND-FAULT-ANALYZER.md) |
| Known limitations | [docs/KNOWN_LIMITATIONS.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/KNOWN_LIMITATIONS.md) |
| CLI deep dive | [docs/CLI.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/CLI.md) |
| Auditors guide | [docs/AUDITORS-GUIDE.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/ANNEX-IV.md) |
| AIUC-1 domains | [docs/standards/aiuc-1-evidence.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/standards/aiuc-1-evidence.md) |
| SCITT | [docs/standards/scitt-predicate.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/standards/scitt-predicate.md) |

```bash
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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/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](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/ARCHITECTURE.md), [docs/archive/SITE.md](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/docs/archive/SITE.md).

---

## License

MIT — see [LICENSE](https://github.com/mohdibrahimaiml/epi-recorder/blob/v4.6.0/LICENSE).

**Site:** [epilabs.org](https://epilabs.org) · **Issues:** [GitHub Issues](https://github.com/mohdibrahimaiml/epi-recorder/issues)
