Architecture
This section covers the internal design of Gestalt: how the server starts up, how it stores and protects credentials, and how requests flow through to upstream APIs. For day-to-day configuration, see Configuration. For operator-facing security guidance, see Security.
How startup works
When gestaltd serve starts, it goes through a specific sequence that matters when you’re debugging boot failures.
First, the YAML config file is loaded and ${ENV_VAR} placeholders are expanded. If a variable isn’t set, Gestalt checks for a corresponding _FILE variable and reads that file instead (this is how Docker secrets work). If neither exists, config loading fails. Use ${ENV_VAR:-} when an explicit empty default is intentional. Unknown fields are rejected.
Next, Gestalt initializes the secret managers from providers.secrets. The secret manager config blocks themselves cannot use structured secret refs, because the secret managers do not exist yet. Credentials for providers.secrets.<name>.config have to come in through ${ENV_VAR} or ${ENV_VAR_FILE}.
Once the secret managers are ready, Gestalt resolves every structured secret ref across the rest of the config: server settings, auth, indexeddb, telemetry, audit, cache, and all plugin configs. Every ref names its provider explicitly, and each providers.secrets.<name>.config block is skipped to avoid a circular dependency.
With secrets resolved, server.encryptionKey gets converted into a 32-byte AES key. A 64-character hex string is decoded directly; anything else goes through Argon2id derivation. This derived key protects all stored tokens.
From there, the authentication and datastore providers are spawned as child processes over gRPC, schema migrations run, plugins start, and the HTTP listeners come up. The /ready endpoint returns 503 until everything reports ready.
How requests flow
Authentication middleware runs first on every request. It checks for a session_token cookie, then for a Bearer header. If the bearer token starts with gst_api_, it’s hashed with SHA-256 and looked up in the datastore. Any other bearer value is treated as a raw provider token.
For operation invocations (/api/v1/{integration}/{operation} or /mcp), the request reaches the broker. The broker resolves the caller’s plugin token from the datastore, decrypts it, refreshes it if needed, and dispatches to the plugin (over gRPC, or to a REST/GraphQL/MCP upstream). The same broker handles both HTTP and MCP calls.
Further reading
- Data Model: database tables, encryption boundaries, token storage granularity
- Security Internals: encryption implementation, token lifecycle, key rotation