Skip to main content

Python SDK for the Daimon protocol — sovereign personal AI

Project description

daimon — Python SDK

Thin Python client over the Daimon daemon's Unix-socket JSON-RPC surface (SPEC §6.1). Mirrors the Go cmd/daimon CLI's wire-level behaviour: one connection per RPC, no pipelining, JSON-RPC 2.0.

Status: v0.1.0 GA on latest (identity / memory / provider / activity verbs). v0.2.0-dev.1 pre-release on the --pre channel adds wallet + x402 payment verbs.

Install

Default install — v0.1.0 GA:

pip install daimon-protocol

Pre-release install — v0.2.0-dev.1 with wallet + x402 payments:

pip install --pre daimon-protocol

From a checkout of the Daimon repo:

pip install -e sdk/python

The PyPI distribution name is daimon-protocol because the unqualified daimon name on PyPI is held by an unrelated dormant package, and daimon-sdk / daimon-client belong to other active projects. The import name remains daimonfrom daimon import Client works regardless of how you installed it.

Use

The SDK assumes a running daimon daemon on the local machine, reachable via the same socket path the Go CLI uses ($DAIMON_HOME/daimon.sock, with the same long-path fallback rules). Start the daemon first:

daimon unlock

Then:

from daimon import Client

client = Client()                                  # resolves $DAIMON_HOME
print(client.identity.get())                       # {"did": "did:key:..."}

# memory.Kind.Valid() accepts: "fact", "preference", "task", "observation"
mid = client.memory.write(kind="fact", content="the sky is blue")
print(mid)                                         # {"id": "01K..."}

mem = client.memory.read(mid["id"])
hits = client.memory.search("sky")                 # [{...mem, "score": 0.7}, ...]
all_mems = client.memory.list()                    # search with empty query

# provider verbs
providers = client.provider.list()
env = client.provider.invoke(
    provider="ollama",
    model="llama3.2:latest",
    messages=[{"role": "user", "content": "hi"}],
)
print(env["response"]["content"])

# streaming — yields delta strings; final envelope on .final
stream = client.provider.stream(
    provider="ollama",
    model="llama3.2:latest",
    messages=[{"role": "user", "content": "count to 3"}],
)
for delta in stream:
    print(delta, end="", flush=True)
print()
print("usage:", stream.final["response"]["usage"])

# activity verbs (audit trail)
client.activity.append(kind="custom.event", payload={"n": 1})
entries = client.activity.query(limit=20)
result = client.activity.verify()                  # {"verified": N, "ok": True}

Wallet + payments (v0.2 pre-release)

Available in 0.2.0.dev1 (pip install --pre daimon-protocol). The wallet keystore is auto-created by the daemon on first daimon unlock — the 24-word BIP-39 mnemonic is surfaced exactly once in that unlock's RPC response. Wallets are MetaMask-compatible: importing the mnemonic into MetaMask reproduces the same address the daimon derived.

# Derive a fresh EVM wallet for Base mainnet
w = client.wallet.create(chain="evm:base")
print(w["address"])  # 0x... (EIP-55 checksummed)

# List wallets in the keystore
for w in client.wallet.list():
    print(f"{w['chain']:20s} {w['address']}")

# Quick lookup by chain
addr = client.wallet.address(chain="evm:base")

# Re-display the BIP-39 mnemonic, password-gated. Returns the
# 24-word list. Useful for verifying the backup was written down
# correctly, or exporting the seed to MetaMask / Phantom / Rabby.
# Wrong password raises RPCError with code -32008 (CodeWrongPassword),
# distinct from -32001 (CodeIdentityLocked) — the daemon IS unlocked,
# the password check is a separate attestation.
words = client.wallet.show_mnemonic(password="my-unlock-password")
assert len(words) == 24

# Pay an x402-protected URL end-to-end. The daimon parses the
# 402's PAYMENT-REQUIRED header, signs EIP-3009 transferWithAuthorization
# with the matching wallet, and retries with PAYMENT-SIGNATURE.
# ceiling_smallest_unit caps the payment in USDC smallest-unit (6 dec):
# 100000 == $0.10.
resp = client.payment.pay(
    url="https://protected.example.com/api/data",
    method="POST",
    body=b'{"prompt": "hi"}',
    ceiling_smallest_unit=100_000,
)
print(resp["status_code"], resp["body"])
if resp["payment_response"]:
    pr = resp["payment_response"]
    print(f"settled: tx={pr['transaction']} payer={pr['payer']}")

The audit log gains wallet.created, payment.signed, and payment.settled rows automatically — every wallet generation and every payment chains into the same Ed25519-signed log that carries the v0.1 memory and provider rows. Walk the whole chain with client.activity.verify().

Typed RPC error codes for the wallet + payment surface propagate via RPCError.code:

  • -32006 — payment exceeds local ceiling. The daimon refused to sign; no on-the-wire signature was emitted.
  • -32007 — no wallet in the keystore matches the resource's PaymentRequirements (chain not in registry, or wallet for that chain not yet created).
  • -32008show_mnemonic was called with the wrong password. Distinct from -32001 (CodeIdentityLocked) so callers can branch on it without their "daemon is locked, run daimon unlock" rewrite kicking in when really the user just mistyped the password.

See examples/x402-smoke for an end-to-end runnable example against a local mock x402 server.

Recovering a daimon from an existing seed

If you have a 24- (or 12-) word BIP-39 phrase already — a backup you wrote down, or a seed you'd like to import from MetaMask / Phantom / Rabby — the CLI daimon wallet recover writes a fresh wallet keystore from that phrase. It's an offline-only operation, so there's no SDK wrapper: the daimon daemon must be stopped (or have never run against this $DAIMON_HOME) when you run it, because a live seed swap on a running daemon would orphan every wallet derived from the previous seed.

daimon wallet recover
# Recovery phrase: (hidden input, paste your 12 or 24 words)
# Choose a password: (must match your daimon unlock password)
# Confirm password:
# Wallet keystore written.
# Next: `daimon unlock` to bring up the daemon against this seed.

After recover, the daemon's next unlock loads the imported keystore instead of generating a fresh mnemonic, and every wallet you create with client.wallet.create(...) derives from the imported seed. The canonical abandon ... about 12-word vector produces 0x9858EfFD232B4033E47d90003D41EC34EcaEda94 at m/44'/60'/0'/0/0 — the same address every BIP-39 derivation tool produces for that seed, so cross-wallet recovery is trivially verifiable.

Errors

from daimon import DaemonNotRunning, DaemonLocked, RPCError

try:
    client.memory.write(kind="fact", content="x")
except DaemonNotRunning:
    # daimon binary isn't serving on this $DAIMON_HOME
    ...
except DaemonLocked:
    # daemon is running but `daimon unlock` hasn't been called
    ...
except RPCError as e:
    # any other JSON-RPC error from the daemon
    print(e.code, e.message, e.data)

Development

cd sdk/python
pip install -e .[dev]
pytest

The test suite uses a stub Unix-socket daemon (no real keys, no real storage) plus optional smoke tests against a live daimon when one is running.

See also

  • TypeScript SDK — sister SDK, same wire shape, async-iterator streaming surface.
  • examples/streaming — cross-language streaming reference: both SDKs round-trip token deltas through one daemon, audit chain verified three ways.
  • CHANGELOG.md — release notes per version.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

daimon_protocol-0.2.0.dev1.tar.gz (32.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

daimon_protocol-0.2.0.dev1-py3-none-any.whl (23.9 kB view details)

Uploaded Python 3

File details

Details for the file daimon_protocol-0.2.0.dev1.tar.gz.

File metadata

  • Download URL: daimon_protocol-0.2.0.dev1.tar.gz
  • Upload date:
  • Size: 32.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for daimon_protocol-0.2.0.dev1.tar.gz
Algorithm Hash digest
SHA256 5ece9de14d1f3b25d321facbc5d37937af77c0c53d8e56611c4580de81d5d8e5
MD5 65c6dd47cb89844025c3941257f23d60
BLAKE2b-256 3efef80767e831dddb813ed17260332473ea7cdfd27fda5b4faae1a8035248fc

See more details on using hashes here.

File details

Details for the file daimon_protocol-0.2.0.dev1-py3-none-any.whl.

File metadata

File hashes

Hashes for daimon_protocol-0.2.0.dev1-py3-none-any.whl
Algorithm Hash digest
SHA256 018564e919e2d7e78135aeb723c58f21ccc9c870ebae1efcf1a9e5a2fe12a251
MD5 2226cb2b305e12e92d00a23238c1b4b9
BLAKE2b-256 b34230dd52ebadaa7b2fa939d2cb9e8c8c5cb179092d709a0a00265bff705e57

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page