.memo/)When you open a folder as a project (an agent chat), Memo can keep its own small
folder in it: working rules, notes, project skills — and give that project a
memory of its own. Design record: docs/plans/PLAN_project_folders.md.
.memo/AGENTS.md first.AGENTS.md, handoff.md and .memo/notes.md./skill add <source> (see below).<project>/AGENTS.md project facts: structure, commands, conventions, pitfalls
<project>/handoff.md session log, newest on top
<project>/.memo/AGENTS.md generic working rules (Memo-managed)
<project>/.memo/project.json id + settings
<project>/.memo/notes.md a few durable facts
<project>/.memo/.gitignore keeps per-user files out of the repo
<project>/.memo/skills/ project skills (created on first install)
Nothing that already exists is overwritten. If the project already has a CLAUDE.md
no second AGENTS.md is created. The root AGENTS.md skeleton never mentions Memo's
folder, because other tools read that file too. .memo/AGENTS.md is the one file Memo
owns: it is refreshed on an update only while its hash still equals what Memo wrote
(rules_hash in project.json), i.e. while nobody has edited it.
The templates are tech-agnostic and about quality: understand before changing, fix root
causes, keep scope tight, never say "done" without running the project's real
verification commands, say what was not verified, never commit unless asked.
~/.memo is not a projectMemo's own install lives in ~/.memo (bin/, binaries/, data/…), and the updater
and the uninstaller manage it. A project's folder has the same name, so a project rooted
at your home folder would put project files into the install — and an update would
delete them. internal/project.Guard therefore refuses: the home folder and every parent
of it, anything inside Memo's install/data/config folders, system folders, and any folder
whose .memo has bin|binaries|data|config in it. A .memo without a validproject.json ("kind":"project" and a p_… id) is foreign and Memo never writes to
it; a symlinked .memo is treated the same way, so Memo can never write through it.
Each project has a stable id (p_ + 128 random bits) in .memo/project.json. It travels
with the folder, so chats and memory follow a project that is moved or renamed. A
per-device registry (data/projects.json, never synced) maps folder → id, hands out an
id before the folder is set up (setup writes that same id), notices a move (old folder
gone → same id) and a copy (the same id in a second live folder → the copy gets a new id,
so two projects never share one memory).
Memory is partitioned by memories.project_id; the empty id is the general pool
(everything from ordinary chats and everything saved before this feature).
| Chat | Reads | Writes |
|---|---|---|
| Ordinary chat | every pool | the general pool |
| Project (agent) chat | only its own project's pool | its own project's pool |
A project chat never receives your general memory, pinned facts, name, learned style
notes or custom persona text; its mood is neither shown nor fed; it learns no personal
facts and its turns do not go to the optional decision model. A project whose id cannot
be determined gets no memory, never the general pool. Code Mode used to have no
memory at all; a project chat now reads and writes its own pool whether or not Code Mode is
on: Code Mode only strips what is personal, and the project's own pool is not. The
Memory switch still decides whether any memory is used. A coding turn's reply is
stored as an excerpt (1500 characters), since retrieval only shows the user's side.
Self-Driving task steps with "task memory" off neither read nor write it.
The upgrade adds memories.project_id (ADD COLUMN … DEFAULT '', idempotent). Before
touching a database that has rows, Memo copies memory.db todata/backups/memory-pre-project-id-<time>.db. If that copy cannot be written the change
is skipped: ordinary chats behave exactly as before and project chats get no memory until
the next start. The existing INSERTs are unchanged, so an older Memo can still read and
write the upgraded file (its rows land in the general pool).
Minimal Mode is a pure wrapper: no session-start line, no setup question, nothing read.
The memory switch is independent: with memory on, only the (scoped) memory block is added.
.memo/skills/<name>/SKILL.md, same format as global skills, with two differences:
/skill:on <name>Add one from GitHub with /skill add <source> or by asking the agent (toolinstall_project_skill, which asks permission). A source can be owner/repo,owner/repo/some/folder, a github.com URL (also /tree/<ref>/<path>), or the line you
pasted from a skill's README such as npx skills add owner/repo --skill name. Memo
never runs npx: it reads the source out of the line and downloads a plain archive over
HTTPS (25 MB compressed / 60 MB unpacked / 5000 entries / 5 MB per file; symlinks,
absolute and .. paths are refused; nothing is made executable). If a place holds
several skills the error lists them. An installed skill is never overwritten./skill remove <name> removes it.
A project can pre-approve a few things so Memo stops asking about them, in.memo/permissions.json (kept out of version control by .memo/.gitignore):
{ "version": 1, "allowed_commands": ["go test", "git status"], "allowed_tools": ["edit_file"] }
run_command hands the line to bash -c, so "starts with go test" is not enough:go test ./... ; rm -rf ~ also starts with it. A line is approved only if it is made of_ . / : = , @ + - and quotes — nothing that can chain; & |), redirect (< >), substitute ($ \``), expand (* ? ~ { }) or comment (#) — and it starts with an allowed prefix at a word boundary (go testmatchesgo test ./..., not go testing). A command that fails this simply asks as before, for example -run 'A|B'; write -run A` or ask once.bash, python3, node…), wrappers (env, xargs, sudo, time,find…), network and container tools (curl, nc, ssh, docker, npx…), destructiverm, mv, dd, chmod…), a bare git / go / npm / cargo (name thegit status), and flags that make a command run another program-exec, -toolexec, -ldflags…). The list is best effort; the character check is theread_file, list_directory,get_file_info, search_files, search_content, write_file, edit_file,insert_line, delete_lines, create_task_md, edit_task_md, save_code_plan.run_command, delete_file, anything that reaches the network, your accounts or Memo's/api/projects/permissions/trust/api/projects/permissions/save). Memo pins the file's SHA-256 in thedata/projects.json); if the file changes afterwards, by anyone, it isAGENTS.md and .memo/AGENTS.md can not grant any of this; only the| Method | Endpoint | |
|---|---|---|
POST |
/api/projects/status |
{path} → {root, id, name, set_up, declined, allowed, reason, enabled, should_ask}; writes nothing |
POST |
/api/projects/setup |
{path} → creates the files; 403 for home / Memo's folders / a foreign .memo |
POST |
/api/projects/decline |
{path, declined?} → remember "never here" (false undoes it) |
POST |
/api/projects/permissions |
{path} → {permissions, status: {exists, trusted, hash}} |
POST |
/api/projects/permissions/save |
{path, permissions} → writes and trusts .memo/permissions.json |
POST |
/api/projects/permissions/trust |
{path} → pins the existing file hash as trusted |
/skill, /skill:on, /skill add.