Elydora Python SDK
Official Python SDK for the Elydora tamper-evident audit platform. Build cryptographically verifiable audit trails for AI agent operations.
Installation
pip install elydora
Requires Python 3.10+.
Quick Start
from elydora import ElydoraClient
# Authenticate
auth = ElydoraClient.login("https://api.elydora.com", "user@example.com", "password")
# Create client
client = ElydoraClient(
org_id=auth["user"]["org_id"],
agent_id="my-agent-id",
private_key="<base64url-encoded-ed25519-seed>",
base_url="https://api.elydora.com",
token=auth["token"],
)
# Create and submit an operation
eor = client.create_operation(
operation_type="data.access",
subject={"user_id": "u-123", "resource": "patient-record"},
action={"type": "read", "scope": "full"},
payload={"record_id": "rec-456"},
)
response = client.submit_operation(eor)
print("Receipt:", response["receipt"]["receipt_id"])
Async Support
from elydora import AsyncElydoraClient
async def main():
client = AsyncElydoraClient(
org_id="org-123",
agent_id="agent-456",
private_key="<base64url-encoded-ed25519-seed>",
token="<api-token>",
)
eor = client.create_operation(
operation_type="inference",
subject={"model": "gpt-4"},
action={"type": "completion"},
)
response = await client.submit_operation(eor)
await client.close()
CLI
The SDK includes a CLI for installing audit hooks into AI coding agents.
Agent IDs map to one physical directory directly under ~/.elydora; portable filename rules and physical directory/config checks apply before writes or recursive removal. Ambiguous uninstall discovery requires an explicit agent ID.
elydora install \
--agent claudecode \
--org_id org-123 \
--agent_id agent-456 \
--private_key_file /secure/path/private.key \
--token_file /secure/path/api.token \
--kid agent-456-key-v1
Credential options may be omitted in an interactive terminal; the CLI then reads the private key and optional API token through hidden prompts. Credential files must contain one UTF-8 line of at most 64 KiB. Unix credential files require owner-only permissions such as chmod 600. The installed signing key is stored once at ~/.elydora/<agent-id>/private.key with mode 0600, and generated audit hooks read that file at runtime.
Codex installation writes exact PreToolUse and PostToolUse command groups to $CODEX_HOME/hooks.json (~/.codex/hooks.json by default) and preserves the complete native event payload. A configured CODEX_HOME follows Codex's existing-directory canonicalization rule. User TOML, project, plugin, and managed sources remain unchanged and continue loading additively. The hook file, generated runtimes, runtime config, and private key commit as one rollback-capable update. Run /hooks after installation and approve both Elydora definition hashes.
Cursor installation writes native global preToolUse, postToolUse, and postToolUseFailure handlers to ~/.cursor/hooks.json. Elydora forwards Cursor's native success and failure payloads, preserves unrelated user hooks, and leaves project and enterprise sources unchanged. The generated guard and audit handlers use a 10-second fail-closed boundary, exact freeze exit-code propagation, protected runtime credentials, and one rollback-capable transaction for all runtime and hook configuration files.
Cline installation writes PreToolUse.mjs and PostToolUse.mjs to $CLINE_DIR/hooks (default ~/.cline/hooks). Elydora leaves the Documents and workspace hook roots unchanged. The wrappers preserve Cline's official payload and translate a frozen guard into JSON stdout cancellation control.
Kimi installation writes exact PreToolUse, PostToolUse, and PostToolUseFailure rules to every detected runtime: Kimi Code's $KIMI_CODE_HOME/config.toml (default ~/.kimi-code/config.toml) and the legacy Python CLI's ~/.kimi/config.toml. A fresh installation targets stable Kimi Code; an existing legacy home activates the migration contract. Empty KIMI_CODE_HOME values use the default. The selected TOML files, generated runtimes, runtime config, and private key commit as one rollback-capable transaction. Managed commands preserve Kimi's native snake_case event JSON, propagate freeze and revocation through exit code 2, and use encoded PowerShell on Windows. Run /hooks to inspect the global rules.
Grok Build installation writes native global PreToolUse, PostToolUse, and PostToolUseFailure hooks to $GROK_HOME/hooks/elydora-audit.json (default ~/.grok/hooks/elydora-audit.json). The hook file, generated runtimes, runtime config, and private key commit as one rollback-capable transaction. Managed commands preserve Grok's native camelCase JSON, return {"decision":"deny","reason":"..."} with exit code 2 for frozen or revoked agents, and use encoded PowerShell on Windows. Project hooks follow Grok's /hooks-trust workflow; Elydora leaves project, plugin, hooks-paths, Claude Code, and Cursor compatibility sources unchanged. Run grok inspect --json to inspect the effective hook inventory.
Auggie installation writes user-level hooks to ~/.augment/settings.json and creates the .cmd or .sh wrappers required by its command runner. System and workspace settings remain unchanged. Sessions started with --augment-cache-dir load settings from that alternate directory.
Factory Droid installation follows its active user source per event: ~/.factory/hooks.json, the legacy ~/.factory/hooks/hooks.json fallback, or hooks nested in ~/.factory/settings.json. Elydora preserves JSONC comments and formatting, leaves project and organization hooks unchanged, and runs both managed commands with absolute Python and script paths. Run /hooks after installation to review the changes.
Qwen Code installation resolves its user settings through explicit QWEN_HOME, ~/.qwen/.env, then ~/.env. Elydora preserves comments and unrelated settings, leaves workspace settings unchanged, and uses Qwen's explicit Bash or PowerShell command contract with millisecond timeouts. Run /hooks after installation to review the changes.
Kiro CLI installation covers both runtime contracts. Kiro CLI v2 uses the generated custom agent through kiro-cli --agent elydora-audit. Kiro CLI v3 loads the global standalone hooks when started with kiro-cli --v3.
Commands
| Command | Description |
|---|---|
elydora install |
Install Elydora audit hook for a coding agent |
elydora uninstall |
Remove Elydora audit hook for a coding agent |
elydora status |
Show installation status for all agents |
elydora agents |
List supported coding agents |
Supported Agents
| Agent | Key |
|---|---|
| Augment Code CLI | augment |
| Claude Code | claudecode |
| OpenAI Codex | codex |
| Cline | cline |
| Kimi Code | kimi |
| Grok Build | grok |
| GitHub Copilot CLI | copilot |
| Cursor | cursor |
| Factory Droid | droid |
| Gemini CLI | gemini |
| Kiro CLI | kirocli |
| Kiro IDE | kiroide |
| Letta Code | letta |
| OpenCode | opencode |
| Qwen Code | qwen |
API Reference
Configuration
client = ElydoraClient(
org_id="org-123", # Organization ID
agent_id="agent-456", # Agent ID
private_key="<seed>", # Base64url-encoded Ed25519 seed
base_url="https://...", # API base URL (default: https://api.elydora.com)
ttl_ms=30000, # Operation TTL in ms (default: 30000)
max_retries=3, # Retries after the initial attempt (default: 3)
token="<api-token>", # Optional API token
)
Automatic retries apply to RFC-idempotent methods.
Connection failures that prove a request was never sent can also retry non-idempotent methods.
Retryable responses honor both Retry-After seconds and HTTP-date values.
Authentication
# Register a new user and organization
reg = ElydoraClient.register(base_url, email, password, display_name=None, org_name=None)
# Login and receive a session token
auth = ElydoraClient.login(base_url, email, password)
# Get current authenticated user profile
me = client.get_me()
# Issue a new API token (with optional TTL in seconds)
token_resp = client.issue_token(ttl_seconds=3600)
Operations
# Create a signed EOR locally (no network call)
eor = client.create_operation(
operation_type="inference",
subject={"model": "gpt-4"},
action={"type": "completion"},
payload={"prompt": "Hello"},
)
# Submit to API
response = client.submit_operation(eor)
# Retrieve an operation
op = client.get_operation(operation_id)
# Verify integrity
result = client.verify_operation(operation_id)
Agent Management
# Register a new agent
agent = client.register_agent({
"agent_id": "my-agent",
"integration_type": "codex",
"display_name": "My Agent",
"responsible_entity": "team@example.com",
"keys": [{"kid": "key-v1", "public_key": "<base64url>", "algorithm": "ed25519"}],
})
# Get agent details
details = client.get_agent(agent_id)
# Freeze an agent
client.freeze_agent(agent_id, reason="security review")
# Revoke a key
client.revoke_key(agent_id, kid, reason="key rotation")
# List all agents in the organization
agents_resp = client.list_agents()
# Unfreeze a previously frozen agent
client.unfreeze_agent(agent_id, reason="review complete")
# Delete an agent permanently
deleted_resp = client.delete_agent(agent_id)
Audit
import time
results = client.query_audit(
agent_id="agent-123",
operation_type="inference",
start_time=int(time.time() * 1000) - 86400000,
end_time=int(time.time() * 1000),
limit=50,
)
Epochs
epochs = client.list_epochs()
epoch = client.get_epoch(epoch_id)
Exports
export = client.create_export(
start_time=start,
end_time=end,
format="json",
)
exports = client.list_exports()
detail = client.get_export(export_id)
# Download export file data
data = client.download_export(export_id)
JWKS
jwks = client.get_jwks()
Health
# Check API health (no authentication required)
health = client.health()
# health["status"], health["version"], health["protocol_version"], health["timestamp"]
Crypto Functions
The SDK exports low-level cryptographic primitives for advanced use:
from elydora import (
jcs_canonicalize, # RFC 8785 JSON Canonicalization
sha256_base64url, # SHA-256 hash as base64url
compute_chain_hash, # Chain hash computation
compute_payload_hash, # Payload hash (SHA-256 of JCS-canonicalized payload)
sign_ed25519, # Ed25519 signing
sign_eor, # Sign an EOR dict
get_public_key_base64url, # Derive public key from private seed
)
Utility Functions
from elydora import (
base64url_encode, # Encode bytes to base64url (no padding)
base64url_decode, # Decode base64url string to bytes
generate_nonce, # Generate 16-byte random nonce (base64url)
generate_uuidv7, # Generate UUIDv7 (time-ordered, RFC 9562)
)
Type Definitions
All types are TypedDict classes for structural typing:
from elydora import (
# Protocol types
EOR, # Elydora Operation Record
EAR, # Elydora Acknowledgment Receipt
# Entity types
Agent, AgentKey, Operation, Receipt, Epoch, Export, Organization, User,
# API response types
RegisterAgentResponse, GetAgentResponse, ListAgentsResponse,
DeleteAgentResponse, SubmitOperationResponse, GetOperationResponse,
VerifyOperationResponse, AuditQueryResponse, GetEpochResponse,
ListEpochsResponse, CreateExportResponse, GetExportResponse,
ListExportsResponse, JWKSResponse, AuthRegisterResponse,
AuthLoginResponse, GetMeResponse, IssueTokenResponse, HealthResponse,
# Request types
RegisterAgentRequest,
)
Error Handling
from elydora import ElydoraError
try:
client.submit_operation(eor)
except ElydoraError as e:
print(e.code) # e.g. "INVALID_SIGNATURE"
print(e.message) # Human-readable message
print(e.status_code) # HTTP status code
print(e.request_id) # Request ID for support
Dependencies
- requests - Sync HTTP client
- aiohttp - Async HTTP client
- cryptography - Ed25519 signing
License
MIT