A Stackbone project is a workspace: a set of agents plus durable workflows. The workspace is discovered by convention, the CLI scans the files on disk, so most projects need no extra manifest. An agent is any folder under
deep-agents/with anindex.tsentry; a workflow is anyworkflows/<name>.workflow.ts.The workspace may also carry a single, optional
agent.yamlat its root: it overrides the database schema/migrations paths and a handful of other project-wide settings. Most workspaces need none of it, the CLI falls back to sensible conventions.
stackbone.config.tsis a separate, also optional override: drop one in only when you need to name your workflows explicitly, and it wins over the convention scan for workflows.
stackbone devandstackbone publishboth read the same convention: they discover the workspace from everydeep-agents/<name>/index.tson disk (agents are always discovered this way;stackbone.config.tscan only override the workflow list). When present,agent.yamlis validated strictly and belongs under version control.
Workspace discovery by convention
The runtime derives the workspace from the files on disk, there is no
hand-maintained registry. stackbone dev and stackbone publish both read the
same two conventions:
- Agents: every folder under
deep-agents/that contains anindex.tsentry file. The folder name is both the agent's identity and themodela client selects to talk to it. There is no dependency marker to declare, the folder plus the entry file is what makes it an agent. - Workflows: every
workflows/<name>.workflow.ts. The workflow name is the file basename without the.workflow.tssuffix, and the exported function is the camel-cased name plusWorkflow(e.g.workflows/qualify-lead.workflow.tsexportsqualifyLeadWorkflow).
You add pieces with stackbone add (stackbone add agent <name>,
stackbone add workflow <name>, stackbone add workflow-agent <name>), which
writes the files in these locations so the next stackbone dev picks them up
automatically, nothing else to wire.
An agent directory
An agent is a folder under deep-agents/ with an index.ts that default-exports
an agent definition built with defineDeepAgent from @stackbone/sdk/deep:
import { defineDeepAgent } from '@stackbone/sdk/deep';
export default defineDeepAgent({
model: 'anthropic/claude-haiku-4.5',
systemPrompt: 'You are a support agent. Keep answers short.',
});The system prompt lives inline (or imported from a sibling .ts file for a
long one). An agent carries no per-piece manifest and no package.json of
its own: its runtime dependencies live in the workspace root
package.json, so the runtime resolves one copy of each. See
Agents for how an agent runs and how tools reach
the ambient stackbone client.
A workflow file
A workflow lives at workflows/<name>.workflow.ts and exports a durable async
function marked 'use workflow' (named <camelCase(name)>Workflow) that runs on
top of the durable Workflow SDK, each
'use step' inside runs once, is persisted, and is replayed from its event log
on retry. See Workflows for the full model.
A workflow declares its input/output contract by convention too. Export
inputSchema and outputSchema (a Zod, or any
Standard-Schema, object) alongside the workflow function, or put them in a
<name>.contract.ts sibling. The build harvests those sibling exports:
import { z } from '@stackbone/sdk';
export const inputSchema = z.object({ email: z.string().email(), plan: z.string() });
export const outputSchema = z.object({ welcomed: z.boolean() });
export async function onboardingWorkflow(input: z.infer<typeof inputSchema>) {
'use workflow';
// ...
return { welcomed: true };
}These sibling schemas drive validation on the workflow's start endpoint and the
schema the CLI shows for stackbone workflows schema.
Editor types stackbone dev writes
While stackbone dev runs, it generates a few .d.ts files under .stackbone/
from the discovered workspace (your deep-agents/ folders and workflows/
files, plus your connectors + config schema) so the ambient client is typed in
your editor. They are derived artifacts, git-ignore them:
| File | Types |
|---|---|
.stackbone/agents.d.ts |
callDeepAgent('<name>', ...) narrowed to your agent names. |
.stackbone/connect.d.ts |
stackbone.connection('<id>') and its connector methods. |
.stackbone/config.d.ts |
stackbone.config.get('<key>') typed from your config schema. |
What stackbone publish produces
For a workspace, stackbone publish compiles every agent and every workflow on
your machine and packs them into a single tar at
dist/stackbone/workspace-bundle.tar, next to a workspace-bundle.json pointer
(digest, size, the agent + workflow names). Inside the tar is a
.well-known/workspace.json manifest the runtime reads at boot to know which
agents and workflows to launch. The published bundle is pure JavaScript, native
add-ons are rejected up front, so publish fails early if a dependency is not
pure-JS.
agent.yaml: the optional workspace manifest
A workspace may carry a single agent.yaml at its root describing project-wide
runtime, database, connections and protocol settings. Most workspaces don't
need one, the CLI falls back to sensible defaults for everything it would
configure. Its schema is locked under apiVersion: stackbone.ai/v1 and validated
in strict mode when present: any unknown top-level or nested key fails the
parse with an agent.yaml:<line> pointer at the offending entry. Typo runtim:
or add a custom key and the next stackbone <command> exits non-zero.
Minimum manifest
apiVersion: stackbone.ai/v1
name: my-workspaceWhen you omit a block, the CLI fills the defaults documented below. Path strings are kept verbatim, the CLI resolves them at command execution time relative to the project root, never at parse time.
apiVersion (required)
Locked to stackbone.ai/v1. Bumped only on a breaking change.
name (required)
Human-readable workspace name. Must be a non-empty string.
version (optional)
A display-only label copied verbatim onto the build at publish time (e.g.
v0.2.0). Never validated as semver, never made unique, it just renders next to
the build on the install detail page. Omit it and the build carries no label.
version: v0.2.0runtime (optional)
runtime:
engine: node # only supported value today
entry: src/index.ts # defaultPicks the interpreter and the entry file the runtime boots. engine must be
node today; bun is reserved and rejected, switch to node until the release
notes flip the gate.
database (optional)
database:
schema: ./src/schema.ts # default
migrations: ./.stackbone/migrations # defaultWhere the CLI looks for your Drizzle schema and the
SQL files emitted by stackbone db migrate create. A workspace's agents and
workflows all talk to the one install Postgres, so this is one shared schema for
the whole workspace, not one per agent. Override either path when your layout
differs:
database:
schema: db/schema/index.ts
migrations: ../shared/migrations/Both values are kept verbatim and resolved against the project root at command
time, so relative paths work regardless of where you ran stackbone from.
dev (optional)
dev:
autoMigrate: true # default; set false to opt outstackbone dev always applies pending migrations before booting and applies any
new .sql you drop into the migrations folder while it runs. dev.autoMigrate
only controls what happens when you edit src/schema.ts mid-session: with the
default true the CLI generates a migration from the diff, applies it, and
restarts; with false it just prints a hint to run
stackbone db migrate create <name> yourself.
rag (optional)
rag:
embeddingModel: openai/text-embedding-3-small # defaultThe embedding model the RAG pipeline uses, the same model your code reads
through the ambient client at stackbone.rag. Change it before ingesting the
first document: collections are pinned to the dimensionality of the model that
created them. Unknown keys fail the parse.
connections (optional)
connections:
required:
- stub-mail
- acme-crmThe connector accounts this workspace needs. At install time the operator is
prompted to connect any of these that the workspace has not connected yet, so
your agents' tools and workflow steps can call them through
Stackbone Connect (stackbone.connection('<id>')).
Each id is validated for format; whether the connector exists is resolved by
the control plane at install time. Defaults to an empty list.
automations (optional)
automations:
recipes:
- key: new-lead-greeting
name: Greet new CRM leads
trigger: { connector: acme-crm, trigger: lead.created }
handler: greetLead
action: { connector: stub-mail, action: send }Recipe automations the install flow seeds as ordinary, editable automations. Each
recipe is a pipeline, trigger → input mapping → handler → output mapping → action, where handler names the workflow or tool that runs on the event. The
action side is optional (a trigger-only recipe just runs the handler). key is
the per-install idempotency key the seeder upserts on. Defaults to an empty list.
protocol (optional)
protocol:
required: 10A creator-side floor on the Stackbone Agent Protocol contract version. When the
datapath advertises a contract below this floor, the SDK fails closed with
contract_version_unsupported before any per-module capability check runs. This
gates the stackbone.* capability contract (database, storage, rag, secrets, …);
the chat and workflow routes your workspace serves are deliberately not
capabilities. Omit the block to fall back to the SDK's built-in floor. Unknown
keys fail the parse.
Reserved runtime.* fields
The schema recognises four builder keys that are rejected today. They are reserved so future support can land without changing the manifest shape; each one fails the parse with a hand-written message explaining why.
runtime.customDockerfile (reserved)
runtime:
customDockerfile: ./Dockerfile # ✗ rejectedThe Stackbone builder owns the image, it renders a Dockerfile from a template
against your package.json and lockfile. Bringing your own is intentionally not
supported today.
runtime.systemDeps (reserved)
runtime:
systemDeps: ['libvips', 'poppler-utils'] # ✗ rejectedThe builder runs Node-only on a fixed base image. The field is reserved until a real consumer arrives, at which point it lands with a whitelist of allowed packages rather than a free-form array.
runtime.buildSecrets (reserved)
runtime:
buildSecrets: ['NPM_TOKEN'] # ✗ rejectedBuild-time secret storage is not built yet. Runtime secrets, the values your published agents read from their environment, keep working through the existing secrets surface; this entry covers build-time secrets only.
runtime.packageManager (reserved)
runtime:
packageManager: yarn # ✗ rejectedThe builder detects pnpm / npm / yarn from the lockfile in the project root
(pnpm-lock.yaml, package-lock.json, or yarn.lock). Explicit overrides are
unnecessary today, so the field is reserved.
Which manifest each command reads
The workspace is discovered by convention, every deep-agents/<name>/index.ts
on disk, plus every workflows/<name>.workflow.ts (or an explicit
stackbone.config.ts override for workflows). The optional root agent.yaml
supplies the project-wide database/runtime knobs when it is present.
| Command | What it reads |
|---|---|
stackbone dev |
The discovered workspace, boots every agent + every workflow. |
stackbone publish |
The discovered workspace → dist/stackbone/workspace-bundle.tar. |
stackbone db ... |
The root agent.yaml database block, when present. |
Every manifest is yours to edit and belongs under version control, the control plane never writes to them.
Mirror of the inline help available via
stackbone docs agent-yaml. Every CLI release bundles the version of this page that shipped with it, upgrade the CLI (pnpm add -g @stackbone/cli@latest) to receive doc updates.