@stackbone/sdk overview
Official TypeScript SDK for Stackbone — every published agent depends on it.
@stackbone/sdk overview
The TypeScript SDK every published Stackbone agent depends on. One client, lazy modules, a uniform
Result<T>envelope, and a protocol-level handshake that fails fast when the agent and the datapath drift apart.
Install
pnpm add @stackbone/sdkThe package is a thin convenience layer over the partner SDKs the
platform provisions for you (postgres-js + Drizzle, @aws-sdk/*,
the openai SDK pointed at OpenRouter, …). You should never have to
add those partner libraries to your package.json directly — the
SDK's barrel re-exports the symbols you need.
Create a client
createClient(config?) returns a StackboneClient. Every module is
built lazily on first access, so createClient() itself is cheap and
side-effect-free.
import { createClient } from '@stackbone/sdk';
const client = createClient();
// Drizzle handle, S3 client, OpenRouter pool, etc. are built only when
// you actually touch the corresponding module.
const rows = await client.database.select().from(leads);config is fully optional. Each field falls back to a documented env
var when omitted. The full shape lives in ClientConfig; the most
common overrides are:
| Config key | Falls back to env | Used by |
|---|---|---|
agentJwt |
STACKBONE_AGENT_JWT |
Every facade HTTP call. |
stackboneApiUrl |
STACKBONE_API_URL |
Contract handshake + every facade HTTP call. |
agentId |
STACKBONE_AGENT_ID |
client.storage key prefix. |
installationId |
STACKBONE_INSTALLATION_ID |
Sent as X-Stackbone-Installation-Id. |
databaseUrl |
DATABASE_URL |
client.rag, observability exporter. |
openrouterKey |
OPENROUTER_API_KEY |
client.ai. |
openrouterBaseUrl |
OPENROUTER_BASE_URL |
client.ai. Defaults to OpenRouter cloud. |
s3.{accessKeyId,…} |
AWS_ACCESS_KEY_ID, … |
client.storage. |
approvalSigningKey |
STACKBONE_APPROVAL_SIGNING_KEY |
client.approval.verify. |
protocolRequired |
agent.yaml.protocol.required |
Contract handshake floor (see below). |
client.database reads STACKBONE_POSTGRES_URL and is intentionally
decoupled from DATABASE_URL — see
client.database → Connection lifecycle.
The modules at a glance
| Module | Purpose | Required capability |
|---|---|---|
client.database |
Drizzle handle bound to the agent's Postgres. | database.postgres_direct |
client.storage |
S3-compatible object storage with per-agent key prefixing. | storage.s3 |
client.ai |
OpenAI-compatible chat, embeddings, image generation, model catalogue. | ai.openrouter |
client.rag |
Parse → chunk → embed → store → retrieve on top of client.database. |
rag.basic |
client.approval |
Human-in-the-loop inbox with HMAC-signed callbacks and an LLM-tool wrapper. | approval.fire_and_forget |
client.secrets |
Read workspace-encrypted secrets registered in the dashboard. | secrets.read_write |
client.config |
Typed reads of dynamic per-agent config set in the dashboard. | config.read_write |
client.queues |
Publish to the platform's pgmq backed by STACKBONE_API_URL. |
queues.pgmq |
client.events |
Emit named events into the agent's run timeline. | events.bus |
client.memory |
Long-term memory (mem0) — not gated on the contract handshake. | — |
client.observability |
OTel exporter helpers and the RunStepsSpanProcessor. |
— |
client.connections, client.prompts |
Reserved surfaces. Currently return not_implemented. |
— |
Every method on every module returns a Result<T>:
import type { Result, SdkError } from '@stackbone/sdk';
type Result<T> = { data: T; error: null } | { data: null; error: SdkError };Narrowing on result.error refines result.data to the success
payload. The SDK never throws for expected failure modes — auth,
validation, missing config, contract drift, partner errors all
surface through error.code. The one deliberate exception is
client.database: its query-builder verbs return Drizzle's native
chainable types (typed rows, not envelopes), and a contract-gate
failure throws a tagged Error instead of swallowing the signature.
The contract handshake
Every Stackbone datapath (the local emulator started by
stackbone dev, and the cloud apps/api) exposes
GET /api/contract returning the negotiated Stackbone Agent
Protocol version and the set of capabilities it advertises:
{
"version": 10,
"minSupported": 1,
"capabilities": [
"database.postgres_direct",
"rag.basic",
"rag.async_ingest",
"queues.pgmq",
"events.bus",
"secrets.read_write",
"config.read_write",
"approval.fire_and_forget",
"storage.s3",
"ai.openrouter"
],
"build": { "name": "stackbone-cli", "version": "0.x.y" }
}The SDK fires this handshake lazily on the first gated module
call, caches the result for the process lifetime (per baseUrl),
and reuses it for every subsequent call. Two concurrent first-callers
share a single in-flight fetch (single-flight). You can inspect the
last resolved contract synchronously through client.contract:
const c = client.contract;
if (c) {
console.log('Speaking protocol v', c.version, 'against', c.build.name);
}client.contract is null until at least one gated module call has
resolved successfully. It never fetches and never throws.
Capability gating
Each module declares the single capability its surface depends on (see the table above). Before forwarding any call to its underlying implementation, the module awaits the handshake and asserts the capability is advertised. If it is not, the call short-circuits with one of two stable error codes:
contract_version_unsupported— the negotiatedcontract.versionis below eitherMIN_SUPPORTED_CONTRACT_VERSION(the SDK's hard floor) or your agent's declaredagent.yaml.protocol.requiredfloor, whichever is higher.capability_unavailable— the version check passes but the datapath does not advertise the capability the module needs.
Both errors carry actionable meta (detected, required,
available, …). When the handshake itself cannot complete (network
error, 404, malformed body) the call surfaces
contract_unreachable or contract_malformed instead — these are
always hard errors because the SDK genuinely cannot tell what it
is talking to.
Pinning a minimum protocol version
If your agent depends on capabilities introduced after a specific
contract version, pin the floor in agent.yaml:
protocol:
required: 10The CLI forwards this to the SDK as protocolRequired. The
gate enforces the agent floor before the per-module capability
check, so a stale datapath that happens to advertise the capability
under an older protocol still fails closed.
Escape hatch — STACKBONE_REQUIRE_CONTRACT=0
For migrations and local debugging, set STACKBONE_REQUIRE_CONTRACT=0
to suppress the gate. The handshake still runs (client.contract is
still populated) and the SDK still computes the gate, but
capability/version errors are downgraded to a one-shot stderr warning
per (baseUrl, capability) and the underlying call is allowed
through. Reachability errors are never suppressed — the SDK can
still not let the call through if it has no idea what the datapath
is.
This flag is intended as a safety valve while bumping
STACKBONE_CONTRACT_VERSION; production agents should leave it
unset.
Tuning
| Env var | Default | Meaning |
|---|---|---|
STACKBONE_API_URL |
— | Base URL the handshake (and every facade) targets. Required. |
STACKBONE_REQUIRE_CONTRACT |
1 (gating on) |
Set to 0 to suppress capability/version errors (warning instead). |
STACKBONE_CONTRACT_TTL_MS |
unset (process) | Re-fetch the handshake after this many milliseconds. Default is no TTL. |
STACKBONE_DEBUG |
unset | Set to 1 to log a one-line handshake-resolved message per baseUrl. |
stackbone dev --print-contract
The CLI also exposes the negotiated contract on demand without booting the agent:
stackbone dev --print-contract…prints the GET /api/contract payload of the local emulator and
exits. Handy for sanity-checking the version + capability set before
debugging a capability_unavailable error.
Where to go next
client.database— typed Postgres with Drizzle, migrations,pgvector, and a test helper.client.rag— parse, chunk, embed, ingest and retrieve on top of the same Postgres pool.client.storage,client.ai,client.approval,client.secrets,client.config— the rest of the surfaces.- CLI reference —
stackbone dev,stackbone db migrate ...,stackbone deploy.