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
stackboneclient, 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
actually has:
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 | undefinedKeys absent from the agent's config come back as omissions in the
returned Partial — getMany never fails just 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 setTyped 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:
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() }),
});That one file drives three things at once: the form the operator sees in the dashboard, the validation applied when they save, and your types. Generate the types with:
stackbone config typesThis emits .stackbone/config.d.ts in your project. (stackbone dev
regenerates it automatically on every config.schema.ts change, so you
usually never 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 — it is regenerated locally and
does not need to be committed.
No
config.schema.ts? Reads stay loose — values come back asunknown. You can pass a per-call type as the fallback, e.g.(await stackbone.config.get('greeting')).data as string, but the schema-driven path is the recommended one.
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; nothing is edited in place, so you
can always 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.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 actually run.