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 an index.ts entry; a workflow is any workflows/<name>.workflow.ts.

The workspace may also carry a single, optional agent.yaml at 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.ts is a separate, also optional override. It wins over the convention scan for workflows, it can restate the deep-agent registry under deepAgents (to hide one from Studio with $internal: true), and it carries build.external, which nothing on disk can imply. See Configuration.

stackbone init writes neither file. A fresh workspace runs on the conventions alone, and you add either file by hand when you need it. When agent.yaml is 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 an index.ts entry file. The folder name is both the agent's identity and the model a 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.ts suffix, and the exported function is the camel-cased name plus Workflow (e.g. workflows/qualify-lead.workflow.ts exports qualifyLeadWorkflow).

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:

deep-agents/support/index.ts
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:

workflows/onboarding.workflow.ts
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-workspace

When 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.0

runtime (optional)

runtime:
  engine: node # only supported value today
  entry: src/index.ts # default

A 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 # default

Where 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 out

stackbone 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 # default

Names 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: 10

A 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 time

connections.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 # ✗ rejected

The 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'] # ✗ rejected

The 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'] # ✗ rejected

Build-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 # ✗ rejected

The 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 docs prints how to connect one to this site over MCP.

BUILT WITH ❤️ FROM CANADA AND SPAIN