How OpenHuman Implements Agent-to-Agent Encrypted Communication Using Signal-Protocol E2E Sessions

OpenHuman implements agent-to-agent encrypted communication using Signal-protocol E2E sessions by leveraging the Double-Ratchet algorithm via the tinychannels crate, where each agent maintains an X25519 identity key in an encrypted store and establishes per-session AES-256-GCM encryption through X3DH key exchange.

OpenHuman treats every autonomous Agent as a first-class participant in a distributed system requiring confidential data exchange. When two agents need to share sensitive payloads—such as confidential tool results or user secrets—without exposing content to the host process, they communicate through a Signal-protocol based end-to-end channel managed by the core's abstraction layer.

Architecture of the Signal-Protocol Channel

The encrypted communication stack separates cryptographic identity management from transport logic. Each component operates through a specific module in the OpenHuman codebase.

Core Components

  • Channel Trait (src/openhuman/channels/mod.rs): Defines the generic interface that every messaging backend implements, providing send_message, receive_message, and session lifecycle hooks.

  • SignalChannel (src/openhuman/channels/providers/signal.rs): Re-exports the Signal-protocol implementation from the external tinychannels crate, which manages the connection to a local signal-cli HTTP bridge.

  • EncryptedStore (src/openhuman/security/keyring/encrypted_store.rs): Persists each agent’s permanent X25519 identity key pair encrypted at rest, ensuring private keys remain inaccessible without the user’s master key.

  • RPC Surface (src/openhuman/api/rest.rs around line 946): Exposes the send_message JSON-RPC endpoint that routes encrypted payloads through the selected channel backend.

Encryption Flow

When Agent A initiates communication with Agent B, the following sequence occurs:

  1. Identity Generation: Upon creation, the agent’s keyring generates an X25519 key pair. The public key registers with the Signal server via the bridge, while the private key remains encrypted in the store.

  2. X3DH Exchange: Agent A retrieves Agent B’s public key from the core’s Channel Registry. The SignalChannel executes the X3DH (Extended Triple Diffie-Hellman) algorithm using both long-term identity keys and ephemeral one-time pre-keys to derive a shared secret.

  3. Double-Ratchet Initialization: From the shared secret, the channel initializes a Double-Ratchet state. Each message transmission advances the ratchet, generating unique AES-256-GCM keys for every communication round.

  4. Encrypted Transport: The ciphertext travels through the standard Channel::send_message RPC. The remote SignalChannel applies the ratchet in reverse to decrypt the payload before delivery to the recipient agent.

  5. Forward Secrecy: Because the Double-Ratchet updates key material with every message, compromise of a long-term identity key does not expose historical traffic.

Implementation Details

Channel Registration

In src/openhuman/channels/mod.rs, the core maintains a ChannelRegistry that maps protocols to their implementations. The Signal backend registers under the ChannelId::Signal enum variant. When the channels Cargo feature is enabled, the registry loads the full cryptographic implementation; otherwise, it compiles a no-op stub to satisfy type requirements without linking heavy dependencies.

Identity Key Storage

Long-term cryptographic identities reside in src/openhuman/security/keyring/encrypted_store.rs. The EncryptedStore uses platform-specific keyrings (or file-based encryption) to protect the X25519 private key. During agent startup, the core unlocks this store and passes the identity key to the SignalChannel constructor, ensuring the private key never exists in plaintext outside the encrypted boundary.

The SignalChannel Backend

The concrete implementation in src/openhuman/channels/providers/signal.rs simply re-exports tinychannels::providers::signal::SignalChannel. This external crate handles:

  • HTTP communication with the signal-cli bridge
  • X3DH handshake execution
  • Double-Ratchet state machine management
  • AES-256-GCM encryption and decryption

By isolating the protocol logic in tinychannels, OpenHuman ensures the core repository remains focused on agent orchestration while leveraging audited cryptographic primitives.

Sending and Receiving Encrypted Messages

Agents interact with the encryption layer through the core RPC client, remaining unaware of the underlying cryptographic operations.

Sending a Secret

use openhuman_core::rpc::client::CoreRpcClient;
use openhuman_core::channel::ChannelId;

async fn send_secret(
    rpc: &CoreRpcClient,
    recipient: &str,
    payload: &[u8],
) -> Result<(), anyhow::Error> {
    // Select the Signal protocol backend
    let channel = ChannelId::Signal;
    
    // The core handles X3DH, ratchet advancement, and AES-256-GCM encryption
    rpc.send_message(channel, recipient, payload.to_vec()).await?;
    Ok(())
}

The send_message handler defined in src/openhuman/api/rest.rs (line 946) serializes the payload, retrieves the active SignalChannel session for the recipient, and transmits the encrypted ciphertext to the Signal network.

Receiving and Decrypting

async fn handle_incoming(
    rpc: &CoreRpcClient,
) -> Result<(), anyhow::Error> {
    // Blocks until the next message arrives; decryption happens internally
    let msg = rpc.receive_message().await?;
    
    // msg.payload contains the plaintext bytes
    process_payload(msg.payload).await?;
    Ok(())
}

The SignalChannel::receive_message method in the tinychannels crate automatically applies the reverse Double-Ratchet operation and AES-256-GCM decryption before returning data to the agent.

Security Model

The Signal-protocol integration provides forward secrecy and post-quantum resilience through X25519 elliptic-curve cryptography. Each session derives independent encryption keys, ensuring that:

  • Past communications remain secure even if long-term keys leak
  • No single compromised message exposes the session state
  • The host process never observes plaintext during transit

Communication is opt-in via the channels Cargo feature. When disabled, the core compiles stub implementations that return unimplemented! errors, allowing lightweight builds that exclude cryptographic dependencies.

Summary

  • OpenHuman agents communicate via Signal-protocol E2E sessions implemented through the tinychannels crate’s SignalChannel.
  • X25519 identity keys are generated per agent and stored encrypted in src/openhuman/security/keyring/encrypted_store.rs.
  • The X3DH handshake and Double-Ratchet algorithm generate per-message AES-256-GCM keys, providing forward secrecy.
  • Agents invoke rpc.send_message(ChannelId::Signal, ...) defined in src/openhuman/api/rest.rs, while decryption occurs transparently in the channel backend.
  • The implementation is gated behind the channels feature flag to support minimal builds.

Frequently Asked Questions

How does OpenHuman establish the initial encryption keys between agents?

OpenHuman uses the X3DH (Extended Triple Diffie-Hellman) key agreement protocol. When Agent A initiates contact, it retrieves Agent B’s public X25519 identity key from the core’s Channel Registry, combines it with ephemeral one-time pre-keys from the Signal server, and derives a shared secret that seeds the Double-Ratchet state machine. This handshake occurs entirely within the SignalChannel implementation in the tinychannels crate.

What cryptographic algorithms protect messages in transit?

Messages are protected by AES-256-GCM for authenticated encryption. The encryption keys are derived from the Double-Ratchet algorithm, which itself is initialized via X3DH using X25519 elliptic-curve keys. This combination ensures forward secrecy and cryptographic integrity for every message exchanged.

Where are the long-term identity keys stored?

Each agent’s permanent X25519 key pair resides in the EncryptedStore located at src/openhuman/security/keyring/encrypted_store.rs. The private key remains encrypted at rest using the host platform’s keyring or a file-based master key, and is only decrypted briefly during agent initialization to initialize the Signal session state.

Is Signal-protocol communication mandatory for all agents?

No. The Signal-protocol channel is opt-in and controlled by the channels Cargo feature at compile time. When enabled, agents can select ChannelId::Signal for encrypted communication. When disabled, the codebase compiles without the tinychannels dependency, and the Signal backend exists as a no-op stub that prevents build failures while excluding cryptographic functionality.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →