Memory Sharing
The SDK provides a simple API for creating and using grants to share memories between agents.
Creating Grants
Use createGrant() to generate a signed grant that allows another agent to access your memories:
import { fromPrivateKey } from "@zkstash/sdk/rest";
const client = await fromPrivateKey(process.env.PRIVATE_KEY!);
const { grant, shareCode } = await client.createGrant({
grantee: "0xAgentBAddress...", // Recipient's wallet address
agentId: "my-agent", // Optional: limit to specific agent
duration: "7d", // How long the grant is valid
});Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
grantee | string | Yes | Wallet address of the agent receiving access |
agentId | string | No | Limit access to a specific agent’s memories |
duration | string | Yes | Grant validity period (e.g., "1h", "24h", "7d", "30d") |
Return Value
{
grant: SignedGrant; // The signed grant object
shareCode: string; // Base64-encoded string starting with "zkg1_"
}The shareCode is a compact, shareable representation of the grant that can be easily transmitted.
Using Grants
Adding Grants to Client Instance
Add grants to your client for automatic inclusion in all searches:
import { fromPrivateKey } from "@zkstash/sdk/rest";
const client = await fromPrivateKey(process.env.PRIVATE_KEY!);
// Add using share code (string starting with "zkg1_")
client.addGrant(shareCode);
// Or add using SignedGrant object
client.addGrant(grantObject);Searching with Grants
Once grants are added, search results automatically include shared memories:
const results = await client.searchMemories({
query: "research findings",
filters: { agentId: "researcher" },
});
// Results include source annotation
results.memories.forEach((m) => {
console.log(m.source); // "own" | "shared"
if (m.source === "shared") {
console.log(`From: ${m.grantor}`);
}
});Search Scope
Control which namespaces to search using the scope option:
// Search only your own memories (ignores all grants)
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ scope: "own" }
);
// Search only shared memories (requires at least one valid grant)
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ scope: "shared" }
);
// Search both own and shared (default)
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ scope: "all" } // or omit - "all" is the default
);Per-Request Grants
Pass grants for a single request without adding them to the instance:
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ grants: [grantFromAgentA, grantFromAgentB] }
);Grant Management
Remove a Grant
client.removeGrant(grantObject);List Instance Grants
const grants = client.getInstanceGrants();
console.log(`Active grants: ${grants.length}`);Working with Share Codes
The SDK provides utilities to convert between grant objects and share codes:
import { grantToShareCode, grantFromShareCode } from "@zkstash/sdk/utils";
// Convert grant to share code
const shareCode = grantToShareCode(grant);
// "zkg1_eyJwIjp7ImYiOiIweC4uLiIs..."
// Convert share code back to grant
const grant = grantFromShareCode(shareCode);Example: Research Agent Collaboration
Here’s a complete example of two agents sharing memories:
import { fromPrivateKey } from "@zkstash/sdk/rest";
// === Agent A: The Researcher ===
const researcher = await fromPrivateKey(process.env.RESEARCHER_KEY!);
// Store some research findings
await researcher.storeMemories("research-bot", [
{
kind: "ResearchFinding",
data: {
topic: "AI memory systems",
summary: "Long-term memory improves agent performance by 40%",
source: "arxiv:2024.12345",
},
},
]);
// Create a grant for the coordinator
const { shareCode } = await researcher.createGrant({
grantee: "0xCoordinatorAddress...",
agentId: "research-bot",
duration: "30d",
});
console.log("Send this to coordinator:", shareCode);
// === Agent B: The Coordinator ===
const coordinator = await fromPrivateKey(process.env.COORDINATOR_KEY!);
// Add the grant from researcher
coordinator.addGrant(shareCode);
// Search includes researcher's memories
const results = await coordinator.searchMemories({
query: "AI memory performance",
filters: { agentId: "research-bot" },
});
results.memories.forEach((m) => {
if (m.source === "shared") {
console.log(`Research from ${m.grantor}:`, m.data);
}
});Chain Support
Grants work with both EVM and Solana wallets:
// EVM (Base, Ethereum, etc.)
const evmClient = await fromPrivateKey("0x...");
const { grant } = await evmClient.createGrant({
grantee: "0x...", // EVM address
duration: "7d",
});
// grant.c === "evm"
// Solana
const solanaClient = await fromPrivateKey("base58PrivateKey...");
const { grant: solGrant } = await solanaClient.createGrant({
grantee: "base58PublicKey...", // Solana address
duration: "7d",
});
// solGrant.c === "sol"API Key Grantees
If the grantee uses API key authentication, use their Clerk userId as the grantee:
// Grant for an API key user
const { grant } = await grantor.createGrant({
grantee: "user_2abc123xyz...", // Clerk userId (shown in dashboard)
duration: "7d",
});The Clerk userId is displayed in the zkStash dashboard. When verifying, zkStash checks if the grantee matches either the caller’s wallet address (for wallet auth) or their Clerk userId (for API key auth).
Best Practices
- Use short durations when possible—grants cannot be revoked
- Scope to specific agents using
agentIdto limit access - Don’t log share codes in production—treat them like passwords
- Validate grantee addresses before creating grants
- Use per-request grants for one-time access instead of adding to instance
Error Handling
try {
const { grant } = await client.createGrant({
grantee: "0x...",
duration: "invalid", // Invalid duration
});
} catch (error) {
console.error("Failed to create grant:", error.message);
}
// Searching with scope="shared" requires grants
await client.searchMemories(
{ query: "...", filters: { agentId: "..." } },
{ scope: "shared" } // Throws if no valid grants
);
// Error: "scope='shared' requires at least one valid grant"