Skip to Content
Configuration

Configuration

Gestalt is configured from one or more YAML files. This page covers the key concepts and how to set up each part. The Config File reference documents every field and its validation rules.

Config file structure

A Gestalt config has six top-level keys: server for host settings and host-provider selection, authorization for shared subject access policy, providers for named host-scoped providers and UIs, runtime for named plugin runtime backends, workflows for config-managed schedules and event triggers, and plugins for executable integrations and optional plugin-backed UI mounts.

authorization: # shared subject access policies policies: ... plugins: <name>: ... # each is a ProviderEntry providers: # named audit pipelines audit: <name>: ... # default: providers.audit.default = inherit # named authentication providers authentication: <name>: ... # each is a ProviderEntry # named authorization providers authorization: <name>: ... # each is a ProviderEntry # named cache providers cache: <name>: ... # each is a ProviderEntry # named storage providers indexeddb: <name>: ... # each is a ProviderEntry # named S3 object-store providers s3: <name>: ... # each is a ProviderEntry # named secret managers secrets: <name>: ... # each is a ProviderEntry # named telemetry pipelines telemetry: <name>: ... # default: providers.telemetry.default = stdout # named UI bundles ui: <name>: ... # each is a UIEntry # named workflow providers workflow: <name>: ... # each is a ProviderEntry runtime: providers: <name>: ... # each is a RuntimeProviderEntry workflows: schedules: {} # config-managed schedules eventTriggers: {} # config-managed triggers server: # public URL for OAuth callbacks baseUrl: ... # outbound request policy egress: defaultAction: ... # "allow" or "deny" # required root deployment secret encryptionKey: ... # optional management listener management: host: ... port: ... # host-provider selection providers: audit: ... authentication: ... authorization: ... indexeddb: ... secrets: ... telemetry: ... # default runtime selection for plugins that opt in runtime: defaultHostedProvider: ... # public listener (default port 8080) public: host: ... port: ...

ProviderEntry-backed entries accept source (where to load it from), config (provider-specific settings), egress.allowedHosts, and optional metadata fields. Plugin entries additionally support plugin-only fields such as connections, allowedOperations, ui.path, ui.bundle, and execution.runtime. See the Config File reference for every field.

Key concepts

Gestalt has ten core concepts you configure in YAML:

  • Plugin. A tool backed by a provider package. Plugins expose operations that agents and users invoke over HTTP, MCP, or the CLI. Plugins can come from published packages or local source.
  • Connection. How Gestalt authenticates to an upstream API on behalf of a user or service. Connections handle OAuth flows, API keys, and manual credentials.
  • IndexedDB. The persistent storage layer. Stores users, sessions, tokens, and credentials through a provider that implements the IndexedDB interface.
  • Authorization. Optional host-scoped policy storage and decision engine for dynamic subject authorization state. The first-party authorization/indexeddb provider persists models and relationships in the selected host IndexedDB provider.
  • S3. Plugin-bound object storage. The provider config chooses the backend, credentials, and endpoint; plugin code chooses buckets, keys, and versions at runtime.
  • Authentication. Platform login for the Gestalt server. Separate from connection authentication, which is per-plugin and per-upstream.
  • Secrets. Resolves structured secret refs in the config at startup. Backed by environment variables, files, or cloud secret managers.
  • Workflow. Global workflow runs, schedules, and triggers backed by a workflow provider and optional host IndexedDB storage.
  • UI. Public static UI bundles served under configured path prefixes, either directly from providers.ui or through plugins.<name>.ui.path. Omit providers.ui to run headless.
  • Runtime. Optional execution backends for executable plugins. Plugins stay host-local by default; plugins.<name>.execution.mode: hosted opts a plugin into hosted execution and runtime.providers names the available backends.

Adding a plugin

Each entry under plugins registers a plugin backed by a provider package:

plugins: github: displayName: GitHub source: https://artifacts.example.com/plugin/github/v0.0.1-alpha.1/provider-release.yaml surfaces: openapi: baseUrl: https://api.github.com ui: path: /github bundle: github_console

For published providers, source usually points at a provider-release.yaml metadata URL. When the release is hosted in GitHub and you want checked-in config to stay readable, use source.githubRelease instead. Gestalt resolves the matching platform archive during init and records the exact release in the lockfile. First-party providers are published from github.com/valon-technologies/gestalt-providers/.

For a private GitHub release, pair source.githubRelease with inline source auth:

plugins: support: source: githubRelease: repo: valon-technologies/private-providers tag: plugins/support/v2.4.0 asset: provider-release.yaml auth: token: ${GITHUB_TOKEN}

During development, point at a local provider manifest instead:

plugins: support: source: ./plugins/support/manifest.yaml

See Custom Providers if you need to build your own.

Filtering operations

A provider package may expose a large catalog. Use allowedOperations to publish only the subset you want:

plugins: support: source: https://artifacts.example.com/plugin/support/v2.4.0/provider-release.yaml allowedOperations: tickets.list: alias: list_tickets tickets.get: alias: get_ticket

Each entry can set an alias to rename the operation as it appears to callers.

Egress control

Every provider supports egress.allowedHosts to declare which upstream hosts it can reach. When server.egress.defaultAction is deny, providers without an explicit allowlist are blocked from host-mediated outbound requests. Executable plugin providers may also use runtime-backed isolation when egress restrictions are active, but the selected runtime has to preserve Gestalt’s hostname-based policy model. Backends that only support CIDR or firewall controls cannot faithfully implement egress.allowedHosts by themselves, so Gestalt rejects those runtime/plugin combinations instead of silently weakening policy.

plugins: example: source: https://artifacts.example.com/plugin/example/v1.0.0/provider-release.yaml egress: allowedHosts: - api.example.com - cdn.example.com

Choosing a plugin runtime

Executable plugins run on the same machine as gestaltd unless the plugin opts into a hosted runtime. Runtime selection is two-step: define named backends under runtime.providers, then opt an individual plugin in with plugins.<name>.execution.mode: hosted.

server.runtime.defaultHostedProvider is only a default selector for plugins that already set execution.mode: hosted; it does not move every plugin into a hosted runtime automatically.

runtime: providers: modal: source: path: ./vendor/gestalt-providers/runtime/modal/manifest.yaml default: true config: app: gestalt-runtime environment: main server: runtime: defaultHostedProvider: modal plugins: support: source: ./plugins/support/manifest.yaml execution: mode: hosted runtime: template: python-dev image: ghcr.io/example/support-plugin:2026-04-21 metadata: environment: production owner: support-platform

plugins.<name>.execution.runtime.template, image, and metadata are forwarded to the selected runtime backend. Their meaning is backend-specific: a local runtime may ignore them, while a hosted runtime may use them to choose a sandbox template, container image, or labels/tags for lifecycle management. When runtime.image is set, Gestalt expects the image to contain the provider package at the image working directory. It starts the executable declared by the provider manifest entrypoint and passes the manifest entrypoint args:

execution: mode: hosted runtime: provider: modal image: ghcr.io/example/support-plugin@sha256:...

The built-in local runtime handles same-machine execution. Installable kind: runtime providers such as Modal handle hosted execution.

The current Modal backend requires runtime.providers.<name>.config.app and plugins.<name>.execution.runtime.image. Modal can satisfy plugins that need Gestalt-owned services or hostname-based egress when the host is configured for the public relay and proxy path. In practice that means server.baseURL and server.encryptionKey must be set, and the runtime must report a compatible support profile. Without those prerequisites, Gestalt rejects Modal for plugins that require relay-backed host services or hostname-based egress.

For the runtime-provider lifecycle and support model, see Providers > Runtime.

Binding S3 object stores

S3 providers are plugin-only bindings. Configure named entries under providers.s3, then grant a plugin access with plugins.<name>.s3.

providers: s3: assets: source: https://artifacts.example.com/s3/s3/v0.0.1-alpha.1/provider-release.yaml config: region: us-east-1 endpoint: https://s3.us-east-1.amazonaws.com accessKeyId: ${AWS_ACCESS_KEY_ID} secretAccessKey: secret: provider: default name: aws-secret-access-key plugins: media: source: ./plugins/media/manifest.yaml s3: - assets

With one S3 binding, the SDK can use its default helper (GESTALT_S3_SOCKET). With multiple bindings, use the named helper such as gestalt.S3("archive") or new S3("archive"). See S3 Providers for backend-specific configuration and operational notes.

Connecting to upstream APIs

Connections define how Gestalt authenticates to upstream services on behalf of callers. Each plugin declares its connections under connections::

plugins: github: connections: default: mode: user auth: type: oauth2

Connection modes

ModeBehavior
noneNo upstream credential needed.
userCredentials are stored for the calling subject (for example user:<id> or service_account:<id>).

Authentication Types

TypeUse case
oauth2Standard OAuth 2.0 with authorization URL, token URL, scopes, PKCE.
mcp_oauthDelegates authentication to an upstream MCP server.
bearerStatic bearer token. Prompts the user for the value.
manualCustom credential fields (API key, org ID, etc.).
noneNo authentication.

OAuth app credentials

Plugins that use oauth2 connections typically require you to register an OAuth app with the upstream service and provide the app credentials in the server config. The provider’s manifest declares the OAuth URLs and scopes; your config supplies the client ID and client secret.

For example, the GitHub plugin requires OAuth app credentials from the GitHub Developer Settings :

plugins: github: source: https://artifacts.example.com/plugin/github/v0.0.1/provider-release.yaml config: clientId: ${GITHUB_CLIENT_ID} clientSecret: secret: provider: default name: github-client-secret

Not all providers need this. Some providers (like Slack) use OAuth flows that do not require operator-provided app credentials. Check the provider’s documentation or config schema for required fields.

Connection naming

connections.default is the common case. When a provider defines more than one connection, callers pass _connection and _instance through the HTTP API to select which stored credential to use. Connection parameters (params) and post-connect discovery are only supported on connections.default.

Gestalt stores upstream credentials keyed by user, plugin name, connection name, and instance. Renaming a plugin key under plugins is a breaking change for stored connections.

Exposing over MCP

MCP tool exposure is declared in the provider manifest via spec.mcp: true. When enabled, the plugin’s operations are available at /mcp using the same auth model as the HTTP API.

Secrets

The providers.secrets map configures named secret managers. Each config secret ref names one of those providers explicitly:

server: encryptionKey: secret: provider: default name: gestalt-encryption-key

When providers.secrets is omitted entirely, the runtime secrets manager defaults to built-in env, but structured refs still require an explicit named provider entry:

providers: secrets: default: source: env

For production, use file (reads from a directory, works with Kubernetes volume-mounted secrets) or a cloud secret manager:

providers: secrets: default: source: file config: dir: /etc/gestalt-secrets

Cloud backends (Google Secret Manager, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) are also available. See Secret Providers for the full list.

UI

The providers.ui map controls public static UI bundles served under explicit non-root paths. Omit it to run headless:

providers: {}

Or point at a custom UI bundle:

providers: ui: roadmap: source: https://artifacts.example.com/ui/your-ui/v1.0.0/provider-release.yaml path: /create-customer-roadmap-review

The built-in admin UI at /admin is always available regardless of this setting. See UIs for the bundle format.

Platform Authentication

The providers.authentication map protects the UI, HTTP API, and /mcp endpoint. For local development, omit it:

For multi-user deployments, point at a published authentication provider:

server: providers: authentication: oidc providers: authentication: oidc: source: https://artifacts.example.com/auth/oidc/v0.0.1-alpha.1/provider-release.yaml config: issuerUrl: https://login.example.com clientId: ${OIDC_CLIENT_ID} clientSecret: secret: provider: default name: oidc-client-secret

For production OIDC providers, keep issuerUrl on https://. If you run a local plaintext issuer on loopback for development, add allowInsecureHttp: true under providers.authentication.<name>.config.

ScenarioConfiguration
Local developmentOmit providers.authentication
Multi-user deploymentproviders.authentication.<name> with a published OIDC or other authentication provider
Per-user upstream accessmode: user on plugin connections

API tokens

For programmatic access, Gestalt issues API tokens prefixed with gst_api_. They are accepted through Authorization: Bearer ... and use the same auth middleware as browser sessions. Token lifetime is controlled by server.apiTokenTtl, which defaults to 30d.

Configuring storage

The providers.indexeddb block declares storage providers. server.providers.indexeddb selects which one Gestalt uses:

server: providers: indexeddb: main providers: indexeddb: main: source: https://artifacts.example.com/indexeddb/relationaldb/v0.0.1-alpha.1/provider-release.yaml config: dsn: sqlite://./gestalt.db

SQLite works for single-instance deployments. For production, use a networked IndexedDB provider such as indexeddb/relationaldb with Postgres or MySQL, indexeddb/dynamodb, or indexeddb/mongodb. See IndexedDB for the full provider model.

Configuring authorization storage

The providers.authorization block is optional. When configured, server.providers.authorization selects the host authorization provider Gestalt uses for dynamic subject authorization relationships and model storage:

server: providers: indexeddb: main authorization: indexeddb providers: indexeddb: main: source: https://artifacts.example.com/indexeddb/relationaldb/v0.0.1-alpha.1/provider-release.yaml config: dsn: sqlite://./gestalt.db authorization: indexeddb: source: https://artifacts.example.com/authorization/indexeddb/v0.0.1-alpha.1/provider-release.yaml config: indexeddb: main

If providers.authorization is omitted, static policy members from config still work, but there is no provider-backed relationship store and the dynamic subject authorization control plane is unavailable. When it is present, Gestalt uses that provider for dynamic subject authorization, subject authorization checks, and the built-in admin authorization APIs.

Configuring workflow providers

The providers.workflow block declares workflow backends. Config-managed entries under top-level workflows.schedules and workflows.eventTriggers select a provider with provider, or inherit the sole/default providers.workflow entry when that field is omitted. User-owned schedules use the same provider pool through the global workflow API and CLI surface: /api/v1/workflow/schedules and gestalt workflow ....

providers: indexeddb: workflow_state: source: https://artifacts.example.com/indexeddb/relationaldb/v0.0.1-alpha.1/provider-release.yaml config: dsn: sqlite://./gestalt.db workflow: local: source: https://artifacts.example.com/workflow/indexeddb/v0.0.1-alpha.1/provider-release.yaml indexeddb: provider: workflow_state db: workflow config: pollInterval: 1s workflows: schedules: nightly_sync: provider: local cron: "0 3 * * *" timezone: America/New_York target: plugin: name: roadmap operation: sync_items eventTriggers: item_updated: provider: local match: type: roadmap.item.updated target: plugin: name: roadmap operation: sync_items

Workflow targets are always explicit through target.plugin or target.agent. For plugin targets, set name, operation, and optional connection, instance, and input. Config-managed schedules and event triggers are reconciled into the provider at startup. See Workflow for the user-facing and provider model.

Configuring agent providers

The providers.agent block declares the global pool of agent backends. There is no server.providers.agent: a session request either names a provider explicitly or inherits the sole/default providers.agent entry when it omits provider.

providers: agent: simple: source: ./providers/agent/simple/manifest.yaml default: true indexeddb: provider: default db: simple_agent config: runStore: runs idempotencyStore: run_idempotency defaultModel: fast aliases: fast: openai/gpt-4.1-mini deep: anthropic/claude-sonnet-4-20250514

Agent providers receive canonical session and turn requests with messages, optional tools, and optional structured-output schema. The same provider pool is used by the global agent API/CLI and by plugins that use the host agent manager surface. Providers that need durable state can opt into a host IndexedDB binding with indexeddb and use the SDK IndexedDB helper against GESTALT_INDEXEDDB_SOCKET.

See Agent for the provider model and Custom Providers > Agent if you need to implement your own backend.

Telemetry and audit

Gestalt ships with built-in telemetry and audit providers that work without any configuration. The defaults emit to stdout:

providers: telemetry: default: source: stdout audit: default: source: inherit

inherit routes audit events through the telemetry provider. For production observability with OpenTelemetry, see Observability.

Config file mechanics

Lookup order

When --config is not supplied, gestaltd looks for a config file in this order:

  1. GESTALT_CONFIG environment variable
  2. ./config.yaml in the working directory
  3. ~/.gestaltd/config.yaml

If nothing is found, gestaltd generates a default config at ~/.gestaltd/config.yaml with SQLite storage, no auth, a random encryption key, and an HTTPBin test plugin.

When --config is repeated, Gestalt loads the files left-to-right and merges them with these rules:

  • maps deep-merge
  • later scalar values win
  • later lists replace inherited lists
  • null deletes inherited keys

By default, lockfiles and prepared-artifact paths stay rooted at the leftmost config file. --lockfile overrides only the lockfile path and resolves like a normal CLI path. Relative config values still resolve from the directory of the file that declared them, so override files can safely use their own source.path, iconFile, and server.artifactsDir entries.

Environment expansion

Gestalt expands ${ENV_VAR} placeholders before YAML decoding. When a referenced variable is unset, Gestalt checks for a corresponding ENV_VAR_FILE variable and reads that file path instead. If neither is set, config loading fails. Use ${ENV_VAR:-} when an explicit empty default is intentional. This is useful for container runtimes that mount secrets as files.

Secret resolution

Structured secret refs are resolved through the named secret manager during bootstrap, not during YAML parsing. This lets you reference secrets from Vault, AWS Secrets Manager, Google Secret Manager, or Azure Key Vault without embedding them in the config file.

Validation

Unknown YAML fields are rejected at load time. When multiple config files are layered, each relative path resolves from the directory of the file that introduced it. You can check a config without starting the server:

gestaltd validate --config ./config.yaml gestaltd validate --config ./deploy/base.yaml --config ./deploy/overrides/local.yaml

Defaults and required fields

Gestalt defaults server.public.port to 8080, the runtime secrets manager to built-in env when providers.secrets is omitted, providers.telemetry.default to stdout, and providers.audit.default to inherit. The required fields are providers.indexeddb, server.providers.indexeddb, and server.encryptionKey. Platform auth is optional.

server.encryptionKey is the root deployment secret. Gestalt derives token encryption and session material from it. Changing this key invalidates all existing sessions and tokens, so treat it as a meaningful deployment event.

Full example

A production deployment with OIDC auth, a GitHub plugin with per-user connections, and MCP enabled:

server: baseUrl: https://gestalt.example.com encryptionKey: secret: provider: default name: gestalt-encryption-key providers: authentication: oidc secrets: default indexeddb: main providers: secrets: default: source: file config: dir: /etc/gestalt-secrets authentication: oidc: source: https://artifacts.example.com/auth/oidc/v0.0.1-alpha.1/provider-release.yaml config: issuerUrl: https://login.example.com clientId: ${OIDC_CLIENT_ID} clientSecret: secret: provider: default name: oidc-client-secret indexeddb: main: source: https://artifacts.example.com/indexeddb/relationaldb/v0.0.1-alpha.1/provider-release.yaml config: dsn: sqlite://./gestalt.db plugins: github: displayName: GitHub source: https://artifacts.example.com/plugin/github/v0.0.1-alpha.1/provider-release.yaml surfaces: openapi: baseUrl: https://api.github.com connections: api: mode: user auth: type: bearer credentials: - name: token label: Personal Access Token mcp: mode: user auth: type: mcp_oauth

Minimal config

The smallest useful config:

server: public: port: 8080 baseUrl: http://localhost:8080 encryptionKey: ${GESTALT_ENCRYPTION_KEY} providers: indexeddb: main providers: indexeddb: main: source: https://artifacts.example.com/indexeddb/relationaldb/v0.0.1-alpha.1/provider-release.yaml config: dsn: sqlite://./gestalt.db

Set GESTALT_ENCRYPTION_KEY once with openssl rand -hex 32 before starting Gestalt. Do not use a shared placeholder value in a real deployment.

See Observability for adding metrics, logs, and OTLP export. See Deploy for production startup workflows, lockfiles, and prepared artifacts.