Skip to content

The .reef File Format

The .reef file declares the habitat. reef-mcp reads it once, resolving every config value it needs — where arms live, where TRACE writes, where the dotnet-script runtime is — before anything runs.

The same philosophy as .reach and .octo: write the declaration. The system resolves the implementation.


Basic Structure

Flat key value lines, # comments, one sources list block. No nesting, no wrapper syntax:

# reef <name>

trace_file    <path to reach-trace.ndjson>
arms_dir      <path to arm .cs files>
octo_dir      <path to .octo workflow files>
workspace     <path to workspace root>
reach_repo    <path to reach repo>
dotnet_root   <path to the .NET installation>
dotnet_script <path to the dotnet-script binary>

sources
  - <dot.path>
  - <dot.path>

Every key is optional — reef-mcp falls back to an environment variable of the same name (ARMS_DIR, TRACE_FILE, and so on) if the .reef key is missing, and the environment variable always wins if both are set. Adopting .reef is zero-risk for that reason: nothing already working via env vars changes just because a .reef file exists alongside it.


The Keys

KeyEnv var fallbackWhat it declares
trace_fileTRACE_FILEThe append-only TRACE log
arms_dirARMS_DIRDirectory containing .cs arm files
octo_dirOCTO_DIRDirectory containing .octo workflow files
workspaceWORKSPACERoot of the local workspace
reach_repoREACH_REPOPath to a REACH spec/example repo, if used
dotnet_rootDOTNET_ROOTRoot of the .NET installation dotnet-script needs
dotnet_scriptDOTNET_SCRIPTAbsolute path to the dotnet-script binary

trace_file, arms_dir, and octo_dir may be relative — they resolve relative to wherever the .reef file itself lives, which is the actual portability win: clone the whole thing to a new machine, and those three just work without editing a single path. workspace, reach_repo, dotnet_root, and dotnet_script are genuinely machine-specific and should stay absolute.

dotnet_root/dotnet_script have no built-in fallback default — if neither the env var nor .reef supplies them, run_arm refuses to execute anything and says so clearly, rather than silently guessing at a path that might be wrong on a different machine.


The sources Block

Declares what this habitat is allowed to touch:

sources
  - git.commits
  - reef.trace
  - fetch-market-data

Current state: parsed and reported — check_habitat surfaces the declared list — but not enforced. Nothing yet cross-checks an arm's own SOURCES: line against what's declared here. That's real, deliberately deferred work: enforcing it means parsing every arm's header and gating run_arm on the result, which hasn't been built because nothing has needed it yet. Declaring sources now means the habitat is honestly described even before anything reads that description as a boundary.


A Real Example

# reef dev-machine

trace_file    reach-trace.ndjson
arms_dir      arms
octo_dir      octo
workspace     /Users/<you>/workspace
reach_repo    /Users/<you>/workspace/reach
dotnet_root   /opt/homebrew/opt/dotnet/libexec
dotnet_script /Users/<you>/.dotnet/tools/dotnet-script

sources
  - git.commits
  - reef.trace

trace_file/arms_dir/octo_dir are relative — this whole file can move with the repo it lives in. The rest are this machine's actual paths.


.reef and .reef.example

.reef holds real, machine-specific values — gitignored, never committed. .reef.example is the committed template, placeholder values (/Users/<you>/...) instead of real ones — the same split as .env/.env.example. Copy the example, fill in real paths, done.


.reef vs .octo — Side by Side

# my-machine's .reef — the habitat (declared once)
trace_file  reach-trace.ndjson
arms_dir    arms
sources
  - outlook.inbox
  - outlook.calendar
  - git.commits
# morning.octo — the workflow (runs on demand)
name  morning-brief
arms
  - MORNING-BRIEF
  - REPO-STATUS
surface
  title  "Good morning."
close
  tone  warm

.reef doesn't change when a workflow changes. .octo doesn't need to know where arms_dir resolves to. Independent, complementary declarations — habitat and intent, each doing exactly one thing.


Design Principles

Declare once, resolve everywhere. Every path, every source — declared once, resolved through one function (resolveConfig) everywhere it's needed.

Env var wins, .reef is the fallback. Not a competing config system — a safety net under the one that already works. This is what makes adopting .reef additive rather than a migration.

Declaration is not configuration. git.commits in the sources block isn't a connection string — it's a statement that this source is part of this habitat, whether or not anything checks it yet.

The file travels with the habitat, not the machine. Relative paths for what lives inside the repo; absolute paths only for what's genuinely tied to this machine.