Configuration

Everything Lattice does in a repo is driven by one file: lattice.json at the repo root. This page enumerates every field it accepts — type, whether it’s required, its default, and what it’s for. For what these fields mean in practice, see Workspaces, Task graph, Caching, and Engines and provisioning.

Where the file lives

lattice.json lives at the repo root. Every command that reads config walks up from the current directory looking for the nearest lattice.json and treats that directory as the repo root — so subcommands work from any workspace subdirectory. If no lattice.json is found in the current directory or any parent, Lattice fails immediately:

Error: no lattice.json found in this directory or any parent; run `lattice init` to create one

An empty lattice.json — literally {} — is valid: every top-level key is optional. Running a task against it fails only because no tasks are declared:

{}

The $schema field and editor validation

{ "$schema": ".lattice/schema.json" }
Field Type Required Default
$schema string no none

$schema is a plain string reference, conventionally .lattice/schema.json — a copy of Lattice’s bundled JSON Schema written next to your config so editors with JSON Schema support (VS Code, JetBrains) validate and autocomplete lattice.json as you type.

Every command that loads config first calls ensure_schema, which writes .lattice/schema.json only if it is absent — an existing copy (including one you’ve pinned or hand-edited) is never overwritten. lattice init writes it explicitly as part of scaffolding. Commit .lattice/schema.json; it isn’t one of the gitignored .lattice/ artifacts (cache/, toolchains/, bin/ are).

The bundled schema is stricter than Lattice’s own parser in one respect: the schema sets additionalProperties: false at every level, so an editor flags an unknown key (a stray projects, a typo’d field) immediately. The parser itself silently ignores unknown keys at parse time — they simply have no effect. Treat editor validation against .lattice/schema.json as the authoritative check for typos; don’t rely on lattice run to catch them.

latticeVersion

{ "latticeVersion": "1.0.0-beta-2" }
Field Type Required Default
latticeVersion string no none

Pins the version of Lattice this repo runs, so every contributor and every CI job runs the same build. See Upgrading for how the version-drift check uses this field and settings.versionCheck.

workspaces

{
  "workspaces": [
    { "name": "web", "path": "apps/web" },
    { "name": "api", "path": "apps/api", "dependsOn": ["web"] }
  ]
}
Field Type Required Default
workspaces array of workspace objects no []

Each entry is one workspace — a single project directory. There is no glob form; the schema and the parser both reject a bare string array or a glob key on a workspace entry.

The workspace object

Field Type Required Default Description
name string yes Workspace name. Must be unique across the file.
path string yes Literal directory path, relative to the repo root. Never a glob. Must be non-empty.
auto boolean no true When true, infer the engine and task commands from the workspace’s native manifest. false disables all inference — declare scripts and engines yourself.
engines engine map no {} Per-workspace toolchain constraints. A key here overrides the same key at the root.
dependsOn array of string no none Other workspaces, by name, this one depends on.
scripts object of stringstring no {} Explicit script-name → command overrides.

A workspace without name or without path is rejected at parse time:

Error: failed to parse lattice.json

Caused by:
    missing field `name` at line 2 column 32

A workspace with an empty (or whitespace-only) path, or two workspaces sharing a name, are rejected by validation after parsing:

Error: workspace 'empty-path' has an empty path
Error: duplicate workspace name 'dup': workspace names must be unique

Minimal validating example:

{
  "workspaces": [
    {
      "name": "utils",
      "path": "libs/utils",
      "auto": false,
      "scripts": { "build": "python3 setup.py build" }
    }
  ]
}

engines

{ "engines": { "node": ">=20.0.0", "rust": ">=1.75.0" } }
Field Type Required Default
engines engine map no {}

An engine map is a name-keyed object. Each value is either the string form (a bare version constraint) or the object form (an explicit spec). Root engines are defaults for every workspace; a workspace’s own engines entry overrides the root entry of the same name — see Engines and provisioning for the full mode gradient this drives.

String form

{ "engines": { "node": ">=20.0.0" } }

A bare version-constraint string. The engine name must be one of the well-known engines Lattice has a built-in version rule for:

node deno bun pnpm yarn npm
rust cargo go python python3 ruby
bundler java gradle maven dotnet

A string-form engine outside this list fails validation:

Error: engine 'alpes' in root uses the string (version-only) form, but 'alpes'
is not a well-known engine Lattice can version-check on its own. Use the
object form with an explicit `versionCmd`, e.g. "alpes": { "version":
">=1.0.0", "versionCmd": "alpes --version" }

Object form

{
  "engines": {
    "alpes": {
      "version": ">=2.6.7",
      "versionCmd": "alp --version",
      "installCmd": "curl https://example.com/alp.sh | sh",
      "bin": "bin"
    }
  }
}
Field Type Required Default Description
version string no none Version constraint, e.g. ">=13.0.0".
versionCmd string no none Command that prints the tool’s version. Required for any engine name not in the well-known list.
installCmd string no none Command that installs the toolchain. Its presence is what selects provisioning mode. Receives $LATTICE_TOOLCHAIN_DIR both as an environment variable and by literal substitution into the command string.
bin string no "bin" Bin directory relative to the toolchain install, prepended to the task’s PATH.

The object form works for any engine name, well-known or not, as long as an unknown name supplies versionCmd. Which fields are present selects the mode — host PATH, validate-only, or provision — as described in Engines and provisioning.

tasks

{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**/*", "package.json"],
      "outputs": ["dist/**"]
    }
  }
}
Field Type Required Default
tasks object of string → task object no {}

Keys are task names; order of declaration is preserved but does not affect execution order (that’s the dependency graph — see Task graph). Running a task name that isn’t a key in this map fails immediately, listing what is defined:

Error: task 'build' is not defined in lattice.json; available tasks: test

The task object

Field Type Required Default Description
dependsOn array of string no none Task dependencies. ^task means the same task name in this task’s dependency workspaces; a bare task means that task in the same workspace.
inputs array of string no none File globs hashed to compute the cache key.
outputs array of string no none File globs captured as the cached artifact.
ignore array of string no none File globs excluded from input hashing.
env array of string no none Environment variable names whose resolved values feed the cache key.
persistent boolean no false Long-running task (e.g. a dev server). Never cached regardless of cache. See Persistent tasks.
cache boolean no true Set false to opt a non-persistent task out of caching entirely.

An empty task object, {}, is valid — a task with no declared dependencies, inputs, or outputs.

{
  "tasks": {
    "dev": { "persistent": true, "cache": false },
    "clean": {}
  }
}

settings

{
  "settings": {
    "maxCacheSize": "10GB",
    "cacheDir": ".lattice/cache",
    "loquacious": false,
    "versionCheck": true
  }
}
Field Type Required Default Description
maxCacheSize string no none Upper bound on the local cache size. Human byte size: an integer plus B/KB/MB/GB/TB (base 1024, case-insensitive), or a bare integer of bytes. Used by lattice prune when --max-size isn’t passed.
logging string no none Logging verbosity mode.
cacheDir string no ".lattice/cache" Directory for the local cache, relative to the repo root.
loquacious boolean no false Equivalent to always passing -l/--loquacious: forces raw, unbuffered output.
versionCheck boolean no true When true, compare the running binary against latticeVersion and nag on drift. false disables the check entirely — see Upgrading.

maxCacheSize accepts either unit form:

{ "settings": { "maxCacheSize": "512MB" } }
{ "settings": { "maxCacheSize": 536870912 } }

If settings.maxCacheSize is unset and lattice prune is run without --max-size, it fails:

Error: no max cache size set (pass --max-size or set settings.maxCacheSize in lattice.json)

Validation summary

Problem When caught Error
No lattice.json in this or any parent directory before parsing no lattice.json found in this directory or any parent; run \lattice init` to create one`
Malformed JSON parsing failed to parse lattice.json (with the underlying JSON error and position)
A workspace object missing name or path parsing missing field \name`/missing field `path`` (with line/column)
An engine value that is neither a string nor a valid object parsing data did not match any variant of untagged enum EngineSpec
A workspace path that is empty or whitespace-only validation workspace '<name>' has an empty path
Two workspaces with the same name validation duplicate workspace name '<name>': workspace names must be unique
A string-form engine whose name isn’t well-known validation names the engine and suggests the object form with versionCmd
A task name passed to lattice run not present in tasks after config loads task '<name>' is not defined in lattice.json; available tasks: ...
lattice prune with no size limit anywhere after config loads no max cache size set (pass --max-size or set settings.maxCacheSize in lattice.json)

An unrecognized top-level or nested key (for example a leftover projects map, or a glob key on a workspace) is accepted by the parser and silently ignored — it has no effect on any command. The bundled JSON Schema rejects the same input, which is why editor validation against .lattice/schema.json catches typos the CLI itself will not.

Complete example

A repo with a Node app, a Rust service, and a Python library wired for tasks, selective caching, and pinned tool versions:

{
  "$schema": ".lattice/schema.json",
  "latticeVersion": "1.0.0-beta-2",
  "workspaces": [
    { "name": "web", "path": "apps/web", "engines": { "node": ">=20.0.0" } },
    {
      "name": "api",
      "path": "services/api",
      "dependsOn": ["shared"]
    },
    {
      "name": "shared",
      "path": "libs/shared",
      "auto": false,
      "scripts": {
        "build": "python3 -m build",
        "test": "python3 -m pytest",
        "lint": "python3 -m ruff check ."
      }
    }
  ],
  "engines": {
    "rust": ">=1.75.0",
    "protoc": {
      "version": ">=25.0.0",
      "versionCmd": "protoc --version",
      "installCmd": "curl -fsSL https://example.com/protoc.sh | sh -s -- \"$LATTICE_TOOLCHAIN_DIR\"",
      "bin": "bin"
    }
  },
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**/*", "Cargo.toml", "package.json"],
      "ignore": ["**/*.test.*"],
      "outputs": ["dist/**", "target/release/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "inputs": ["src/**/*", "tests/**/*"],
      "env": ["DATABASE_URL"]
    },
    "lint": {
      "inputs": ["src/**/*"]
    },
    "dev": {
      "persistent": true,
      "cache": false
    },
    "clean": {}
  },
  "settings": {
    "maxCacheSize": "10GB",
    "versionCheck": true
  }
}