Skip to content

Build a Node vault client

In this tutorial you build a Node.js script that talks to a vault from outside the platform. By the end it opens a vault, connects an agent, publishes a message, reads the agent’s reply, reads the agent’s cubby, and prints the run’s task logs.

The agent here handles user_message events, writes each message to a history cubby with a messages table, and publishes a reply event. See Write an agent to build one. Any agent works the same way; change the event types and the SQL.

Prerequisites

  • Node.js with ES module support, and a project with "type": "module".
  • A 32-byte ed25519 seed for the wallet the script acts as.
  • The vault API, agreement registry, and chain URLs for your environment.
  • The agent’s id, <agentServicePubkey>:<alias>, from ROC.

Step 1: Install

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

Step 2: Construct the SDK with a server signer

client.ts
import { VaultSDK, KeypairWallet } from "@cef-ai/vault-sdk";
const seed = Uint8Array.from(Buffer.from(process.env.SEED_HEX!, "hex"));
const sdk = new VaultSDK({
endpoint: process.env.VAULT_URL!,
garEndpoint: process.env.GAR_URL!,
chainUrl: process.env.CHAIN_URL!,
signer: await KeypairWallet.fromSeed(seed),
});

KeypairWallet signs every request directly, with no prompt. garEndpoint and the signer together let the script sign the connect agreement in Step 4.

Step 3: Open the vault

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

The first run claims the vault; later runs return it. If you see OnboardingRequiredError, authorize the storage gateway with @cef-ai/account’s provisioning.ensure, then run again.

Step 4: Connect the agent

import { VaultRequestError } from "@cef-ai/vault-sdk";
const agentId = process.env.AGENT_ID!;
try {
await vault.agents.connect({ agentId, scope: "default" });
} catch (e) {
if (!(e instanceof VaultRequestError && e.code === "AGENT_ALREADY_CONNECTED")) throw e;
}
let connection = await vault.agents.get(agentId);
while (connection.status === "provisioning") {
await new Promise((r) => setTimeout(r, 1000));
connection = await vault.agents.get(agentId);
}
console.log("connection", connection.status);

connect signs one agreement with your key, submits it, and the vault provisions the agent’s cubbies. The connection moves from provisioning to active. A second run finds it already connected.

Step 5: Send a message and wait for the reply

const scope = vault.scope("default");
const context = `thread-${Date.now()}`;
const reply = new Promise<string>((resolve) => {
const sub = scope.subscribe<{ text: string }>(
{ context, types: ["reply"] },
(event) => {
sub.unsubscribe();
resolve(event.payload.text);
},
);
});
await scope.publish({ type: "user_message", context, payload: { text: "hi" } });
console.log("agent said:", await reply);

Subscribe first, then publish, so the reply cannot slip past. The subscription polls the stream every second.

Step 6: Read the agent’s cubby

const asPubKey = agentId.split(":")[0]!;
const { columns, rows } = await vault
.service(asPubKey)
.cubby("history")
.query("SELECT text, ts FROM messages ORDER BY ts DESC LIMIT 5");
console.log(columns, rows);

A service’s cubbies are global within the vault, addressed by the service’s public key and the cubby alias.

Step 7: Print the run’s logs

const jobs = await connection.jobs.list({ limit: 1 });
const job = vault.jobs.get(jobs.items[0]!.jobId);
const tasks = await job.tasks.list();
for (const t of tasks.items) {
const { logs } = await job.tasks.logs(t.taskId);
for (const line of logs) console.log(t.status, line.message);
}

Each event your script published became a Task in the agent’s Job; the log lines are what the agent wrote with console.*.

Run it

Terminal window
SEED_HEX=… VAULT_URL=… GAR_URL=… CHAIN_URL=… AGENT_ID=… npx tsx client.ts

What you built

A client that does, from outside the platform, what ROC does for a person: open a vault, connect an agent under a signed agreement, talk to it through events, and read what it stored and logged.