> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firedrill.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Write the local plan

> The local plan tells the Firedrill CLI how to start your agent's services and reach your agent on your machine.

The **local plan** is a JSON file that says how to start each service in your descriptor, how to know it is ready and how the driver reaches it. It stays on your machine. Firedrill only receives a digest of it with your paths removed.

Keep the plan out of version control. It contains absolute paths for your machine.

## Use the plan

```bash theme={null}
firedrill run --plan plan.json --descriptor descriptor.json --campaign <campaign-id>
```

Before anything starts, `run` checks the plan against the descriptor and prints every command, folder and environment variable name it will use. It asks you to approve. With `--json`, pass `--approve` after a person has approved.

Check your drivers against the plan without running anything:

```bash theme={null}
firedrill status --plan plan.json
```

## Example

```json plan.json theme={null}
{
  "version": 1,
  "descriptor_sha256": "<digest printed by firedrill register>",
  "roots": [
    { "alias": "app", "path": "/Users/you/code/support-agent", "identity_sha256": "<root identity>" }
  ],
  "services": [
    {
      "alias": "agent",
      "root_alias": "app",
      "mode": "launch",
      "command": { "executable": "node", "args": ["server.js"] },
      "cwd": ".",
      "env_passthrough": [],
      "env_static": { "LOG_LEVEL": "info" },
      "local_secret_env": ["OPENAI_API_KEY"],
      "readiness": { "kind": "http", "url": "http://127.0.0.1:0/health", "expect_status": [200] },
      "readiness_timeout_ms": 30000,
      "stop_timeout_ms": 5000,
      "per_drill": true,
      "port_env": "PORT"
    }
  ],
  "driver": {
    "profile": "http.v1",
    "service_alias": "agent",
    "path": "/chat",
    "method": "POST",
    "stream": "none",
    "session_header": null,
    "request_template": "message-text",
    "response_pointer": "/reply"
  },
  "state_hooks": [],
  "max_parallel_drills": 1
}
```

Firedrill starts `node server.js` with a free port in `PORT` and your `OPENAI_API_KEY` from the shell where you run `firedrill run`. It waits for `/health` to return 200, then sends each message to `POST /chat`.

## Top-level fields

| Field | Description |
| - | - |
| `version` | Always `1`. |
| `descriptor_sha256` | The digest `firedrill register` printed. |
| `roots` | Each source root: its `alias`, its absolute `path` and the same `identity_sha256` as the descriptor. |
| `services` | How to start or find each descriptor service. Every service must be listed. |
| `driver` | Driver settings. See [Drivers](https://docs.firedrill.run/connect/drivers.md). |
| `state_hooks` | Reset scripts for `approved-hook` state domains. |
| `max_parallel_drills` | How many drills to run at once. Forced to 1 when any state is serial. |

## Services

| Field | Description |
| - | - |
| `alias`, `root_alias` | Match the descriptor. |
| `mode` | `launch` to have Firedrill start and stop it, or `attach` for a service that is already running. |
| `command` | `{ "executable", "args" }`, run directly with no shell. Use `null` for `attach`. |
| `cwd` | A folder inside the root, relative to it. |
| `env_passthrough` | Names of variables the service may inherit from your shell. Everything else is removed. |
| `env_static` | Fixed, non-secret values. |
| `readiness` | How to tell the service is ready. See below. |
| `readiness_timeout_ms`, `stop_timeout_ms` | How long to wait for start and stop. |
| `per_drill` | `true` to start a fresh copy for each drill. |
| `port_env` | A variable that receives a free port, or `null`. |
| `dependency_env` | Routes to synthetic tools. See [Connect synthetic tools](https://docs.firedrill.run/connect/tools.md). |
| `mcp_config_env` | A variable that receives the path of an MCP configuration file. |
| `local_secret_env` | Secret names to read from your environment and pass to this service. |

### Readiness

| Kind | Example | Use for |
| - | - | - |
| `http` | `{"kind": "http", "url": "http://127.0.0.1:0/health", "expect_status": [200]}` | HTTP services |
| `tcp` | `{"kind": "tcp", "port": 5432}` | Databases and other TCP services |
| `stdout` | `{"kind": "stdout", "pattern": "READY"}` | Processes that print a line when ready |
| `none` | `{"kind": "none"}` | Services with no readiness signal |

Readiness URLs must use `http` on `127.0.0.1`, `localhost` or `[::1]`. Use port `0` when the service gets its port from `port_env`.

## Rules

* Commands run as an argument list. Shell strings, pipes and `&&` do not work. `.bat` and `.cmd` files are refused.
* Secret names cannot go in `env_static` or `env_passthrough`. Use `local_secret_env` on a `launch` service with `per_drill: true`.
* `env_static` cannot set proxy, certificate or loader variables such as `HTTP_PROXY`, `NODE_OPTIONS` or `LD_PRELOAD`.
* An `attach` service cannot be `per_drill` or have a `port_env`.
* A `per_drill` network service needs a `port_env`.
* If the plan or a root folder changes after you approve it, `run` stops and asks again.

Local services run as normal processes with your user's permissions. They are not sandboxed.

## Reset hooks

For a state domain with `reset: approved-hook`, add one entry to `state_hooks`:

```json theme={null}
{
  "domain_alias": "db",
  "root_alias": "app",
  "path": "scripts/reset-db.sh",
  "sha256": "<same as hook_sha256 in the descriptor>",
  "runtime": { "executable": "bash", "args": [] },
  "timeout_ms": 30000
}
```

> [card] [Choose a driver](https://docs.firedrill.run/connect/drivers.md)
>
> Driver settings and what your agent must implement.

> [card] [Run drills](https://docs.firedrill.run/run/run-drills.md)
>
> Plan, apply and run.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.