ithuriel
ITHURIEL/1 is a vendor-neutral protocol and Python reference implementation for authenticated attention signals between agents, services, automations, and local runtimes.
The governing invariant is:
A ithuriel may reference authority, but may never constitute authority.
A ITHURIEL packet contains no executable task body. It identifies a canonical object and the SHA-256 digest of the exact bytes the sender observed. The recipient authenticates the sender, checks routing/time/replay policy, independently resolves the canonical object, verifies the digest, and surfaces an attention event only. Execution authority remains local.
Version 0.6.1 is the current experimental PyPI release. 0.6.0, 0.5.41, and 0.5.4 remain listed. Canonical git is Google Drive (gdrive::ithuriel), not GitHub. ITHURIEL remains a reference implementation, not a production runtime.
Install
From PyPI:
python -m pip install ithuriel
python -m pip install 'ithuriel[learn]' # optional sidecar; no model client
# or: pipx install ithuriel
From this source tree:
python -m pip install .
The project is MIT licensed.
The source distribution also contains an independent Go verifier and a
POSIX local cross-language conformance check (scripts/release-check-local.sh).
The Python implementation is not the only implementation of the signing vector.
Architecture
ITHURIEL separates five things that agent products often blur together:
identity trust -> who signed this?
attention transport -> how did the packet arrive?
canonical byte proof -> is this the exact referenced state?
attention -> should the local runtime look now?
execution authority -> may anything actually happen?
Only the first four are relevant to receiving a ithuriel, and ITHURIEL still does not grant execution authority.
Minimal Python example
import json
from pathlib import Path
from ithuriel import (
MappingTrustStore,
VerificationPolicy,
create_signal,
generate_keypair,
load_private,
local_resolvers,
verify_signal,
)
work = Path("work-order.txt")
work.write_text("canonical work order\n")
private_path, public_path = generate_keypair(Path("sender"))
resolvers = local_resolvers(allow_file=True)
envelope = create_signal(
sender="agent:sender@example.org",
recipient="agent:receiver@example.net",
kind="handoff",
object_id="wo:184",
canonical_uri=work.resolve().as_uri(),
private_key=load_private(private_path),
resolvers=resolvers,
)
trust = MappingTrustStore({
"agent:sender@example.org": json.loads(public_path.read_text())
})
result = verify_signal(
envelope,
trust=trust,
policy=VerificationPolicy(
expected_recipient="agent:receiver@example.net",
allowed_schemes=frozenset({"file"}),
),
resolvers=resolvers,
)
assert result.accepted
assert result.authority == "not-granted-by-ithuriel"
# ITHURIEL never grants execution. Any later action is a separate
# recipient-local decision over result.snapshot.content.
Do not reopen result.canonical_uri later and assume it is still the same object version. Downstream action should consume result.snapshot.content or re-resolve and re-hash immediately before acting.
Optional local learning (ithuriel.learn)
Learning is a host-local sidecar, not part of verification. import ithuriel does not load it. verify_signal / receive_signal / HTTP accept never import it. There is no default LLM.
After a durable receipt and ack, a host may extract scoped observations into a separate SQLite file:
from ithuriel.learn import KnowledgeStore, decode_utf8_extractor
knowledge = KnowledgeStore(Path("learn.sqlite3"), create=True)
observed, = knowledge.observe(
received, # ReceiveResult from receive_signal
project_id="proj-alpha",
extractor=decode_utf8_extractor, # or a host callback
extractor_version="fixture-v1",
acceptance_store=store,
)
current = knowledge.retrieve_current(project_id="proj-alpha", recipient=envelope.recipient)
Observations are assertions (source X asserts Y), always unreviewed, and never write trust, enrollment, tickets, or limiter state. Promotion to shared knowledge raises learn-promotion-forbidden.
First-contact trust enrollment
The receiver must never trust a public key supplied by the ithuriel packet itself. 0.5.0 added a vendor-neutral enrollment ceremony:
receiver creates one-time invite for exact sender ID
-> shares invite through authenticated/OOB channel
sender signs claim with proposed Ed25519 key
-> receiver checks invite token + proof of possession
receiver atomically installs sender->key trust binding
Create an invite:
ithuriel enroll-invite \
--store trust.sqlite3 \
--recipient agent:receiver@example.net \
--sender agent:sender@example.org \
--ttl 900 \
--out invite.json
Sender creates a claim:
ithuriel enroll-request \
--invite invite.json \
--private-key sender.private.pem \
--out claim.json
Receiver accepts it:
ithuriel enroll-accept claim.json --store trust.sqlite3 --recipient agent:receiver@example.net
trust.sqlite3 can then be used directly by ithuriel serve. See ENROLLMENT_PROFILE.md.
Create and verify a ithuriel
Generate keys:
ithuriel keygen --out sender
Create a ithuriel from a local file:
ithuriel create \
--sender agent:sender@example.org \
--recipient agent:receiver@example.net \
--kind handoff \
--object-id wo:184 \
--canonical "file://$(pwd)/work-order.txt" \
--private-key sender.private.pem \
--allow-file \
--out ithuriel.json
Fully verify and materialize the exact verified bytes:
ithuriel verify ithuriel.json \
--trust trust.sqlite3 \
--recipient agent:receiver@example.net \
--file-root "$(pwd)" \
--snapshot-out verified-work-order.bin
Signature-only inspection is intentionally a different operation and never returns acceptance:
ithuriel inspect ithuriel.json \
--trust trust.sqlite3 \
--recipient agent:receiver@example.net
Direct HTTP receiver
The reference direct receiver is deny-by-default for canonical resolvers. File receivers require explicit roots and HTTP receivers require explicit host allowlists.
ithuriel serve \
--host 127.0.0.1 \
--port 8788 \
--trust trust.sqlite3 \
--inbox ./inbox \
--recipient agent:receiver@example.net \
--file-root "$(pwd)"
Accepted envelopes and exact canonical snapshots are stored transactionally in inbox/.accepted.sqlite3. Verification is not durable receipt, and durable receipt is not a consumer execution claim.
Discovery is GET /.well-known/ithuriel; direct delivery is POST /.well-known/ithuriel/v1 with Content-Type: application/ithuriel+json.
Offline relay and anti-spam
The optional relay profile is closed by default. A sender cannot enqueue merely because it knows a recipient address; it needs a recipient-issued delivery ticket.
Issue a bounded ticket:
ithuriel relay-ticket-create \
--store relay.sqlite3 \
--recipient agent:receiver@example.net \
--sender agent:sender@example.org \
--uses 20 \
--ttl 604800 \
--out sender-ticket.json
A relay mailbox configuration can require tickets (the default):
{
"version": "ithuriel-relay-config/2",
"public_push_endpoint": "https://relay.example.net/.well-known/ithuriel/relay/v1/envelopes",
"mailboxes": {
"agent:receiver@example.net": {
"pull_token_sha256": "<sha256-of-recipient-pull-secret>",
"max_pending": 1000,
"require_ticket": true
}
}
}
Run the relay:
ithuriel relay-serve --config relay.json --store relay.sqlite3
Send with a ticket:
ithuriel relay-send ithuriel.json \
--endpoint https://relay.example.net/.well-known/ithuriel/relay/v1/envelopes \
--ticket-file sender-ticket.json
The relay never resolves canonical state and never grants authority.
Domain federation
For address-like recipients such as agent:alice@example.com, the optional federation profile maps the DNS domain to a relay:
GET https://example.com/.well-known/ithuriel-relay?recipient=...
The discovery document must advertise an HTTPS push endpoint and ticket_required:true. The sender can omit --endpoint from ithuriel relay-send and discover the relay from the recipient domain.
This creates an email-like routing model without requiring a central broker:
address -> recipient-owned DNS domain -> relay -> offline queue -> local verification
See RELAY_PROFILE.md.
Provider and framework neutrality
The core package has no dependency on a model vendor, agent framework, storage provider, MCP implementation, or cloud service.
Extension points:
- Python library:
ithuriel - CLI:
ithuriel - resolver plugins:
ithuriel.resolvers - transport plugins:
ithuriel.transports - MCP: thin adapter over the Python API
- Skills: teach an agent when to invoke ITHURIEL; never redefine trust semantics
- enrollment: optional first-contact profile
- relay/federation: optional ticket-gated store-and-forward profile
Installed plugins are never auto-loaded by accepting receiver defaults.
Security boundary
ITHURIEL distinguishes:
- Sender authentication — receiver-owned trust says which key belongs to the sender.
- Routing/time/replay — this packet is for this recipient and is fresh/idempotent.
- Canonical byte identity — these exact bytes match the sender-referenced digest.
- Relay delivery permission — a ticket may permit queue use; it is not sender identity or execution authority.
- Execution authority — not provided by ITHURIEL; recipient-local policy/grants decide this.
A valid digest does not prove the canonical content is approved or safe. A delivery ticket does not prove the packet signature is valid. An enrollment binding does not authorize actions. Those separations are deliberate.
Conformance and tests
The package includes:
- RFC 8785-compatible strings-only signing rules;
- a fixed Ed25519 conformance vector;
- exact snapshot persistence;
- duplicate-key JSON rejection;
- bounded receiver/relay surfaces;
- enrollment, replay, relay, resolver, transport, and HTTP tests.
Run:
python -m pytest -q
Documents
PROTOCOL.md— ITHURIEL/1 wire and receive semanticsENROLLMENT_PROFILE.md— first-contact trust bootstrapRELAY_PROFILE.md— ticket-gated store-and-forward federationSECURITY.md— security boundary and deployment guidanceTHREAT_MODEL.md— adversaries and non-claimsPLUGIN_API.md— resolver/transport extension pointsdocs/adr/— accepted decisions (0001–0007); 0006 is the 0.6 learning sidecar, 0007 is the 0.6.1 provenance/quota/isolation correctionsMCP_TOOL_SURFACE.md— thin MCP adapter contractCONFORMANCE.md— cross-language signing vectorNAMING.md— public naming/PyPI collision reviewPUBLISHING.md— publication checklist and current PyPI status
Status
ITHURIEL/1 remains an experimental protocol/reference implementation. 0.6.1 is the current PyPI release of this package; 0.6.0, 0.5.41, and 0.5.4 remain listed. Signed, digest-matching canonical content remains untrusted input; the HTTP commands remain reference servers for deployment behind hardened TLS termination.
Release files for ithuriel 0.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 | |
|---|---|---|---|
| ithuriel-0.6.1.tar.gz | 154.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ithuriel-0.6.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 244.9 kB
Release files / ithuriel-0.6.1.tar.gz
| Download URL | ithuriel-0.6.1.tar.gz |
|---|---|
| Size | 154.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4fac5f9a15f6fa8d61dd3410449aedafc3fdf074705f806fe9ff7a97979de815
|
|
BLAKE2b-256 checksum How to use checksums |
698a022724366501ac9ecce655e227cddf8f1c18c7fb39553ae5128d26ecce77
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|
Release files / ithuriel-0.6.1-py3-none-any.whl
| Download URL | ithuriel-0.6.1-py3-none-any.whl |
|---|---|
| Size | 90.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bacab24d08395e98dff8f7231b03de5ce47572a9c8bdaec7169b39ceec9efb64
|
|
BLAKE2b-256 checksum How to use checksums |
f53b5103768dd51148c2c05dce8f4982c930fdf56beff9a95adfb5bb0aec8c47
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.6
|