Most v3 apps should use the dedicated service clients directly: KeyValueClient, VectorClient, StreamClient, and @agentuity/storage. When a feature needs another backend, hide that decision behind a small app-owned interface.
import { KeyValueClient } from '@agentuity/keyvalue';
interface Session {
readonly userId: string;
readonly email: string;
}
interface SessionStore {
get(id: string): Promise<Session | undefined>;
set(id: string, session: Session): Promise<void>;
}
class AgentuitySessionStore implements SessionStore {
readonly #kv = new KeyValueClient();
async get(id: string): Promise<Session | undefined> {
const result = await this.#kv.get<Session>('sessions', id);
return result.exists ? result.data : undefined;
}
async set(id: string, session: Session): Promise<void> {
await this.#kv.set('sessions', id, session, {
ttl: 60 * 60 * 24 * 30,
});
}
}Keep the app interface as small as the feature needs. A session store usually does not need search, stats, namespace deletion, or the full storage API.
When to customize
| Need | Pattern |
|---|---|
| use Agentuity-managed storage | instantiate the dedicated client directly |
| swap Redis, Postgres, or another backend | define an app-owned interface and provide another implementation |
| share the complete Agentuity method shape | implement the exported storage interface |
| use a different S3-compatible object store | pass explicit credentials to @agentuity/storage or a runtime-native S3 client |
| test without managed credentials | use fakes, mocks, or Bun-only local implementations |
Dedicated Clients
Direct clients are the default v3 path. They read AGENTUITY_SDK_KEY, then AGENTUITY_CLI_KEY, and also accept explicit apiKey, url, orgId, and logger options.
import { KeyValueClient } from '@agentuity/keyvalue';
import { StreamClient } from '@agentuity/stream';
import { VectorClient } from '@agentuity/vector';
const kv = new KeyValueClient();
const streams = new StreamClient();
const vector = new VectorClient();
const kvForAnotherOrg = new KeyValueClient({
orgId: 'org_123',
});Use constructor options when you are writing admin scripts, tests, or multi-org tools. Most application code should let the client read project environment variables.
Full Storage Interfaces
The dedicated packages re-export the complete service interfaces from @agentuity/core. Use these when you want a drop-in adapter with the same method names as Agentuity storage.
import type { KeyValueStorage } from '@agentuity/keyvalue';
import type { StreamStorage } from '@agentuity/stream';
import type { VectorStorage } from '@agentuity/vector';| Interface | Core methods |
|---|---|
KeyValueStorage | get, set, delete, search, getKeys, getStats, namespace methods |
VectorStorage | upsert, get, getMany, search, delete, exists, stats and namespace methods |
StreamStorage | create, get, download, list, delete |
Only implement these full interfaces when callers need full compatibility. For most apps, a smaller interface is easier to test and harder to misuse.
Object Storage
Object storage is not part of the key-value, vector, or stream interface set. Use @agentuity/storage for the Agentuity bucket linked to your project. In Bun-only code, use S3Client when you need Bun-specific helpers or another S3-compatible provider.
import { S3Client } from 'bun';
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`${name} is required`);
}
return value;
}
const assets = new S3Client({
accessKeyId: requireEnv('ASSETS_ACCESS_KEY'),
secretAccessKey: requireEnv('ASSETS_SECRET_KEY'),
bucket: 'assets',
endpoint: 'https://example.r2.cloudflarestorage.com',
});
await assets.write('healthcheck.txt', 'ok', { type: 'text/plain' });Use a runtime-native S3-compatible client instead when the code does not run on Bun.
Local Tests
For unit tests, prefer a small fake that matches your app-owned interface. That keeps tests independent from managed credentials and avoids pulling in storage behavior your feature does not use.
class MemorySessionStore implements SessionStore {
readonly #sessions = new Map<string, Session>();
async get(id: string): Promise<Session | undefined> {
return this.#sessions.get(id);
}
async set(id: string, session: Session): Promise<void> {
this.#sessions.set(id, session);
}
}In-memory or mock implementations are useful for constructor and API-shape tests. Validate managed-service behavior, search relevance, TTLs, and permissions against the managed service before relying on those details.
Next Steps
- Key-Value Storage: store exact-key cache and state
- Vector Storage: use semantic search when exact keys are not enough
- Durable Streams: write generated output that remains readable by URL
- Object Storage: store files and binary blobs with
@agentuity/storage - API Reference: inspect generated REST details before matching a full service interface