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

> Discover how OpenHuman secures agent-to-agent communication with Signal-protocol E2E sessions. Learn about Double-Ratchet, X25519 keys, and AES-256-GCM encryption for robust data protection.

- Repository: [Tiny Humans/openhuman](https://github.com/tinyhumansai/openhuman)
- Tags: deep-dive
- Published: 2026-09-01

---

**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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/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

```rust
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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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`](https://github.com/tinyhumansai/openhuman/blob/main/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.