Skip to main content

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.5.4 is the current experimental PyPI release. 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
# 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 platform-neutral local cross-language conformance check. 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.

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:

  1. Sender authentication — receiver-owned trust says which key belongs to the sender.
  2. Routing/time/replay — this packet is for this recipient and is fresh/idempotent.
  3. Canonical byte identity — these exact bytes match the sender-referenced digest.
  4. Relay delivery permission — a ticket may permit queue use; it is not sender identity or execution authority.
  5. Execution authoritynot 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 semantics
  • ENROLLMENT_PROFILE.md — first-contact trust bootstrap
  • RELAY_PROFILE.md — ticket-gated store-and-forward federation
  • SECURITY.md — security boundary and deployment guidance
  • THREAT_MODEL.md — adversaries and non-claims
  • PLUGIN_API.md — resolver/transport extension points
  • MCP_TOOL_SURFACE.md — thin MCP adapter contract
  • CONFORMANCE.md — cross-language signing vector
  • NAMING.md — public naming/PyPI collision review
  • PUBLISHING.md — publication checklist and current PyPI status

Status

ITHURIEL/1 remains an experimental protocol/reference implementation. 0.5.4 is the current PyPI release of this package. 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.5.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ithuriel 0.5.4
File Size Uploaded
ithuriel-0.5.4.tar.gz 112.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ithuriel 0.5.4
File Interpreter ABI Platform
ithuriel-0.5.4-py3-none-any.whl Python 3 none any Details

Total release size: 181.8 kB

Release files / ithuriel-0.5.4.tar.gz

Download URL ithuriel-0.5.4.tar.gz
Size 112.8 kB
Tags Source
SHA-256 checksum
How to use checksums
79e0e95308d869ad1b1be829d3698d42277186fd5404cc2ac7d06eb9ae5a9bef
BLAKE2b-256 checksum
How to use checksums
066cd5b059d5f8364c0d72ff0b9d639a55d33a3a5523431c3434abac97f73015
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.5.4-py3-none-any.whl

Download URL ithuriel-0.5.4-py3-none-any.whl
Size 69.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3baa78a9c3f104709412bbc59d6155437787ddb126f6155c86e75fbf5e7011bd
BLAKE2b-256 checksum
How to use checksums
00c3e873f6301ca4f5cd6f9eb38d60cc0968d56c5a46e653a171793a6cfb68fd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.6

Release history Release notifications | RSS feed

0.6.1

2 release files

0.6.0

2 release files

This release

0.5.4 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page