stackbone dev runs your agents and durable workflows against a local Postgres, Redis and object store. It serves the Studio API on http://127.0.0.1:4242 with 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:

  1. 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 named stackbone-dev-<agent-slug> so multiple projects can run in parallel.
  2. Platform migrations (one-shot): applies the stackbone_platform.* schemas the emulator needs so its tables match cloud, then exits.
  3. Your migrations (one-shot): applies your own schema migrations against the local Postgres, then exits.
  4. Your agents and workflows: builds every discovered agent in-process and boots the durable Workflow runtime that hosts your discovered workflows.
  5. Studio API: the local control-plane emulator on 127.0.0.1:4242 (or --port), speaking the same protocol as api.stackbone.ai.
  6. frpc tunnel (mandatory): exposes 127.0.0.1:4242 over HTTPS through Stackbone's relay at *.tun.stackbone.ai so 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 an index.ts exporting an agent definition. The folder name is both the agent name and the model a 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.ts suffix, and the exported function is the camel-cased name plus Workflow (so qualify-lead.workflow.ts exports qualifyLeadWorkflow).

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 init to scaffold a workspace shell (a deep-agents/ folder, a workflows/ folder and supporting files), or stackbone link to connect an existing directory. Add pieces over time with stackbone add agent <name>, stackbone add workflow <name> or stackbone add workflow-agent <name>.
  • At least one agent or workflow. stackbone dev discovers your workspace by convention, any deep-agents/<name>/index.ts (or a workflows/<name>.workflow.ts) is enough to activate the runtime. A stackbone.config.ts is 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, set STACKBONE_FRPC_BIN to 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/frpc

Pin 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.ai
  • https://chat.stackbone.ai
  • http://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 dev

Precedence: 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: narrows callDeepAgent(name, input) (the workflow-to-agent helper) to your declared agent names, so a typo is a compile error.
  • .stackbone/connect.d.ts: types stackbone.connection(id) and each connector's methods from its schema.
  • .stackbone/config.d.ts: types stackbone.config from your config.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/completions and POST /anthropic/v1/messages: chat with any agent in the workspace, selected by the model field. See Agent protocol.
  • GET /api/discovery: the workspace manifest, every agent and workflow it found.
  • POST /api/workflows/:name/start and POST /api/workflows/:name/chat: trigger a durable workflow run (one returns the runId, 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 -v

Using 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 dev

The 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, frpc relay handshake failure, mismatched protocol version). Tracked alongside Troubleshooting.

The platform for agent developers.Build, ship and observe agentsthat survive prod.

© 2026 · STACKBONEBUILT WITH ❤️ FROM CANADA AND SPAIN