SAP Explorer Docs
SDK Reference

SessionManager

Unified memory API that orchestrates vault, session, and ledger operations behind a single interface.

SessionManager

The SessionManager is the recommended way to work with agent memory. It wraps the vault and ledger modules into a single cohesive API that handles all setup automatically.

Access via client.session.

How It Works

client.session.start("conversation-123")

  ├── 1. Ensure vault exists       (skip if already created)
  ├── 2. Open session              (skip if already open)
  └── 3. Initialize ledger         (skip if already initialized)


SessionContext { sessionPda, ledgerPda, vaultPda, agentPda, ... }

start() is idempotent. It skips any step that has already been completed on-chain. Safe to call multiple times for the same session ID.

Start a Session

const ctx = await client.session.start("conversation-123");

// ctx contains all derived PDAs:
// ctx.sessionId     "conversation-123"
// ctx.sessionHash   SHA-256 of the session ID
// ctx.agentPda      derived from wallet
// ctx.vaultPda      derived from agent
// ctx.sessionPda    derived from vault + session hash
// ctx.ledgerPda     derived from session
// ctx.wallet        provider wallet

Write Data

const result = await client.session.write(ctx, "User requested SOL to USDC swap");

console.log("TX:", result.txSignature);
console.log("Hash:", result.contentHash);
console.log("Size:", result.dataSize, "bytes");

// Accepts string, Buffer, or Uint8Array
await client.session.write(ctx, Buffer.from([0x01, 0x02, 0x03]));

Read Latest

Reads from the ring buffer using getAccountInfo(). This is free on any RPC and does not require an archival node.

const entries = await client.session.readLatest(ctx);

for (const entry of entries) {
  console.log(`[${entry.size} bytes] ${entry.text}`);
}

Seal to Permanent Archive

Sealing creates a permanent LedgerPage PDA containing a snapshot of the current ring buffer. The ring buffer is then reset for new writes.

const sealResult = await client.session.seal(ctx);

console.log("Sealed page index:", sealResult.pageIndex);
console.log("TX:", sealResult.txSignature);

Cost: approximately 0.031 SOL per sealed page.

Read Sealed Pages

// Read a specific page
const pageEntries = await client.session.readPage(ctx, 0);

// Read all data: sealed pages (oldest) + ring buffer (latest)
const allEntries = await client.session.readAll(ctx);

Get Session Status

const status = await client.session.getStatus(ctx);

console.log("Vault exists:", status.vaultExists);
console.log("Session exists:", status.sessionExists);
console.log("Ledger exists:", status.ledgerExists);
console.log("Closed:", status.isClosed);
console.log("Total entries:", status.totalEntries);
console.log("Total data:", status.totalDataSize, "bytes");
console.log("Sealed pages:", status.numPages);
console.log("Merkle root:", status.merkleRoot);

Close Session

Full teardown that closes the ledger and session. Reclaims all rent. Idempotent.

await client.session.close(ctx);

Derive Context Without Creating

If you need the PDAs without creating anything on-chain:

// Pure computation, no network calls
const ctx = client.session.deriveContext("conversation-123");

Complete Example

const client = SapClient.from(AnchorProvider.env());

// Start (creates vault + session + ledger if needed)
const ctx = await client.session.start("conv-001");

// Write conversation data
await client.session.write(ctx, "User: What is the price of SOL?");
await client.session.write(ctx, "Agent: SOL is trading at $142.50");
await client.session.write(ctx, "User: Swap 10 SOL to USDC");
await client.session.write(ctx, "Agent: Swap executed. Received 1,425 USDC");

// Read back
const messages = await client.session.readLatest(ctx);
messages.forEach((m) => console.log(m.text));

// Archive to permanent storage
await client.session.seal(ctx);

// Check status
const status = await client.session.getStatus(ctx);
console.log(`${status.totalEntries} entries, ${status.numPages} sealed pages`);

// Cleanup when done
await client.session.close(ctx);