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, or from a durable workflow 'use step'. There is no createClient() and no per-invoke handle to wire up: import the ambient client and call it.

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 envelope: check .error before touching .data.

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:

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:

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:

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:

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:

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:

// .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: same ambient pattern, but for encrypted credentials.
  • stackbone.settings: the platform's own knobs for this workspace, read the same way but owned by the operator, not by you.
  • stackbone.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 and Workflows: the two places your config reads run.
BUILT WITH ❤️ FROM CANADA AND SPAIN