--- title: 'stackbone.config' description: 'Typed reads of dynamic per-agent configuration set in the dashboard.' position: 8 --- # stackbone.config > Typed reads of dynamic per-agent configuration. Operators tweak > values in the dashboard (feature flags, thresholds, JSON snippets); > your code reads them at runtime through the ambient `stackbone` > client, and the value propagates without republishing. ## Mental model `stackbone.config` is the read side of the agent's _non-secret_ operational config. It is **agent-local**: the operator edits values in the dashboard, Stackbone stores them in your agent's own database, and `stackbone.config` reads them straight out of that database. Values can be any JSON-serialisable shape. You read config wherever you need it: from inside an [agent tool](/docs/sdk/agents/overview), or from a [durable workflow](/docs/sdk/workflows/overview) `'use step'`. There is no `createClient()` and no per-invoke handle to wire up: import the ambient client and call it. ```ts import { stackbone } from '@stackbone/sdk'; const result = await stackbone.config.get('greeting'); ``` A config read is a direct query against the agent's own database: no control-plane round-trip, nothing to negotiate. The facade does not cache, so every call re-reads the database and dashboard edits show up on the next read. Because the read goes through the agent database, your agent must have its Postgres connection configured (the runtime injects `STACKBONE_POSTGRES_URL` for you). ## Read a single key Read one key from inside a tool or a workflow step. `get` returns a [`Result`](/docs/sdk/reference/overview) envelope: check `.error` before touching `.data`. ```ts import { tool } from '@langchain/core/tools'; import { stackbone, z } from '@stackbone/sdk'; const needsApproval = tool( async ({ amountCents }: { amountCents: number }) => { const result = await stackbone.config.get('refund_threshold_cents'); if (result.error) throw new Error(result.error.code); const threshold = result.data; // number, once typed (see below) return JSON.stringify({ needsApproval: amountCents > threshold }); }, { name: 'needs_approval', description: 'Decide whether a refund needs human approval.', schema: z.object({ amountCents: z.number() }), }, ); ``` The same call works unchanged inside a durable workflow step: ```ts async function loadPolicy() { 'use step'; const result = await stackbone.config.get('refund_threshold_cents'); if (result.error) throw new Error(result.error.code); return result.data; } ``` `get` rejects empty keys with `config_invalid_request`. A key the agent does not have set surfaces `config_not_found`. ## Read many at once `getMany` takes a list of keys and returns whatever the agent DB holds: ```ts import { stackbone } from '@stackbone/sdk'; const result = await stackbone.config.getMany(['greeting', 'retries', 'features']); if (result.error) throw new Error(result.error.code); console.log(result.data.greeting); // string | undefined ``` Keys absent from the agent's config come back as **omissions** in the returned `Partial`: `getMany` never fails because one key is unset. Access fields with `?.` and provide defaults at the call site. ## Read the whole config `getAll` returns the agent's entire config payload as one object. When no config is set yet, it resolves to an empty object (`{}`), never an error: ```ts const result = await stackbone.config.getAll(); if (result.error) throw new Error(result.error.code); const config = result.data; // {} when nothing is set ``` ## Typed config By default, config reads are loose: keys are `string` and values are `unknown`. To get autocompletion and compile-time key checking, declare the shape once in a `config.schema.ts` at your project root, exporting a Zod schema named `configSchema`: ```ts // config.schema.ts import { z } from '@stackbone/sdk'; export const configSchema = z.object({ greeting: z.string(), retries: z.number().int(), refund_threshold_cents: z.number().int(), features: z.object({ betaInbox: z.boolean() }), }); ``` The operator's dashboard form comes from that one file, and Stackbone validates their saves against it. Your types come from it too. `stackbone build` converts the schema to JSON Schema and writes it into the bundle, so a deployed container renders the same form your laptop does. Generate the types with: ```sh stackbone config types ``` This emits `.stackbone/config.d.ts` in your project. (`stackbone dev` regenerates it on every `config.schema.ts` change, so you rarely run `config types` by hand.) The generated file augments the SDK's `ConfigRegistry` interface: ```ts // .stackbone/config.d.ts: generated, do not edit declare module '@stackbone/sdk' { interface ConfigRegistry extends StackboneAgentConfig {} } ``` Once that file exists, `stackbone.config.get('greeting')` is typed `string`, `get('refund_threshold_cents')` is typed `number`, and `get('unknownKey')` is a compile error. `getMany` and `getAll` become strictly keyed against the schema too. Add `.stackbone/` to your `.gitignore`: the CLI regenerates it locally, so you do not commit it. > **Without a `config.schema.ts`,** reads stay loose: values come back as > `unknown`. You can pass a per-call type as the fallback, e.g. > `(await stackbone.config.get('greeting')).data as string`. Prefer the > schema-driven path. ## Setting config The values your code reads come from a single versioned config document. Operators edit it in the dashboard, but you can also drive it from the CLI: | Command | What it does | | --------------------------------------------- | ------------------------------------------------------------ | | `stackbone config get` | Print the active config document (value, version, author). | | `stackbone config set --file cfg.json` | Save a new version from a JSON object (or pipe it on stdin). | | `stackbone config versions` | List recent versions, newest first. | | `stackbone config rollback --version N --yes` | Roll the active config back to a prior version. | Every `set` appends a new version and edits nothing in place, so you can roll back. The agent reads the active version on its next config read. ## Errors Config reads hit the agent database, so failures are database-shaped rather than HTTP-shaped. | Code | When | | ------------------------- | --------------------------------------------------------------------------------- | | `config_invalid_request` | Empty `key`, or an empty `keys` array. | | `config_not_found` | `get(key)` and the agent has no value for that key. | | `config_unavailable` | The read against the agent database failed. | | `database_not_configured` | The agent has no Postgres connection configured (`STACKBONE_POSTGRES_URL` unset). | ## Where to go next - **[`stackbone.secrets`](/docs/sdk/platform/secrets)**: same ambient pattern, but for encrypted credentials. - **[`stackbone.settings`](/docs/sdk/platform/settings)**: the platform's own knobs for this workspace, read the same way but owned by the operator, not by you. - **[`stackbone.approval`](/docs/sdk/humans/approval)**: pair config thresholds with a human-approval gate so a value change can flip an action from automatic to "needs approval" without republishing. - **[Agents & sessions](/docs/sdk/agents/overview)** and **[Workflows](/docs/sdk/workflows/overview)**: the two places your config reads run.