# agentlane.json

`agentlane.json` says what a lane of the project holds: the mobile app, the web app, the services and their ports, per-lane resources, hooks, preconditions, capture hygiene, the build cache and the driver. `up`, `doctor` and `restart` read it from the current folder or the ones above it, and `agentlane schema` prints its JSON Schema.

The `setup` skill writes this file for you. This page is for reading it and changing it by hand. The site builds each field table from the JSON Schema in the package, so the tables match the version they document.

Point `$schema` at the copy in `node_modules` and your editor checks the file as you type:

```json title="agentlane.json"
{
  "$schema": "node_modules/agentlane/schema/agentlane.schema.json"
}
```

`doctor`'s `config` line validates the file and names each field that breaks the schema. `npx agentlane schema` prints the schema. Every command in the file runs with `sh -c` in the folder that holds `agentlane.json`, with the lane's `AGENTLANE_*` variables, the values of its resources, and `LANG` and `LC_ALL` set to a UTF-8 locale: `en_US.UTF-8` on macOS and `C.UTF-8` on Linux. [Environment variables](environment.md) lists what each command gets.

## Top level

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `$schema` | `string` | No | The path or URL of this schema, for editors. |
| `app` | `object` | No | The mobile app: what `up` builds, installs and launches on the claimed simulator or emulator. |
| `web` | `object` | No | The web app: the service that serves it and the page to open. `up --platform web` (the default when there is no app) claims ports and no device, starts the services, and ends on doctor with the app's URL, also given to commands as `AGENTLANE_WEB_URL`. |
| `services` | `object by name` | No | The long-running servers the app needs, by name. Each gets a claimed port in `AGENTLANE_<NAME>_PORT` (the name upper-cased, dashes as underscores). |
| `resources` | `object by name` | No | What each lane gets of its own besides ports, such as a database, by name. `up` runs each create once per lane, in this order, after the setup hook and the build preconditions (also under `--no-setup`). The values a create exports then reach the lane's services, the hooks after setup, and `agentlane exec`, as `AGENTLANE_<NAME>_PORT` does; a command never takes an exported variable from the caller's environment. When the lane ends (release, the session-end release, or a reap of a lane whose owner died or was taken over), each teardown runs, last created first. |
| `hooks` | `object` | No | Fields: `setup`, `build`, `launch`, `signin`, `teardown`. |
| `preconditions` | `array of object` | No | Checks `up` runs before any slow step. A failing check refuses with its fix. |
| `hygiene` | `object` | No | Fields: `location`, `appearance`. |
| `buildCache` | `object` | No | Lets the toolkit skip the native build when its inputs have not changed. The key also covers the platform, the build variant, the toolchain and the contents of the env file the build uses. The toolchain is the Xcode version for iOS. For Android it is the version of the JDK at `AGENTLANE_JAVA_HOME` or JAVA_HOME (else the JDK 17 the toolkit finds), which the build hook gets as JAVA_HOME, the Gradle distribution in android/gradle/wrapper/gradle-wrapper.properties when that file is not git-ignored (a generated wrapper follows the inputs that generate it, such as the lockfile), and the revision of every installed Android SDK build-tools, platform, NDK and CMake package. |
| `driver` | `object` | No | The project drives its app with the toolkit (Maestro flows); `up` and `doctor` then check for Java 17. |

## app

The mobile app: what `up` builds, installs and launches on the lane's simulator or emulator. `app.ios` and `app.android` hold what differs per platform, and replace `bundleId`, `output` and `device` when `up` runs for that platform.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `app.platform` | `"ios" or "android"` | Yes | The platform `up` claims a device for, unless `up --platform` names another. |
| `app.bundleId` | `string` | Yes | The bundle id or package name. |
| `app.scheme` | `string` | No | A URL scheme the app handles. It is approved on the simulator before it boots, so opening a link never stops at "Open in ...?". |
| `app.launch` | `"dev-client" or "bundler-pin" or "hook"` | Yes | How the app is launched. dev-client: an Expo dev client opened on the bundler with `--initial`Url. bundler-pin: a React Native app whose bundler location is written to its defaults and read back, then launched. hook: the project's launch hook. |
| `app.bundler` | `string` | No | The service the app loads its JavaScript from. Default: the only service of type metro. |
| `app.variant` | `string` | No | The build variant, given to hooks as `AGENTLANE_VARIANT` and used by the build cache. Default: `"Debug"`. |
| `app.output` | `string` | No | The app the build hook produces (an .app or .apk), relative to this file. The toolkit installs it. Leave it out when the build hook installs the app itself; the installed app is then read back for the cache. |
| `app.device` | `object` | No | Its fields follow. |
| `app.device.type` | `string` | No | A simulator device type, or an Android device profile such as pixel_8. Default: the newest iPhone the runtime supports, or pixel_8. |
| `app.device.runtime` | `string` | No | A simulator runtime, or an Android system image (a package id or an API level). Default: the newest installed iOS runtime, or the newest Google APIs image. |
| `app.device.strict` | `boolean` | No | Take only a device of exactly this type and runtime. Without it, when the shared pool is full, a lane takes a free device of another type rather than deleting one to make this type. Default: `false`. |
| `app.ios` | `object` | No | What differs on iOS: these replace app.bundleId, app.output and app.device when `up` runs for iOS. |
| `app.ios.bundleId` | `string` | No | The bundle id or package name. |
| `app.ios.output` | `string` | No | The app the build hook produces (an .app or .apk), relative to this file. The toolkit installs it. Leave it out when the build hook installs the app itself; the installed app is then read back for the cache. |
| `app.ios.device` | `object` | No | Fields: `type`, `runtime`, `strict`. |
| `app.android` | `object` | No | What differs on Android: these replace app.bundleId, app.output and app.device when `up` runs for Android. |
| `app.android.bundleId` | `string` | No | The bundle id or package name. |
| `app.android.output` | `string` | No | The app the build hook produces (an .app or .apk), relative to this file. The toolkit installs it. Leave it out when the build hook installs the app itself; the installed app is then read back for the cache. |
| `app.android.device` | `object` | No | Fields: `type`, `runtime`, `strict`. |

## web

The web app: the service that serves it and the page to open. `up --platform web` claims ports and a browser, no device, starts the services and opens the page.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `web.service` | `string` | No | The service that serves the web app. Default: the only service of type web. |
| `web.path` | `string` | No | The page to open, on the service's port. Default: `"/"`. |
| `web.browser` | `object` | No | The lane's own browser, which `up` opens on the web app. Each lane gets its own profile folder, debugging port and driver session, released with the lane. The toolkit installs the driver and its browser at pinned versions in its own data folder (`agentlane browser install`). Drive it with the driver's own CLI under `agentlane exec`, which sets the driver's session, the web app's URL and the lane's saved login. The signin hook writes that saved login, in Playwright's storage-state format, to the file named by `$AGENTLANE_STORAGE_STATE`, and the browser starts with it. |

### web.browser

The lane's own browser. [Browsers for web apps](browsers.md) explains the drivers.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `web.browser.driver` | `"playwright" or "agent-browser"` | No | playwright: Playwright's own CLI, playwright-cli, and Playwright tests run with `exec -- npx playwright test`. agent-browser: Vercel's agent-browser CLI, headless. Both drive the toolkit's pinned Chrome for Testing. Default: `"playwright"`. |
| `web.browser.headed` | `boolean` | No | Show the browser window, so a person can watch it. Default: headless. Playwright only: agent-browser runs headless, on the headless shell of Chrome for Testing. Default: `false`. |

## services

The long-running servers the apps need, by name. A name is lowercase letters, digits and dashes, and it becomes the port variable: `api-v2` gets `AGENTLANE_API_V2_PORT`. [Ports and dev servers](services.md) shows them in use.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `services.<name>.type` | `string` | Yes | The port block the service's port comes from: metro, api, web, or a name of your own with `ports`. |
| `services.<name>.ports` | `[integer, integer]` | No | [first, last]: the port block for a type of your own. The Mac's own blocks (metro, api, web, and any in its registry config) take precedence. |
| `services.<name>.start` | `string` | Yes | The command that runs the server in the foreground on `$AGENTLANE_<NAME>_PORT`. It is started in its own process group and logged to a file per lane. |
| `services.<name>.cwd` | `string` | No | The directory to start it in, relative to this file. |
| `services.<name>.platforms` | `array of "ios" or "android" or "web"` | No | The platforms that need it, such as ["ios", "android"] for a bundler the web app does not use. `up`, `doctor` and `restart` for another platform leave it out. Leave it out to run it for every platform. |
| `services.<name>.ready` | `httpReady or tcpReady` | Yes |  |
| `services.<name>.timeoutSeconds` | `number` | No | How long to wait for the readiness check. Default: `60`. |
| `services.<name>.dial` | `object` | No | The app dials this service: launch it with this argument, so it talks to this lane's server. |
| `services.<name>.dial.argument` | `string` | Yes | The launch argument, such as -apiBaseUrl. |
| `services.<name>.dial.value` | `string` | No | The argument's value; {port} is the service's port. Default: `"http://localhost:{port}"`. |

### ready

`ready` is one of two checks. An HTTP check:

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `ready.kind` | `"http"` | Yes |  |
| `ready.path` | `string` | Yes | The path to GET. |
| `ready.status` | `integer` | No | The status that means ready. Default: `200`. |
| `ready.body` | `string` | No | Text the response body must contain. |
| `ready.host` | `string` | No | The host the readiness check connects to. Default: `"127.0.0.1"`. |

Or a TCP check, which passes when the port accepts a connection:

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `ready.kind` | `"tcp"` | Yes |  |
| `ready.host` | `string` | No | The host the readiness check connects to. Default: `"127.0.0.1"`. |

## resources

Data each lane gets its own copy of, such as a database, by name. `up` runs each `create` once per lane, in the order listed, after the setup hook. Teardowns run when the lane ends, last created first. [A database per lane](resources.md) walks through one.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `resources.<name>.create` | `string` | Yes | A shell command that makes the resource for this lane. It writes each value it exports as a NAME=value line (quotes around the value are removed) to the file named by `$AGENTLANE_EXPORTS`. Its output goes to its log, and a failing create stops `up` with it. It runs again at the next `up` when it failed or never finished, so it must cope with what a partial run left. |
| `resources.<name>.exports` | `array of string` | No | The variables create writes, such as DATABASE_URL. Create must write each of them and no other. Names starting with `AGENTLANE_`, and LANG and LC_ALL, are the toolkit's. |
| `resources.<name>.teardown` | `string` | Yes | A shell command that removes the resource when the lane ends. What it runs is recorded when create starts: this command, its folder and its variables (the lane's, plus the values create exported), so it runs even after this file changes and after the session that made the lane died. When the folder is gone (the worktree was deleted), it runs in the lane's log folder. A failed teardown is kept and tried again at the next release or reap, so it must cope with a resource that is already gone. |
| `resources.<name>.cwd` | `string` | No | The directory create and teardown run in, relative to this file. |
| `resources.<name>.teardownTimeoutSeconds` | `number` | No | How long teardown may run before it is stopped and counted as failed. Default: `120`. |

## hooks

Shell commands the project owns. Each one reads the lane from the environment, runs without asking anything, and exits non-zero when it didn't do its job. `up` writes each hook's output to `hooks/<name>.log` in the lane's folder.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `hooks.setup` | `string` | No | A shell command run with `sh -c` first, to install dependencies. |
| `hooks.build` | `string` | No | A shell command run with `sh -c` to build the app for the claimed device. |
| `hooks.launch` | `string` | No | A shell command run with `sh -c` to launch the app when app.launch is "hook"; `AGENTLANE_LAUNCH_ARGS` holds the dial arguments. |
| `hooks.signin` | `string` | No | A shell command run with `sh -c` after launch, to sign in; for the web app it writes the lane's saved login, in Playwright's storage-state format, to `$AGENTLANE_STORAGE_STATE`. |
| `hooks.teardown` | `string` | No | A shell command run with `sh -c` by `agentlane release`, after the services stop and before the resources' teardowns. |

## preconditions

Checks `up` runs before a slow step, each with the fix to print when it fails. Use one per tool a hook calls that the project doesn't install, one for the dependency install, and one per env file a hook needs.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `preconditions[].name` | `string` | Yes | What is checked, as a short phrase. |
| `preconditions[].run` | `string` | Yes | A shell command that exits 0 when the precondition holds. |
| `preconditions[].fix` | `string` | Yes | What to do when it fails. |
| `preconditions[].before` | `"claim" or "build"` | No | claim: checked before anything is claimed. build: checked after the setup hook, before the build. Default: `"claim"`. |
| `preconditions[].platforms` | `array of "ios" or "android" or "web"` | No | The platforms it applies to. Leave it out to check it for every platform. |

```json title="agentlane.json"
{
  "preconditions": [
    { "name": "CocoaPods is installed", "run": "command -v pod", "fix": "brew install cocoapods", "platforms": ["ios"] },
    { "name": "node_modules is installed", "run": "test -d node_modules", "fix": "run pnpm install", "before": "build" }
  ]
}
```

## hygiene

What capture hygiene sets on the lane's device. Leave it out for the defaults: the location 37.3349, -122.009, and light appearance. [Simulators and emulators](devices.md#capture-hygiene) lists every step.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `hygiene.location` | `object` | No | Its fields follow. |
| `hygiene.location.latitude` | `number` | Yes |  |
| `hygiene.location.longitude` | `number` | Yes |  |
| `hygiene.appearance` | `"light" or "dark"` | No |  |

## buildCache

Lets `up` skip the native build when its inputs haven't changed. [The build cache](build-cache.md) explains the key.

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `buildCache.inputs` | `array of object or object` | Yes |  |
| `buildCache.cacheableVariants` | `array of string` | Yes | The build variants whose output may be stored and served. Leave out any variant that embeds its JS bundle, such as a Release build. |
| `buildCache.envFile` | `string` | No | An env file the build bakes in, relative to the config file's directory. Its contents enter the key; a missing file counts as absent. Use this for files git ignores, which a files input never sees. |
| `buildCache.maxAgeDays` | `number` | No | An entry unused for longer than this is pruned. Default: `14`. |
| `buildCache.maxEntries` | `integer` | No | Only the most recently used entries up to this count are kept. Default: `6`. |

### A files input

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `buildCache.inputs[].kind` | `"files"` | Yes |  |
| `buildCache.inputs[].paths` | `array of string` | Yes | Files, directories or globs (`*`, `**`), relative to the directory holding the config file. Matched as git glob pathspecs over tracked files and untracked files git does not ignore; each enters the key by path, mode and content. |
| `buildCache.inputs[].exclude` | `array of string` | No | Files, directories or globs to leave out of `paths`. |
| `buildCache.inputs[].platforms` | `array of "ios" or "android"` | No | The platforms whose key includes this input. Leave it out to include it for every platform. |

### A command input

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `buildCache.inputs[].kind` | `"command"` | Yes |  |
| `buildCache.inputs[].run` | `string` | Yes | A shell command run with `sh -c` in the config file's directory, with the lane's environment. Its stdout enters the key; a non-zero exit means no key, so the build runs uncached. |
| `buildCache.inputs[].platforms` | `array of "ios" or "android"` | No | The platforms whose key includes this input. Leave it out to include it for every platform. |

## driver

| Field | Type | Required | What it is |
| --- | --- | --- | --- |
| `driver.flows` | `string` | No | The folder of committed flows, relative to this file. |

With a `driver` block, `up` and `doctor` check for Java 17, and `maestro` under `exec` is agentlane's Maestro build on the lane's device.
