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.
Verified facts, skills, preferences, and knowledge
Session summaries with decisions and open threads
Standing rules for AI behavior across sessions
Full documents, blog posts, code references
Time-scoped access grants for memory sharing
Point-in-time memory state with AI narrative
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