About stackbone
stackbone is a skill for coding agents working in Stackbone workspaces. It explains how to build deep agents and durable workflows with the SDK, consult documentation, and verify changes in the emulator.
Key features
- 01Workspace and code-layout conventions
- 02Deep-agent and workflow patterns
- 03Ambient SDK client usage
- 04Idempotent workflow-step guidance
- 05Documentation lookup through MCP
- 06Emulator-based verification
Use cases
- 01Adding an agent to a Stackbone workspace
- 02Building a durable workflow with checkpoints
- 03Calling an agent from a workflow
- 04Verifying workspace changes in the emulator
MCP support
The skill uses the public `stackbone-docs` MCP server to find SDK documentation through `list_docs`, `get_doc`, and `search_docs`.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ stackbone/ stackbone-skills --skill stackboneAfter installing, type/stackbonein Claude Code to confirm it loaded.
Details
From the project READMEStackbone SDK skill
This skill tells you how to work inside a Stackbone workspace. It holds no API reference: every signature, option, error code and limit lives in the Stackbone docs, and you read them from there.
Where the facts live
The docs are served over MCP as the stackbone-docs server (https://docs.stackbone.ai/mcp, public, tools search_docs, get_doc, list_docs). The CLI wires it into your coding agent on stackbone init / stackbone link; stackbone docs prints the connection details.
- Start the session with
list_docs. It returns the table of contents: every page under its area and section, with a one-line description. How to find the page below says which page a task needs; take its path from that list andget_docit. Never type a path from memory: pages move, titles stay. - Before you write a call to a surface you have not read about in this session, read its page.
- When you do not know which page holds a method, an option, an error code or a limit,
search_docswith the question in plain words (passareawhen you know it:sdk,cli,examples,faqs,home), thenget_docthe best hit. - Do not write a signature from memory. A wrong one compiles and fails at runtime in a
'use step'.
No stackbone-docs tools in your session? Fetch https://docs.stackbone.ai/llms.txt: the same index, one line per page with its title, its description and a link to its raw markdown. Pick the page by title and fetch that link. https://docs.stackbone.ai/llms-full.txt is every page in one file (~700 KB).
The mental model
- A workspace is discovered by convention from the files on disk: every
deep-agents/<name>/index.tsis an agent (the folder name is its name and themodela client selects), everyworkflows/<name>.workflow.tsis a workflow (name = file basename, export =<camelCase(name)>Workflow). There is no registry to edit. - A deep agent is one file:
export default defineDeepAgent({ name, model, tools })from@stackbone/sdk/deep. Its instruction is not in that file — it is a prompt in the box's catalogue, owned by the agent and keyed by its name, written and published in Studio. It runs in-process and the runtime serves it over the standard OpenAI / Anthropic chat wire. You write no HTTP code. - A workflow is a plain async function marked
'use workflow'whose side effects live in helper functions marked'use step'. Each step is a durable checkpoint: it runs once, is persisted, and is retried on failure. - Every surface is reached through the ambient client:
import { stackbone } from '@stackbone/sdk', from any tool body or any step. NocreateClient(), no connection strings.
How to work
- Locate the piece. The agent is
deep-agents/<name>/index.ts; the workflow isworkflows/<name>.workflow.ts. If it does not exist, scaffold it withstackbone add agent|workflow|workflow-agent <name>(the stackbone-cli skill). Do not hand-write the file layout or thepackage.jsonpins. - Read before you write. For each surface the change touches, read its page (how to find it). Read the whole page: the option you need is usually in the second half.
- Write the code in the shapes below. Keep a tool body or a step small; put every ambient call inside it.
- Verify in the emulator.
stackbone dev, then chat with the agent orstackbone workflows start <name> --input '…', thenstackbone runs get <runId>andstackbone logs tail --run <runId>(the stackbone-cli skill). A change is not done until a run has passed through it. - Something fails? Switch to the stackbone-debug skill to locate the cause, then come back here for the fix.
The three shapes
A deep agent: deep-agents/<name>/index.ts
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
import { defineDeepAgent } from '@stackbone/sdk/deep';
import { stackbone } from '@stackbone/sdk';
const readTone = tool(
async () => {
const tone = await stackbone.config.get('tone'); // any ambient surface works here
return tone.error ? 'neutral' : String(tone.data);
},
{
name: 'read_tone',
description: "Return the agent's current tone setting.",
schema: z.object({}),
},
);
export default defineDeepAgent({
name: 'support', // must equal the folder name — it is who owns this agent's prompts
model: 'openai/gpt-4o-mini', // a bare id, resolved through the deployment's model provider
// No instruction here on purpose: it is a prompt keyed by this agent's name, written in
// Studio. Add `instructions: { key, variables }` to point at another key or feed it {{vars}}.
tools: [readTone],
// interruptOn: { send_mail: true }, // pause for a human before this tool runs
});
A durable workflow: workflows/<name>.workflow.ts
import { z } from 'zod';
import { stackbone } from '@stackbone/sdk';
import { welcomes } from '../src/schema'; // your Drizzle tables live at src/schema.ts
export const inputSchema = z.object({ userId: z.string(), email: z.string().email() });
export const outputSchema = z.object({ userId: z.string(), welcomed: z.boolean() });
type OnboardingInput = z.infer<typeof inputSchema>;
export async function onboardingWorkflow(input: OnboardingInput) {
'use workflow'; // cheap, deterministic glue: it replays on resume
const valid = await validateSignup(input);
return await persistWelcome(valid.userId);
}
async function validateSignup(input: OnboardingInput) {
'use step';
if (!input.email.includes('@')) throw new Error(`Invalid email: ${input.email}`);
return { userId: input.userId };
}
async function persistWelcome(userId: string) {
'use step'; // runs once, persisted, retried: make it idempotent on userId
await stackbone.database.insert(welcomes).values({ userId }).onConflictDoNothing();
return { userId, welcomed: true };
}
A workflow that calls an agent: one step delegates the turn
import { callDeepAgent } from '@stackbone/sdk/workflow';
async function askAgent(message: string) {
'use step'; // the whole agent turn is this one durable step
const { text } = await callDeepAgent('support', message);
return text;
}
streamDeepAgent (same import) is the streaming twin the workflow-agent scaffold uses: the reply streams onto the run's chat surface and Studio shows the workflow as a chat.
Rules that hold everywhere
- Destructure
{ data, error }and handle both branches. Every ambient surface returns that envelope.throwto fail the step, or branch onerror.codefor a case you handle. Never swallowerror. - Three surfaces throw instead.
stackbone.databaseis native Drizzle (rows back, throws on error);callDeepAgent/streamDeepAgentthrow and fail the enclosing step;stackbone.connection(id)throws aConnectorCallError, matched byerr.code, never byinstanceof. - Steps are idempotent. A
'use step'may run twice. Write it so that running twice is safe. - A workflow that writes through the connector whose trigger started it needs
isOwnEcho. That write causes the change the trigger delivers, so the run starts itself again, and deduplication cannot stop it because nothing is being duplicated. Name anechoPathon the write, then guard the step before it writes:if (await isOwnEcho(connectorId, value)) return { … }(from@stackbone/sdk/workflow, and the early return still owes your declaredoutputSchemaa value). MATCHvalueTO THE TRIGGER. A trigger that watches for new objects: the new object’s id. A trigger that follows a change column such asupdatedAt: that column, because an id never changes and a guard on it would swallow every later human edit of that object. Never the id of the event, which is new on every lap. Read the Trigger events page under Home before you pick, and take the exact path from the trigger’s own caution. requestApproval()runs in the workflow body, never inside a'use step', and is imported from@stackbone/sdk/workflow, not the main barrel. Gate the side effect ondecision.status === 'approved'.- The instruction is never in the code. It is a prompt in the box's catalogue, owned by the agent and keyed by its name, written and published in Studio (or with
stackbone prompts create).instructions: { key, variables }points at another key or feeds it{{vars}}— it never carries the text. An agent whose prompt nobody wrote still boots and answers. - The model is a bare id (
'openai/gpt-4o-mini') the deployment resolves through its model provider, or a built LangChain chat-model instance. Do not construct a client with a key. - Runtime dependencies live in the workspace root
package.json(deepagents,@langchain/*,workflow), one copy per process. No per-agentpackage.json, no nestednode_modules. - Never hardcode or ask for injected env.
STACKBONE_POSTGRES_URL,MODEL_PROVIDER_API_KEY,MODEL_PROVIDER_BASE_URL,STACKBONE_INSTALLATION_ID, storage credentials: the runtime injects them. Operator-managed values go throughstackbone.secretsandstackbone.config. - Declare the workflow contract as sibling
inputSchema/outputSchemaexports. The input type derives frominputSchemawithz.infer; the output is declared, not inferred. - The RAG schema is platform-provisioned. Your own tables come from
src/schema.ts+stackbone db migrate create(the stackbone-cli skill); never hand-write the RAG tables.
How to find the page
The docs follow the code's names, so you rarely need to search:
- An ambient surface has a page titled like it under SDK:
stackbone.database,stackbone.storage,stackbone.rag,stackbone.ai,stackbone.config,stackbone.secrets,stackbone.settings,stackbone.prompts,stackbone.connection. Before you write a call to one,get_docthat page (path fromlist_docs). Two break the rule:stackbone.workflowsis documented onBackground jobs & workflow triggersandrequestApproval/ the approval surface onHuman-in-the-loop. - Each shape has an
Overviewand aGetting startedunder its SDK section (Agents, Workflows, Workflow + Agents). Read both the first time you author that shape in a session; the section's other pages (Calling a sibling agent,Browser tools,Serial execution) sit next to them in the list. - A helper or a type with no page of its own (
interruptOn,callDeepAgent,callConnector,connectorTool,defineWorkspace,Result<T>, an error code):search_docsthe identifier verbatim with areasdkand take the top hit. - A worked example:
list_docswith areaexamples. A product question (agent or workflow? which model?): areafaqs.
Other skills
- stackbone-cli: scaffold (
init,add), run (dev), migrate (db migrate), operate runs / approvals / logs, ship (build,package,link). - stackbone-debug: locate the cause of an error, a failed or stuck run, a parked approval.
- stackbone-coder: start a new piece from a one-line idea through an interview, then come back here for the code.