ENYAL SDK
Your agent thinks locally. ENYAL proves permanently.
Installation
pip install enyal-sdk
Quick Start — Local Knowledge + Permanent Proof
from enyal_sdk import EnyalAgent
agent = EnyalAgent(api_key="eyl_your_key")
# Remember locally (free, private, instant)
agent.remember("Tesla has 100GWh battery capacity",
node_type="entity",
properties={"frontier": "energy", "capacity": "100GWh"})
agent.remember("SpaceX launches 90 rockets per year",
node_type="entity",
properties={"frontier": "space", "launches": 90})
# Recall from local memory
results = agent.recall("battery capacity")
# Check what you know
health = agent.health()
print(health)
# {"status": "healthy", "total_nodes": 2, "contradictions": 0}
# Get compact context for your AI prompt (120 tokens)
context = agent.compact()
print(context)
# E:Tesla|100GWh battery capacity|0c
# E:SpaceX|90 launches per year|0c
# Archive permanently (costs joules, encrypted, provable)
agent.archive(
chunk_type="decision_record",
chunk_key="my-agent:decision:invest-energy",
data={"decision": "Invest in energy infrastructure",
"confidence": 0.85,
"entities": ["Tesla"]}
)
# Pull remote knowledge locally
agent.sync_from_enyal()
# Close when done (releases the SQLite file)
agent.close()
Retry Safety
All state-changing API calls automatically retry on network errors, timeouts, 5xx, and 429 (rate limit) responses. Retries use exponential backoff with jitter and a deterministic idempotency key to prevent double-charges.
# Automatic retry with SDK-generated idempotency key (default)
agent.archive(chunk_type="decision_record",
chunk_key="my-agent:decision:001",
data={"decision": "Invest in space"})
# Explicit idempotency key (e.g., your own dedup token)
agent.archive(chunk_type="decision_record",
chunk_key="my-agent:decision:001",
data={"decision": "Invest in space"},
idempotency_key="my-dedup-key-12345678901234567890")
# Disable retry for a single call
agent.archive(..., retry=False)
Default retry policy: 3 retries, 0.5s initial delay, 2x backoff, 8s max delay, 0.1-0.3s jitter. On a 429 the server's Retry-After is honoured in full when it is 60 seconds or less; a longer wait is not slept through — the RuntimeError is raised with .status, .retry_after and .headers set so you decide.
Agent ids are yours alone. An agent_id another account has already used is refused (403), and web-console, web-app and share-extension are reserved for the account owner's own apps. Use an id of your own — not a copied example value. EnyalAgent uses your account's own JoulePAI wallet id when you pass none; the plain archive() function has no default — pass agent_id (your wallet id from get_account() works).
Retry-safe endpoints: archive, prove, disclose, client-side disclose, share-proof, timestamp, agreement/create, compliance/attest, message/send.
Not available via SDK: knowledge/synthesise requires session auth (web console only).
Two Layers
| Feature | Local (free) | ENYAL (paid) |
|---|---|---|
| Storage | SQLite on your machine | Permanent cryptographic ledger |
| Speed | Instant | ~200ms |
| Privacy | Never leaves your device | Encrypted, you hold the key |
| Proof | None | ZK proofs, BSV settlement |
| Cost | Free | Joules per operation |
| Persistence | Until you delete it | Permanent, immutable |
Installation
pip install enyal-sdk
If using natural language remember() with a local LLM:
- Install Ollama: https://ollama.com
- Pull a model:
ollama pull mistral-nemo - Or set
ENYAL_LOCAL_MODEL=your-model-name - Without a pulled model, extraction falls back to using the full text as the entity name (10s timeout)
Node.js
npm install @enyalai/sdk
const { EnyalAgent } = require('@enyalai/sdk');
const agent = new EnyalAgent('eyl_your_key');
// Local (free, private, instant, synchronous)
agent.remember('SpaceX', 'entity', '90 launches/yr', { launches: 90 });
const results = agent.recall('SpaceX');
// Natural language (async, optional Ollama)
await agent.rememberText('Tesla has 100GWh battery capacity');
// Compact context for AI prompt
const ctx = agent.compact();
// Permanent proof (costs joules, async)
await agent.archive('decision_record',
'my-agent:decision:001',
{ decision: 'Invest in space', confidence: 0.85 });
// Always close when done (releases SQLite file lock)
agent.close();
Encrypted backup and restore
Back up the local knowledge graph to ENYAL and restore it on any device of the same account. It is encrypted on your device before it is sent (AES-256-GCM; the key is derived with scrypt from your password and your ENYAL account id). ENYAL stores only ciphertext and cannot read it. The password is never sent. Keep it: without it the backup cannot be decrypted, by you or anyone.
agent.sync_to_enyal("a long passphrase") # → {"snapshot_id", "parts", "joule_cost", …}
# on a new device, same account (any API key of that account):
agent = EnyalAgent(api_key="eyl_your_key")
agent.restore_from_enyal("a long passphrase") # zero-knowledge accounts: add customer_share="<66 hex>"
- Cost: backup = 10 J per part (each part is one
state_snapshotarchive of at most 40 KB); restore = 100 J per part (one disclosure per part). Finding the backup is free. Most graphs fit in one part; the limit is 30 parts (about 900 KB after compression and encryption) — a larger graph is refused before anything is sent or charged. - Zero-knowledge accounts restore with your own customer share (from your recovery file); ENYAL cannot decrypt for you.
- Restore uses the newest complete backup (a backup interrupted part-way is skipped), checks it end to end before
touching anything, keeps your previous local graph as
<db>.pre-restore.<unix time>, then replaces it. - A wrong password costs one part, not the whole backup. Restore fetches part 1 alone first and checks the password on it (and, on a zero-knowledge account, your customer share); if it is wrong it stops there — 100 J — and changes nothing locally. (Backups made with 2.2.7 have no password check: for those a wrong password is found only after every part is paid for.)
- An interrupted backup is resumed, not paid for twice. If sync_to_enyal() stops part-way, the unfinished backup is kept on this device; run sync_to_enyal() again with the same password (and no changes to the graph) and the parts ENYAL already holds are not charged again — only the missing ones are. A changed graph or password starts a new backup (the result names the unfinished one).
- Your password is the only protection — choose a long, unique one. The key is salted with your ENYAL account id, which is not a secret (earlier versions salted with the API key; the account id is what lets a new device with a new key restore). So anyone who obtains a copy of the encrypted backup — including ENYAL, which can read its own layer on a recoverable account — can try passwords against it offline. What stands in the way is scrypt (N=2^15, r=8, p=1: every guess costs about 32 MB of memory and a fraction of a second) and the strength of your password. A random passphrase of 5 or more words, or 16+ random characters, used nowhere else, is out of reach; a short or reused one is not.
- Backups are archives, so they are permanent. A backup made with this SDK restores with @enyalai/sdk and greenlandai too.
- ENYAL allows 30 disclosed chunks per request and 50 per day per API key.
If api.enyal.ai is unreachable while enyal.ai loads, your address may have been firewalled by our intrusion detection. Email info@enyal.ai (mail is delivered off-host, so it reaches us even when the API does not) and we will review the block.
Limitations
- Local knowledge uses SQLite WAL mode. Do not place the database file on a network filesystem (NFS/SMB).
Client-Side Disclosure & Verification
Client-side disclosure, verification, and proof generation for ENYAL's intelligence archival system.
Three trust tiers — choose based on your security requirements:
| Tier | Trust in ENYAL | What runs where | Use case |
|---|---|---|---|
| Tier 1 | None | Everything client-side | Maximum security, auditor self-verification |
| Tier 2 | During proof generation | Proof on ENYAL's server | Third-party auditor proofs |
| Tier 3 | None | Self-hosted Rust binary | Zero-trust zero-knowledge proof generation |
Installation
JavaScript — npm install @enyalai/sdk (bundles @noble/curves +
@noble/post-quantum for the v6 post-quantum path). Web Crypto API
(browsers) or Node.js 18.7+.
Python — pip install enyal-sdk[pq] for the full client-side decrypt
path (zero-knowledge v6 archives are post-quantum hybrid encrypted; the
[pq] extra pulls kyber-py==1.2.0 + cryptography). Plain
pip install enyal-sdk + cryptography suffices for v5-classical-only
workflows.
Usage
Tier 1 — Full client-side (zero trust)
import { requestClientDisclosure, decryptCustodialShare, decryptDisclosedChunk, verifyShareCombination } from '@enyalai/sdk/enyal-client.js';
// 1. Request disclosure materials (no share sent to server)
const materials = await requestClientDisclosure(apiKey, 'https://api.enyal.ai', ['chunk-id'], 'audit');
// 2. Unwrap ENYAL's custodial share with YOUR customer share (wrap-v1:
// AES-256-GCM under HKDF(customer share) — no private key, no ECDH)
const custodialShare = await decryptCustodialShare(myCustomerShare, materials.custodial_share);
// 3. Decrypt the data — version-aware: v6 chunks (zero-knowledge accounts,
// post-quantum P-256 + ML-KEM-768 hybrid) and v5 classical both handled
const plaintext = await decryptDisclosedChunk(myCustomerShare, custodialShare, materials.chunks[0]);
// 4. Verify share combination locally (WASM, 78KB, zero server calls)
const verification = await verifyShareCombination(myCustomerShare, custodialShare, materials.poseidon_key_hash);
console.log(verification.valid); // true — verified locally, ENYAL never saw your share
from enyal_sdk import (request_client_disclosure, decrypt_custodial_share,
decrypt_disclosed_chunk, verify_share_combination)
# 1. Request disclosure materials
materials = request_client_disclosure(api_key, 'https://api.enyal.ai', ['chunk-id'], 'audit')
# 2. Unwrap the custodial share with YOUR customer share (wrap-v1:
# AES-256-GCM under HKDF(customer share) — no private key, no ECDH)
custodial_share = decrypt_custodial_share(my_customer_share, materials['custodial_share'])
# 3. Decrypt — version-aware (v6 post-quantum hybrid or v5 classical)
plaintext = decrypt_disclosed_chunk(my_customer_share, custodial_share, materials['chunks'][0])
# 4. Verify locally (requires shamir-circuit binary for content integrity hash)
result = verify_share_combination(my_customer_share, custodial_share, materials['poseidon_key_hash'],
binary_path='/path/to/shamir-circuit')
print(result['valid']) # True
Post-quantum (v6) decryption — the [pq] extra
Zero-knowledge accounts archive with v6 hybrid encryption (P-256 ECDH +
ML-KEM-768, FIPS 203). decrypt_disclosed_chunk dispatches on
encryption_metadata.version and needs an ML-KEM-768 backend for v6 chunks:
- liboqs (
liboqs-python+ the native liboqs library) is auto-preferred when importable — the production-grade C implementation, the same library the ENYAL server uses. - kyber-py 1.2.0 (pure Python, exact-pinned, installed by
[pq]) is the fallback. Honest caveat: its author flags it as educational and not constant-time. For decrypting YOUR OWN records on YOUR OWN device this is an acceptable trade-off, and both backends are verified byte-identical to the server's liboqs by the interop KAT (kat/interop_kat.py).
Tier 2 — Proof for auditors (share sent to server)
import { requestShareProof } from './enyal-client.js';
const proof = await requestShareProof(apiKey, 'https://api.enyal.ai', myShareHex);
// proof.verified === true
// proof.share_attestation.wiped === true
// proof.share_attestation.trust_level === "operational_transparency"
// Hand proof to auditor — they verify at enyal.ai/docs/verify
Tier 3 — Self-hosted (zero trust, full zero-knowledge proof)
# Clone and build
git clone https://github.com/GreenlandAI/shamir-circuit.git
cd shamir-circuit && cargo build --release
# Generate proof locally
echo '{"command":"verify_share_combination","share1_hex":"01...","share2_hex":"02...","expected_poseidon_hash":"abcd..."}' \
| ./target/release/shamir-circuit
# Your shares never leave your machine. Full zero-knowledge proof generated locally.
Test Vector
Use this to verify your GF(256) implementation produces identical results to ENYAL's.
Test key: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
Share 1: 010712cc64a718cdf5517aa4a7540d943eee4148c6a308d3147509e70e361e3672
Share 2: 020d2286c45a3a88e9b9ea4e41bf08224cf4b4a5ab622a8400c13cf8384b1e4d84
Share 3: 030b3249a4f8244214e19ae1eae60bb9620be7fe79d434400cad2f042a601e64d6
Expected: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
Combine any two shares → must reconstruct to 010203...1f20 (32 bytes).
JavaScript verification:
import { shamirCombine, hexToBytes, bytesToHex } from './enyal-client.js';
const share1 = hexToBytes('010712cc64a718cdf5517aa4a7540d943eee4148c6a308d3147509e70e361e3672');
const share2 = hexToBytes('020d2286c45a3a88e9b9ea4e41bf08224cf4b4a5ab622a8400c13cf8384b1e4d84');
const secret = shamirCombine(share1, share2);
console.log(bytesToHex(secret));
// Expected: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
Python verification:
from enyal_sdk import shamir_combine
share1 = bytes.fromhex('010712cc64a718cdf5517aa4a7540d943eee4148c6a308d3147509e70e361e3672')
share2 = bytes.fromhex('020d2286c45a3a88e9b9ea4e41bf08224cf4b4a5ab622a8400c13cf8384b1e4d84')
secret = shamir_combine(share1, share2)
print(secret.hex())
# Expected: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
Agent Messaging
Send encrypted messages between agents. Messages are archived as immutable chunks — the chain proves existence, the relay proves order. Send costs 10 joules; inbox/thread/read are free.
Messages are addressed by the recipient's JoulePAI wallet id. Agent names and other free-form ids are private labels of the account that holds them and cannot be addressed (409). Read your inbox with your own wallet id (get_account(api_key)["wallet_id"]; EnyalAgent.inbox() does this when you pass no id).
JavaScript:
import { sendMessage, getInbox, getThread, markRead } from './enyal-client.js';
// Send a trade offer
const msg = await sendMessage(apiKey, {
senderAgentId: '<your-wallet-id>',
threadId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', // use match_id for RAREEAI trades
recipientAgentId: '<recipient-wallet-id>', // the recipient's JoulePAI wallet id
messageType: 'offer',
payload: { resource: 'compute_4gpu', price: 500, duration_hours: 24 },
});
// msg.sequence_number === 1, msg.chunk_id archived on-chain
// Check inbox
const inbox = await getInbox(apiKey, { agentId: '<your-wallet-id>' });
// inbox.messages[0].delivered_at set automatically
// Check outbox (what did I send?)
const sent = await getInbox(apiKey, { agentId: '<your-wallet-id>', direction: 'outbox' });
// Full thread view (both sides)
const thread = await getThread(apiKey, { threadId: msg.thread_id });
// thread.messages ordered by sequence_number
// Mark as read
await markRead(apiKey, { messageIds: [inbox.messages[0].message_id] });
Python:
from enyal_sdk import send_message, get_inbox, get_thread, mark_read
# Send a trade offer
msg = send_message(api_key, '<your-wallet-id>',
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
'<recipient-wallet-id>', 'offer', # the recipient's JoulePAI wallet id
{'resource': 'compute_4gpu', 'price': 500, 'duration_hours': 24})
# Check inbox
inbox = get_inbox(api_key, '<your-wallet-id>')
# Check outbox
sent = get_inbox(api_key, '<your-wallet-id>', direction='outbox')
# Full thread view
thread = get_thread(api_key, msg['thread_id'])
# Mark as read
mark_read(api_key, [inbox['messages'][0]['message_id']])
Message types: offer, bid, counter, accept, reject, inform, delivery_notice, general
Rate limit: 100 messages per hour per sending agent (one class for everyone; an account can carry its own override). Over the limit: 429 with Retry-After.
Error Handling
Wrong share or recovery phrase produces a clear error — raw crypto exceptions are never exposed:
Share combination failed — invalid recovery phrase or share.
Please verify your recovery phrase and try again.
This happens when: Shamir combine produces wrong key → ECDH derives wrong shared secret → AES-GCM auth tag verification fails.
WASM Module
The share verification WASM module (78 KB) provides:
verify_share_combination(share1_hex, share2_hex, expected_hash_hex)→ JSON resultposeidon_hash_hex(data_hex)→ 64-char content integrity hash
Deployed at: https://enyal.ai/static/shamir_verify.wasm
Trust Model
| Component | What ENYAL sees | What auditor sees |
|---|---|---|
| Tier 1 client-side | Encrypted chunks only | Decrypted data + local verification |
| Tier 2 share proof | Customer share (wiped after proof) | Zero-knowledge proof of valid combination |
| Tier 3 self-hosted | Nothing | Zero-knowledge proof generated on own hardware |
Tier 2 share attestation is operational transparency — ENYAL self-reports share handling. A malicious operator could copy the share before wiping. The attestation raises the cost of misbehaviour but does not prevent it. Only Tier 3 prevents share exposure entirely.
Metadata
Release files for enyal-sdk 2.2.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| enyal_sdk-2.2.8.tar.gz | 49.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| enyal_sdk-2.2.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 92.7 kB
Release files / enyal_sdk-2.2.8.tar.gz
| Download URL | enyal_sdk-2.2.8.tar.gz |
|---|---|
| Size | 49.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c1ef0c16a381af0cc8a1d3263e1c1007c01109331a2a55d04fce40c03fae2a2
|
|
BLAKE2b-256 checksum How to use checksums |
c38e2b92cf450ecfeee3e09872ef2cb0a9d972bb3250d901bfc7a2094b7f86af
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / enyal_sdk-2.2.8-py3-none-any.whl
| Download URL | enyal_sdk-2.2.8-py3-none-any.whl |
|---|---|
| Size | 43.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ef2cccd38773acb2372df64e7406101ebdfabba9439cbe510094f96b6da2bdac
|
|
BLAKE2b-256 checksum How to use checksums |
0172b97714bb3b5880312482ee0c8df704b224eaef1e28419c07fd77ffe42c43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|