Configuration reference

Global flags, environment variables, persistent state on disk, the --json envelope and the semantic exit codes. Everything you need to drive the CLI from a script (or from a coding agent inside an IDE) without parsing prose.

Global flags

Every command accepts these. The CLI reads them from process.argv before subcommand parsing, so they work wherever you put them on the line.

Flag Env equivalent Default Description
--json STACKBONE_JSON=1 off Emit machine-readable JSON wrapped in { "schema_version": 1, ... }. Errors get error + exit_code keys.
-y, --yes (none) off Skip every confirmation prompt. Required in CI / non-TTY contexts.
--verbose STACKBONE_VERBOSE=1 off Stream the raw firehose of log lines and docker compose output instead of the per-stage spinner UI.

--version (or -v) is a root shortcut rather than a per-command flag. Alone on the line it prints two lines and exits 0: the CLI's own version, then the agent protocol contract version this binary advertises.

$ stackbone --version
stackbone-cli 0.3.3
contract 15

Environment variables

The CLI reads these from the parent shell:

Variable Effect
STACKBONE_JSON =1 ⇒ same as --json.
STACKBONE_LOG_LEVEL Log level on stderr (trace, debug, info, warn, error, fatal). Defaults to warn on a terminal, so the per-stage spinners carry the UI, and to info under --json or --verbose. An explicit value wins over both.
STACKBONE_API_URL Control-plane the CLI talks to. Wins over the project's .stackbone/project.json and your global config. Point it at your own deployment when you run Stackbone in-house.
STACKBONE_FRPC_BIN Absolute path to a frpc binary. Used by stackbone dev for the relay tunnel.
STACKBONE_FRPC_VERSION Override the pinned frpc release the auto-fetcher downloads.
STACKBONE_DOCKER_BIN Path to a Docker-compatible binary stackbone dev drives. Defaults to docker. Set it for Podman or a non-standard install.
STACKBONE_DEV_PORT_OFFSET Non-negative integer that shifts every port stackbone dev binds by offset × 10, so two projects can run side by side. See below.
STACKBONE_POSTGRES_URL Connection string the stackbone db migrate verbs run against. Wins over the connection a running stackbone dev records for them. Set it to migrate a database that is not your local dev one.
STACKBONE_VERBOSE =1 ⇒ same as --verbose (raw firehose of log + docker compose output).
STACKBONE_CORS_ALLOW_ORIGINS CSV of origins added to the stackbone dev Studio CORS allowlist.
MODEL_PROVIDER_API_KEY Your own model-provider key (OpenRouter, or any OpenAI-compatible gateway). When set in your shell it wins over the provider configured in the Studio Model provider screen.
MODEL_PROVIDER_BASE_URL Override the OpenAI-compatible base URL the model client uses. Defaults to OpenRouter cloud. Enough on its own for a keyless local gateway (Ollama, LM Studio).
OPENROUTER_API_KEY Deprecated alias for MODEL_PROVIDER_API_KEY, read only when the neutral name is unset.
OPENROUTER_BASE_URL Deprecated alias for MODEL_PROVIDER_BASE_URL, read only when the neutral name is unset.
STACKBONE_NO_UPDATE_CHECK =1 ⇒ silence the "a new release of the Stackbone CLI is available" banner. The check is a background local-cache read that never blocks a command, and it is already suppressed in CI, on a non-TTY, and under --json.

Either model-provider variable also opens the first-run gate. Export one and stackbone dev never stops for setup: it goes straight to your workflows and agents. See The first run.

Running two projects at once

One stackbone dev session owns a fixed set of ports, so a second one on the same machine collides. STACKBONE_DEV_PORT_OFFSET shifts the whole set: the API port, and the Postgres, Redis and MinIO containers behind it.

STACKBONE_DEV_PORT_OFFSET=1 stackbone dev   # API on :4252, Postgres on :5443, …

Each step of the offset is worth 10 ports. 0 (the default) keeps the ports you already know. An explicit --port always wins for the API port, but the containers still shift, so pass the offset rather than only overriding --port.

stackbone dev also injects the env vars your agents and workflows read through the ambient stackbone client (STACKBONE_POSTGRES_URL, WORKFLOW_REDIS_URL, STACKBONE_AGENT_ID, …). See SDK integration → how the CLI connects to the SDK.

State on disk

Per-machine (global)

Lives under ~/.stackbone/:

Path What it stores Permissions
~/.stackbone/credentials.json Map keyed by control-plane URL → SessionInfo. Lets one user stay logged into local + staging + prod simultaneously; only one is active at a time. 0600 (owner read/write).
~/.stackbone/config.json Global preferences: defaultApiUrl, telemetry (opt-in PostHog, default false until V1). 0644.
~/.stackbone/update-check.json The newest published CLI version seen on the last registry check, and when that check ran. Only ever read to print the upgrade banner. 0600 (owner read/write).

Per-machine (cache)

Path What it stores
~/.cache/stackbone/bin/ Auto-downloaded binaries (today: frpc-<version>).

Per-project

Lives at the project root:

Path Tracked? What it stores
stackbone.config.ts Optional An optional workflow + build override. By default stackbone dev derives the workspace registry by convention (scanning every deep-agents/<name>/index.ts and every workflows/<name>.workflow.ts file), so most projects need no config file at all. When this file is present (default-exporting defineWorkspace({ agents, workflows }) from @stackbone/sdk) its workflows win over the convention scan; deep agents keep coming from the deep-agents/ scan unless you also list them under deepAgents, which you only need to hide one from Studio with $internal: true. agents and workflows are required fields: keep agents: []. It also carries build.external (see Keeping a package out of the bundle). See Workflows.
.stackbone/project.json No (gitignored by stackbone init) { schemaVersion, organizationId, agentId, controlPlaneUrl }, plus localDevInstallationId once stackbone dev has registered one. Identifies which organization this workspace points at, and which installation a verb targets when you pass no --agent.
.stackbone/dev/session.json No (gitignored) Written by stackbone dev while it runs: where the emulator listens and the connection string of the dev database. It is how a verb with no --agent finds the local installation, and how stackbone db migrate finds the database without you exporting anything.
.stackbone/*.d.ts No (gitignored) Generated editor types (config.d.ts, agents.d.ts, workflows.d.ts, connect.d.ts) that give the ambient stackbone client typed autocompletion. Regenerated by stackbone dev; config.d.ts also by stackbone config types (no control plane needed).
stackbone.config.json Yes { schemaVersion: 1, studio: { corsOrigins } }, the team-shared knobs. Reviewable in PRs. Different file from stackbone.config.ts above: the .json holds CLI/Studio knobs, the .ts holds the workspace manifest.
agent.yaml Yes Manifest. See agent.yaml reference.
.claude/, .windsurf/, .agents/, … No (gitignored by stackbone init / link) Per-coding-agent skill directories the upstream skills registry materialises. See Agent skills overview.
.mcp.json, .cursor/mcp.json, .vscode/mcp.json, .gemini/settings.json Yes The stackbone-docs MCP server entry init / link write for the coding agents you tick. Committable on purpose: a teammate who clones the repository inherits the documentation server. See coding agents.

Keeping a package out of the bundle

stackbone dev and stackbone build compile your deep agents the same way: each deep-agents/<name>/index.ts is bundled into a single index.mjs next to it, with your own dependencies inlined. The local run and the deployed container behave alike because production resolves no dependencies a second time.

A few packages break when you bundle them: ones with a native binary, a WASM asset, or that read their own files from disk at runtime. Name those in build.external and they stay bare imports, resolved from the box's node_modules at boot:

stackbone.config.ts
import { defineWorkspace } from '@stackbone/sdk';

export default defineWorkspace({
  agents: [],
  workflows: [],
  build: { external: ['sharp'] },
});

The list applies to both paths. stackbone build records the full effective set in the bundle manifest and warns about it at the end of the build, so whoever packages the image knows which packages to install alongside it. Nothing installs them for you. The image carries a node_modules decided at image build time, so add these packages yourself, at the same versions this workspace uses. Miss one and the agent fails to start with ERR_MODULE_NOT_FOUND.

@stackbone/sdk, deepagents and @langchain/* are always external, because exactly one copy of each has to exist per process. You never list them.

The compiled index.mjs is a build artifact: stackbone init already gitignores deep-agents/*/index.mjs.

Output contract

Human mode (default)

Free-form text on stdout. May span multiple lines, may include @clack/prompts chrome and ANSI colour codes. Never parse this. Logging (debug, trace, deprecation warnings) goes to stderr through Pino regardless of mode.

JSON mode

Trigger it with --json or STACKBONE_JSON=1. Every command emits a single JSON line on stdout wrapped in:

// success
{ "schema_version": 1, ...payload }

Errors go on stderr and include the original exit code:

{
  "schema_version": 1,
  "error": {
    "code": "auth",
    "message": "No active session for https://api.stackbone.ai",
    "suggestion": "Run `stackbone login`",
  },
  "exit_code": 2,
}

schema_version is an integer that only bumps on backward-incompatible shape changes. If a coding agent sees a value greater than the one it was written against, it should refuse to parse and surface the version mismatch.

Paginated lists

A cursor-paginated list command (runs list, hitl list, the storage, rag and db table listings) takes --limit <n> and --cursor <opaque>, and its JSON payload carries an items array plus nextCursor and prevCursor (either may be null). To walk forward, pass the previous page's nextCursor value back as --cursor. Some lists add domain-specific fields alongside items (for example common_prefixes on a storage listing); on a paginated list the three keys are always present and always shaped the same way. A short list (stackbone list, stackbone workflows list) returns everything at once, with no cursor keys.

{
  "schema_version": 1,
  "items": [
    /* … */
  ],
  "nextCursor": "eyJ…",
  "prevCursor": null,
}

Exit codes

Code Name When
0 ok Command succeeded.
1 generic Catch-all error. Anything that isn't auth / project / not-found / permission.
2 auth Not logged in or session expired. Protected commands never auto-trigger login: run stackbone login first.
3 no project The command needs a target it can't resolve: the cwd has no .stackbone/project.json, or it targets the local-dev agent but stackbone dev is not running (dev_not_running).
4 not found The target does not exist: an unknown workspace, agent, run or object, or an agent with no registered deployment (runtime_not_configured).
5 permission denied Caller is authenticated but unauthorised for the resource.

error.code in the JSON envelope carries the same decision at a finer grain. Five names match the rows above one for one (generic, auth, no_project, not_found, permission). The rest fold into one of those exit codes and keep a name of their own, so a caller can tell two failures with the same integer apart:

error.code Exit Raised when
auth_required 2 The dev tunnel needs a session and there is none.
dev_not_running 3 The verb targets your local installation but stackbone dev is not up.
runtime_not_configured 4 The agent has no registered deployment, so no runtime can answer.
workspace_tunnel_cap_reached 5 Your workspace already holds the maximum number of live tunnels.
unsupported_platform 1 The CLI started in a native Windows shell (PowerShell or CMD).
tunnel_relay_unhealthy 1 The tunnel relay is down and cannot grant a tunnel.
cli_upgrade_required 1 The control plane refuses this CLI version. Upgrade it.
eval_below_threshold 1 stackbone eval ran a suite to the end and it missed the pass mark.

Branch on the integer for the coarse answer, on error.code when you need the distinction. Neither needs the message parsed:

# In a shell script
if ! out=$(stackbone whoami --json 2>err); then
  case "$(jq -r '.error.code' < err)" in
    auth)       stackbone login ;;
    not_found)  echo "stale session" ;;
    *)          echo "unexpected: $out" ; exit 1 ;;
  esac
fi

The six exit codes are stable across CLI releases, so scripts and coding agents can pin to them. A new error.code name can appear inside an exit code that already exists, so treat a name you do not recognise as its integer.

Putting it together

A coding-agent-friendly invocation looks like:

STACKBONE_JSON=1 stackbone metadata

It produces one line describing the organization state.

Not on this page: the stackbone config document

This page is about CLI and on-disk configuration: flags, env vars, and the files that live in your project. It is not about the versioned AGENT_CONFIG document your running agents and workflows read at runtime through the ambient client (stackbone.config.get(...)). That runtime config is its own surface, edited with the stackbone config command family (get, set, versions, rollback, types) and shaped by a config.schema.ts in your project. See the config module reference.

BUILT WITH ❤️ FROM CANADA AND SPAIN