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

# File versions

> Versions for config, plans, tools, and the data directory

graph reads four kinds of file it did not write in the same release: `config.toml`, plan documents, tool documents, and the data directory. Each carries a **file version** — an integer, separate from the binary's version and from each other — that moves every time that file's schema changes.

| File                               | Where the version lives                    | Missing means |
| ---------------------------------- | ------------------------------------------ | ------------- |
| `config.toml` (global and project) | top-level `version = 1`                    | version 1     |
| plan documents                     | top-level `version: 1`                     | version 1     |
| tool documents                     | top-level `version: 1`                     | version 1     |
| data directory                     | `<data_dir>/FORMAT`, written on first open | version 1     |

`graph version` prints the binary version and the file version each kind is written at:

```text theme={null}
$ graph version
graph 0.13.0
file versions: config 1, plan 1, tool 1, store 1
```

Each file kind has a **support window**: the oldest version this binary still reads through the one it writes. The line shows the window whenever it is wider than one version (`config 3 (reads 2-3)`), and `graph version --json` reports `oldest` and `current` under `fileVersions`.

## The compatibility promise

* **Every schema change bumps the file version.** A new optional key, a removed key, a rename, a changed shape: all of them. The version is the schema's generation number, so two binaries that agree on it agree on every key.
* **A binary reads every version in its window, and nothing outside it.** Older files inside the window are migrated in memory as they load; the file on disk is not modified until you run a `migrate` command. Upgrading graph within a window never requires touching a file first.
* **A file outside the window fails, by name, before the schema is consulted.** The message says which file, which version it found, which versions this binary reads, and what to do: upgrade graph for a newer file, or migrate an older file with a release that still reads it. Never an "unknown field" for a key that is correct.
* **The window narrows only at a major release**, announced one minor release ahead. `graph config check` reports files that would fall outside it.

Version 1 is the schema graph has always read; introducing the versions changed nothing for existing files.

## What a bump carries

| Change                                     | What the migration does                                      |
| ------------------------------------------ | ------------------------------------------------------------ |
| add an optional key with a default         | nothing — a no-op step, so the generation number still moves |
| remove a key                               | deletes it; notes a value the new version cannot honor       |
| rename a key or an enum value              | moves the value under the new name                           |
| change a key's shape (string → table)      | rewrites the value into the new shape                        |
| change a key's meaning, same shape         | rewrites the value so it means what it did                   |
| add a required key with a derivable value  | derives and writes it                                        |
| add a required key with no derivable value | fails, naming the key to set                                 |

A migration is a forward-only step from version N to N+1. The binary carries the chain for its whole window, so a version 2 file on a binary that writes version 4 runs two steps; each step is verified by a frozen fixture pair in the repository (the version N file and the version N+1 file it must produce), and the fixtures for every version in the window must still load on every release.

Files graph writes always carry the current version. A plan re-saved from a newer workbench therefore carries the newer number even if it uses nothing new, and an older binary outside that window refuses it — the number is the contract, not the keys the file happens to use.

## Config

Both layers are migrated **independently, before they merge**: the global file and the project file may sit at different versions. The `version` key itself never reaches the merged config.

```text theme={null}
graph config check              # each file's version and whether the merged config loads
graph config migrate            # rewrite ./.graph/config.toml to the current version
graph config migrate --global   # the same for ~/.config/graph/config.toml
```

`migrate` preserves comments and layout; it writes `version` as the first key and leaves everything else where it was. A file already at the current version is left untouched. On a terminal, any command that loads a config file older than the current version prints a one-line hint to run `migrate`; non-interactive runs (CI, `graph mcp serve`) stay silent, since the in-memory upgrade already happened.

## Plans and tools

A plan file outside the binary's window is **skipped with a diagnostic** and stays out of the catalog, exactly like a file that fails to parse: `plan list --json` reports it under `skipped`, and naming it (`plan run`, `plan show`) reports the version mismatch rather than "no plan named". A tool file outside the window fails the tool catalog by name.

```text theme={null}
graph plan migrate <name|path>      # rewrite a plan file to the current plan version
graph tools migrate <path>          # rewrite a tool file to the current tool version
```

Both rewrite the document through the YAML tree, so field order survives. A **leading comment block** (the lines before the first key) is preserved verbatim; comments elsewhere in the file do not survive a YAML round-trip, and the command says how many it dropped. Files written by graph itself — the workbench's save, `plan new`, `plan draft`, `plan show` — always carry the current `version`.

Every plan and tool file is read strictly: an unknown key anywhere in a step or a tool document is a load error naming the key, never silently ignored. A misspelled `timeout_secs` used to be dropped on the floor; now the file is skipped and the diagnostic says why.

## Data directory

`FileStore` writes `<data_dir>/FORMAT` holding the store version on first open and refuses to open a directory whose marker is outside its window. That refusal matters more here than anywhere else: thread messages are appended under a lock without re-reading the file, so an older graph must never write into a layout it does not understand. Records inside the directory carry their own `version` field for per-record migration.


## Related topics

- [config.toml](/reference/configuration.md)
- [CLI](/reference/cli.md)
- [Changelog](/changelog.md)
- [CI checks](/cookbook/ci-checks.md)
- [User-defined tools](/tools/user-defined.md)
