EMILIA Protocol guard for LangChain tools: a named human approves the exact irreversible action (Face ID / passkey) before it executes — fail-closed, with offline-verifiable Trust Receipts.
Project description
langchain-emilia
Before your LangChain agent does anything irreversible, a named human approves that exact action on their own device. Face ID / Touch ID / passkey. Fail-closed. Every approval mints a Trust Receipt that verifies offline — years later, with no account and no EMILIA server.
pip install langchain-emilia
export EP_API_KEY=ep_live_... EP_ORG_ID=your-org
from langchain_emilia import EmiliaGuard, guard_tools
guard = EmiliaGuard() # enforce mode
tools = guard_tools([transfer_funds, send_email, calculator], guard)
# hand `tools` to your agent exactly as before — nothing else changes
When the agent calls transfer_funds(amount=82000, beneficiary="Northwind"):
- Gate — the call is held pre-execution; EP policy returns
allow,require_signoff, ordeny. - Signoff — on
require_signoff, a named human approves on their own device. The approval is cryptographically bound to the exact action parameters — change one digit and it is invalid. - Receipt — execution releases only after approval; the signed, Merkle-anchored receipt is permanent, offline-verifiable evidence.
Denials and pending holds are returned to the model as the tool's output
("EMILIA — BLOCKED … transfer_funds was NOT executed."), so the agent loop
explains itself instead of crashing. The tool body never runs unless the
gate allows it.
Why this is an executor-side precondition, not an "approval tool"
Approval tools the model calls have three failure modes: the model forgets to
call them, approves action A then executes action B, or barrels past an error.
langchain-emilia instead wraps the tool itself: the action digest is computed
from the actual arguments at execution time and the gate runs before the
tool body, unconditionally. The model-facing schema is unchanged; only the
executor gains the gate.
Zero-setup dry run (observe mode)
No account, no network, nothing blocked — see what enforcement would cover:
guard = EmiliaGuard(mode="observe")
tools = guard_tools(my_tools, guard)
# ... run your agent, then:
for r in guard.records:
print(r["tool"], r["digest"][:16], r["note"])
Configuration
| Option | Default | Meaning |
|---|---|---|
mode |
"enforce" |
"observe" = log-only local dry run, keyless |
match |
money/external-action regex | Callable[[str], bool] — which tool names are gated |
action_types |
auto | map tool name → EP action_type (see ACTION_TYPES) |
wait_for_approval |
True |
block (≤ timeout) while the human approves; False = surface the signoff URL immediately |
return_errors |
True |
denials become tool output for the model; False = raise EmiliaDenied / EmiliaApprovalPending |
on_event |
None |
callback for observed/allowed/denied/pending/unreachable events (SIEM hook) |
EmiliaGateClient(api_key, org_id, base_url, signoff_timeout_s=280, poll_interval_s=3)
reads EP_API_KEY / EP_ORG_ID / EP_BASE_URL from the environment by default.
Fail-closed semantics
| Situation | What happens |
|---|---|
| Policy denies | Tool not executed; model told why |
| Human rejects on device | Tool not executed; receipt records the rejection |
| Signoff window times out | Tool not executed; signoff URL surfaced for retry |
| EMILIA unreachable / network error | Tool not executed — never fail open |
Tool name doesn't match match |
Runs ungated (scope your match deliberately) |
Verify the evidence
Every allowed action carries a receipt_id. Anyone can verify it with zero
trust in us or in you:
- In a browser (nothing uploads): https://www.emiliaprotocol.ai/verify
- Offline CLI:
npx @emilia-protocol/verify receipt.json - Spec: draft-schrock-ep-authorization-receipts (IETF)
Try the human side yourself (no signup): https://www.emiliaprotocol.ai/try
Development
cd integrations/langchain-emilia
python3 -m venv .venv && .venv/bin/pip install -e '.[dev]'
.venv/bin/pytest -q
Apache-2.0. The digest layer is pinned byte-for-byte to the JS verifier by
cross-language vectors in tests/test_digest.py.
Building with LangChain.js instead? The JS sibling is
@emilia-protocol/langchain — a thin gate Proxy
for .invoke()-style tools on npm.
Project details
Release history Release notifications | RSS feed
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 langchain_emilia-0.1.0.tar.gz.
File metadata
- Download URL: langchain_emilia-0.1.0.tar.gz
- Upload date:
- Size: 12.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a03547e1b8d3abddaf0a8655222ed93d556d8150f4fe0effcfbaa08524955aea
|
|
| MD5 |
0fdbe11acde24a5ad2d3730209118ffb
|
|
| BLAKE2b-256 |
678075372aad5126b0821e3c8125813463e9339e2c2220fbfd036e59943099ce
|
Provenance
The following attestation bundles were made for langchain_emilia-0.1.0.tar.gz:
Publisher:
publish-langchain-python.yml on emiliaprotocol/emilia-protocol
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_emilia-0.1.0.tar.gz -
Subject digest:
a03547e1b8d3abddaf0a8655222ed93d556d8150f4fe0effcfbaa08524955aea - Sigstore transparency entry: 1794817609
- Sigstore integration time:
-
Permalink:
emiliaprotocol/emilia-protocol@81def2e4947d45e0652e0cf64067cc83feb5c3d8 -
Branch / Tag:
refs/tags/langchain-emilia-v0.1.0 - Owner: https://github.com/emiliaprotocol
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-langchain-python.yml@81def2e4947d45e0652e0cf64067cc83feb5c3d8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file langchain_emilia-0.1.0-py3-none-any.whl.
File metadata
- Download URL: langchain_emilia-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.4 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 |
e6c87f1ced0ae51f70eac4cf148d7a5a5bc4e47e15d33652b0ae7d4d7218d124
|
|
| MD5 |
2b35b108068e7dfe8366ca8d1f3d3328
|
|
| BLAKE2b-256 |
0499b67b73b9a37c744d5a2116234be80a28123154e3482269ddb21e912bcc92
|
Provenance
The following attestation bundles were made for langchain_emilia-0.1.0-py3-none-any.whl:
Publisher:
publish-langchain-python.yml on emiliaprotocol/emilia-protocol
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_emilia-0.1.0-py3-none-any.whl -
Subject digest:
e6c87f1ced0ae51f70eac4cf148d7a5a5bc4e47e15d33652b0ae7d4d7218d124 - Sigstore transparency entry: 1794817768
- Sigstore integration time:
-
Permalink:
emiliaprotocol/emilia-protocol@81def2e4947d45e0652e0cf64067cc83feb5c3d8 -
Branch / Tag:
refs/tags/langchain-emilia-v0.1.0 - Owner: https://github.com/emiliaprotocol
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-langchain-python.yml@81def2e4947d45e0652e0cf64067cc83feb5c3d8 -
Trigger Event:
push
-
Statement type: