Skip to main content

Configuration

Fased reads an optional config from ~/.fased/fased.json. If the file is missing, Fased uses safe defaults. In most setups, fased onboard should write the first version for you. Common reasons to add a config:
  • Connect channels and control who can message the bot
  • Set models, tools, sandboxing, or automation (cron, hooks)
  • Tune sessions, media, networking, or UI
See the full reference for every available field.
New to configuration? Start with fased onboard, then finish normal setup from the selected Agent in the Control UI. Agent > Models, Agent > Channels, Agent > Services, Agent > Skills, Agent > Memory, and Agent > Tasks own the common user-facing controls. Use this page for the raw config contract and advanced fields.

Local profile vs hosting profile

The onboarding profile sets the baseline runtime shape:
  • Local profile
    • personal machine
    • loopback-first gateway bind
    • easiest path for private browser access and direct local operation
    • add Tailscale Serve only when you want access from other devices
  • Hosting profile
    • VPS or always-on machine
    • supervised service install is expected
    • still loopback-first by default
    • remote access should normally come from Tailscale Serve, a direct tailnet bind, a trusted reverse proxy, or SSH
In both profiles, the raw gateway port should stay closed to the public internet unless you intentionally designed around a wider exposure model.

Minimal config

// ~/.fased/fased.json
{
  agents: { defaults: { workspace: "~/.fased/workspace" } },
  channels: { whatsapp: { allowFrom: ["+15555550123"] } },
}

Editing config

fased onboard       # CLI onboarding
fased configure     # config wizard

Strict validation

Fased only accepts configurations that fully match the schema. Unknown keys, malformed types, or invalid values cause the Gateway to refuse to start. The only root-level exception is $schema (string), so editors can attach JSON Schema metadata.
When validation fails:
  • The Gateway does not boot
  • Only diagnostic commands work (fased doctor, fased logs, fased health, fased status)
  • Run fased doctor to see exact issues
  • Run fased doctor --fix (or --yes) to apply repairs

Common tasks

Use Agent > Channels for normal setup. Each channel still has an underlying config section under channels.<provider>, and dedicated channel pages document the exact account flow:All channels share the same DM policy pattern:
{
  channels: {
    telegram: {
      enabled: true,
      botToken: "123:abc",
      dmPolicy: "pairing",   // pairing | allowlist | open | disabled
      allowFrom: ["tg:123"], // only for allowlist/open
    },
  },
}
Use Agent > Models for normal setup. Raw config can still set the primary model and optional fallbacks:Set the primary model and optional fallbacks:
{
  agents: {
    defaults: {
      model: {
        primary: "anthropic/claude-sonnet-4-5",
        fallbacks: ["openai/gpt-5.5"],
      },
      models: {
        "anthropic/claude-sonnet-4-5": { alias: "Sonnet" },
        "openai/gpt-5.5": { alias: "GPT" },
      },
    },
  },
}
  • agents.defaults.models defines the model catalog and acts as the allowlist for /model.
  • Model refs use provider/model format (e.g. anthropic/claude-opus-4-6).
  • agents.defaults.imageMaxDimensionPx controls transcript/tool image downscaling (default 1200); lower values usually reduce vision-token usage on screenshot-heavy runs.
  • See Models CLI for switching models in chat and Model Failover for auth rotation and fallback behavior.
  • For custom/self-hosted providers, see Custom providers in the reference.
DM access is controlled per channel via dmPolicy:
  • "pairing" (default): unknown senders get a one-time pairing code to approve
  • "allowlist": only senders in allowFrom (or the paired allow store)
  • "open": allow all inbound DMs (requires allowFrom: ["*"])
  • "disabled": ignore all DMs
For groups, use groupPolicy + groupAllowFrom or channel-specific allowlists.See the full reference for per-channel details.
Group messages default to require mention. Configure patterns per agent:
{
  agents: {
    list: [
      {
        id: "main",
        groupChat: {
          mentionPatterns: ["@fased", "fased"],
        },
      },
    ],
  },
  channels: {
    whatsapp: {
      groups: { "*": { requireMention: true } },
    },
  },
}
  • Metadata mentions: native @-mentions (WhatsApp tap-to-mention, Telegram @bot, etc.)
  • Text patterns: regex patterns in mentionPatterns
  • See full reference for per-channel overrides and self-chat mode.
Sessions control conversation continuity and isolation:
{
  session: {
    dmScope: "per-channel-peer",  // recommended for multi-user
    threadBindings: {
      enabled: true,
      idleHours: 24,
      maxAgeHours: 0,
    },
    reset: {
      mode: "daily",
      atHour: 4,
      idleMinutes: 120,
    },
  },
}
  • dmScope: main (shared) | per-peer | per-channel-peer | per-account-channel-peer
  • threadBindings: global defaults for thread-bound session routing (Discord supports /focus, /unfocus, /agents, /session idle, and /session max-age).
  • See Session Management for scoping, identity links, and send policy.
  • See full reference for all fields.
Run agent sessions in isolated Docker containers:
{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main",  // off | non-main | all
        scope: "agent",    // session | agent | shared
      },
    },
  },
}
Build the image first: scripts/sandbox-setup.shSee Sandboxing for the full guide and full reference for all options.
{
  agents: {
    defaults: {
      heartbeat: {
        every: "30m",
        target: "last",
      },
    },
  },
}
  • every: duration string (30m, 2h). Set 0m to disable.
  • target: last | whatsapp | telegram | discord | none
  • directPolicy: allow (default) or block for DM-style heartbeat targets
  • See Heartbeat for the full guide.
Use Agent > Tasks for normal recurring work. The raw cron block below controls scheduler-wide retention and concurrency.
{
  cron: {
    enabled: true,
    maxConcurrentRuns: 2,
    sessionRetention: "24h",
    runLog: {
      maxBytes: "2mb",
      keepLines: 2000,
    },
  },
}
  • sessionRetention: prune completed isolated run sessions from sessions.json (default 24h; set false to disable).
  • runLog: prune cron/runs/<jobId>.jsonl by size and retained lines.
  • See Scheduled Tasks for feature overview and CLI examples.
Enable HTTP webhook endpoints on the Gateway:
{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
    defaultSessionKey: "hook:ingress",
    allowRequestSessionKey: false,
    allowedSessionKeyPrefixes: ["hook:"],
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        agentId: "main",
        deliver: true,
      },
    ],
  },
}
See full reference for all mapping options and Gmail integration.
Run multiple isolated agents with separate workspaces and sessions:
{
  agents: {
    list: [
      { id: "home", default: true, workspace: "~/.fased/workspace-home" },
      { id: "work", workspace: "~/.fased/workspace-work" },
    ],
  },
  bindings: [
    { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
    { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
  ],
}
See Multi-Agent and full reference for binding rules and per-agent access profiles.
Use $include to organize large configs:
// ~/.fased/fased.json
{
  gateway: { port: 18789 },
  agents: { $include: "./agents.json5" },
  broadcast: {
    $include: ["./clients/a.json5", "./clients/b.json5"],
  },
}
  • Single file: replaces the containing object
  • Array of files: deep-merged in order (later wins)
  • Sibling keys: merged after includes (override included values)
  • Nested includes: supported up to 10 levels deep
  • Relative paths: resolved relative to the including file
  • Error handling: clear errors for missing files, parse errors, and circular includes
Recommended baseline:
{
  gateway: {
    mode: "local",
    bind: "loopback",
  },
}
Then layer remote access on top with:
  • Tailscale Serve for tailnet-only browser and WebSocket access
  • SSH tunnels for operator-only access
  • direct tailnet bind only when you intentionally want tailnet clients to talk to the gateway without Serve
Avoid exposing the raw gateway port publicly unless you deliberately designed that deployment and reviewed the auth and proxy posture.

Config hot reload

The Gateway watches ~/.fased/fased.json and applies changes automatically — no manual restart needed for most settings.

Reload modes

ModeBehavior
hybrid (default)Hot-applies safe changes instantly. Automatically restarts for critical ones.
hotHot-applies safe changes only. Logs a warning when a restart is needed — you handle it.
restartRestarts the Gateway on any config change, safe or not.
offDisables file watching. Changes take effect on the next manual restart.
{
  gateway: {
    reload: { mode: "hybrid", debounceMs: 300 },
  },
}

What hot-applies vs what needs a restart

Most fields hot-apply without downtime. In hybrid mode, restart-required changes are handled automatically.
CategoryFieldsRestart needed?
Channelschannels.*, web — all built-in and extension channelsNo
Agent & modelsagents, models, bindingsNo
Automationhooks, cron, agents.list[].heartbeat, agents.defaults.heartbeatNo
Sessions & messagessession, messagesNo
Tools & mediatools, browser, skills, audio, talkNo
UI & miscui, loggingNo
Gateway servergateway.* (port, bind, auth, tailscale, TLS, HTTP)Yes
Infrastructurediscovery, canvasHost, pluginsYes
gateway.reload and gateway.remote are exceptions — changing them does not trigger a restart.

Config RPC (programmatic updates)

Control-plane write RPCs (config.apply, config.patch, update.run) are rate-limited to 3 requests per 60 seconds per deviceId+clientIp. When limited, the RPC returns UNAVAILABLE with retryAfterMs.
Validates + writes the full config and restarts the Gateway in one step.
config.apply replaces the entire config. Use config.patch for partial updates, or fased config set for single keys.
Params:
  • raw (string) — JSON5 payload for the entire config
  • baseHash (optional) — config hash from config.get (required when config exists)
  • sessionKey (optional) — session key for the post-restart wake-up ping
  • note (optional) — note for the restart sentinel
  • restartDelayMs (optional) — delay before restart (default 2000)
Restart requests are coalesced while one is already pending/in-flight, and a 30-second cooldown applies between restart cycles.
fased gateway call config.get --params '{}'  # capture payload.hash
fased gateway call config.apply --params '{
  "raw": "{ agents: { defaults: { workspace: \"~/.fased/workspace\" } } }",
  "baseHash": "<hash>",
  "sessionKey": "agent:main:whatsapp:dm:+15555550123"
}'
Merges a partial update into the existing config (JSON merge patch semantics):
  • Objects merge recursively
  • null deletes a key
  • Arrays replace
Params:
  • raw (string) — JSON5 with just the keys to change
  • baseHash (required) — config hash from config.get
  • sessionKey, note, restartDelayMs — same as config.apply
Restart behavior matches config.apply: coalesced pending restarts plus a 30-second cooldown between restart cycles.
fased gateway call config.patch --params '{
  "raw": "{ channels: { telegram: { groups: { \"*\": { requireMention: false } } } } }",
  "baseHash": "<hash>"
}'

Environment variables

Fased reads env vars from the parent process plus:
  • .env from the current working directory (if present)
  • ~/.fased/.env (global fallback)
Neither file overrides existing env vars. You can also set inline env vars in config:
{
  env: {
    OPENROUTER_API_KEY: "sk-or-...",
    vars: { GROQ_API_KEY: "gsk-..." },
  },
}
If enabled and expected keys aren’t set, Fased runs your login shell and imports only the missing keys:
{
  env: {
    shellEnv: { enabled: true, timeoutMs: 15000 },
  },
}
Env var equivalent: FASED_LOAD_SHELL_ENV=1
Reference env vars in any config string value with ${VAR_NAME}:
{
  gateway: { auth: { token: "${FASED_GATEWAY_TOKEN}" } },
  models: { providers: { custom: { apiKey: "${CUSTOM_API_KEY}" } } },
}
Rules:
  • Only uppercase names matched: [A-Z_][A-Z0-9_]*
  • Missing/empty vars throw an error at load time
  • Escape with $${VAR} for literal output
  • Works inside $include files
  • Inline substitution: "${BASE}/v1""https://api.example.com/v1"
For fields that support SecretRef objects, you can use:
{
  models: {
    providers: {
      openai: { apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" } },
    },
  },
  skills: {
    entries: {
      "nano-banana-pro": {
        apiKey: {
          source: "file",
          provider: "filemain",
          id: "/skills/entries/nano-banana-pro/apiKey",
        },
      },
    },
  },
  channels: {
    googlechat: {
      serviceAccountRef: {
        source: "exec",
        provider: "vault",
        id: "channels/googlechat/serviceAccount",
      },
    },
  },
}
SecretRef details (including secrets.providers for env/file/exec) are in Secrets Management.
See Environment for full precedence and sources.

Full reference

For the complete field-by-field reference, see Configuration Reference.
Related: Configuration Examples · Configuration Reference · Doctor