---
name: eternal-memory
description: "Cloud persistence for agent memories, data, and embeddings — survives sandbox lifecycle events, enables cross-agent knowledge sharing"
compatibility: "Phala Network TEE (CVM or WASM runtime)"
metadata:
  platform: phala
  version: "0.3.0"
---

## Eternal Memory — Phala TEE Integration

Cloud persistence API for Phala agents. Complements enclave-local storage with durable, portable, cross-agent memory.

**Base URL:** `https://api.eternal-memory.xyz`

**First 100 operations free (up to 10 MB total) — no USDC required to start.**

### Endpoints & Pricing

| Method | Endpoint | Cost (USDC) | Description |
|--------|----------|-------------|-------------|
| `POST` | `/v1/store` | $0.001 + $0.001/KB (+$0.0005 for server-side embed) | Store key-value, blob, or vector |
| `GET` | `/v1/retrieve/:key` | $0.0005 | Retrieve by key |
| `POST` | `/v1/query` | $0.001 (+$0.0005 for server-side embed) | Semantic vector search |
| `GET` | `/v1/list` | $0.0005 | List entries (filterable, `?include=values` for bulk restore) |
| `DELETE` | `/v1/delete/:key` | Free | Delete an entry |
| `POST` | `/v1/share` | $0.001 | Grant access to another agent |
| `GET` | `/v1/export` | $0.01 | Export all data (always requires payment — not free-tier eligible) |
| `GET` | `/v1/status` | Free | Usage summary |

Payment is automatic via x402 (HTTP 402 flow). First 100 paid operations are free per wallet (10 MB total).

---

## CVM Mode (Node.js in TEE)

Phala CVM runs Node.js inside an Intel SGX / TDX enclave. The full TypeScript SDK works here.

### Setup

```typescript
// Option A: Bundled SDK (no npm required)
import { EternalMemory } from 'https://api.eternal-memory.xyz/sdk.js';

// Option B: npm install
// npm install @eternal-memory/sdk ethers
// import { EternalMemory } from '@eternal-memory/sdk';
```

### Key Derivation from Enclave

Derive the agent's private key deterministically from the enclave master key. The key never leaves the TEE:

```typescript
import { createHash } from 'crypto';

function deriveAgentKey(enclaveMasterKey: Buffer, salt: string): string {
  return '0x' + createHash('sha256')
    .update(enclaveMasterKey).update(salt).digest('hex');
}

const memory = new EternalMemory({
  baseUrl: 'https://api.eternal-memory.xyz',
  privateKey: deriveAgentKey(
    process.env.ENCLAVE_MASTER_KEY as unknown as Buffer,
    'eternal-memory-agent-v1',
  ),
});
```

### Usage

All SDK methods work inside CVM — including encrypted storage (Node.js `crypto` available):

```typescript
// Store with encryption
await memory.store('enclave-state', { model: 'llama-3', step: 4200 }, {
  namespace: 'soul', encrypt: true,
});

// Retrieve and auto-decrypt
const { data } = await memory.retrieve('enclave-state', 'soul');

// Semantic search — server-side embedding
const results = await memory.query(undefined, { queryText: 'what patterns have I learned?', threshold: 0.7 });

// Bulk restore a namespace
const all = await memory.list({ namespace: 'soul', include: 'values' });

// Share with a child enclave
await memory.share({ granteeId: '0xChildEnclave...', accessLevel: 'inherit' });
```

### TEE Security Notes

- Private key derived inside the enclave, never exposed to host
- All signing (ERC-8004 auth, EIP-3009 payments) happens within the TEE boundary
- Use `encrypt: true` for data that should be encrypted client-side before leaving the enclave
- Enclave attestation proves key derivation code is genuine

---

## WASM Mode (pink_extension)

Phala's WASM runtime uses `pink_extension` for HTTP — no Node.js APIs. Use raw HTTP calls.

**Note:** The browser SDK bundle (`/sdk-browser.js`) does not apply here — WASM mode is Rust, not JavaScript.

### Authentication

Construct the EIP-712 auth header inside the TEE WASM environment:

```rust
use pink_extension as pink;
use pink::http_req;
use serde_json::json;
use alloy::sol;
use alloy::signers::local::PrivateKeySigner;
use alloy::signers::Signer;
use alloy::sol_types::SolStruct;
use alloy::primitives::{Address, U256};

sol! {
    #[derive(serde::Serialize)]
    struct AgentRequest {
        address agentId;
        string method;
        string path;
        uint256 timestamp;
        string nonce;
    }
}

fn build_auth_header(signer: &PrivateKeySigner, method: &str, path: &str) -> String {
    let timestamp = pink::ext().untrusted_millis_since_unix_epoch() / 1000;
    let nonce = format!("{:x}", pink::ext().untrusted_millis_since_unix_epoch());

    let request = AgentRequest {
        agentId: signer.address(),
        method: method.to_string(),
        path: path.to_string(),
        timestamp: U256::from(timestamp),
        nonce: nonce.clone(),
    };

    let domain = alloy::sol_types::Eip712Domain {
        name: Some("AgentEternalMemory".into()),
        version: Some("1".into()),
        chain_id: Some(U256::from(8453u64)),
        verifying_contract: None,
        salt: None,
    };

    let signing_hash = request.eip712_signing_hash(&domain);
    let signature = signer.sign_hash_sync(&signing_hash).unwrap();

    let payload = json!({
        "agentId": format!("{:?}", signer.address()),
        "method": method, "path": path,
        "timestamp": timestamp, "nonce": nonce,
        "signature": format!("0x{}", signature),
    });

    format!("ERC8004 {}", base64::encode(serde_json::to_string(&payload).unwrap()))
}
```

### HTTP via pink_extension

All endpoints follow the same pattern — build auth header, make request, parse JSON. Two examples:

```rust
const BASE_URL: &str = "https://api.eternal-memory.xyz";

/// Store a key-value pair (POST)
fn store(signer: &PrivateKeySigner, key: &str, value: serde_json::Value, namespace: &str) -> serde_json::Value {
    let path = "/v1/store";
    let auth = build_auth_header(signer, "POST", path);
    let body = json!({ "key": key, "value": value, "namespace": namespace });

    let response = http_req!(
        method: POST,
        url: format!("{}{}", BASE_URL, path),
        headers: [("Content-Type", "application/json"), ("Authorization", auth.as_str())],
        body: serde_json::to_vec(&body).unwrap(),
    );
    serde_json::from_slice(&response.body).unwrap()
}

/// Retrieve a value by key (GET)
fn retrieve(signer: &PrivateKeySigner, key: &str, namespace: &str) -> serde_json::Value {
    let path = format!("/v1/retrieve/{}", key);
    let auth = build_auth_header(signer, "GET", &path);

    let response = http_req!(
        method: GET,
        url: format!("{}{}?namespace={}", BASE_URL, path, namespace),
        headers: [("Authorization", auth.as_str())],
    );
    serde_json::from_slice(&response.body).unwrap()
}
```

Other endpoints (`list`, `delete`, `query`, `status`, `share`) follow the same pattern — build auth, make request, parse response. See the [OpenAPI spec](https://api.eternal-memory.xyz/v1/docs) for full request/response shapes.

### WASM Limitations

- **No encryption** — Node.js `crypto` not available. Rely on the TEE enclave's isolation or use CVM mode for encrypted storage.
- **No standard `fetch`** — all HTTP goes through `pink_extension::http_req!`
- **Synchronous signing only** — use `sign_hash_sync` instead of async signing

---

### Vector Search

**Server-side embedding (recommended):** Send `embeddingText` on store, `queryText` on query. 384-dim all-MiniLM-L6-v2. Surcharge: +$0.0005. Free tier included.

**Client-side embedding:** Send `embedding` + `embeddedWith` (model name). Model consistency enforced — cross-model queries return empty results.

### Namespaces

- `soul` — identity, SOUL.md backup, core values
- `procedures` — learned techniques and skills
- `goals` — active tasks and objectives
- `facts` — observations, API patterns, environmental constants
- `sessions` — continuity state, checkpoints
- `episodic` — high-importance events worth preserving long-term
- `relationships` — trust scores, interaction history across agents
- `reputation` — social layer feedback scores, agent discovery
