#Project config (.me/config.yaml)
A project can pin its Memory Engine server and space — and, optionally, the
tree its integrations write to — in a .me/config.yaml file at the repo root.
Any me invocation inside the project (the CLI, the me mcp server, the
Claude/OpenCode capture hooks, me import git) then targets that server and space
automatically, with no per-command flags or environment variables.
This is what lets a project say "my memories live in space X on server Y,
under tree Z" once, and have every tool that touches the repo agree. For how
to choose that tree and its grants, see Projects.
The interactive me project init wizard writes this file
for you — server + space (pinned together, so a committed config is
self-contained), the tree, a dedicated agent, and the capture flag.
Every harness surface (MCP, the Claude/OpenCode capture hooks, and a plain
me call from an agent's own shell) then resolves that agent automatically
— see Agent-by-config below — so
there's no separate .claude/settings.json pin to keep in sync.
#The file
# .me/config.yaml
server: https://api.memory.build # pin the server
space: xjjg3kmq6vvb # pin the space (slug)
tree: /share/projects/acme # optional: where integrations write (see below)
agent: acme-agent # optional: the project's agent (see below)
capture: true # optional: session capture on/off (see below)
import: # optional: the CI import run (see below)
docs_include: ["docs/**"]
All fields are optional. A .me that sets only tree still inherits its
server/space from your global config. A malformed file — invalid YAML, or a
field that fails validation — is a hard error: me fails with a clear message
rather than silently ignoring the pins the project meant to apply. (The
best-effort capture hooks are the exception — they log and skip, so a typo never
breaks an agent session.)
agent:names the project's default agent and is the value source for the.mesentinel:--as-agent .me/ME_AS_AGENT=.meresolves to it and sendsX-Me-As-Agent. A harness surface (MCP, the capture hooks, a harness's own shell) activates this automatically — see Agent-by-config. A committedagent: .useris a fatal error (see below); it's valid only in.me/config.local.yamlor your global config.
#Trusted servers (credential safety)
A .me/config.yaml is untrusted input — you might cd into someone else's
repo. Because an api key (ME_API_KEY) and ME_SESSION_TOKEN are global
credentials (sent to whichever server is resolved), a malicious .me pinning
server: https://attacker.example could otherwise exfiltrate them.
So me only honors a .me server pin that is on a trusted list:
- Trusted by default: the prod server (
https://api.memory.build) and the dev server. me login --server <url>adds that server (logging in is an explicit act of trust).- You can hand-add more via
server_whitelistin your global~/.config/me/config.yaml:server_whitelist: - https://me.internal.example
A .me that pins an untrusted server is refused with a fatal error rather
than sending credentials to it. This gate applies only to a server chosen by
a project's .me — an explicit --server / ME_SERVER and your stored
default_server are your own choices and are never gated.
#Discovery
me finds the config by walking up from the current directory to the first
ancestor that contains a .me/config.yaml or .me/config.local.yaml — so it
works from any subdirectory of the project, just like .git, and a machine-local
project (only a .local file, no committed one) is discovered too.
To point at a specific project without being inside it, pass --config-dir <dir>
(the directory that contains .me/) or set ME_CONFIG_DIR. Either skips the
walk and uses that directory's .me/ directly. (The one exception is the bulk
session sweep, me import <tool>: it routes every session by its own project's
config, so it rejects an explicit pin — see
agent session imports.)
There's also --project-dir <dir> / ME_PROJECT_DIR — an ANCHOR
rather than an exact location: me still walks up from it, it just replaces
cwd as the walk-up's starting point. This is what the harness-injected
shell contract sets on every command an agent's tool shell runs (see
MCP Integration), so a cd /tmp && me … from an
agent's shell still discovers the right project. You won't usually set this
by hand. Precedence, highest first: --config-dir/ME_CONFIG_DIR (exact) >
--project-dir/ME_PROJECT_DIR (anchor) > cwd walk-up > a validated
harness-provided fallback (today: Claude's CLAUDE_PROJECT_DIR, accepted
only if it actually contains a .me/).
#Committed vs. local (.me/config.local.yaml)
A sibling .me/config.local.yaml overrides the committed .me/config.yaml
per field — the same split as Claude Code's settings.json vs
settings.local.json:
.me/config.yaml— commit it to share the project's server/space/tree with everyone who clones the repo..me/config.local.yaml— gitignore it; personal overrides that never leave your machine. This is how a private project keeps its home tree (~/projects/…) out of version control.
#Precedence
Each of server / space resolves highest-first:
--flag > ME_* env > .me/config.local.yaml > .me/config.yaml
> ~/.config/me global config > built-in default
So an explicit --server / --space (or ME_SERVER / ME_SPACE) still wins for
a one-off, and a project's .me beats your global default when you're working
inside it.
#Changing the pinned space (me space use)
You don't have to hand-edit the file to repoint a project: me space use <space>
writes to whichever config currently defines the effective space — the
git config model of editing the effective scope. Inside a project whose .me
defines space, it updates that pin (.me/config.local.yaml if it defines
space, since it overrides the committed file; else the committed
.me/config.yaml — comments are preserved). When no .me file defines space,
the global ~/.config/me/config.yaml is updated as usual — a tree-only .me
keeps following your global active space. The command prints which file it
saved to.
#The tree field (for integrations)
tree is the full project tree — the exact node integrations write under, with
no project slug appended. Given tree: /share/projects/acme:
- Captured/imported agent sessions land under
/share/projects/acme/agent_sessions - Imported git history lands under
/share/projects/acme/git_history
Contrast the default (no .me), where captures nest privately under
~/projects/<auto-slug>/… — your own home tree, visible only to you. Pinning
tree is how a project chooses its own home — e.g. a shared team subtree
(/share/projects/acme, so the whole team works off common memories), a
group-writable subtree that the team can still read (/share/payments/acme), or
a private group subtree outside /share (/payments/acme). See
Projects for common layouts.
A leading ~ (your home) and /-separated paths are accepted; the path is
normalized server-side. An explicit me import git --tree <path> /
me import <tool> --tree-root <path> still overrides the .me tree for that
run.
The Claude/OpenCode capture hooks resolve the .me for the session's
project, so a single globally-installed plugin routes each project to its own
tree — and the bulk me import <tool> sweep does the same per session
(server/space/tree from each session's own project), so backfills and live
capture always agree.
#Changing the default tree root (tree_root)
Without a .me tree, captures and imports nest per project under a tree
root — <tree_root>/<slug>/… — which defaults to the private ~/projects.
To change that default machine-wide, set tree_root in your global
~/.config/me/config.yaml (no command writes this; edit it by hand):
# ~/.config/me/config.yaml
tree_root: ~/work # captures now nest at ~/work/<slug>/…
Resolution, highest-first: an explicit --tree-root flag → the project's
.me tree (a full node — no slug appended) → the global tree_root →
~/projects. A leading ~ and /-separators are accepted; the path is
normalized server-side.
#The capture field (session capture on/off)
Session capture is off by default — the Claude capture hook ships inert. Whether a session is captured resolves highest-first:
- the project's
.me/config.yamlcapture—true: capture this project's sessions even if the member never opted in globally. A committedcapture: true(+ a sharedtree) is what makes a team repo capture for everyone who clones it.false: never capture this project (e.g. a sensitive repo) — this opt-out wins over every other setting.
- else the machine-wide setting in
~/.config/me/config.yaml(capture: true), written when you opt in at theme claude installprompt; - else off.
When capture is off the hooks exit silently — no error, nothing written. The
same resolution applies to the capture hooks that exist today (Claude Code and
OpenCode); me claude install and me opencode install both ask the capture
question and write the machine-wide flag. Codex and Gemini currently install
shell env hooks for agent-by-config, not session capture hooks.
#The import block (CI imports)
The optional import: block shapes the orchestrated CI run —
me import ci, the command the scaffolded
GitHub workflow calls on every push to the default branch. Targeting
(server/space/tree) stays in the top-level fields; this block only controls
WHAT the run imports and which identity is expected to run it:
import:
git: true # run the git-history phase (default true)
docs: true # run the docs phase (default true)
docs_include: ["docs/**"] # docs globs, replacing the default markdown set
docs_exclude: ["docs/internal/**"]
service_account: github-import # the SA expected to hold the CI credentials
Like the rest of the schema it is strict — a typo'd key (docs_includes:)
is a fatal error, never a silently-widened walk.
service_account is read by me project ci
(setup/verify), not at import time — in CI the ME_API_KEY bearer is the
identity. The name is not a secret (any space member can resolve it).
Committing it is how an org using per-project grants makes onboarding
self-documenting: plain me project ci then verifies the shared account and
extends its grant to this repo's tree without anyone remembering a flag. Orgs
using a single parent-level grant (write@/share/projects) don't need it at
all.
#Agent-by-config and the agent field
A harness surface — the MCP server (me mcp), the capture hooks, and a plain
me call from an agent's own tool shell (Claude, OpenCode, and — soon — Codex
and Gemini CLI) — resolves and acts as the configured agent automatically,
with no --as-agent flag needed. This is what makes an agent's work
attributable (it shows up as that agent, not as you) and access-scoped (the
server clamps the agent to its own grants, whatever your own access is).
Resolution, per harness invocation:
- the project's
.me/config.yamlagent, else - the global
agentin~/.config/me/config.yaml, else - nothing in scope —
me mcpfails to start with an actionable error; the capture hooks silently skip (never captures as you); a harness's own shell errors rather than falling back to your credentials.
Case 3 is rare in practice: every me claude install / me opencode install
provisions-or-adopts a default agent (named coder) and writes it as the
global fallback the first time you install (skip with --no-default-agent). If
a global agent: is already set but does not resolve to an agent you own, an
interactive install offers to create it; a non-interactive install fails with an
actionable error instead of leaving harnesses broken.
#The .user sentinel — opting a project (or machine) out
Sometimes you deliberately want harness work to run as you — a personal
project, a script you trust. Set agent: .user in .me/config.local.yaml
(never the committed .me/config.yaml — see below) or in your global
~/.config/me/config.yaml, or pass --as-agent .user / ME_AS_AGENT=.user
on a one-off invocation.
A committed agent: .user is a fatal ProjectConfigError — a repo author
writing it into the tracked .me/config.yaml would silently switch every
cloning teammate's harness surfaces to their own full user credentials, which
is a bigger blast radius than a committed agent: <name> (that can at worst
403, since names resolve against the caller's own agents). Put it in
.me/config.local.yaml (gitignored, personal) instead.
See MCP Integration for how this plays out for me mcp, and the harness-injected shell contract that makes a plain me call
from an agent's own tool shell resolve the same way.