The whole agent stack, in one SDK.
Build a workspace of agents and durable workflows. One typed client wires in your database, RAG, storage, models, secrets and human approval — the same code on your laptop and in the cloud.
- 13 surfaces
- durable workflows
- zero keys to manage
import { requestApproval } from '@stackbone/sdk/workflow' export async function refundWorkflow(input) { 'use workflow' const decision = await requestApproval({ token: `refund-${input.orderId}`, topic: 'refund', timeout: '24h', fallback: 'reject', }) if (decision.status !== 'approved') return { refunded: false } return await performRefund(input.orderId) // only runs once a human approves}A project is one folder. Drop an agent in agents/, a workflow in workflows/ — stackbone dev discovers both by convention. No registry to hand-edit.
my-workspace/ agents/ support/ # one agent per folder billing/ lead-qualifier/ workflows/ onboarding.workflow.ts # one durable workflow / file reconcile.workflow.ts refund.workflow.ts package.json- Agent
- A model, a system prompt and its own tools. One folder, one deployable agent.
- Workflow
- Deterministic steps you author, durable per step. Calls agents, gates on humans.
stackbone initscaffold a new workspacestackbone add agent <name>add an agentstackbone add workflow <name>add a durable workflowstackbone devrun the whole workspace locally
An agent is a system prompt, a model and the typed client. Point it at any model on the gateway, write its instructions, give it tools — and it can read your database, search RAG and hand off to other agents.
agents/support/ agent/ instructions.md # the system prompt tools/ search_kb.ts # a tool, gets the client package.json # name + modelYou are the support agent for an online store. Answer questions about orders and write short,friendly replies. When you need an order status,call the search_kb tool — never invent one.export default defineTool({ description: 'Search the product knowledge base.', inputSchema: z.object({ query: z.string() }), async execute({ query }) { const res = await stackbone.rag.retrieve({ namespace: 'kb', text: query }) return res.data ?? [] },})Every tool reaches the same ambient stackbone client — database, RAG, storage, secrets and more.
Mark a function "use workflow" and each "use step" becomes a durable checkpoint — kill the runtime mid-run and it resumes from the last completed step, not the top.
import { stackbone } from '@stackbone/sdk' export async function onboardingWorkflow(input) { 'use workflow' const signup = await validate(input) // deterministic step const copy = await draftWelcome(signup) // a single LLM call const tips = await askSupport(signup) // hand off to an agent return persist(signup, { ...copy, tips })} async function askSupport(signup) { 'use step' const session = stackbone.agent('support').session() const res = await session.send({ message: `Onboarding tips for ${signup.plan}?` }) return (await res.result()).data}The contract is a sibling inputSchema / outputSchema — no wrapper, no second type to drift.
requestApproval({ token, topic, payload, timeout })pause the run for a human decisionstackbone.workflows.start(name, input)kick off another workflow, fire-and-forgetstackbone.workflows.startAndWait(name, input)run another workflow as a durable sub-routinestackbone.workflows.schedule(name, input, cron)run a workflow on a cron scheduleingestDocuments({ collection, storageKey })ingest files into RAG from a step
Composition, approval, scheduling and RAG ingest all import from @stackbone/sdk/workflow.
One import, every surface. The same stackbone handle from any tool
Retrieval
Ingest documents and retrieve by meaning — pgvector, auto-embedded, ranked by similarity.
const res = await stackbone.rag.retrieve({ namespace: 'kb', text: question, model: 'openai/text-embedding-3-small',})res.data // RetrieveHit[], ranked by similarity→ Result<RetrieveHit[]>
auto-embeds the query; the model must match ingestAgent hand-off
Hand a turn to any other agent in the workspace, by name. Sessions are durable and resumable.
const session = stackbone.agent('support').session()const res = await session.send({ message: 'Where is order #2231?',})const answer = (await res.result()).data→ a durable, resumable session turn
workflows orchestrate; agents converseDatabase
The agent's own Postgres, as native Drizzle. Your schema, your migrations, full SQL.
await stackbone.database .select() .from(orders) .where(eq(orders.id, id))// native Drizzle — your schema, your SQL→ Drizzle rows (throws on misuse)
the one surface that is not a Result — it throws like DrizzleModels
Every model behind the managed gateway, plus image generation. No provider keys to wire.
const res = await stackbone.ai.chat.completions.create({ model: 'anthropic/claude-haiku-4.5', messages,})// also stackbone.ai.images.generate(...)→ Result<…>
one managed OpenRouter gateway, every modelObject storage
S3-compatible storage on R2 or MinIO — upload, download and signed URLs.
const res = await stackbone.storage.upload(key, body)if (res.error) return resres.data // { key, etag? }→ Result<{ key; etag? }>
Human approval
Open a human-in-the-loop gate and resume the moment a person decides.
await stackbone.approval.request({ topic: 'refund', payload: { orderId, amount }, onDecide,})→ Result<ApprovalRequest>
or requestApproval(...) straight from a workflowSecrets
Injected at runtime by the control plane. Read them; never paste them.
const res = await stackbone.secrets.get('STRIPE_KEY')res.data // string — injected, never pasted→ Result<string>
Config
Per-agent configuration, typed and resolved from the control plane.
const res = await stackbone.config.get('greeting')res.data // typed per-agent config value→ Result<…>
Observability
OpenTelemetry spans for every call, exported out of the box.
// OpenTelemetry spans for every call,// exported out of the box.stackbone.observability→ OpenTelemetry spans
The same ambient stackbone client runs on your laptop and in the cloud. You never paste a key.
import { stackbone } from '@stackbone/sdk' // no keys, no config await stackbone.secrets.get('STRIPE_KEY') // injected, never pastedawait stackbone.ai.chat.completions.create(…)the credentials change. the code doesn't.
Write the agents. The SDK is already wired.