flowlayer
// Config Reference v1.0.0

Configuration

Complete reference for the FlowLayer JSONC configuration file: file discovery, top-level fields, service definitions, readiness probes, log limits, and validation rules.

JSONC Config File

FlowLayer is configured with a JSONC file — JSON with comments and trailing commas. The parser uses strict mode: unknown fields are rejected.

"Unknown fields are rejected. Typos in field names produce an error at boot."

Auto-discovery order (current directory):

1.flowlayer.jsonc
2.flowlayer.json
3.flowlayer.config.jsonc
4.flowlayer.config.json

A config path can be set explicitly with -c <path>. CLI flags take precedence over config values. See Server for full CLI reference.

flowlayer.jsonc
{
  // Session settings (optional)
  "session": {
    "bind": "127.0.0.1:6999",
    "token": "dev-token"
  },
  // Log retrieval limits
  "logView": {
    "maxEntries": 500,
    "all": { "maxEntries": 800 }
  },
  // Services
  "services": {
    "service_name": { ... }
  }
}

Top-Level Structure

session object

Shared session settings: server uses bind/token; TUI supports Direct (-addr/-token) or Config (flowlayer-client-tui -config reads addr/token).

logs object

Optional log persistence to disk as JSONL files.

logView object

Default limits for get_logs when clients do not specify a limit.

services object

Map of service name → service definition. Each key is the service name.

Session settings, logs, logView

router Session settings

Controls the server Session API and shared client connection settings. The session API is only started when bind is configured or the -s CLI flag is used. The TUI can connect in Direct mode with -addr and -token, or in Config mode where flowlayer-client-tui -config reads session.addr and session.token.

session.bind string

Listen address. Accepts host:port, :port, or a bare port (bare port binds to 127.0.0.1). Port must be 1–65535.

session.addr string TUI-only

Connection address used by flowlayer-client-tui -config. Usually mirrors session.bind.

session.token string

Bearer token for API authentication. If the key is present, the value must not be empty. When the API is enabled without a token, a random token (fl_<uuid>) is generated and printed at boot.

description logs

logs.dir string

Directory for JSONL log files. When set, FlowLayer writes all.jsonl and <service>.jsonl files to this directory. Optional; in-memory only when absent.

logView — Retrieval Limits

Controls default limits for get_logs responses when clients do not send an explicit limit. An explicit client limit is always used as-is.

Limit resolution order

all-services query (no service filter):

  1. logView.all.maxEntries
  2. logView.maxEntries
  3. Built-in fallback: 500

per-service query:

  1. services.<name>.logView.maxEntries
  2. logView.maxEntries
  3. Built-in fallback: 500

Clients should use effective_limit from get_logs responses as the authoritative limit. See Protocol for details.

logView.maxEntries integer > 0

Global default limit. Applied to both per-service and all-services queries when no more specific override exists.

logView.all.maxEntries integer > 0

Override for all-services queries (no service filter in get_logs). Takes precedence over logView.maxEntries for those queries.

services.<name>.logView.maxEntries integer > 0

Per-service limit override. Applied when querying logs for a specific named service. Takes precedence over logView.maxEntries.

// Example logView configuration
"logView": {
  "maxEntries": 500,   // global default
  "all": {
    "maxEntries": 800   // override for all-services queries
  }
},
"services": {
  "billing": {
    "logView": { "maxEntries": 5 }  // per-service override
  }
}

Service Definition

cmd string | string[] Required

Commands are executed directly via the OS exec interface. No shell is invoked.

  • String form — split on whitespace (strings.Fields). The resulting tokens become argv.
  • Array form — passed directly as argv. No splitting, no interpretation. Recommended when arguments contain spaces.

Because no shell is invoked:

  • No &&, ||, pipes (|), or redirections (>, <)
  • No environment variable expansion ($VAR)
  • No globbing (*, ?)
  • No shell builtins (cd, export, source)

To use shell features, invoke a shell explicitly via the array form:

check_circle Shell invocation (correct)
{ "cmd": ["sh", "-c", "cd backend && pnpm start"] }

warning Anti-pattern — does NOT work

"cmd": "sh -c 'cd backend && pnpm start'"

The string form splits on whitespace, producing ["sh", "-c", "'cd", "backend", "&&", "pnpm", "start'"]. The quoting is not interpreted — sh receives 'cd as the command string, which fails.

String form (simple command)
{ "cmd": "npm --prefix ./billing run start:dev" }
check_circle Array form (recommended)
{ "cmd": ["npm", "--prefix", "./billing", "run", "start:dev"] }
Array form (paths with spaces)
{ "cmd": ["/opt/my app/server", "--port", "3000"] }
Shell form (when you need shell features)
{ "cmd": ["sh", "-c", "cd backend && pnpm start"] }
stopCmd string | string[]

Custom stop command. Follows the same execution semantics as cmd (direct exec, no shell). When present, FlowLayer runs this instead of sending SIGTERM to the process group. Executed with the same effective environment as cmd.

String form
{ "stopCmd": "docker compose -f ./docker/python.compose.yml down" }
Shell form
{ "stopCmd": ["sh", "-c", "cd backend && npm run cleanup"] }
kind string Default: "daemon"

Process type: daemon (long-running, stays alive until shutdown) or oneshot (runs to completion, exit 0 → running/ready).

port integer ≥ 0

Port for preflight checks. Also used as the default port for TCP readiness probes when ready.port is not specified.

dependsOn string[]

Services that must reach terminal startup state before this service starts. Terminal state is running when the dependency has no readiness probe, or ready when it has one. See dependsOn semantics below.

ready object

Readiness probe configuration. When absent, the service is considered ready immediately upon process spawn. See Readiness Probes below.

env object

Key-value pairs merged with the parent process environment. Service values override duplicate keys and apply to both cmd and stopCmd.

logView.maxEntries integer > 0

Per-service log retrieval limit override. When a client queries logs for this specific service without specifying a limit, this value takes precedence over the global logView.maxEntries.

Readiness Probes

When a ready block is present, FlowLayer polls until the probe succeeds before considering the service ready. Polling interval is 200ms. Probes repeat until success or process death.

See TCP Probe and HTTP Health patterns in Examples.

HTTP 2xx-3xx

GET requests to the specified URL until a 2xx or 3xx response is received.

"ready": {
  "type": "http",
  "url": "http://localhost:3002/healthz"
}
TCP Handshake

TCP connection attempts until a successful handshake. Falls back to service port when ready.port is not specified.

"ready": {
  "type": "tcp",
  "port": 8088
}
None Immediate

No probe. Service is considered ready immediately upon process spawn. Dependencies wait for running state, not ready.

// Default when no "ready" field
// is provided in the config

DAG Construction & dependsOn

At boot, FlowLayer builds a directed acyclic graph (DAG) from all dependsOn declarations. The graph determines execution order and groups services into parallel waves.

A dependent service waits for each dependency's terminal startup state:

See the Dependencies pattern in Examples.

running — when the dependency has no readiness probe
ready — when the dependency has a readiness probe
check_circle Self-references are rejected
check_circle References to undefined services are rejected
check_circle Duplicates are automatically removed
check_circle Order is normalized alphabetically

warning Dependency cycles cause immediate startup failure

wave_plan.output
Wave 0
billing ping
Parallel
Wave 1
users
After Wave 0
// dependsOn config "users": { "dependsOn": ["billing"] }

Daemon vs Oneshot

Daemon (default)

Long-running service

Stays alive for the entire runtime session. Ideal for web servers, background workers, and databases. An unexpected exit during runtime transitions the service to failed.

check Stays alive until shutdown signal
close No automatic restart on crash
Oneshot

Run to completion

Short-lived tasks: migrations, code generation, seed scripts. Expected to exit. Use with dependsOn to sequence them before dependent services.

check_circle Exit code 0
→ running / ready
error Non-zero exit
→ failed

Validation Rules

validation_summary
Rule Result
Unknown fields in any objectParse error
Empty service nameRejected
kind not daemon or oneshotRejected
port < 0Rejected
logView.maxEntries ≤ 0Rejected
session.token set to empty stringRejected
session.bind port outside 1–65535Rejected
dependsOn references itselfRejected
dependsOn references unknown serviceRejected
Circular dependency between servicesBoot failure
ready without typeRejected
ready.type=http without urlRejected
ready.type=tcp without port and no service portRejected

Configuration Examples

A realistic configuration managing two Node.js services and a Docker-based service, with session API, log limits, readiness probes, and dependency ordering.

flowlayer.jsonc
{
  "session": {
    "bind": "127.0.0.1:6999",
    "token": "flowlayer-dev-token"
  },
  "logView": {
    "maxEntries": 500,
    "all": { "maxEntries": 800 }
  },
  "services": {
    "billing": {
      "cmd": "npm --prefix ./billing run start:dev -- --preserveWatchOutput",
      "port": 3002,
      "ready": {
        "type": "http",
        "url": "http://localhost:3002/healthz"
      },
      "logView": { "maxEntries": 5 }
    },
    "users": {
      "cmd": ["npm", "--prefix", "./users", "run", "start:dev"],
      "port": 3003,
      "dependsOn": ["billing"],
      "ready": {
        "type": "http",
        "url": "http://localhost:3003/healthz"
      }
    },
    "ping": {
      "cmd": "docker compose -f ./docker/python.compose.yml up",
      "stopCmd": "docker compose -f ./docker/python.compose.yml down",
      "port": 8088,
      "ready": {
        "type": "tcp",
        "port": 8088
      }
    }
  }
}

sync Oneshot migration before API

"migrate": {
  "kind": "oneshot",
  "cmd": ["sh", "-c", "cd backend && npx prisma migrate deploy"],
  "dependsOn": ["postgres"]
},
"api": {
  "cmd": ["node", "src/server.js"],
  "dependsOn": ["migrate"],
  "ready": { "type": "http", "url": "..." }
}

verified TCP probe for Docker service

"postgres": {
  "cmd": "docker compose up postgres",
  "stopCmd": "docker compose down postgres",
  "port": 5432,
  "ready": { "type": "tcp", "port": 5432 }
}

Next steps

Browse Examples for copy-ready patterns, or use the AI prompt builder to generate a config.