# Configuration

> The canonical configuration reference for Flowly. All settings live in a single JSON file with camelCase keys; most users never edit it by hand, but every key is documented here.

Source: https://useflowlyapp.com/en/docs/using-flowly/configuration
Language: en

## File path

```
<FLOWLY_HOME>/config.json
```

`FLOWLY_HOME` defaults to `~/.flowly`. The file is stored with owner-only (`0600`) permissions because it holds API keys.

Sibling files in the same directory:

- `config.json.bak` — a self-heal backup, seeded from the last good config.
- `config.json.broken-<unix-ts>` — a corrupted config moved aside during recovery.

## Profiles

A profile is an isolated `FLOWLY_HOME` with its own config, sessions, workspace, and credentials. The active profile is resolved in this order:

1. `-p` / `--profile <name>` CLI flag
2. `FLOWLY_PROFILE` environment variable
3. `~/.flowly/active_profile` sticky pointer file
4. `"default"`

The `default` profile is `~/.flowly`. A named profile `coder` lives at `~/.flowly/profiles/coder`. Profile names must match `^[a-z0-9][a-z0-9_-]{0,63}$`; the names `flowly`, `default`, `test`, `tmp`, `root`, and `sudo` are reserved. See [Environment variables](https://useflowlyapp.com/en/docs/reference/environment-variables).

## Self-healing loader

The loader is resilient by design:

1. It parses `config.json`. If valid, it's used (and a `.bak` is seeded if missing).
2. If parsing or validation fails, it tries `config.json.bak`. If that's good, the broken file is renamed `config.json.broken-<ts>` and the backup is restored.
3. If both fail, Flowly falls back to in-code defaults so the agent still boots.

> [!WARNING]
> If both `config.json` and `config.json.bak` fail to load, **secrets in the broken file are lost from the running process** (but the broken file is preserved on disk).

> [!NOTE]
> Saving is read-modify-write with a deep merge, so **unknown or manually-added keys are preserved**. A `None` value in the model never overwrites an existing non-`None` value on disk — to truly clear a value, write `""` or delete the key. Unknown top-level keys are ignored (`extra = "ignore"`), not errors. A handful of behaviors are overridable with the documented [`FLOWLY_*` environment variables](https://useflowlyapp.com/en/docs/reference/environment-variables); note that a generic nested override like `FLOWLY_GATEWAY__PORT` only takes effect on the fallback path (when `config.json` is missing or unparseable) — when a valid `config.json` exists, it is **not** consulted for those.

## Top-level keys

| Key | Purpose |
|---|---|
| `agents` | Agent defaults, compaction, heartbeat, memory search, multi-agent |
| `channels` | Messaging channels (Telegram, Discord, Slack, …) |
| `providers` | LLM providers and API keys |
| `gateway` | Gateway host / port |
| `tools` | Built-in tool toggles and limits |
| `integrations` | Third-party integrations (Trello, voice, X, …) |
| `audit` | Local audit-log retention |
| `plugins` | Enable / disable plugins |
| `mcpServers` | MCP server definitions |
| `backgroundMode` | `bool` (default `false`) |

### agents

`agents.defaults` — applied to the main agent:

| Key | Default |
|---|---|
| `workspace` | `"~/.flowly/workspace"` |
| `cwd` | `""` (empty → workspace; overridden by `FLOWLY_CWD`) |
| `model` | `"anthropic/claude-haiku-4.5"` |
| `maxTokens` | `8192` |
| `temperature` | `0.7` |
| `actionTemperature` | `0.1` |
| `actionToolRetries` | `2` |
| `maxToolIterations` | `100` |
| `softWarnAtIteration` | `30` |
| `contextMessages` | `100` |
| `persona` | `"default"` |
| `saveTrajectories` | `false` |
| `memoryNudgeInterval` | `10` |
| `skillNudgeInterval` | `15` |

`agents.defaults.compaction` — see [Sessions](https://useflowlyapp.com/en/docs/using-flowly/sessions):

| Key | Default |
|---|---|
| `mode` | `"safeguard"` (`"default"` \| `"safeguard"`) |
| `reserveTokensFloor` | `20000` |
| `maxHistoryShare` | `0.5` |
| `contextWindow` | `128000` |
| `memoryFlush.enabled` | `true` |
| `memoryFlush.softThresholdTokens` | `4000` |

`agents.defaults.goals` — see [Standing goals](https://useflowlyapp.com/en/docs/features/goals):

| Key | Default |
|---|---|
| `enabled` | `true` |
| `maxTurns` | `20` (turn budget before a goal pauses for review) |
| `judgeProvider` | `""` (empty inherits the conversation's provider) |
| `judgeModel` | `""` (empty inherits the conversation's model) |
| `judgeTimeoutSeconds` | `30` |
| `judgeMaxTokens` | `4096` |
| `gateTimeoutSeconds` | `300` |
| `gateMaxRetries` | `3` |

`agents.defaults.heartbeat`:

| Key | Default |
|---|---|
| `enabled` | `true` |
| `everyMinutes` | `30` |
| `activeHours` | `null` (or `{ start: "09:00", end: "23:00", timezone: "" }`) |
| `deliver` | `"none"` (`"none"` \| `"message_tool"`) |

`agents.defaults.memorySearch`:

| Key | Default |
|---|---|
| `enabled` | `true` |
| `provider` | `"auto"` (`"auto"` \| `"openai"` \| `"gemini"` \| `"none"`) |
| `model` | `""` |
| `apiKey` | `""` |
| `apiBase` | `""` |
| `chunkTokens` | `400` |
| `overlapTokens` | `80` |
| `maxResults` | `6` |
| `minScore` | `0.35` |
| `vectorWeight` | `0.7` |
| `textWeight` | `0.3` |

`agents.defaults.memoryDreaming` — cross-session "dreaming" + autonomous consolidation (see [Memory](https://useflowlyapp.com/en/docs/features/memory)):

| Key | Default |
|---|---|
| `enabled` | `true` (the whole governance/dreaming layer) |
| `commitMode` | `"selective"` (`"selective"` \| `"manual"` \| `"aggressive"`) |
| `idleMinutes` | `30` (run after this much agent inactivity; background heartbeats don't count) |
| `dailyEnabled` | `true` |
| `dailyTime` | `"03:30"` (HH:MM local) |
| `turnInterval` | `10` (also run every N user turns; `0` disables the coarse pass) |
| `autoFloor` | `0.80` (≥ → auto-active when unconflicted and not sensitive) |
| `reviewFloor` | `0.55` (< → dropped instead of queued) |
| `maxMessagesPerRun` | `500` (bound per pass so a backlog can't blow up one run) |
| `autoConsolidate` | `true` (background cleanup: merge duplicates, retire stale) |
| `consolidateTurnInterval` | `50` (consolidate every N user turns; `0` off) |
| `consolidateEveryMinutes` | `30` (background consolidation timer; `0` off) |
| `freezeInjectedMemory` | `false` (advanced: freeze the injected memory block per session for prefix-cache stability) |

`agents.agents` (per-agent map) — `name`, `provider` (`"anthropic"` default \| `"openai"` \| `"flowly"`), `model=""`, `workingDirectory=""`, `persona=""`.
`agents.teams` (per-team map) — `name`, `agents=[]`, `leaderAgent=""`.

### channels

Channels are off by default. See [Channels overview](https://useflowlyapp.com/en/docs/channels/overview).

| Channel | Key defaults |
|---|---|
| `whatsapp` | `enabled=false`, `bridgeUrl="ws://localhost:3001"`, `allowFrom=[]` |
| `telegram` | `enabled=false`, `token=""`, `allowFrom=[]`, `dmPolicy="pairing"` (`"open"`\|`"pairing"`\|`"allowlist"`) |
| `discord` | `enabled=false`, `token=""`, `allowFrom=[]`, `gatewayUrl="wss://gateway.discord.gg/?v=10&encoding=json"`, `intents=37377` |
| `slack` | `enabled=false`, `mode="socket"`, `botToken=""`, `appToken=""`, `groupPolicy="mention"`, `groupAllowFrom=[]`, `dm.enabled=true`, `dm.policy="open"`, `dm.allowFrom=[]` |
| `web` | `enabled=false`, `relayUrl=""`, `serverId=""`, `authToken=""`, `jwtSecret=""` |
| `email` | `enabled=false`, `pollIntervalSeconds=30`, `allowFrom=[]` |
| `teams` | `enabled=false`, `webhookUrl=""`, `defaultChatLabel=""`, `allowFrom=[]` |

### providers

| Key | Default / notes |
|---|---|
| `active` | `""` — explicit default provider slug; `""` falls back to the API-key cascade |
| `flowly` | `enabled=true`, `apiBase="https://useflowlyapp.com/api/v1"` (Flowly Cloud; uses account token when signed in) |
| `xaiOAuth` | `enabled=true`, `clientId=""`, `apiBase="https://api.x.ai/v1"` (tokens stored in the OS keychain, not config) |
| `openaiCodex` | ChatGPT subscription (Codex OAuth) — tokens in the OS keychain, not config |
| `zaiCoding` | Z.AI GLM Coding Plan — tokens in the OS keychain, not config |

BYOK provider slots — each with `apiKey=""`, `apiBase=null`, `fallbackKeys=[]`: `anthropic`, `openai`, `openrouter`, `zhipu`, `sakana`, `vllm`, `gemini`, `groq`, `xai`.

When `active=""`, the cascade picks the first usable provider in priority order:

```
openrouter → anthropic → openai → openai_codex → zai_coding → xai
           → xai_oauth → gemini → groq → zhipu → sakana → vllm
```

Setting a provider explicitly (`/provider`, `flowly setup`, or `active` in this file) skips the cascade entirely — worth doing if more than one credential is present, so the choice is yours rather than the order's. See [Providers and models](https://useflowlyapp.com/en/docs/using-flowly/providers-and-models).

> [!NOTE]
> Two of those slots can read a login you created elsewhere: `openai_codex`
> falls back to the Codex CLI's `~/.codex/auth.json`, and `zai_coding` to
> OpenCode's `auth.json`. That's why an existing subscription can work with
> no setup — and why `flowly setup` asks before using one instead of
> adopting it silently.

### gateway

| Key | Default |
|---|---|
| `host` | `"127.0.0.1"` |
| `port` | `18790` (1–65535) |
| `token` | `""` — static gateway administration token; the CLI ensures one on a non-loopback bind |

The dedicated `POST /api/mcp/manage` route always requires a valid, non-empty
gateway token, including through an SSH loopback forward. An empty token may
retain legacy local behavior on other routes; it never unlocks MCP management.
The token is not an external-agent MCP access key. SSH passwords and host-key
verification are client responsibilities, not `gateway` or `mcpServers` settings.
See [Remote MCP setup](https://useflowlyapp.com/en/docs/using-flowly/remote-mcp) and the
[management API](https://useflowlyapp.com/en/docs/reference/mcp-management-api).

### tools

See [Sandbox and approvals](https://useflowlyapp.com/en/docs/using-flowly/sandbox-and-approvals) for execution policy.

| Tool | Key defaults |
|---|---|
| `web.search` | Brave defaults (`apiKey=""`, `maxResults=5`, `proxyUrl=""`) + backend selectors (`backend`/`searchBackend`/`extractBackend`) + per-backend sub-sections (`ddgs`, `searxng`, `tavily`, `exa`, `firecrawl`, `parallel`). See [Web & research](https://useflowlyapp.com/en/docs/features/web). |
| `exec` | `enabled=true`, `timeoutSeconds=300`, `maxOutputChars=200000`, `approvalTimeoutSeconds=120`, `cronMode="deny"` (`"deny"`\|`"approve"`) |
| `artifact` | `enabled=true`, `maxContentLength=500000` |
| `browserTab` | `enabled=false` |
| `computer` | `enabled=false`, `actionDelayMs=100`, `failsafe=true` |
| `codexSession` | `enabled=false`, `codexBin="codex"`, `codexHome=""`, `cwd=""`, `turnTimeoutS=600`, `postToolQuietTimeoutS=90`, `approvalPolicy="on-request"`, `sandbox="workspace-write"`, `exposeFlowlyTools=true` |

> [!IMPORTANT]
> The exec allowlist / per-command approval policy is **not** in `config.json` — only `enabled` and the runtime knobs above are. Approval policy lives in the approvals store at `~/.flowly/credentials/exec-approvals.json`. See [Sandbox and approvals](https://useflowlyapp.com/en/docs/using-flowly/sandbox-and-approvals).

### integrations

| Integration | Key defaults |
|---|---|
| `trello` | `apiKey=""`, `token=""` |
| `voice` | `enabled=false`, `bridgeUrl="http://localhost:8765"`, plus Twilio / STT / TTS credentials (`sttProvider="groq"`, `ttsProvider="elevenlabs"`, `ttsVoice="21m00Tcm4TlvDq8ikWAM"`, `language="en-US"`) |
| `x` | `bearerToken`, `apiKey`, `apiSecret`, `accessToken`, `accessTokenSecret` (all `""`) |
| `googleWorkspace` | `enabled=false`, `email=""` |
| `linear` | `apiKey=""` |
| `homeAssistant` | `url=""`, `token=""` (tools register only when both are set) |

### audit

| Key | Default |
|---|---|
| `enabled` | `true` |
| `retentionDays` | `90` (`-1` disables the age cap) |
| `maxSizeMb` | `100` (`0` disables the size cap) |

Audit records are written as daily JSONL files under `<FLOWLY_HOME>/audit/`. These keys control retention only.

### plugins

| Key | Default |
|---|---|
| `enabled` | `[]` |
| `disabled` | `[]` |

Bundled plugins load by default unless listed in `disabled`. User plugins under `$FLOWLY_HOME/plugins/<name>/` load only if listed in `enabled`. `disabled` overrides `enabled`.

### mcpServers

A map of server name → server config. Each server uses either local stdio (`command`, `args`, `env`) or HTTP/SSE (`url`, `headers`). Common defaults include `enabled=true`, `transport="auto"`, `protocol="auto"`, `timeout=120`, and `connectTimeout=60`. HTTP connections can use native OAuth with `auth="oauth"`; TLS, mTLS, scope, trust, elicitation, bounded sampling, diagnostics, pagination, binary-content, concurrency, and supervised lifecycle controls are available per server.

Tool permissions use `tools.mode`: `all`, `selected`, `none`, or `legacy`. `selected` with an empty `include` list means no tools. `legacy` preserves older hand-written behavior, where a non-empty include list wins, otherwise exclude applies, otherwise all tools load. Resource and prompt utilities are separately controlled by `tools.resources` and `tools.prompts`.

In `all` and `selected` modes, `exclude` still removes matching tool names. `all` includes future discoveries; use `selected` to pin exact tool names. App confirmation of `none` disables the connection and clears resource/prompt access. Configuration publication and live application are separate steps: an apply failure can leave the new settings saved, so inspect the reported runtime state and retry.

App-managed setup writes an internal `oauthCredentialId` only after OAuth, connection testing, discovery, and explicit permission confirmation succeed. Do not copy that identifier between server entries. Server names and `env`/`headers` keys are preserved verbatim by the loader. See the complete [MCP configuration reference](https://useflowlyapp.com/en/docs/features/mcp#mcpservers-configuration-reference) for every key, default, bound, and lifecycle behavior.

## Example

A minimal `config.json` after setting an Anthropic key and a Telegram bot:

```json
{
  "providers": {
    "active": "anthropic",
    "anthropic": { "apiKey": "sk-ant-..." }
  },
  "agents": {
    "defaults": {
      "model": "claude-sonnet-4-5",
      "persona": "default"
    }
  },
  "channels": {
    "telegram": { "enabled": true, "token": "123:ABC", "dmPolicy": "pairing" }
  },
  "gateway": { "host": "127.0.0.1", "port": 18790 }
}
```

## Related

- [Sessions](https://useflowlyapp.com/en/docs/using-flowly/sessions)
- [Personas](https://useflowlyapp.com/en/docs/using-flowly/personas)
- [Running as a service](https://useflowlyapp.com/en/docs/using-flowly/service)
- [Providers and models](https://useflowlyapp.com/en/docs/using-flowly/providers-and-models)
- [Sandbox and approvals](https://useflowlyapp.com/en/docs/using-flowly/sandbox-and-approvals)
- [Channels overview](https://useflowlyapp.com/en/docs/channels/overview)
- [Setup wizard](https://useflowlyapp.com/en/docs/getting-started/setup-wizard)
- [CLI commands](https://useflowlyapp.com/en/docs/reference/cli-commands)
- [Environment variables](https://useflowlyapp.com/en/docs/reference/environment-variables)
