Skip to content

Work with the Vault SDK

@cef-ai/vault-sdk is the client for talking to a vault from outside an agent: a web or mobile app, a console, a server job. It signs requests, handles canonicalization, and wraps the vault’s API in typed handles. It runs in the browser and in Node.

Code running inside an agent uses ctx from @cef-ai/agent-sdk (ctx.vault, ctx.cubby, ctx.memory, ctx.models), not this SDK.

Install

Terminal window
npm install @cef-ai/vault-sdk@5.5.0

1. Construct the client

import { VaultSDK, CereWallet } from "@cef-ai/vault-sdk";
const sdk = new VaultSDK({
endpoint: config.vaultUrl, // vault API
garEndpoint: config.garUrl, // agreement registry; needed for agents.connect
chainUrl: config.chainUrl, // needed for vault.ensure() with a signer
signer: await CereWallet.fromMnemonic(mnemonic),
});
Field Required Purpose
endpoint yes Vault API base URL.
signer one of signer / auth Signs every request with the person’s key.
auth one of signer / auth A custom auth provider, e.g. new DelegationAuthProvider(token). Overrides signer.
garEndpoint for agents.connect Agreement registry the connect flow submits the signed agreement to.
chainUrl for vault.ensure() with a signer Used to grant the vault service write delegation before the vault is claimed. Can also be passed per call.
s3GatewayAuthInfoUrl Storage gateway the onboarding check talks to.
marketplaceEndpoint Marketplace API for sdk.marketplace reads.
fetch, timeoutMs Custom fetch; per-request timeout (default 30 s).

Signers

Environment Signer
Browser or mobile, from a mnemonic await CereWallet.fromMnemonic(mnemonic)
From a JSON keystore await CereWallet.fromKeystore(json, passphrase)
Server, from a raw 32-byte ed25519 seed await KeypairWallet.fromSeed(seed)
Acting on someone’s behalf without their key auth: new DelegationAuthProvider(token)

Any object implementing the Signer interface (type, address, publicKey, isReady(), sign(bytes)) works.

2. Open a vault

const vault = await sdk.vault.ensure({
name: "My Vault",
onProgress: (e) => console.log(e.kind),
});

ensure() is idempotent: it returns the signer’s vault, claiming it on first use. With a signer it checks onboarding status, grants the vault service write delegation (this needs chainUrl), then claims the vault.

onProgress kind Meaning
inspecting-wallet Onboarding status check in flight.
funding-wallet Development networks only: the wallet is being funded.
gateway-authorization-required The wallet still needs its storage-gateway authorization. ensure() throws OnboardingRequiredError next.
authorizing-vault Granting the vault service write delegation.
provisioning-vault Claiming the vault.

On OnboardingRequiredError, authorize the gateway with @cef-ai/account’s provisioning.ensure({ signer, chainUrl, gateway }), then call ensure() again.

Other ways to open a vault:

Call Returns
sdk.vault.current() Your own vault, without onboarding.
sdk.vault.byId(vaultId) A vault you are a member of.

3. Scopes

await vault.scopes.create({ name: "health", displayName: "Health" });
const scopes = await vault.scopes.list();

vault.scopes also has get(name), update(name, patch), and delete(name). See Vaults for naming rules.

4. Publish and follow events

const scope = vault.scope("default");
const { eventId } = await scope.publish({
type: "user_message",
context: "thread-123",
payload: { text: "hi" },
});
const sub = scope.subscribe(
{ context: "thread-123", types: ["reply"] },
(event) => console.log(event.type, event.payload),
);
// later
sub.unsubscribe();
await sub.closed;
  • publish sends one event and throws if the vault rejects it. role defaults to user; timestamp defaults to now.
  • subscribe polls (default every 1000 ms, 100 events per page), de-duplicates by event id, and reports poll errors to onError. Set maxBackoffMs to back off while polls fail.
  • subscribeAll({ types }, handler) follows every stream in the scope and picks up new ones (every 30 s by default; refreshIntervalMs).
  • scope.streams.list() and scope.stream(context).events.list({ cursor, limit }) page history.

To start a workflow from your app, publish workflow.start with target: "<agentServicePubkey>:<workflowId>".

5. Store objects

const res = await scope.objects.upload("notes/2026-10-08.txt", bytes, {
contentType: "text/plain",
});
const data = await scope.objects.get(res.vaultPath);

scope.objects also has head, presignedUrl(path, { ttlSeconds }), list({ prefix }), and delete. Pass publishEvent: { type, context, payload } to upload to announce the object in the same call; the event’s payload gets vaultPath.

6. Connect an agent

const connection = await vault.agents.connect({
agentId: "<agentServicePubkey>:<alias>",
scope: "default", // or scopes: ["support", "sales"]
settings: { language: "en" },
});
console.log(connection.status); // "provisioning" → "active"

connect builds one agreement for the scope set, signs it, submits it to the agreement registry, and creates the connection. It needs garEndpoint and a signer. Optional fields: ceiling ({ gpuUnits?, a2aTokens? }) and bundleCid (the bundle the person reviewed). See Connections and consent.

Call Does
vault.agents.list(), vault.agents.get(agentId) Read connections.
connection.update(settings) Change the settings values.
connection.setCeiling({ gpuUnits, a2aTokens }) Change the spend ceiling; {} clears it.
connection.disconnect() Remove the connection; cubby data is kept.

7. Read cubbies

A service’s cubbies are global within the vault. Address them by the service’s public key:

const { columns, rows } = await vault
.service(agentServicePubkey)
.cubby("history")
.query("SELECT text, ts FROM messages ORDER BY ts DESC LIMIT ?", [20]);

.exec(sql, params) writes. vault.service(pubkey).list() lists the service’s cubbies.

8. Read the Memory Bank

const page = await vault.memory.search("refund approval", { scope: "finance", limit: 20 });

Also list(type?, opts), get(id), neighbours(id, opts), and countByType(opts). Results are { columns, rows, cursor? }; pass cursor back for the next page. Writes are agent-only. See Memory Bank.

9. Follow runs

const jobs = await vault.jobs.list({ limit: 20 });
const job = vault.jobs.get(jobs.items[0].jobId);
const tasks = await job.tasks.list();
const logs = await job.tasks.logs(tasks.items[0].taskId, { limit: 100 });

connection.jobs.list() narrows to one agent. job.tasks.subscribe({ since }, handler) follows new tasks.

Handle errors by code

import { VaultRequestError, BundleChangedError } from "@cef-ai/vault-sdk";
try {
await vault.agents.connect({ agentId, scope: "default" });
} catch (e) {
if (e instanceof BundleChangedError) {
// show the person the current bundle and ask again
} else if (e instanceof VaultRequestError && e.code === "AGENT_ALREADY_CONNECTED") {
// reuse the existing connection
} else {
throw e;
}
}
Error Meaning
VaultRequestError Any error from the vault; read .code, .status, .retryable.
BundleChangedError, ReconsentRequiredError The connect’s bundle consent does not match.
VaultSignerRequiredError The call needs a signer and none was configured.
VaultNotImplementedError The vault answered 501.
OnboardingRequiredError The wallet needs gateway authorization before ensure().
OnboardingTimeoutError Onboarding did not finish in time.