Skip to content

Going to production

Out of the box, flowgate uses an in-memory store and no audit sink. That’s fine for trying things out. It’s not fine for production, because a restart erases all workflow state and you have no record of what happened.

Here’s what to change before you go live.

The default memory store loses everything on restart. Switch to a durable backend:

# SQLite -- the durable backend for production
store:
kind: sqlite
path: /var/lib/flowgate/workflows.db

SQLite is the durable path. One file, no extra infrastructure, handles thousands of concurrent workflows, and it’s the only backend that keeps Evidence and acknowledgment stores durable. A serve deployment refuses kind: file because a file store persists workflows but keeps governance state (evidence, acknowledgments) in memory — set gateway.allow_ephemeral: true only for development if you want to override that. Multiple processes on the same host can share one SQLite file; there is no networked store backend.

By default, audit events go nowhere. You want them going somewhere:

audit:
sink: file
path: /var/log/flowgate/audit.jsonl

Every workflow start, transition, executor call, guard evaluation, and error gets written as a structured JSON line. Each line is a self-contained event with a timestamp, workflow ID, event type, and relevant details.

Set up log rotation (logrotate, systemd journal, or your preferred tool) so the file doesn’t grow unbounded. The JSON lines format plays well with any log aggregation system — pipe it to your observability stack and you get full visibility into what every model did, when, and with what inputs.

Don’t find out your config is broken when you deploy it. Run the checker in your CI pipeline:

Terminal window
mcp-flowgate check --config gateway.yaml

This catches problems that YAML syntax checking misses:

  • Dangling targets — a transition points to a state that doesn’t exist.
  • Unreachable states — a state that no transition ever leads to.
  • Dead-ends — a non-terminal state with no outbound transitions.
  • Schema issues — malformed input schemas, invalid executor references.

It’s fast and deterministic. Add it right next to your linter.

Point your editor’s YAML language server at the config schema for autocomplete and inline validation:

.vscode/settings.json
{
"yaml.schemas": {
"./schemas/gateway-config.schema.json": "gateway.yaml"
}
}

You get red squiggles for typos, autocomplete for field names, and documentation on hover. Catches mistakes before you even save the file.

You don’t need to restart the gateway to pick up config changes. Send SIGHUP:

Terminal window
kill -HUP $(pgrep flowgate)

Definitions, executors, connections, and the discovery index all rebuild and swap atomically. In-flight workflows keep running on their current definitions. A config.reloaded audit event confirms the reload happened. See the hot reload guide for details.

flowgate runs as a single-user, same-trust-boundary system. If every model connecting to your gateway is operating on behalf of the same user or within the same trust boundary, you’re good to go.

For cross-trust-boundary deployments — where different users or teams need isolation — put an identity proxy in front of the gateway. Envoy, OAuth2-proxy, or any reverse proxy that injects identity headers will work. The gateway sees the identity from headers and can scope workflows accordingly.

Don’t skip this if you have untrusted callers. The gateway itself doesn’t authenticate requests.

Multiple gateway processes can run on a single host and share one SQLite store file — any process can serve any request, since all state lives in that shared file. SQLite’s WAL mode and the store’s optimistic locking handle concurrent access.

Cross-host HA is not supported: there is no networked store backend, so state can’t be shared across machines today. If you need a remote/clustered backend, you can implement the WorkflowStore trait yourself — nothing beyond memory, file, and sqlite ships built in.

Every audit event is structured JSON. This is your monitoring surface. Pipe audit output to your observability stack and you get:

  • Workflow throughput — count workflow.started events per time window.
  • Error rates — count executor.failed events, break down by workflow and transition.
  • Latency — measure time between workflow.started and terminal state events.
  • Guard rejections — track guard.rejected events to see what’s being blocked and why.
  • Human approval queues — monitor approval.pending events to catch bottlenecks.

You don’t need a custom metrics integration. The audit stream already has everything. Build dashboards from the JSON lines the same way you would from any structured log source.