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/indexeddbprovider 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.uior throughplugins.<name>.ui.path. Omitproviders.uito run headless. - Runtime. Optional execution backends for executable plugins. Plugins stay host-local by default;
plugins.<name>.execution.mode: hostedopts a plugin into hosted execution andruntime.providersnames 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_consoleFor 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.yamlSee 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_ticketEach 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.comChoosing 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-platformplugins.<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:
- assetsWith 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: oauth2Connection modes
| Mode | Behavior |
|---|---|
none | No upstream credential needed. |
user | Credentials are stored for the calling subject (for example user:<id> or service_account:<id>). |
Authentication Types
| Type | Use case |
|---|---|
oauth2 | Standard OAuth 2.0 with authorization URL, token URL, scopes, PKCE. |
mcp_oauth | Delegates authentication to an upstream MCP server. |
bearer | Static bearer token. Prompts the user for the value. |
manual | Custom credential fields (API key, org ID, etc.). |
none | No 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-secretNot 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-keyWhen 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: envFor 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-secretsCloud 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-reviewThe 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-secretFor 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.
| Scenario | Configuration |
|---|---|
| Local development | Omit providers.authentication |
| Multi-user deployment | providers.authentication.<name> with a published OIDC or other authentication provider |
| Per-user upstream access | mode: 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.dbSQLite 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: mainIf 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_itemsWorkflow 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-20250514Agent 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: inheritinherit 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:
GESTALT_CONFIGenvironment variable./config.yamlin the working directory~/.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
nulldeletes 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.yamlDefaults 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_oauthMinimal 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.dbSet 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.