Developer Docs

Integrate Arkiv-powered sovereign AI memory into your application

Exo stores all AI memory as encrypted entities on the Arkiv Braga testnet . Each entity is owned by the user's wallet, encrypted client-side with AES-256-GCM, and queryable via the Arkiv SDK. No server ever sees plaintext.

Chain

Braga (60138453102)

RPC

braga.hoodi.arkiv.network/rpc

Explorer

explorer.braga.hoodi.arkiv.network

SDK

@arkiv-network/sdk@0.6.8

Auth

Privy embedded wallet

Encryption

AES-256-GCM + HKDF

Exo uses 6 entity types, all tagged with app=exo:v1 for namespacing.

semantic

Verified facts, skills, preferences, and knowledge

Permanent
episodic

Session summaries with decisions and open threads

90 days
instruction

Standing rules for AI behavior across sessions

Permanent
document

Full documents, blog posts, code references

365 days
grant

Time-scoped access grants for memory sharing

Configurable
snapshot

Point-in-time memory state with AI narrative

365 days

Install and configure the Arkiv SDK client.

npm install @arkiv-network/sdk@0.6.8
import { createPublicClient, http } from "@arkiv-network/sdk";
import { braga } from "@arkiv-network/sdk/chains";

export const publicClient = createPublicClient({
  chain: braga,
  transport: http(),
});

Use buildQuery() with where(), ownedBy(), and predicate helpers.

import { eq, gte, and } from "@arkiv-network/sdk/query";

// Fetch all semantic memories for a wallet
const result = await publicClient
  .buildQuery()
  .where([
    eq("app", "exo:v1"),
    eq("type", "semantic"),
  ])
  .ownedBy("0xYOUR_WALLET_ADDRESS")
  .orderBy("importance", "number", "desc")
  .withPayload(true)
  .withAttributes(true)
  .withMetadata(true)
  .limit(50)
  .fetch();

const entities = result.entities;
// Count entities by type
const count = await publicClient
  .buildQuery()
  .where([eq("app", "exo:v1"), eq("type", "instruction")])
  .ownedBy(walletAddress)
  .count();

Use a wallet client to create entities. Payloads must be Uint8Array via jsonToPayload().

import { createWalletClient, http, jsonToPayload } from "@arkiv-network/sdk";
import { braga } from "@arkiv-network/sdk/chains";

const walletClient = createWalletClient({
  chain: braga,
  transport: http(),
  account: yourAccount,
});

const result = await walletClient.createEntity({
  payload: jsonToPayload({
    // your encrypted payload object
    iv: "...",
    ciphertext: "...",
    authTag: "...",
    version: "aes-256-gcm-v1",
  }),
  contentType: "application/json",
  expiresIn: 0, // 0 = permanent
  attributes: [
    { key: "app", value: "exo:v1" },
    { key: "type", value: "semantic" },
    { key: "topic", value: "engineering" },
    { key: "importance", value: 80 },
  ],
});

console.log(result.txHash, result.entityKey);

Master key is derived from a wallet signature using HKDF. All payloads are encrypted before leaving the browser. The server never sees plaintext.

// 1. Derive master key from wallet signature
const message = `Exo sovereign memory key derivation v1 — ${address}`;
const signature = await signMessage(message);

const sigBytes = new Uint8Array(
  signature.slice(2).match(/.{1,2}/g)!.map(b => parseInt(b, 16))
);

const baseKey = await crypto.subtle.importKey(
  "raw", sigBytes, { name: "HKDF" }, false, ["deriveKey"]
);

const masterKey = await crypto.subtle.deriveKey(
  {
    name: "HKDF",
    hash: "SHA-256",
    salt: new TextEncoder().encode(address),
    info: new TextEncoder().encode("exo-master-key-v1"),
  },
  baseKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);
// 2. Encrypt payload
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(JSON.stringify(data));
const encrypted = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  masterKey,
  encoded
);

// Last 16 bytes are the auth tag
const ciphertext = new Uint8Array(encrypted).slice(0, -16);
const authTag = new Uint8Array(encrypted).slice(-16);

const payload = {
  iv: btoa(String.fromCharCode(...iv)),
  ciphertext: btoa(String.fromCharCode(...ciphertext)),
  authTag: btoa(String.fromCharCode(...authTag)),
  version: "aes-256-gcm-v1",
};

Drop-in hooks for reading and writing memory in your React app.

// Reading memories
import { useSemanticMemory } from "@/hooks/useSemanticMemory";

function MyComponent() {
  const { walletAddress, masterKey } = useExoAuth();
  const { data: memories, isLoading } = useSemanticMemory(
    walletAddress,
    masterKey
  );

  return memories?.map(m => (
    <div key={m.entityKey}>
      <p>{m.payload.content}</p>
      <span>{m.topic} · importance {m.importance}</span>
    </div>
  ));
}
// Writing a memory
import { useCreateSemanticMemory } from "@/hooks/useSemanticMemory";

function AddMemory() {
  const { walletAddress, masterKey, getWalletClient } = useExoAuth();
  const createMemory = useCreateSemanticMemory(walletAddress, masterKey);

  const handleSave = async () => {
    await createMemory.mutateAsync({
      topic: "engineering",
      importance: 80,
      agentId: "claude",
      confirmed: true,
      payload: {
        content: "The user prefers TypeScript strict mode.",
        source: "manual",
        confidence: 1.0,
        tags: ["typescript", "preferences"],
        relatedKeys: [],
      },
      getWalletClient,
    });
  };
}

Load Arkiv context and inject it into your AI system prompt.

import { buildSystemPrompt } from "@/lib/ai/systemPrompt";

// Build context from Arkiv
const exoContext = {
  instructions: activeInstructions,    // from fetchInstructions()
  semanticMemories: confirmedFacts,    // from fetchConfirmedSemanticMemories()
  recentEpisodes: lastSessions,        // from fetchRecentEpisodic()
  documents: contextDocs,              // from fetchDocuments()
  userAddress: walletAddress,
};

// Use in AI call
const response = await anthropic.messages.create({
  model: "claude-opus-4-7",
  system: buildSystemPrompt(exoContext),
  messages: [{ role: "user", content: userMessage }],
});

Built for the ETHns × Arkiv Hackathon

All source code available on GitHub · Deployed on Vercel · Data on Braga testnet