stackbone devruns your agents and durable workflows against a local Postgres, Redis and object store. It serves the Studio API onhttp://127.0.0.1:4242with the identical wire shape the Stackbone control plane serves in cloud.The headline goal: a fast local loop with no publish, no cloud, no API key fiddling. Save, restart, and your agents and workflows run against real datastores.
What it does
stackbone dev walks through a short sequence of boot stages and
keeps the long-lived processes alive until you press Ctrl-C, then tears
them down in reverse order:
- Docker stack: Postgres (with
pgvector) on:5433, Redis on:6380(the backend that powers durable workflows and the job runtime), and MinIO on:9004/:9005. The Compose project is namedstackbone-dev-<agent-slug>so multiple projects can run in parallel. - Platform migrations (one-shot): applies the
stackbone_platform.*schemas the emulator needs so its tables match cloud, then exits. - Your migrations (one-shot): applies your own schema migrations against the local Postgres, then exits.
- Your agents and workflows: builds every discovered agent in-process and boots the durable Workflow runtime that hosts your discovered workflows.
- Studio API: the local control-plane emulator on
127.0.0.1:4242(or--port), speaking the same protocol asapi.stackbone.ai. frpctunnel (mandatory): exposes127.0.0.1:4242over HTTPS through Stackbone's relay at*.tun.stackbone.aiso the cloud-hosted Studio (https://app.stackbone.ai/app) can reach the emulator without tripping mixed-content / local-network-access rules in Safari, Brave or Chrome.
How the workspace is discovered
A workspace is discovered by convention from the files on disk, you do
not maintain a registry. stackbone dev (and stackbone publish) scan your
project the same way:
- Agents: every folder under
deep-agents/that contains anindex.tsexporting an agent definition. The folder name is both the agent name and themodela client selects to talk to it. There is no per-agent manifest, and no port: every agent runs inside the one emulator process. - 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(soqualify-lead.workflow.tsexportsqualifyLeadWorkflow).
stackbone init scaffolds a workspace shell, a deep-agents/ folder, a
workflows/ folder and supporting files, and (depending on the optional
first piece you ask for) may add your first agent. From there stackbone dev
boots every discovered agent plus the durable
Workflow runtime for your workflows.
stackbone.config.ts is an optional override for workflows only, not a
requirement. Most projects need none. If it exists it default-exports
defineWorkspace(...) and its workflows win over the convention scan
(agents are always discovered from deep-agents/, the config format does
not declare them):
// stackbone.config.ts (optional — overrides the workflow convention scan)
import { defineWorkspace } from '@stackbone/sdk';
export default defineWorkspace({
workflows: [{ name: 'onboarding', dir: 'workflows' }],
});See Agents and Workflows for what these are and how to author them.
Prerequisites
- Docker. Docker Desktop on macOS; Docker Engine on Linux. On Windows the CLI must run from WSL2, Docker Desktop with WSL2 integration enabled (or Docker Engine inside the Ubuntu distro) covers it.
- A project. Run
stackbone initto scaffold a workspace shell (adeep-agents/folder, aworkflows/folder and supporting files), orstackbone linkto connect an existing directory. Add pieces over time withstackbone add agent <name>,stackbone add workflow <name>orstackbone add workflow-agent <name>. - At least one agent or workflow.
stackbone devdiscovers your workspace by convention, anydeep-agents/<name>/index.ts(or aworkflows/<name>.workflow.ts) is enough to activate the runtime. Astackbone.config.tsis optional and only needed when you want to override the workflow scan. frpc(managed automatically). The CLI auto-fetches a pinned, per-platform binary into~/.cache/stackbone/bin/on first run if missing. The tunnel is mandatory, so there is no opt-out flag, setSTACKBONE_FRPC_BINto point at a system install when your environment forbids untrusted binaries.
Synopsis
stackbone dev [--port 4242] [--listen] [--print-contract] [--verbose]| Flag | Default | Why you'd flip it |
|---|---|---|
--port |
4242 |
Pick another Studio API port. |
--listen |
off | Bind the Studio server to 0.0.0.0 so other machines on the LAN can reach it. The CLI prints a visible warning. |
--print-contract |
off | Print the JSON contract this CLI advertises (same payload as the emulator's /api/contract) and exit without booting the dev session. |
--verbose |
off | Stream every log line and docker compose output. Default UI uses per-stage spinners; --verbose switches back to the raw firehose. |
Override the frpc binary location with the env var:
export STACKBONE_FRPC_BIN=/opt/homebrew/bin/frpcPin a specific frpc release with STACKBONE_FRPC_VERSION if the default
pinned version is incompatible with your platform.
The boot banner
Once the stack is up the CLI prints a banner with both URLs:
┌─ stackbone dev ───────────────────────────────────────────────┐
│ Studio (cloud, recommended): https://app.stackbone.ai/app/<orgSlug>/installations/<id>?stackbone-dev=https://<subdomain>.tun.stackbone.ai
│ Studio (local fallback): http://127.0.0.1:4242
└────────────────────────────────────────────────────────────┘The "cloud" deeplink is the recommended path, it gives you the production Studio UI pointed at your local emulator. The "local fallback" is for offline work or when the relay handshake fails.
CORS allowlist
The emulator enforces an explicit CORS allowlist (no *). Default origins:
https://app.stackbone.aihttps://chat.stackbone.aihttp://localhost:*(any port; covers any local dev server you run)
Add origins per-repo via stackbone.config.json at the project root
(committable, unlike .stackbone/project.json):
{
"schemaVersion": 1,
"studio": {
"corsOrigins": [
"https://app.stackbone.ai",
"https://chat.stackbone.ai",
"https://staging.stackbone.ai",
"http://localhost:*",
],
},
}Or override per run with the env var (CSV):
STACKBONE_CORS_ALLOW_ORIGINS="https://staging.stackbone.ai,http://localhost:*" stackbone devPrecedence: env > file > default. Each entry is either an exact
origin or a wildcard with * matching a single path segment (no dot
crossing): https://*.stackbone.ai works; localhost:* matches ports
but not subdomains.
Environment injected into your agents
The runtime injects these env vars so the ambient stackbone client,
stackbone.database, .storage, .rag, .config, .secrets, .ai,
.approval, plus stackbone.connection(id), resolves the same
datastores locally as in production. You import that client directly
inside a tool's execution or a workflow 'use step'; there is no handler
wiring to configure.
| Variable | Points at |
|---|---|
PORT |
The port the emulator listens on. |
STACKBONE_POSTGRES_URL |
Local Postgres on :5433 with the stackbone_dev database. |
STACKBONE_S3_ENDPOINT |
MinIO on :9004. |
STACKBONE_S3_BUCKET |
stackbone-dev. |
STACKBONE_S3_ACCESS_KEY |
stackbone. |
STACKBONE_S3_SECRET_KEY |
stackbone-secret. |
STACKBONE_S3_REGION |
us-east-1 (MinIO accepts any region). |
STACKBONE_AGENT_ID |
The slug of the workspace (the multi-tenant key prefix for storage and observability). |
WORKFLOW_REDIS_URL |
Local Redis on :6380, the durable backend the workflow runtime replays from. |
OPENROUTER_API_KEY |
Resolved in order: your shell OPENROUTER_API_KEY wins, else your org key pulled from the control plane, else none. |
See Configuration → Environment variables for the full list.
Generated files
While a dev session runs, the CLI writes editor-type artifacts under
.stackbone/ so your code gets typed autocompletion off your workspace:
.stackbone/agents.d.ts: narrowscallDeepAgent(name, input)(the workflow-to-agent helper) to your declared agent names, so a typo is a compile error..stackbone/connect.d.ts: typesstackbone.connection(id)and each connector's methods from its schema..stackbone/config.d.ts: typesstackbone.configfrom yourconfig.schema.ts.
When your workspace declares workflows, the durable Workflow build output
also lands under .well-known/workflow/v1/. All of these regenerate on
every stackbone dev boot, so add .stackbone/ (and .well-known/) to
your .gitignore.
What the emulator serves locally
The local emulator on 127.0.0.1:4242 exposes the same surfaces the cloud
control plane serves, which is how Studio and the inspection commands talk
to your running session:
GET /api/contract: the protocol-version handshake (below).POST /openai/v1/chat/completionsandPOST /anthropic/v1/messages: chat with any agent in the workspace, selected by themodelfield. See Agent protocol.GET /api/discovery: the workspace manifest, every agent and workflow it found.POST /api/workflows/:name/startandPOST /api/workflows/:name/chat: trigger a durable workflow run (one returns therunId, the other streams the run as it executes).GET /api/health: a lightweight readiness check for the dev session.
These are the surfaces the rest of the CLI targets against a running dev
session: stackbone runs (list, get, retry, cancel runs),
stackbone workflows (list, schema, start) and stackbone hitl
(approve/reject pending approvals).
Tearing down
Ctrl-C stops your agents, the HTTP server, the tunnel and the Compose
stack in reverse order. The stackbone-dev-<slug> Compose project is left
in place between runs so Postgres data persists across stackbone dev
restarts. Wipe it with:
docker compose -p stackbone-dev-<slug> down -vUsing a system frpc binary
When the auto-fetcher cannot run (corporate proxy, unsupported platform,
or you simply prefer a system-managed binary), install frpc from
fatedier/frp releases and
point the CLI at it:
# macOS / Linux: download the release archive, extract, drop `frpc` on PATH
export STACKBONE_FRPC_BIN=/opt/homebrew/bin/frpc
stackbone devThe CLI uses STACKBONE_FRPC_BIN verbatim, so any path works. The tunnel
is mandatory and there is no --no-tunnel opt-out, the
local_tunnel_url field on the installation row is what makes the
session shareable from the dashboard.
Studio handshake
The cloud-hosted Studio (under app.stackbone.ai/app/<orgSlug>/installations/<id>)
performs a handshake against the emulator's GET /api/contract to detect
the protocol version. If your CLI is older than minSupported the UI shows a
version drift banner, upgrade with pnpm add -g @stackbone/cli@latest
(or update the project-local install) and rerun stackbone dev.
Troubleshooting
TODO: common failure modes (Docker not running, port collisions,
frpcrelay handshake failure, mismatched protocol version). Tracked alongside Troubleshooting.