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):
A config path can be set explicitly with -c <path>. CLI flags take precedence over config values. See Server for full CLI reference.
{ // 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):
- logView.all.maxEntries
- logView.maxEntries
- Built-in fallback: 500
per-service query:
- services.<name>.logView.maxEntries
- logView.maxEntries
- 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 becomeargv. - 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:
{ "cmd": ["sh", "-c", "cd backend && pnpm start"] }
warning Anti-pattern — does NOT work
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.
{ "cmd": "npm --prefix ./billing run start:dev" }
{ "cmd": ["npm", "--prefix", "./billing", "run", "start:dev"] }
{ "cmd": ["/opt/my app/server", "--port", "3000"] }
{ "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.
{ "stopCmd": "docker compose -f ./docker/python.compose.yml down" }
{ "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.
GET requests to the specified URL until a 2xx or 3xx response is received.
"ready": { "type": "http", "url": "http://localhost:3002/healthz" }
TCP connection attempts until a successful handshake. Falls back to service port when ready.port is not specified.
"ready": { "type": "tcp", "port": 8088 }
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 probeready — when the dependency has a readiness probewarning Dependency cycles cause immediate startup failure
Daemon vs Oneshot
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.
Run to completion
Short-lived tasks: migrations, code generation, seed scripts. Expected to exit. Use with dependsOn to sequence them before dependent services.
Validation Rules
| Rule | Result |
|---|---|
| Unknown fields in any object | Parse error |
| Empty service name | Rejected |
kind not daemon or oneshot | Rejected |
port < 0 | Rejected |
logView.maxEntries ≤ 0 | Rejected |
session.token set to empty string | Rejected |
session.bind port outside 1–65535 | Rejected |
dependsOn references itself | Rejected |
dependsOn references unknown service | Rejected |
| Circular dependency between services | Boot failure |
ready without type | Rejected |
ready.type=http without url | Rejected |
ready.type=tcp without port and no service port | Rejected |
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.
{ "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.