ELYDORA
Quick Start

Integrate Elydora.
Step by step.

The complete guide to integrating Elydora responsibility infrastructure into your AI agent platform. From key generation to production deployment, covering every concept and API you need.

01 — Getting Started

What is Elydora.

Elydora is the responsibility layer for AI agents. It produces verifiable, tamper-evident, audit-ready operation records for every action your AI agents perform. Think of it as the cryptographic backbone that binds agent decisions to provable accountability. Every operation is signed, chained, receipted, and sealed into time-bounded epochs — creating an unbreakable evidentiary trail that satisfies compliance, legal, and governance requirements.

When you need responsibility records

You need Elydora when your AI agents perform actions with legal, financial, or compliance implications. If an agent approves a loan, executes a trade, authorizes a payment, triages a patient, or modifies infrastructure — those decisions carry real-world consequences. Regulators, auditors, and courts will eventually ask: who decided, when, under what authority, and can you prove it? Elydora provides the cryptographic proof. Without it, you have logs. With it, you have evidence.

How it works in 30 seconds

The flow is straightforward. Your agent signs an operation record using its Ed25519 private key, which produces an Elydora Operation Record (EOR). The agent submits this EOR to the Elydora edge network. Elydora validates the signature, verifies chain integrity, assigns a sequence number, and returns an Elydora Acknowledgment Receipt (EAR) — a platform-signed proof of acceptance. Periodically, all accepted operations within a time window are sealed into an epoch via a Merkle tree, producing an Elydora Epoch Record (EER) that anchors the entire batch to a single verifiable hash.

The core loop
Agent signs with Ed25519
Submit to Elydora Edge
Receive attestation receipt
Operation sealed into epoch
02 — System Requirements

Before you begin.

Ensure your environment meets these requirements before installing the SDK.

RuntimeNode.js 18+ / Python 3.9+ / Go 1.21+
AccountAn Elydora organization account (org_id)
KeysEd25519 key pair for each agent (base64url-encoded 32-byte seed)
NetworkHTTPS access to api.elydora.com (port 443)
ClockSystem clock synchronized via NTP (required for TTL validation)
Note

The Ed25519 private key must be a base64url-encoded 32-byte seed (not PEM format). The SDK provides key generation utilities, or you can generate keys using OpenSSL or libsodium. Each agent should have its own unique key pair — never share private keys between agents.

03 — Installing the SDK

Install the SDK.

Elydora provides official SDKs for Node.js, Python, and Go. Each SDK wraps the full Elydora API, handles Ed25519 signing, chain hash management, nonce generation, and receipt verification automatically.

Node.js
$ npm install @elydora/sdk
# or: yarn add @elydora/sdk

Requires Node.js 18 or later. The SDK uses the native crypto module for Ed25519 operations — no additional native dependencies required.

Python
$ pip install elydora
# or: poetry add elydora

Requires Python 3.9 or later. Uses PyNaCl for Ed25519 operations, which is installed automatically as a dependency.

Go
$ go get github.com/Elydora-Infrastructure/Elydora-Go-SDK

Requires Go 1.21 or later. Uses the standard library crypto/ed25519 package — zero external dependencies for cryptographic operations.

04 — Creating an Agent Identity

Register your agent.

Every agent in Elydora has a unique cryptographic identity. Before an agent can submit operations, it must be registered with your organization. Registration binds an agent_id to its Ed25519 public key, a display name, and the responsible legal entity. This identity is the foundation of the entire accountability chain — it answers the question "who performed this action?" with cryptographic certainty.

Step 1: Generate an Ed25519 key pair

Each agent needs its own Ed25519 key pair. The private key (a 32-byte seed, base64url-encoded) is used to sign operations. The public key is registered with Elydora and used to verify signatures. Never share private keys between agents, and never hardcode them in source code.

# Generate a key pair using OpenSSL
openssl genpkey -algorithm Ed25519 -outform DER | \
  tail -c 32 | basenc --base64url -w0

# Or use the SDK's built-in key generation:
# Node.js
import { generateKeyPair } from '@elydora/sdk';
const { privateKey, publicKey } = generateKeyPair();

# Python
from elydora import generate_key_pair
private_key, public_key = generate_key_pair()

# Go
privateKey, publicKey, err := elydora.GenerateKeyPair()

Step 2: Initialize the client and register

Create an ElydoraClient instance with your organization ID, agent ID, and private key. Then authenticate and register the agent. Registration is a one-time operation — once registered, the agent can submit operations immediately.

Node.js
import { ElydoraClient } from '@elydora/sdk';

// Authenticate with the Elydora API
const auth = await ElydoraClient.login(
  'https://api.elydora.com',
  '[email protected]',
  'password'
);

// Initialize the client
const client = new ElydoraClient({
  orgId: 'org_acme',
  agentId: 'agent_underwriter',
  privateKey: process.env.ELYDORA_KEY!,
});
client.setToken(auth.token);

// Register the agent (one-time operation)
await client.registerAgent({
  agent_id: 'agent_underwriter',
  display_name: 'Loan Underwriter Agent',
  responsible_entity: 'ACME Financial Corp',
  keys: [{
    kid: 'agent_underwriter-key-v1',
    public_key: client.getPublicKey(),
    algorithm: 'ed25519',
  }],
});
Python
import os
from elydora import ElydoraClient

client = ElydoraClient(
    org_id="org_acme",
    agent_id="agent_underwriter",
    private_key=os.environ["ELYDORA_KEY"],
)

# Authenticate and register
client.login("[email protected]", "password")
client.register_agent(
    agent_id="agent_underwriter",
    display_name="Loan Underwriter Agent",
    responsible_entity="ACME Financial Corp",
    keys=[{
        "kid": "agent_underwriter-key-v1",
        "public_key": client.get_public_key(),
        "algorithm": "ed25519",
    }],
)
Go
import (
    "os"
    elydora "github.com/Elydora-Infrastructure/Elydora-Go-SDK"
)

client, err := elydora.NewClient(&elydora.Config{
    OrgID:      "org_acme",
    AgentID:    "agent_underwriter",
    PrivateKey: os.Getenv("ELYDORA_KEY"),
})
if err != nil {
    log.Fatal(err)
}

// Authenticate and register
err = client.Login("[email protected]", "password")
err = client.RegisterAgent(&elydora.AgentRegistration{
    AgentID:           "agent_underwriter",
    DisplayName:       "Loan Underwriter Agent",
    ResponsibleEntity: "ACME Financial Corp",
    Keys: []elydora.AgentKey{{
        KID:       "agent_underwriter-key-v1",
        PublicKey: client.GetPublicKey(),
        Algorithm: "ed25519",
    }},
})

Step 3: Store keys securely

The private key is the root of your agent's identity. Compromise of the private key means an attacker can forge operations that appear to come from your agent. Follow these storage practices:

DevelopmentEnvironment variables via .env files (never committed to version control)
StagingCloud-native secret managers (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault)
ProductionHardware Security Modules (HSMs) or cloud KMS with key wrapping for maximum security
05 — Signing an Operation

Create an EOR.

An Elydora Operation Record (EOR) is the fundamental unit of accountability. It captures what happened, who did it, when, and proves it cryptographically. The SDK handles the complex signing mechanics automatically — you provide the business context, and the SDK produces a fully signed, chain-linked EOR ready for submission.

What happens under the hood

When you call createOperation(), the SDK performs these steps in sequence. Understanding this process helps when debugging signature or chain issues.

1Generate a UUIDv7 for the operation_id, which embeds a timestamp for natural ordering
2Generate a cryptographically random 16-byte nonce, base64url-encoded, to prevent replay attacks
3Compute payload_hash = SHA-256(JCS(payload)) — the payload is JSON Canonicalized (RFC 8785) before hashing for deterministic results
4Compute chain_hash = SHA-256(prev_chain_hash | payload_hash | operation_id | issued_at) — pipe-delimited, linking this operation to the previous one in the agent's chain
5Build the complete EOR structure (without signature) containing all metadata fields
6JCS-canonicalize the entire EOR to produce a deterministic byte representation
7Sign the canonical bytes with the agent's Ed25519 private key
8Attach the base64url-encoded signature to the EOR

Creating an operation

The createOperation() method is synchronous — it returns the signed EOR directly, not a Promise. All cryptographic operations happen locally in your process. The operation is not submitted to Elydora until you explicitly call submitOperation().

// createOperation() is SYNCHRONOUS — returns EOR directly
const record = client.createOperation({
  operationType: 'loan.approve',
  subject: {
    system: 'lending',
    resource: 'application',
    id: 'APP-2026-001',
  },
  action: {
    decision: 'approve',
    amount: 50000,
    currency: 'USD',
  },
  payload: {
    applicant_id: 'cust_123',
    credit_score: 750,
    debt_to_income: 0.32,
    loan_term_months: 360,
    interest_rate: 6.25,
    underwriting_model: 'model_v3.2.1',
  },
});

// record is now a fully signed EOR
// record.operation_id   → UUIDv7
// record.nonce          → base64url random bytes
// record.payload_hash   → SHA-256 of JCS(payload)
// record.chain_hash     → SHA-256 chain link
// record.signature      → Ed25519 signature
// record.timestamp      → ISO 8601 timestamp
Important

The subject field identifies what the operation acts upon. Use a consistent naming convention across your organization: system identifies the business domain, resource identifies the entity type, and id is the specific resource identifier. This structure enables powerful audit queries across your operation history.

06 — Submitting to Elydora Edge

Submit and receive a receipt.

Once you have a signed EOR, submit it to the Elydora edge network. The edge validates the signature, checks chain integrity, assigns a sequence number, and returns an attestation receipt — all in under 100ms at p95.

// submitOperation() is ASYNC — returns a Promise
const { receipt } = await client.submitOperation(record);

console.log('Receipt ID:', receipt.receipt_id);
console.log('Chain Hash:', receipt.chain_hash);
console.log('Sequence:', receipt.seq_no);
console.log('Timestamp:', receipt.server_received_at);
console.log('Signature:', receipt.elydora_signature);

The Elydora edge responds with HTTP 202 Accepted upon successful validation. The response body contains the Elydora Acknowledgment Receipt (EAR) — a platform-signed proof that Elydora has accepted, validated, and sequenced the operation. This receipt is your agent's proof of submission.

What the receipt contains

receipt_idUnique identifier for this receipt (UUIDv7)
operation_idThe operation this receipt acknowledges
chain_hashThe updated chain hash after this operation — cache this locally
seq_noMonotonically increasing sequence number for this agent's chain
server_received_atServer-side Unix epoch milliseconds timestamp of acceptance
elydora_signatureEd25519 signature from the Elydora platform, verifiable against the public JWKS
Tip

Always store the receipt alongside the original operation in your system. The receipt is your proof that Elydora accepted the operation. In the event of a dispute, you need both the EOR (what was submitted) and the EAR (proof it was accepted) to construct a complete audit trail. The SDK caches the chain_hash automatically, but you should persist receipts in your own database.

07 — Receiving & Storing Attestation Receipts

The EAR structure.

The Elydora Acknowledgment Receipt (EAR) is the platform's cryptographic attestation that an operation was received, validated, and sequenced. It serves as a signed proof-of-acceptance that is independently verifiable by any party with access to Elydora's public keys.

// The EAR structure returned from submitOperation()
{
  receipt_version: "1.0",
  receipt_id: "019462a0-7e8b-7c3d-a1b2-3c4d5e6f7890",
  operation_id: "019462a0-7e8a-7b2c-9a8b-7c6d5e4f3210",
  org_id: "org_acme",
  agent_id: "agent_underwriter",
  server_received_at: 1740835800000,
  seq_no: 42,
  chain_hash: "base64url-encoded-sha256-hash",
  queue_message_id: "msg_01HQ4R5S6T7U8V9W0X1Y2Z3A",
  receipt_hash: "base64url-encoded-sha256-hash",
  elydora_kid: "elydora-server-key-v1",
  elydora_signature: "base64url-encoded-ed25519-signature"
}

Parsing and storing receipts

The SDK returns the receipt as a typed object, so parsing is handled automatically. Your responsibility is to persist the receipt in durable storage. You should store receipts alongside the original operation data in your database so you can reconstruct the complete audit trail at any time. Consider storing receipts in an append-only table or immutable storage to prevent accidental modification.

// Store the receipt alongside the operation
const { receipt } = await client.submitOperation(record);

// Persist both the EOR and EAR
await db.operations.insert({
  operation_id: record.operation_id,
  eor: record,          // The signed operation record
  ear: receipt,          // The platform attestation receipt
  created_at: new Date(),
});

// The SDK automatically updates the local chain hash
// No manual chain hash management needed

Verifying receipt authenticity

Every receipt is signed by the Elydora platform using its own Ed25519 key. You can verify the platform signature against Elydora's public JWKS, published at /.well-known/elydora/jwks.json. This ensures the receipt was genuinely issued by Elydora and has not been tampered with. The SDK's verifyReceipt() method performs this check automatically, fetching and caching the JWKS as needed.

// Verify a receipt's authenticity
const isValid = await client.verifyReceipt(receipt);
// Returns true if the platform signature is valid

// For manual verification:
// 1. Fetch JWKS from https://api.elydora.com/.well-known/elydora/jwks.json
// 2. Find the key matching receipt.elydora_kid
// 3. Verify receipt.elydora_signature against the receipt body
08 — Verifying Records

Four layers of verification.

Elydora provides four independent verification checks for every operation. Each check validates a different aspect of the operation's integrity, and together they provide comprehensive proof that a record is authentic, correctly sequenced, and permanently anchored.

// verifyOperation() is ASYNC — returns a Promise
const verification = await client.verifyOperation(record.operation_id);

console.log('Valid:', verification.valid);
console.log('Signature OK:', verification.checks.signature);
console.log('Chain OK:', verification.checks.chain);
console.log('Receipt OK:', verification.checks.receipt);
console.log('Merkle OK:', verification.checks.merkle);
Signature Check
checks.signature

Verifies the Ed25519 signature on the EOR against the agent's registered public key. Confirms the operation was genuinely created by the claimed agent and has not been modified since signing.

Chain Check
checks.chain

Verifies the chain_hash linkage — that this operation's prev_chain_hash correctly references the predecessor operation. Confirms the sequential integrity of the agent's operation chain.

Receipt Check
checks.receipt

Verifies the platform signature on the EAR against Elydora's public JWKS. Confirms the receipt was genuinely issued by the Elydora platform and has not been tampered with.

Merkle Check
checks.merkle

Verifies the operation's inclusion in its epoch's Merkle tree using an audit proof. Confirms the operation was sealed into a specific epoch and is anchored to the epoch root hash.

The top-level verification.valid field is true only when all four checks pass. In practice, signature and chain checks are available immediately after submission, while the Merkle check becomes available once the operation's epoch has been sealed (typically within 5 minutes). If you verify an operation before its epoch is sealed, the Merkle check will return null (pending) rather than false.

09 — Handling Errors & Conflicts

Error codes and recovery.

The Elydora API uses structured error responses. Every error thrown by the SDK is an instance of ElydoraError with typed properties: status (HTTP status code), code (error code string), message (human-readable description), requestId (for support), and details (additional context).

INVALID_SIGNATURE

The Ed25519 signature on the submitted EOR does not verify against the agent's registered public key. This typically indicates a key mismatch between the signing key and the registered key, or a corrupted EOR where the payload was modified after signing.

Handling: Verify that the private key used to sign matches the public key registered for this agent. Re-sign the operation and retry.

UNKNOWN_AGENT

The agent_id in the submitted operation does not match any registered agent within the specified organization. Registration must be completed before submitting operations.

Handling: Register the agent using client.registerAgent() before submitting operations. Double-check the org_id and agent_id values.

KEY_REVOKED

The signing key (identified by kid) has been permanently revoked. Revocation is irreversible — the key can never be used again for signing operations.

Handling: Generate a new Ed25519 key pair, register the new key with the agent, and re-sign the operation with the new key.

AGENT_FROZEN

The agent has been frozen by an administrator. Frozen agents cannot submit new operations. This is a reversible state — the agent can be unfrozen by an administrator.

Handling: Contact your organization administrator to review the freeze reason. Once unfrozen, the agent can resume normal operations.

TTL_EXPIRED

The operation timestamp is outside the acceptable time window. Operations must be submitted within the TTL window (default: 300 seconds, configurable down to 30 seconds in production). This prevents replay of stale operations.

Handling: Regenerate the operation with a fresh timestamp. Ensure your system clock is synchronized via NTP.

REPLAY_DETECTED

The nonce in this operation has already been seen. Each operation must include a unique, cryptographically random 16-byte nonce. This check prevents duplicate submission of the same operation.

Handling: Ensure nonces are generated using a cryptographically secure random number generator. The SDK handles this automatically — if you see this error, you may be resubmitting a cached operation object.

PREV_HASH_MISMATCH

The prev_chain_hash in the submitted operation does not match the expected chain hash for this agent. This occurs when the local chain state is out of sync with the server — typically after a restart, a concurrent submission from another instance, or a network failure during a previous submission.

Handling: Re-sync your local chain state by fetching the latest chain hash from the API, then rebuild and resubmit the operation.

PAYLOAD_TOO_LARGE

The operation payload exceeds the maximum allowed size of 256 KB. This limit applies to the serialized payload before hashing.

Handling: Reduce the payload size. Store large data externally and include only a reference (URL or hash) in the operation payload.

RATE_LIMITED

The agent has exceeded the allowed submission rate. Rate limits are applied per-agent and per-organization. The response includes a Retry-After header indicating when the agent can resume submissions.

Handling: Implement exponential backoff. Respect the Retry-After header. Consider batching operations or distributing load across multiple agents.

Error handling in code

Use the ElydoraError class to catch and handle specific error codes. The code property provides the machine-readable error type, while message provides a human-readable explanation. The requestId is essential for debugging — include it when contacting Elydora support.

import { ElydoraError } from '@elydora/sdk';

try {
  const { receipt } = await client.submitOperation(record);
  console.log('Submitted:', receipt.receipt_id);
} catch (err) {
  if (err instanceof ElydoraError) {
    console.error(`[${err.code}] ${err.message}`);
    console.error('Request ID:', err.requestId);

    switch (err.code) {
      case 'PREV_HASH_MISMATCH':
        // Local chain state is out of sync.
        // Fetch latest chain hash and rebuild the operation.
        const state = await client.getChainState();
        const retryRecord = client.createOperation({
          ...originalParams,
        });
        await client.submitOperation(retryRecord);
        break;

      case 'TTL_EXPIRED':
        // Operation too old — regenerate with fresh timestamp.
        const freshRecord = client.createOperation({
          ...originalParams,
        });
        await client.submitOperation(freshRecord);
        break;

      case 'AGENT_FROZEN':
        // Agent is frozen — alert the administrator.
        await alertAdmin('Agent frozen', err.details);
        break;

      case 'KEY_REVOKED':
        // Signing key revoked — cannot retry with same key.
        await alertAdmin('Key revoked', err.details);
        break;

      case 'RATE_LIMITED':
        // Back off and retry after the indicated delay.
        const retryAfter = err.details?.retryAfterMs ?? 1000;
        await sleep(retryAfter);
        await client.submitOperation(record);
        break;

      default:
        throw err; // Unhandled error — propagate
    }
  } else {
    throw err; // Not an Elydora error
  }
}
10 — Epoch & Batch Sealing

How epochs work.

Epochs are time-bounded batches of operations that are sealed together into a single Merkle tree. This provides a compact, verifiable summary of all operations within a time window and enables efficient audit proofs.

Elydora seals epochs on 5-minute windows. At the end of each window, all operations accepted during that period are collected, ordered, and hashed into a Merkle tree following RFC 9162 (Certificate Transparency v2) conventions. The root of this tree — the Elydora Epoch Record (EER) — is a single hash that cryptographically commits to every operation in the epoch. Optionally, the epoch root can be anchored to an external Trusted Timestamping Authority (TSA) via RFC 3161, providing independent proof that the epoch existed at a specific point in time.

Merkle tree construction

Operations within an epoch are sorted by their sequence number and then hashed into a binary Merkle tree. Each leaf node is the SHA-256 hash of the canonical EOR. Interior nodes are computed as SHA-256(left || right). This structure enables inclusion proofs — you can prove that a specific operation exists within an epoch using only O(log n) hashes, without revealing any other operations in the epoch.

Retrieving epoch data

// List recent epochs
const epochs = await client.listEpochs();

// Get a specific epoch
const epoch = await client.getEpoch(epochs.epochs[0].epoch_id);

console.log('Epoch ID:', epoch.epoch_id);
console.log('Start:', epoch.start_time);
console.log('End:', epoch.end_time);
console.log('Merkle Root:', epoch.merkle_root);
console.log('Operation Count:', epoch.operation_count);
console.log('TSA Timestamp:', epoch.tsa_timestamp);

// Get an inclusion proof for a specific operation
const proof = await client.getInclusionProof(
  epoch.epoch_id,
  record.operation_id
);
console.log('Proof path:', proof.hashes);
console.log('Leaf index:', proof.leaf_index);
Note

Epoch sealing is asynchronous. After submitting an operation, it typically takes up to 5 minutes for the operation to be included in a sealed epoch. During this window, the operation is still valid and its receipt is still proof of acceptance — the epoch simply adds an additional layer of batch-level anchoring and audit capability.

11 — Freezing & Revoking Agents

Agent lifecycle management.

Elydora provides two mechanisms for controlling agent access: freezing (reversible) and key revocation (permanent). These controls are essential for responding to security incidents, compliance requirements, or suspicious agent behavior.

Freeze (Reversible)

Freezing an agent immediately prevents it from submitting new operations. The agent's existing operation history remains intact and verifiable. Freezing is reversible — an administrator can unfreeze the agent to restore normal operation. Use freezing when you need to temporarily suspend an agent while investigating an issue.

active → frozen → active
Revoke (Permanent)

Revoking a key permanently invalidates it. The key can never be used again for signing operations. Revocation is recorded in the agent's audit trail, including the reason and timestamp. The agent can continue operating by registering a new key, but operations signed with the revoked key after the revocation timestamp are rejected.

active → revoked (permanent)
// Freeze an agent (reversible)
await client.freezeAgent(
  'agent_underwriter',
  'Suspicious activity detected — pending investigation'
);

// Unfreeze an agent (restore normal operation)
await client.unfreezeAgent(
  'agent_underwriter',
  'Investigation complete — no issues found'
);

// Revoke a specific key (permanent — cannot be undone)
await client.revokeKey(
  'agent_underwriter',
  'agent_underwriter-key-v1',
  'Key compromised — rotating to new key pair'
);

// After revoking, register a new key for the agent
const { privateKey, publicKey } = generateKeyPair();
await client.registerKey('agent_underwriter', {
  kid: 'agent_underwriter-key-v2',
  public_key: publicKey,
  algorithm: 'ed25519',
});
Warning

Key revocation is permanent and irreversible. Before revoking a key, ensure you have a replacement key pair ready. All operations signed with the revoked key after the revocation timestamp will be rejected. Operations signed before the revocation timestamp remain valid and verifiable — revocation is not retroactive.

12 — Security Best Practices

Protect the chain.

The integrity of your Elydora accountability chain depends on following security best practices. A compromised private key means an attacker can forge operations that appear legitimate. A broken chain hash means operations cannot be properly sequenced. These practices are essential for maintaining the evidentiary value of your operation records.

Never hardcode private keys

Private keys should never appear in source code, configuration files committed to version control, Docker images, or build artifacts. Use environment variables in development and cloud-native secret managers (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault) in production. For maximum security, use Hardware Security Modules (HSMs) or cloud KMS services with key wrapping.

Rotate keys periodically

Establish a key rotation schedule appropriate to your risk profile. Register the new key, transition operations to use it, then revoke the old key. The SDK supports multiple active keys per agent, enabling zero-downtime rotation. A quarterly rotation cadence is a reasonable starting point for most organizations.

Monitor chain hash continuity

The chain hash is the backbone of per-agent operation ordering. If the chain hash becomes desynchronized (due to concurrent submissions, restarts without state persistence, or network failures), you will encounter PREV_HASH_MISMATCH errors. Monitor for these errors as they may also indicate unauthorized use of an agent's credentials.

Set up alerts for agent freezes

Configure monitoring to alert your security team immediately when any agent is frozen. A freeze may indicate suspicious activity, a compliance issue, or an unauthorized access attempt. Investigate freeze events promptly — the frozen agent's recent operation history should be reviewed for anomalies.

Use short TTLs in production

The TTL (time-to-live) window determines how long an operation is valid before it must be submitted. In production, use short TTLs (30 seconds) to minimize the window for replay attacks. In development, longer TTLs (300 seconds) are acceptable for debugging convenience. Configure TTL via the maxTTL option in the client constructor.

Validate receipts immediately

After submitting an operation, validate the returned receipt immediately. Verify the platform signature and confirm the chain hash and sequence number match your expectations. The SDK can be configured to perform this validation automatically on every submission with the validateReceipts: true option.

Isolate agent keys per environment

Never use the same private key across development, staging, and production environments. Each environment should have its own agent registrations with unique key pairs. This prevents accidental cross-environment operations and limits the blast radius of a key compromise.

13 — Performance & Scaling

Optimize for throughput.

Elydora is designed for high-throughput agent workloads. The edge network validates operations in under 100ms at p95, and the architecture scales horizontally across agents and organizations.

<100ms
p95 edge validation latency
256 KB
Maximum payload size
5 min
Epoch sealing window
Keep payloads lean

Payload size directly impacts serialization, hashing, and network transfer time. Store large data (documents, images, raw datasets) externally and include only references or content hashes in the operation payload. Aim for payloads under 10 KB for high-frequency operations.

Cache chain hash locally

The SDK automatically caches the prev_chain_hash in memory, eliminating the need for a round-trip to fetch chain state before each submission. If you restart the process, the SDK re-fetches the latest chain hash from the API on the first submission. For persistent caching across restarts, use the persistChainState option to write the chain hash to disk or a database.

Use async patterns

For high-throughput agents, submit operations asynchronously. The createOperation() call is synchronous and CPU-bound (signing), while submitOperation() is async and IO-bound (network). Pipeline your operations: create the next EOR while the previous submission is in flight. The SDK maintains chain hash order internally, so pipelining is safe.

Batch when possible

If your agent performs many related operations in a short window (e.g., processing a batch of loan applications), consider submitting them as a burst. The SDK queues submissions and handles backpressure automatically. The edge network is optimized for bursty workloads with per-agent rate limits that accommodate legitimate batch patterns.

Monitor submission latency

Track the time between createOperation() and receiving the receipt. The SDK emits timing metrics via the onMetrics callback. Set up alerting for latency spikes — they may indicate network issues, rate limiting, or edge capacity constraints. Under normal conditions, p95 latency should stay below 100ms.

14 — Integration Patterns

Common architectures.

Elydora integrates into a variety of agent architectures. Below are four common patterns, each suited to a different operational profile. These patterns are composable — most production deployments combine elements from multiple patterns.

Pattern 01
API call wrapping

The most common integration pattern. Wrap every outbound API call your agent makes with an Elydora operation record. This creates a complete audit trail of every external action the agent performs — API calls to third-party services, database mutations, payment processing, and more. The operation is created and submitted synchronously as part of the action flow, ensuring the audit trail is always consistent with the actual behavior.

// Wrap an external API call with an Elydora operation
async function approvePayment(paymentId: string, amount: number) {
  // 1. Record the decision BEFORE executing it
  const record = client.createOperation({
    operationType: 'payment.approve',
    subject: { system: 'payments', resource: 'payment', id: paymentId },
    action: { decision: 'approve', amount, currency: 'USD' },
    payload: { payment_id: paymentId, amount, approved_by: client.agentId },
  });

  // 2. Submit to Elydora (get proof of intent)
  const { receipt } = await client.submitOperation(record);

  // 3. Execute the actual payment
  const result = await paymentGateway.charge(paymentId, amount);

  // 4. Optionally record the outcome
  const outcomeRecord = client.createOperation({
    operationType: 'payment.executed',
    subject: { system: 'payments', resource: 'payment', id: paymentId },
    action: { status: result.status, transaction_id: result.txId },
    payload: { receipt_id: receipt.receipt_id, gateway_response: result },
  });
  await client.submitOperation(outcomeRecord);

  return result;
}
Pattern 02
Background job auditing

For agents that run as background workers processing queues or scheduled jobs. Each job execution is wrapped in an Elydora operation, creating a verifiable record of what the agent processed, what decisions it made, and what outcomes it produced. This pattern is ideal for batch processing pipelines, cron-based agents, and event-driven architectures where the agent reacts to incoming messages.

// Background job worker with Elydora auditing
async function processJob(job: QueueJob) {
  const record = client.createOperation({
    operationType: 'batch.process',
    subject: { system: 'pipeline', resource: 'job', id: job.id },
    action: { type: job.type, batch_size: job.items.length },
    payload: {
      job_id: job.id,
      queue: job.queue,
      items_count: job.items.length,
      started_at: new Date().toISOString(),
    },
  });

  const { receipt } = await client.submitOperation(record);

  // Process the job...
  const result = await executeJob(job);

  return { receipt, result };
}
Pattern 03
High-frequency operations

For agents making many decisions per second (trading agents, real-time fraud detection, content moderation). Use the SDK's pipelining mode to overlap signing and submission. Create the next operation while the previous one is in flight. The SDK maintains chain ordering internally, so pipelining is safe. For extremely high throughput, consider partitioning operations across multiple logical agents, each with its own chain.

// High-frequency pipelining
const pipeline = client.createPipeline({ maxConcurrent: 10 });

for (const decision of decisions) {
  const record = client.createOperation({
    operationType: 'fraud.score',
    subject: { system: 'fraud', resource: 'transaction', id: decision.txId },
    action: { score: decision.score, flagged: decision.score > 0.8 },
    payload: { features: decision.features },
  });

  // Pipeline handles submission, ordering, and backpressure
  pipeline.submit(record);
}

// Wait for all in-flight operations to complete
const results = await pipeline.flush();
Pattern 04
Multi-agent coordination

For systems where multiple agents collaborate on a shared workflow. Each agent maintains its own independent chain, but operations can reference each other via the subject field. This creates a cross-agent audit trail that shows how decisions flowed through a multi-agent pipeline. Use a consistent correlation_id in the payload to link related operations across agents.

// Agent A: Triage agent
const triageRecord = triageClient.createOperation({
  operationType: 'ticket.triage',
  subject: { system: 'support', resource: 'ticket', id: 'TKT-5001' },
  action: { priority: 'high', assigned_to: 'agent_resolver' },
  payload: { correlation_id: 'workflow_abc123', sentiment: 'negative' },
});
await triageClient.submitOperation(triageRecord);

// Agent B: Resolver agent (references the same ticket)
const resolveRecord = resolverClient.createOperation({
  operationType: 'ticket.resolve',
  subject: { system: 'support', resource: 'ticket', id: 'TKT-5001' },
  action: { resolution: 'refund_issued', amount: 150 },
  payload: {
    correlation_id: 'workflow_abc123',
    triage_operation_id: triageRecord.operation_id,
  },
});
await resolverClient.submitOperation(resolveRecord);
15 — Troubleshooting

Common issues.

Most issues encountered during integration fall into a handful of categories. This section covers the most common problems, their root causes, and how to resolve them.

PREV_HASH_MISMATCH on startup

Cause: Your local chain state is out of sync with the server. This typically happens when you restart your agent process without persisting the chain hash. The SDK tracks prev_chain_hash automatically in memory, but this state is lost on restart.

Solution: The SDK automatically re-syncs on the first submission after restart by fetching the latest chain hash from the API. If you see this error persistently, it may indicate concurrent submissions from multiple instances of the same agent. Ensure only one instance submits operations for a given agent_id at any time, or use the persistChainState option to write chain state to disk.

TTL_EXPIRED errors

Cause: Your system clock is significantly out of sync with UTC, or your operations are being created long before they are submitted. The TTL window (default 300 seconds) is measured against the operation timestamp.

Solution: Ensure your system clock is synchronized via NTP. On Linux, verify with timedatectl status. On containerized workloads, ensure the host clock is synced. If operations are queued before submission, create the operation immediately before submitting rather than batching the creation step.

REPLAY_DETECTED with unique operations

Cause: Each operation must include a cryptographically random 16-byte nonce. If you are constructing operations manually (bypassing the SDK), you may be reusing nonces. The SDK handles nonce generation automatically.

Solution: Use the SDK's createOperation() method, which generates a unique nonce for each operation. If constructing operations manually, use crypto.getRandomValues() (Node.js) or os.urandom() (Python) to generate the nonce. Never use predictable values like incrementing counters or timestamps as nonces.

Connection timeouts

Cause: The SDK retries failed HTTP requests with exponential backoff (default: 3 retries with 1s, 2s, 4s delays). If all retries fail, the SDK throws a timeout error. This may indicate network issues, DNS resolution failures, or Elydora edge unavailability.

Solution: Check network connectivity to api.elydora.com. Verify DNS resolution works. If behind a corporate firewall or proxy, ensure HTTPS traffic to port 443 is allowed. Configure retry behavior via the maxRetries and retryDelayMs options in the client constructor. The SDK also supports custom HTTP agents for proxy configuration.

Receipt verification fails

Cause: Receipt verification requires the Elydora platform's public JWKS to verify the elydora_signature. If the JWKS is stale, rotated, or inaccessible, verification will fail.

Solution: Ensure your agent can access https://api.elydora.com/.well-known/elydora/jwks.json. The SDK caches the JWKS for 1 hour and refreshes automatically. If verification fails after a JWKS rotation, clear the cache by calling client.clearJWKSCache() and retry. In air-gapped environments, you can configure a local JWKS file via the jwksPath option.

Signature verification fails on valid operations

Cause: The most common cause is a mismatch between the key used to sign and the key registered with Elydora. This can happen after key rotation if the agent still uses the old key, or if the wrong environment variable is loaded.

Solution: Verify that process.env.ELYDORA_KEY contains the correct private key for this agent. Use client.getPublicKey() to derive the public key from the loaded private key and compare it against the registered key via the API. Ensure you are not accidentally loading a key from a different environment (dev vs. prod).

Ready to build with responsibility.

You now have everything you need to integrate Elydora into your agent platform. Start with agent registration, sign your first operation, and verify the receipt. The entire integration takes under 30 minutes.