CLI Reference¶
The memoir command is the primary shell interface to a memory store. It exposes every retrieval and mutation pipeline the Python SDK supports, plus taxonomy-inspection primitives designed for agentic callers.
This page documents the search-adjacent commands — recall, get, and summarize — and the cloud sync commands in depth. For mutation (remember, forget), versioning (branch, checkout, merge, time-travel, branch-match), and crypto (proof, verify, blame) commands, use memoir <command> --help or see the API reference.
Setup¶
Set MEMOIR_STORE once so you can skip -s <path> on every call. Most usage assumes this is exported:
Add --json at the group level for machine-readable output (recommended when scripting or piping into jq). MEMOIR_JSON=1 in the environment has the same effect globally.
Environment variables¶
| Variable | Effect |
|---|---|
MEMOIR_STORE |
Default store path. Avoids -s <path> on every call. |
MEMOIR_JSON |
If 1, all commands output JSON (same as passing --json). |
MEMOIR_QUIET |
If 1, suppresses non-essential output. |
MEMOIR_BRANCH |
Default target branch for remember, recall, get, forget, and search — per-call routing without changing the store's checked-out branch. Each command also accepts an explicit --branch <name> flag which overrides this variable. See Per-call branch routing below. |
MEMOIR_MERGE_POLICY |
Global conflict-resolution strategy for remember when a key already exists, overriding the per-type default but below an explicit --merge-policy. =replace restores the old overwrite-everywhere behaviour. See Conflict resolution. |
MEMOIR_FACET_MAX_ENTRIES |
Cap on facet entries per key for append-style writes (oldest pruned). Default 50; 0/none disables capping. |
MEMOIR_RECALL_MERGE |
If llm, enables merge-on-read: a multi-entry key's content is LLM-consolidated at read time. Off by default (the deterministic projection is used). |
MEMOIR_API_KEY |
memoir-cloud API key; overrides the one saved by memoir login. Unlocks the cloud sync commands (remote, push, pull, fetch). Unset = COMMUNITY tier, no behaviour change. Never written to disk. See Cloud Sync. |
MEMOIR_CLOUD_URL |
memoir-cloud gateway URL for remote add / push --create when --url is not passed. Default: the production gateway. |
Global flags¶
Flags accepted before the subcommand, on the memoir group itself:
| Flag | Effect |
|---|---|
-s, --store <path> |
Override the store path (takes precedence over MEMOIR_STORE). |
--json |
Machine-readable output for every subcommand. |
-q, --quiet |
Suppress non-essential output. |
-v, --verbose |
Enable verbose logging. |
Per-call branch routing (multi-agent)¶
remember, recall, get, forget, and search accept --branch <name> (env: MEMOIR_BRANCH). When set, the call operates against that branch and the store's checked-out branch is restored on exit — no memoir checkout round-trip, no race window on shared deployments.
Set MEMOIR_BRANCH once at the runtime boundary (LangGraph node, MCP client, agent shell). Each agent process injects its own identity once instead of threading a flag through every call site:
# In agent A's process
export MEMOIR_BRANCH=agents/reviewer
memoir remember "found N+1 query in /users migration"
# In agent B's process
export MEMOIR_BRANCH=agents/builder
memoir remember "shipped pagination on /users"
Or pass --branch per call. Explicit flag wins over the env var:
memoir remember "API contract change" --branch=agents/reviewer
memoir recall "N+1" --branch=agents/reviewer
memoir get lessons.builder.api --branch=agents/builder
memoir forget stale.note --branch=agents/builder --force
Reads are isolated. Other branches and main don't see an agent's working memory until it's merged:
$ memoir get lessons.reviewer.sql --branch=agents/builder
✗ default:lessons.reviewer.sql (not found)
$ memoir get lessons.reviewer.sql --branch=typo-here
✗ Failed to get: Branch 'typo-here' does not exist.
Create it explicitly with `memoir branch typo-here`, or use a write
command (e.g. `memoir remember --branch=typo-here ...`) to bootstrap it.
That last error matters — read commands refuse to silently return empty on a misspelled branch, so typos surface immediately instead of looking like "no results".
Auto-create on first write. No separate memoir branch <name> is needed for a new agent:
memoir remember "first note" --branch=agents/new-bot # branch is bootstrapped
memoir branch # → main, agents/new-bot
Cross-agent synthesis stays explicit — promote findings to the shared trunk with memoir merge:
After every --branch call the store's checked-out branch is unchanged — routing is per-call, not a checkout.
Concurrency note. Routing is per-process. Don't run multiple in-process writers concurrently against the same store on different branches. Separate CLI invocations are fine — they rely on the same prollytree file locking as today.
Search commands¶
The three pipelines described in Search Theory are all reachable here. Pick the one that matches how narrow or open-ended your query is.
memoir recall — semantic search (in-engine)¶
Primary search entry point. Accepts a natural-language query and returns ranked IntelligentSearchResult memories. Mode is selected per call via --mode.
# Single-stage (default) — one LLM call, 500-800ms typical
memoir recall "what's my testing setup?"
# Tiered drill-down — 2-3 LLM calls, narrower prompts, ~1-2s typical
memoir recall "what's my testing setup?" --mode tiered
# Scope to a namespace and cap the result count
memoir recall "meeting notes" -n calendar -l 5
# Drop results below a relevance threshold (0.0-1.0)
memoir recall "programming languages" --threshold 0.5
# Machine-readable — best shape for agents / scripts / benchmarks
memoir --json recall "testing setup" --mode tiered
The --json form exposes per-stage observability. For --mode tiered the step_timings block contains l1_survey, l1_pick_llm, descend, key_pick_llm, memory_retrieval, total_search (plus l2_pick_llm when an L1 exceeded the 40-key escalation threshold). Every result carries metadata.mode so a consumer never has to guess which pipeline produced it:
memoir --json recall "testing setup" --mode tiered \
| jq '.memories[0].metadata | {mode, step_timings}'
{
"mode": "tiered",
"step_timings": {
"step1_path_discovery": 0.012,
"l1_survey": 0.001,
"l1_pick_llm": 0.412,
"descend": 0.001,
"key_pick_llm": 0.587,
"memory_retrieval": 0.008,
"total_search": 1.021
}
}
A/B the two modes on the same store:
memoir --json recall "testing setup" --mode single | jq '.timing_ms'
memoir --json recall "testing setup" --mode tiered | jq '.timing_ms'
Picking the LLM¶
Both recall and remember accept a --model flag. Resolution order:
--model <name>flag (highest priority)MEMOIR_LLM_MODELenv varclaude-haiku-4-5default
# Default — Anthropic Haiku, requires ANTHROPIC_API_KEY
memoir recall "what's my testing setup?"
# Per-call override
memoir recall "..." --model gpt-4o-mini # needs OPENAI_API_KEY
memoir remember "..." --model claude-sonnet-4-5
# Shell-wide override
export MEMOIR_LLM_MODEL=gpt-4o-mini
As of v0.1.7, litellm is a default dependency, so pip install
memoir-ai enables both LLM-backed and direct-path commands. (Prior
to v0.1.7 you had to add the [litellm] extra explicitly.)
memoir remember — conflict resolution¶
When a write lands on a key that already exists, remember resolves the conflict with a merge policy. By default the policy is derived from the key's memory type; --merge-policy overrides it per call. See Conflict & Merge theory for the full model.
| Flag | Effect |
|---|---|
--merge-policy <strategy> |
Force a strategy for this write: append, replace, confidence_gated, llm_merge, merge_on_read, or reject. |
-i, --interactive |
On a conflict, show existing vs. incoming and prompt (replace / append / merge / skip) per key. Not valid with --json. |
--replace |
Back-compat alias for --merge-policy replace. |
Resolution order (first one set wins): --merge-policy flag → MEMOIR_MERGE_POLICY env → per-type default:
| Memory type | Example keys | Default strategy |
|---|---|---|
| Episodic | experience.*, metrics.code.* |
append |
| Semantic | knowledge.*, preferences.*, profile.*, context.project.* |
confidence_gated |
| Procedural | workflow.*, behavior.* |
llm_merge |
| Working | context.current.*, metrics.turn.* |
replace |
# No flag → the key's memory type decides.
memoir remember "use spaces" -p knowledge.coding.style # semantic → confidence_gated
memoir remember "deployed v2 at 14:00" -p experience.releases.log # episodic → append
# Override the type default for this call only.
memoir remember "use spaces" -p knowledge.coding.style --merge-policy append
memoir remember "draft note" -p experience.releases.log --merge-policy replace
# Resolve interactively, or refuse to clobber and inspect the conflict (JSON).
memoir remember "new value" -p knowledge.coding.style --interactive
memoir --json remember "new value" -p knowledge.coding.style --merge-policy reject
Note
confidence_gated only changes behaviour for sub-1.0 confidence (LLM classifications). Caller-supplied -p writes are confidence 1.0, so on a semantic key they pass the gate and effectively replace — pass --merge-policy append/llm_merge to accumulate or consolidate instead.
memoir get — direct lookup by taxonomy path¶
No LLM, no search. Pass one or more exact keys; missing keys come back as found: false so you can batch speculative candidates without branching. Latency is typically <10ms.
# Single lookup
memoir get preferences.coding.style
# Batched lookup in one call
memoir get preferences.coding.style profile.professional.skills
# Scope to a namespace, JSON output
memoir --json get preferences.coding.style -n default
This is the primitive an outer-LLM caller-driven flow uses once it has narrowed to exact keys. From the CLI it's also the fastest way to read a known memory.
memoir summarize — taxonomy surveys¶
Pure-compute taxonomy inspection. The building blocks behind the caller-driven [mode=drill] / [mode=flat] / [mode=get] patterns are directly usable from the shell when you want to understand the layout of a store without invoking any LLM.
# Full store breakdown
memoir summarize
# Taxonomy-only view, scoped to one namespace
memoir summarize taxonomy -n default
# Keys matching a glob
memoir summarize --keys "preferences.*"
# Top-level prefix histogram (L1 survey)
memoir summarize --depth 1
# Glob + depth: L2 breakdown under preferences.*
memoir summarize --keys "preferences.*" --depth 2
# JSON for scripting
memoir --json summarize --depth 1 -n default
A shell-only drill-down — mirror of the skill's [mode=drill] — is just three calls:
memoir --json summarize --depth 1 -n default
# → pick L1 prefixes from prefix_counts
memoir --json summarize --keys "preferences.*" -n default
# → pick 3-7 exact keys from matching_keys
memoir --json get preferences.coding.style preferences.tools.editor
# → stored values, <10ms
When to reach for which CLI command¶
- You want semantic search over a natural-language query →
memoir recall(add--mode tieredif the single-stage picker is dropping signal on your store size). - You already know the exact taxonomy path →
memoir get— skip the classifier entirely. - You want to inspect the taxonomy layout (what prefixes exist, how dense each branch is) →
memoir summarize --depth Nwith or without--keys <glob>. - You're scripting an agent / LLM caller and want to avoid a nested LLM call on memoir's side → compose
summarize+getyourself; this is exactly what thememory-recallskill does.
Cloud sync commands¶
memoir remote, push, pull, and fetch round-trip a local store with memoir-cloud, so the same memories follow your agent across machines. They need a login: without one every cloud command exits 1 asking you to run memoir login, and nothing else in memoir changes. The rules behind the commands (addresses, what syncs, fast-forward only, key handling) are in the Cloud Sync reference; this section is a hands-on guide.
From the root of a clone of your code repo, the whole setup is:
memoir login # once per machine: approve in the browser
memoir push --create # New: create <handle>/<repo name> from this repo's memories
# or
memoir pull <owner>/<store> # Open: link an existing cloud store and pull it
Inside a code repo memoir uses that repo's store, the one Claude Code uses (~/.memoir/<slug>), so neither MEMOIR_STORE nor -s is needed; memoir login remembers the gateway, so --url isn't either. On a headless machine use echo "$KEY" | memoir login --with-key; MEMOIR_API_KEY still works and overrides the saved login. The walkthrough below uses explicit names and addresses so each step is visible.
Cloud stores are addressed GitHub-style as <owner>/<store>, where owner is your handle and store is the store name, for example feng-zhang/demo. That address is what you type, what memoir prints, and (prefixed with the gateway) the git remote origin.
| Command | What it does |
|---|---|
memoir login [--url <gateway>] [--with-key] [--default] [--status] / memoir logout [--url <gateway>] [--all] |
Save a key per gateway after approving in the browser (or from stdin); list them; remove one (or all) and revoke it on the cloud. |
memoir push --create [<store>] |
Create <your handle>/<store> in the cloud (default name: the code repo's), link it as origin, and push. The usual first step. |
memoir remote add [<owner>/<store>] [--url <gateway>] [--force] |
Link an existing cloud store. With no argument, proposes <your handle>/<directory name> and asks first. |
memoir remote add … --name <remote> / memoir remote list |
Link another cloud store under its own name (e.g. staging); list every remote with its gateway and login state. |
memoir remote show [<remote>] / memoir remote remove [<remote>] |
Show the address, gateway, branch and cloud summary; or unlink. Default remote: origin. |
memoir push [--branch <b>] [--remote <name>] |
Upload the chunks the cloud is missing, then git push. Default: current branch. |
memoir fetch [--remote <name>] |
Download new refs and chunks. Moves no local branch. |
memoir pull [<owner>/<store>] [--remote <name>] [--url <gateway>] [--branch <b>] [--force] |
With an address, links the remote first when it's missing (never relinks one that points elsewhere). Then fetch, then fast-forward the branch. Creates it from the cloud if missing locally; a never-used local store adopts the cloud history. --force replaces the local branch with the cloud copy. |
memoir status |
Adds origin: <owner>/<store> when a cloud remote is configured. |
All of them accept --json.
First push from your laptop¶
Start from an existing store, or create one:
memoir new ~/memories
export MEMOIR_STORE=~/memories
memoir remember "Always run make lint before opening a PR"
Create the cloud store, link it, and push in one step. The name is yours to pick; the owner is your handle:
Behind the scenes this validated the name locally, created the cloud store, set the git remote origin to https://<gateway>/feng-zhang/memories, uploaded the chunks the cloud lacked in multipart batches, and only then pushed the git history. The chunks are the ProllyTree node files that hold the actual memories. If a chunk upload fails, the git push is never attempted, so the cloud never points at data it does not have. Memoir also records which hashes the server confirmed under .git/memoir-cloud/pushed-origin, so a second push right away needs no round trip to find out that nothing is new:
memoir status and memoir remote show both tell you the address:
origin: feng-zhang/memories
Gateway: https://api-gateway-production-ab56.up.railway.app
Branch: main
Created: 2026-10-03T14:02:11Z
If the name is taken you get store name already exists; pick another or run memoir remote add feng-zhang/memories. If the name is invalid (spaces, a leading dot, a .git suffix) memoir says so before contacting the server.
Pick it up on your desktop¶
You never create the local store by hand. The Claude Code plugin creates one per project on session start, and memoir creates one on first use. So on the second machine, open the same project, log in, and pull the cloud store by its address. With an address, pull links the remote first when it doesn't exist yet:
Running the same command again just pulls what's new. If origin already points at a different cloud store, pull changes nothing and exits 1 with a hint: use --remote <other name> to add a second store, or memoir remote add <address> --force to switch. memoir remote add <address> followed by memoir pull still works if you prefer two steps.
Because the local store had never been used, pull adopted the cloud history outright. From here status, recall, and get see the laptop's memories:
Linking resolves the address first. An unknown store, or one you do not own, fails with store feng-zhang/typo not found (or you don't own it) and nothing is written. After pull, memoir also checks that the chunk named by the store's root hash was downloaded.
If the local store already holds memories of its own, pull refuses rather than guess:
✗ local and cloud histories have diverged; cloud merge is not available yet. To discard the local memories on this branch and adopt the cloud copy: memoir pull --force --branch main
Either push those memories to their own cloud store (memoir remote remove, then memoir push --create <other-name>), or adopt the cloud copy with memoir pull --force, which replaces the local branch and keeps the tip it had before under refs/memoir/backup/<branch> (recover with git branch <name> refs/memoir/backup/<branch>).
Linking without typing the address¶
Run memoir remote add with no argument and it proposes <your handle>/<current directory name> and asks before resolving. In a project folder called memories that is feng-zhang/memories. A full https://<gateway>/<owner>/<store> URL is accepted in place of the address.
The daily loop¶
Treat it like git. Pull before you start, push when you finish:
memoir pull # bring down anything the other machine pushed
# ... work; the agent captures memories ...
memoir push
fetch downloads refs and chunks without moving any local branch, for when you want to look before you merge:
When the cloud is ahead¶
You pushed from the desktop, forgot to pull on the laptop, and kept working there. Pushing from the laptop is refused with exit code 6:
User branches on the cloud are fast-forward only, so one machine can never overwrite another's history. If the laptop has no new commits of its own, memoir pull then memoir push resolves it.
If both machines committed since the last sync, the histories have diverged and pull is refused too:
Cloud-side merge is planned but not available yet. Until then, pick a side. To keep the cloud version, force-pull. Look at what you are giving up first; afterwards the old tip is kept under refs/memoir/backup/main in case you need it:
memoir fetch # cloud refs + chunks are now local
memoir diff main origin/main # see what differs before deciding
memoir pull --force # main := origin/main
memoir remember "..." # re-add anything that mattered
memoir push
✓ replaced main (was 3f9c2b1, kept at refs/memoir/backup/main) with origin/main at 9c1e2f0 (2 new chunks)
To keep the local version instead, unlink with memoir remote remove and start a new cloud store with memoir push --create <new-name>.
More than one branch¶
Every verb takes --branch. Push an experiment branch without touching main:
memoir branch experiments
memoir checkout experiments
memoir remember "Trying Ruff instead of flake8 on the side project"
memoir push --branch experiments
On the other machine, pull --branch creates the branch if it does not exist locally yet:
Branches named cloud/... belong to the cloud. They carry proposals the cloud generates for you, appear after fetch as origin/cloud/..., and cannot be pushed:
Scripting and agents¶
Every command honours --json (or MEMOIR_JSON=1). A sync step in a hook or CI job can branch on the exit code and read the counts:
if out=$(memoir --json push); then
echo "$out" | jq '{origin, branch, chunks_uploaded, chunks_present}'
else
case $? in
6) memoir pull && memoir push ;; # cloud was ahead, retry once
1) echo "auth or network problem: $out" >&2 ;;
esac
fi
{
"success": true,
"message": "pushed main to feng-zhang/memories: 4 chunks (1 new) in 1.1 s, git in 2.4 s",
"branch": "main",
"origin": "feng-zhang/memories",
"chunks_uploaded": 1,
"chunks_present": 3,
"pushed": true,
"seconds": 3.6,
"chunk_seconds": 1.1,
"git_seconds": 2.4
}
Every JSON result carries the address as origin. fetch adds chunks_downloaded and remote_refs; pull adds branch, created, tip, chunks_downloaded, forced, previous_tip, and backup_ref.
To use a different gateway, such as a staging deployment, pass --url to remote add or push --create, or set MEMOIR_CLOUD_URL once. After linking, the gateway lives on the origin URL, so the other verbs need neither.
Unlinking¶
memoir remote remove drops the git remote and nothing else. Local memories, history, and the cloud store are untouched.
Troubleshooting¶
| Message | Cause | What to do |
|---|---|---|
Cloud sync requires a login |
No saved login and no MEMOIR_API_KEY. |
memoir login (or export MEMOIR_API_KEY=...) |
not signed in: run memoir login |
The gateway returned 401. | The key is wrong, expired or revoked: memoir login again. |
not logged in to <gateway> |
No saved login for the gateway this remote (or --url) points at. |
memoir login --url <gateway>; memoir login --status shows what's saved. |
your account has no handle yet |
You have not chosen a handle. | Open <gateway>/app in a browser and pick one. |
store <owner>/<store> not found (or you don't own it) |
Unknown address, or a store belonging to someone else. | Check the address with memoir remote show on the machine that created it. |
"str_..." is a store id |
You pasted an internal id. | Use the <owner>/<store> address instead. |
store name already exists |
--create with a name you already use. |
Pick another name, or memoir remote add <handle>/<name>. |
remote 'origin' already exists (...); pass --force to replace it |
The store is linked already. | Use remote show, or remote add ... --force to relink. |
remote has commits you don't have; run memoir pull first (exit 6) |
The cloud is ahead. | memoir pull, then push again. |
local and cloud histories have diverged (exit 6) |
Both sides have commits the other lacks: both machines captured since the last sync, or the local store grew memories before it was linked. | See "When the cloud is ahead" above, or adopt the cloud copy with memoir pull --force. |
root chunk ... is missing locally after sync |
A chunk download did not complete. | Run memoir fetch again. |
object store unavailable, retry later |
The cloud's object store answered 502 three times in a row. | Wait a little and run memoir push again; chunks already written are not re-sent. |
server rejected N chunk(s) |
The server refused some chunk parts (reason listed per chunk). | Nothing was pushed to git. Fix or report the listed chunks, then push again. |
Memoir never writes the key to disk and never prints the cloud's internal store ids. If either ever appears in output, that is a bug: please report it.
Other command groups¶
The rest of the CLI surface is documented inline via --help. Command groups at a glance:
| Group | Commands | --help |
|---|---|---|
| Store | new, connect, status, refresh |
memoir new --help |
| Memory | remember, recall, get, forget |
memoir remember --help |
| Branch | branch, checkout, merge, time-travel, diff, branch-match |
memoir branch --help |
| Crypto | proof, verify, blame |
memoir proof --help |
| Analysis | summarize |
memoir summarize --help |
| Cloud sync | remote, push, pull, fetch — stores addressed as <owner>/<store> (requires MEMOIR_API_KEY; see Cloud Sync) |
memoir push --help |
For the underlying Python APIs these commands call into, see the API Reference.