Every mount is self-describing. The agent never needs to learn paths from external documentation — cat mount/LAYOUT.md lists everything, and per-integration LAYOUT.md files document the tree shape for each provider. This is deliberate: an agent oriented by reading the tree itself doesn't carry path knowledge in its prompt or schema.
The tree
mount/
├── LAYOUT.md # top-level guide: every provider root
├── _index.json # root listing
├── linear/
│ ├── LAYOUT.md # linear-specific tree shape
│ ├── issues/
│ │ ├── _index.json
│ │ ├── AGE-12__fix-login-bug.json # canonical: <slug>__<id>
│ │ ├── by-title/AGE-12-fix-login-bug.json
│ │ ├── by-id/AGE-12.json
│ │ └── by-state/in-progress/AGE-12__fix-login-bug.json
│ └── users/by-name/dana.json
├── github/
│ ├── LAYOUT.md
│ └── repos/
│ ├── _index.json
│ └── acme/api/
│ ├── pulls/_index.json
│ └── pulls/42__bump-deps/meta.json # <number>__<slug>
└── github/repos/acme__api/ # flat alias namespace
└── pulls/by-id/42.json
Self-describing files
Three kinds of metadata files make the tree navigable without prior knowledge:
LAYOUT.mdat the root is a virtual, read-only guide to the top-level structure. It's the first thing an agent shouldcatafter mounting.<integration>/LAYOUT.mddocuments the tree shape for a single provider — what directories exist underlinear/, what each holds, and which paths are artifacts rather than records. Uppercase, no leading dot._index.jsonin every directory lists the rows that directory contains, so an agent can read one file to understand a directory rather than walking every entry.
Canonical naming
Entity files put an identifier and a human-readable name in one filename, separated by __, so identifiers are recoverable from a listing. Which side the id sits on is per-adapter: Linear writes <sanitized-name>__<uuid> (id last), GitHub writes <number>__<slug> (id first). Read the provider's LAYOUT.md rather than assuming, and prefer _index.json rows for lookup:
inbox/threads/Re_Welcome__01HXYZ.json
inbox/threads/01HXYZ.jsonThe first path is the canonical human-readable form. The second is a legacy fallback that remains readable during the naming transition — consumers should accept both the new-style filename and the bare <id> basename while producers are updated.
Take the exact filename from a listing or _index.json rather than assembling one. Some adapters require the full <number>__<slug> directory on reads — for GitHub, pulls/59/meta.json returns 404 while pulls/59__<slug>/meta.json resolves — and the same adapter may still accept a bare id on the write path.
Alias views
Alias views let an agent navigate the same records by whichever key it has in hand — by-id/, by-title/, by-state/, by-edited/, by-assignee/, by-creator/, by-priority/, by-name/, by-uuid/. They are views over the same canonical entities, not copies you have to keep in sync.
Which views exist, and where they live, is decided by the adapter and the resource — there is no fixed set. Two things to check in the provider's LAYOUT.md before building a path:
- Location. Linear puts them under the canonical subtree (
/linear/issues/by-id/AR-100.json). GitHub puts them in a flat sibling namespace keyed<owner>__<repo>(/github/repos/acme__api/pulls/by-id/42.json) — there is noby-nameunder/github/repos/. - Set. On a live workspace, GitHub pulls expose
by-creator,by-edited,by-id,by-state, andby-title; GitHub issues expose four of those; Linear issues addby-assignee,by-priority, andby-uuid.
A flat alias directory also has an _index.json, and it means something different: it is a manifest of which alias subdirectories exist ({ "rows": [ { "title": "by-id", "file": "by-id/" } ] }), not a record index. Only the canonical tree's _index.json lists records, as a bare JSON array.
Lazy repo materialization
For huge-org workspaces, GitHub repo subtrees can be materialized lazily rather than exported up front. Opt in with --lazy-repos:
RELAYFILE_TOKEN="$TOKEN" go run ./cmd/relayfile-mount \
--workspace ws_demo \
--local-dir ./relayfile-mount \
--lazy-reposThis avoids pulling every repo tree on mount; subtrees populate on demand as they're accessed. Combine it with --remote-path (see Run locally) to scope the mount to specific subtrees from the start.