Project manifests
A Stackbone project is a workspace: a set of agents plus durable workflows. The CLI discovers the workspace by convention: it 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 does one job: it moves the database schema and migrations paths off their defaults. Most workspaces need none of it.
stackbone.config.tsis a separate, also optional override. It wins over the convention scan for workflows, it can restate the deep-agent registry underdeepAgents(to hide one from Studio with$internal: true), and it carriesbuild.external, which nothing on disk can imply. See Configuration.
stackbone initwrites neither file. A fresh workspace runs on the conventions alone, and you add either file by hand when you need it. Whenagent.yamlis present, the CLI validates it in strict mode; both files belong under version control.
Workspace discovery by convention
The runtime derives the workspace from the files on disk, so there is no
hand-maintained registry. stackbone dev reads the same two conventions the
runtime does:
- 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 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,
with 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({
name: 'support',
model: 'anthropic/claude-haiku-4.5',
});name must match the folder. The agent's instruction is not in the file: you
write it in Studio, in the prompt catalogue, under the
prompt keyed by that same name.
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; the runtime persists it and replays it 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/workflows.d.ts |
stackbone.workflows.start('<name>', ...) narrowed the same way. |
.stackbone/connect.d.ts |
stackbone.connection('<id>') and its connector methods. |
.stackbone/config.d.ts |
stackbone.config.get('<key>') typed from your config schema. |
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, because the CLI falls back to defaults for everything it would
configure. Its schema is locked under apiVersion: stackbone.ai/v1. When the file is
present, the CLI validates it in strict mode: any unknown top-level or
nested key fails the parse with an error that names the offending key. Typo runtim:
or add a custom key and the next stackbone <command> exits non-zero.
Read the block reference below with one caveat: the schema accepts more than
the CLI acts on. Only database and dev change behaviour today. Every other
block parses and takes its default, and each section says so.
Minimum manifest
apiVersion: stackbone.ai/v1
name: my-workspaceWhen you omit a block, the CLI fills the defaults documented below. It keeps path strings verbatim and 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 (e.g. v0.2.0). The CLI does not validate it as semver
or check it for uniqueness, and nothing reads it today. It is a note for you and
your team. The
version that identifies what is running is the image tag you register with
stackbone link --tag, see
Commands → stackbone link.
version: v0.2.0runtime (optional)
runtime:
engine: node # only supported value today
entry: src/index.ts # defaultA leftover from the single-file agent shape. A workspace boots from the pieces
the convention scan finds, so nothing reads engine or entry today. The
block still parses (engine must be node; bun is reserved and rejected),
and the four reserved builder keys below still fail the parse. Leave the block
out.
database (optional)
database:
schema: ./src/schema.ts # default
migrations: ./.stackbone/migrations # defaultWhere the CLI looks for your Drizzle schema and the
SQL files stackbone db migrate create writes. A workspace's agents and
workflows all talk to the one install Postgres, so one schema covers the whole
workspace instead of one per agent. Override either path when your layout
differs:
database:
schema: db/schema/index.ts
migrations: ../shared/migrations/The CLI keeps both values verbatim and resolves them against the project root
at command time, so relative paths work regardless of where you ran stackbone
from. Three commands read this block: stackbone dev (pre-boot migrate and the
schema watcher), stackbone db migrate ..., and stackbone build, which
carries the migrations folder into the bundle.
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 prints a hint to run
stackbone db migrate create <name> yourself.
rag (optional)
rag:
embeddingModel: openai/text-embedding-3-small # defaultNames the embedding model for the RAG pipeline. Accepted, but not wired:
nothing forwards this value out of the manifest today, so stackbone.rag falls
back to openai/text-embedding-3-small whatever you write here. Pass model
on the call instead, and pick it before ingesting the first document: the
pipeline pins each collection to the dimensionality of the model that created
it. See stackbone.rag. Unknown keys fail the parse.
protocol (optional)
protocol:
required: 10A creator-side floor on the Stackbone Agent Protocol contract version. The SDK
supports such a floor: below it, every gated stackbone.* call fails closed
with contract_version_unsupported before any per-module capability check runs
(the chat and workflow routes your workspace serves are not capabilities).
Accepted, but not wired: nothing forwards this value out of the manifest
today, so the SDK's built-in floor applies whatever you write here. Unknown keys
fail the parse.
To check a target against the floor by hand, run
stackbone contract validate.
Accepted but not wired yet: connections and automations
Two more blocks parse without error and change nothing today. They are in the schema so a manifest you write now stays valid once the install flow reads them. Leaving them out costs you nothing.
connections:
required: [stub-mail, acme-crm] # connector accounts this workspace expects
automations:
recipes: [] # connector-triggered pipelines to seed at install timeconnections.required is the list of Stackbone Connect
connectors your code calls with stackbone.connection('<id>'). Connecting those
accounts is an operator action in Studio today, whether or not you declare them
here. The CLI checks each id for format only. Both blocks default to empty,
and unknown keys inside them fail the parse.
Reserved runtime.* fields
The schema recognises four builder keys and rejects each one today with a hand-written message explaining why. They are reserved so future support can land without changing the manifest shape.
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. You cannot bring your own
today.
runtime.systemDeps (reserved)
runtime:
systemDeps: ['libvips', 'poppler-utils'] # ✗ rejectedThe builder runs Node-only on a fixed base image. The field stays reserved until someone needs it, and it will then take a whitelist of allowed packages instead of 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). You do not need an
explicit override today, so the field is reserved.
Which manifest each command reads
The CLI discovers the workspace by convention: every deep-agents/<name>/index.ts
on disk, plus every workflows/<name>.workflow.ts. An optional
stackbone.config.ts overrides that registry. The optional root agent.yaml
supplies the database paths when it is present.
| Command | What it reads |
|---|---|
stackbone dev |
The discovered workspace, plus agent.yaml database + dev, when present. |
stackbone db migrate ... |
agent.yaml database, when present. |
stackbone build |
The discovered workspace, plus agent.yaml database.migrations, when present. |
stackbone contract validate |
agent.yaml itself. The verb exits 3 when the file is absent. |
Every file here is yours to edit and belongs under version control. The control plane never writes to them.
This page is the reference for the manifest. A coding agent can read it directly:
stackbone docsprints how to connect one to this site over MCP.