openmymemo
AGENTS.md29 starsChanged 32 days agoApache-2.0View on GitHub
A local-first, open-source AI memory folder with an approval panel. docs/product-design.md is the product definition; if an implementation would contradict it, update the design doc first (§12 and §13 record where v0.1 and v0.2 differ from earlier plans).
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,843 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository3 other files it gives its agents.
- codex-setupSkill
- memorySkill
- rememberSkill
The file
We split this AGENTS.md into the rules it gives an agent
5 rules. About 0 are common, 1 is distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
All user data is plain text: memories/*.md is the source of truth for memory content.
- Very specific: line 12
Don't weaken the panel API guard (guard in web.py: Host, Origin, X-OpenMyMemo header) or the /mcp token check.
- Very specific: line 78
All writes go through Vault methods, which lock, re-read from disk, write atomically and update meta and history.
- Very specific: line 82
Don't add a root .mcp.json: Claude Code would treat it as project config for everyone working on this repo.
- Distinctive: line 83
Commit messages are imperative English.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
OpenMyMemo — agent guide
A local-first, open-source AI memory folder with an approval panel. docs/product-design.md is the product definition; if an implementation would contradict it, update the design doc first (§12 and §13 record where v0.1 and v0.2 differ from earlier plans).
Non-negotiables
- Local only: no cloud storage, sync, accounts or telemetry, except the optional OpenMyMemo Cloud connector (product-design.md §16), which does nothing until the person signs in and never holds memory in readable form at rest unless the person also turns on MyMemo Memory sync (product-design.md §20): a separate consent, shown with what MyMemo stores, who can read it and how to delete it. Sync never sends a private topic the person didn't pick, and when the folder and MyMemo disagree, the folder wins. The server listens on
127.0.0.1only. Memory text leaves the computer only when the person turns the OpenMyMemo Assistant on with a remote model, and only to that model (product-design.md §17), or turns on MyMemo Memory sync, and only to MyMemo (§20); a model on this computer keeps everything on the computer, and private topics never go to a remote model. - All user data is plain text:
memories/*.mdis the source of truth for memory content; metadata, history, activity, proposals and settings under.openmymemo/are plain text too. Don't introduce a database. - Every AI write goes through a proposal (
proposals.py) and never edits files directly. A change that applies without the user (the default, or a connector set toauto) must stay undoable, must never write into a private topic, and must never silently change a memory the user wrote last (last_actor); the table in product-design.md §5.5 is normative. MyMemo Memory sync (product-design.md §20) follows the same rule: what MyMemo's AI features and web apps propose arrives as proposals fromcloud-*connectors and is decided here; only the person's own edits on MyMemo's memory page are applied directly, as their own edits (actor: user). - Works with zero external dependencies: anything that needs a model endpoint is optional and degrades instead of failing.
- No dependency on MyMemo services, accounts or databases. The OpenMyMemo Cloud connector is the one optional piece that talks to MyMemo (the relay and, with the person's consent, MyMemo Memory sync); everything else works without it, and removing it changes nothing else. Code and UI patterns may be borrowed from MyMemo's own apps; other runtime coupling may not.
- Don't weaken the panel API guard (
guardinweb.py: Host, Origin,X-OpenMyMemoheader) or the/mcptoken check.
Commands
uv sync
uv run pytest # all tests, including MCP end to end over stdio and real HTTP
uv run ruff check src tests scripts
uv run openmymemo --vault /tmp/v init # CLI
uv run python scripts/demo_vault.py /tmp/d # demo data
cd web && pnpm install && pnpm build # panel → src/openmymemo/static/ (commit the build output)
cd web && pnpm test # panel tests (vitest)
cd desktop && pnpm install && pnpm tauri dev # Mac app (needs Rust); `pnpm tauri build` for the .app
python3 scripts/stage_desktop.py && (cd desktop/src-tauri && cargo test) # Mac or Windows app tests (`python` on Windows)Code map
| File | Responsibility |
src/openmymemo/mdfile.py | Parse and rewrite topic files (frontmatter, sections, memory lines; format-1 header migration); untouched lines must survive byte for byte |
src/openmymemo/vault.py | The vault: sync (editor edits → meta/history), add/update/archive/restore/rollback, settings, locking; documents (type: document files, product-design.md §23): one doc:<slug> memory each, kept out of memories() and topics() so sync, tidy-up, search by meaning and dedupe never see them; documents(), add_document, update_document |
src/openmymemo/topics.py | The 14 default topics, groups, classification keywords |
docs/MEMORY_FORMAT.md | The folder format; change it before changing what the code reads or writes |
docs/distribution.md, llms-install.md, glama.json | Where OpenMyMemo is published and listed, the shared listing details, and the setup guide AI agents such as Cline follow; glama.json names who may claim the Glama listing |
src/openmymemo/textutil.py | Similarity, search scoring, rule-based classification |
src/openmymemo/secrets.py | Secret patterns for the Memory safety score (product-design.md §18.2): keys and tokens, passwords, payment cards (Luhn), US SSNs and Chinese resident IDs (check character) in memories outside private topics; reports memory id and kind, never the matched text |
src/openmymemo/score.py | The Memory safety score (product-design.md §18.2): five areas by rules (secrets.py, connector access, stale changes, upkeep, a recent commit whose tree holds every memory file, or a full export (transfer.is_full_export), as backup), dismissals and the last full export in .openmymemo/score.json, one line a day in score.jsonl; issues name memory or app ids and never carry memory text. /api/score, /api/score/dismiss, openmymemo status |
src/openmymemo/overview.py | What the panel's Home page shows (product-design.md §18.1), served at /api/overview: memories per group, 30-day growth, sources (you, apps, imports), private as a count, each app's last use and 7-day reads/writes, what waits, reads and writes per day, the safety score and its top three issues; never memory text |
src/openmymemo/brief.py | The daily brief (product-design.md §18.3) for one local calendar day, by rules: changes applied per app (private ones only counted), what waits, reads per app with the topics read most, the safety change from score.jsonl, one long-unchanged non-private memory to resurface; quiet when nothing happened; counts_line is the notification text (counts and app names, no memory text). /api/brief, openmymemo brief |
src/openmymemo/notices.py | Which notifications are due (product-design.md §18.4) for the desktop app to show: yesterday's brief (from the set time, skipped when quiet), changes waiting more than 3 days (once a week), new secret-pattern or private-access safety issues (once each; the first look only notes what's there). .openmymemo/notify.json remembers what went out. Counts and app names only. /api/notices, /api/notices/sent |
src/openmymemo/feedback.py | Send feedback (product-design.md §21): the GitHub issue form and email links with the OpenMyMemo version and system filled in, never anything from the folder; /api/feedback, openmymemo feedback, FeedbackDialog.vue |
src/openmymemo/connectors.py | Connector permissions, tokens, revocation tombstones |
src/openmymemo/clients.py | The catalog of AI apps (product-design.md §15.1): detection, status, one-click connect and disconnect by editing an app's MCP config (JSON or Codex TOML) or running its mcp add, and the manual snippets |
src/openmymemo/appfiles.py | Writing other apps' files (their MCP configs, Codex's hooks.json): atomic, permissions kept from the first byte, through symlinks, with a one-time <file>.openmymemo-backup |
src/openmymemo/proposals.py | Proposal lifecycle, dedupe, auto-apply and undo |
src/openmymemo/context.py | Compiles what an AI sees (documents first and whole, within half the budget when there are memories too), MEMORY.md, log.md, search (search_documents for documents, by their words) |
src/openmymemo/transfer.py | Import (ChatGPT / Claude / Markdown / JSON, and other apps' memory files) and export |
src/openmymemo/memfiles.py | Finds other AI apps' memory and instructions files (CLAUDE.md, AGENTS.md, GEMINI.md, rules, memory.md) for import: known names and places only, never a whole tree, nothing else read |
src/openmymemo/mcp_server.py | MCP tools (mcp SDK 2.x MCPServer); relayed= builds the OpenMyMemo Cloud variant (queue wording only with cloud.queue_enabled(); never the local ASK_NOTE) |
src/openmymemo/ask.py | Asking in the app (product-design.md §22): whether memory_propose asks the person about a change that waits (protected or approval only, never private or sensitive, never relayed, per-app ask), and how: an elicitation/create request during the call up to protocol 2025-11-25, an input_required result with an SDK-sealed request_state from 2026-07-28; the question's wording. Proposals.asked / answered record it; an accept applies as the app's change, into the digest |
src/openmymemo/web.py | Panel API, /mcp and /cloud/callback combined into one ASGI app; panel_server() is the uvicorn server that starts OpenMyMemo Cloud once its port is bound |
src/openmymemo/cloud.py | OpenMyMemo Cloud (product-design.md §16): the per-computer link (cloud.json, never in the folder), linking with PKCE and a loopback callback, and the bridge: one outbound WebSocket that answers relayed tool calls as cloud-<slug> connectors and files the offline queue (HPKE-sealed proposals) through proposals.py, and carries MyMemo Memory sync's frames for sync.py when the person turned it on. The wire format is pinned by tests/fixtures/cloud/ (the MyMemo service tests against the same files); tests/cloud_fake.py is a fake service built from it |
src/openmymemo/sync.py | MyMemo Memory sync (product-design.md §20), off until the person turns it on: the per-computer sync.json next to cloud.json (consent, picked private topics, cursors and hashes of what was sent; never memory text, never in the folder), building sync_push frames from the folder (private topics only when picked; a tombstone when a memory turns private), filing sync_ops from MyMemo (its AI's and web apps' proposals through proposals.py as cloud-* connectors, the person's own MyMemo edits as actor: user edits, the first merge's memories as cloud-mymemo proposals after the preview, which never expire: MyMemo keeps a memory that waits until the person decides it) and reporting their outcomes, later decisions included. The wire format is pinned by tests/fixtures/cloud/sync/ |
src/openmymemo/assistant/ | OpenMyMemo Assistant (product-design.md §17), off by default: settings.py (per-computer assistant.json next to cloud.json, Keychain on macOS, OPENMYMEMO_ASSISTANT_* overrides, monthly usage), client.py (OpenAI-compatible chat or Anthropic Messages over httpx2, and task for MyMemo's hosted model, which takes only a fixed task name and the material; TRANSPORT is the test hook), schema.py (checks the model's JSON), guard.py (what may be sent: read scope, private topics, and a search of everything outgoing for withheld text), core.py (Assistant: available(feature), json_call(...) that returns None instead of raising, propose(...) as connector assistant; privacy and spending rules are enforced here, not in callers), features.py (model-powered import splitting with the rule parser as cross-check, and sort suggestions), tidy.py (Tidy up, §17.6: near-duplicate candidates from textutil.similarity, chunked calls with a per-run call limit and a dry-run estimate, one batch_id of proposals, "not twice" by a fingerprint in idempotency_key, and the per-computer schedule that Scheduler runs inside the panel's process), checks.py (the second look at an app's proposal within check_budget, called by Proposals.propose(check=True); only ever more cautious), chats.py (reading ChatGPT/Claude exports and Claude Code/Codex transcripts in known shapes, chunking newest first with private text left out, the cost estimate, extraction with evidence, ChatImport for the import preview), tell.py (Tell OpenMyMemo, §19: preview turns what the person typed into add/update/archive changes against the memories search and the profile topics give, checks the answer, falls back to rules; apply saves what the person kept as their own edits, version-checked; /api/tell/preview, /api/tell/apply), session.py (openmymemo session-end --hook: per-app consent in assistant.json, a detached worker that files the Assistant's proposals from a transcript), mymemo.py (MyMemo's hosted model, §17.8, offered to everyone unless OPENMYMEMO_ASSISTANT_MYMEMO=0 turns it off on a computer where the settings don't already use it (settings.mymemo_offered); the plan's monthly use in dollars from /models (usage.cost_usd, cost_usd_included: cost_text, the meter's usage_bar/used_share) and a limited-time offer (cost_usd_regular_included, offer_ends_at: offer_text, includes_text), nothing hard-coded about price, allowance or offer: token exchange of the OpenMyMemo Cloud device token or PKCE sign-in through cloud.py's Linker and /cloud/callback, tokens refreshed with rotation, every request bound to the sign-in (generation and service) it started with, the service's 402/429/503 kept as pauses that send features to their rules, sign-out with revoke; tests/assistant_fake.py is its fake service, from tests/fixtures/cloud/assistant/). Every feature keeps its rule-based path as the fallback |
src/openmymemo/recall/ | Recall (product-design.md §17.4): hybrid.py (search: keyword search fused with vectors by reciprocal rank fusion, exact matches first, the Assistant's query expansion and rerank with model=True; recall for openmymemo recall; similar for checking proposals and tidy-up), index.py (the plain-file cache in .openmymemo/index/: incremental by content hash, one model per index, flock + atomic replace), embed.py (the Embedder interface: the local ONNX model, or the Assistant endpoint's /embeddings, which never gets private topics from a remote model), local.py (the list of local models, SHA-256 pins in model_pins.json, downloads into the per-computer folder, the optional openmymemo[semantic] runtime), semantic.py (the switch: status, on, off). With no index and no model help, search returns exactly context.search's results |
src/openmymemo/cli.py | Typer CLI |
src/openmymemo/doctor.py | openmymemo doctor: setup checks (folder, uv, panel port, each app's config and which folder it uses, OpenMyMemo Cloud) that print paths and counts, never memories, and never write: it parses the topic files itself instead of calling Vault.sync() |
src/openmymemo/codex.py | Codex session hook: merges it into ~/.codex/hooks.json and takes it out again (Codex doesn't run plugin hooks) |
web/ | Vue 3 + Tailwind v4 panel source; all copy lives in web/src/i18n.ts |
web/src/components/ConnectApps.vue | The app list with Connect / Disconnect / Set up, used by the Connectors page and the first-run guide; ConnectNote shows what's left to do, ConnectorPanel a connected app's permissions, CloudRow the OpenMyMemo Cloud row (sign-in, status, setup for ChatGPT and claude.ai, its web apps) |
web/src/views/DocumentsView.vue | The Documents page (product-design.md §23.4): list, read, edit with a live diff and a version check (409 when it changed elsewhere), private switch, rename, versions, archive and restore; diff.ts is the line diff, and DocumentDiff shows it here and on the Changes page |
web/src/desktop.ts | The Mac layout switch (?desktop=1), the shell's commands and shortcuts; MemoriesDesktopView, MemoryInspector, DesktopSidebar, DesktopToolbar are its screens; memorySaves.ts queues the inspector's saves per memory. PageHeader becomes the window toolbar there |
desktop/ | Mac and Windows app (Tauri 2, product-design.md §14, §15.2): src-tauri/src/core.rs installs the core (non-editable) and finds or starts the server for the folder, launcher.rs writes the openmymemo launcher AI apps run on a Mac, winlauncher.rs installs desktop/launcher (a small program, since Windows has no sh) as that command on Windows, install.rs moves the app from the disk image to Applications, main.rs has the window, menus, menu-bar item, notifications, settings and link handling; tauri.windows.conf.json makes the Windows installer (NSIS, per user); scripts/stage_desktop.py stages the core, uv and on Windows the launcher before each build |
.claude-plugin/ | Claude Code plugin manifest and marketplace (the repo root is the plugin) |
plugin.json, mcp.json, .agents/plugins/ | Codex plugin (Agent Plugins format) and its marketplace |
skills/ | Skills shared by the Claude Code and Codex plugins |
integrations/hooks/ | Session-start and session-end hook scripts shared by Claude Code (plugin hooks SessionStart and SessionEnd in integrations/claude-code/hooks/hooks.json) and Codex (user-level SessionStart and Stop hooks installed by setup codex; integrations/codex/setup.sh runs it from the installed plugin). The session-end one prints nothing, always exits 0 and hands the work to a background process |
integrations/claude-desktop/ | .mcpb manifest; build with scripts/build_mcpb.py |
integrations/icons/, web/public/logo.png, .claude-plugin/icon.png | The OpenMyMemo logo at each size (black on a white rounded square), rendered from integrations/icons/logo.svg by scripts/make_icons.py; .claude-plugin/icon.png is the listing icon in Claude's plugin directory, which takes it only from the first submission |
Conventions
- Everything user-facing is English: docs, panel copy, CLI output, MCP tool names and descriptions. Chinese belongs only in data handling (classifier keywords, parsing pasted pages, test fixtures), because users' memories may be in Chinese.
- File formats (
memories/*.md,connectors.yaml,.openmymemo/*) are a public contract specified in docs/MEMORY_FORMAT.md: change the spec first and keep reading older files. - Topic slugs are the file names; titles come from the files.
- All writes go through
Vaultmethods, which lock, re-read from disk, write atomically and update meta and history. - The mcp SDK is 2.x:
from mcp.server.mcpserver import MCPServer, Context; static resources can't take aContext. - Plugins run
uv runfrom the installed copy of this repo, so anything the package needs at runtime must be inside the repo (and inside the.mcpbstaging list inscripts/build_mcpb.py). Keep the version inpyproject.toml,src/openmymemo/__init__.py, bothplugin.jsonfiles,server.json(the MCP Registry entry, both of its versions), the.mcpbmanifest,web/package.json, the Mac app (desktop/package.json,desktop/src-tauri/tauri.conf.json,desktop/src-tauri/Cargo.toml) and the version line at the top ofREADME.mdin sync (tests/test_packaging.pychecks), and runuv lockafter a bump. - CI (
.github/workflows/ci.yml) is three jobs, one per runner: the Python tests and lint on Linux (Python 3.11 and 3.13), the Python tests and the Mac app's tests on macOS (Python 3.12), and the panel (its tests, and that the committed build matchesweb/) with packing the.mcpb. Themainruleset requires these three checks by name, so renaming a job means updating it. To release: bump the version, merge, then push a tagv<version>;.github/workflows/release.ymlpublishes the.mcpb, wheel and sdist as a GitHub release, the wheel and sdist on PyPI (trusted publishing, environmentpypi), and thenserver.jsonon the MCP Registry (GitHub OIDC; the registry checks themcp-name:line inREADME.md, so keep it). - Don't add a root
.mcp.json: Claude Code would treat it as project config for everyone working on this repo. The Claude Code MCP server is declared inline in.claude-plugin/plugin.json. - Commit messages are imperative English; one topic per PR.
- OpenMyMemo Cloud (called MyMemo Cloud until its launch on 2026-10-19; the id stays
mymemo-cloud) is listed by default and does nothing until the person signs in; it can be hidden in Settings (oropenmymemo cloud settings --hide) and is always listed while linked (product-design.md §16.11).settings.jsonis format 2: an older file'sshowis ignored, because 0.2.8 and older wrote"show": falseas their default. Only the live relay is launched:cloud.FEATURESstays empty, sohello()doesn't advertisequeue, and nothing user-facing may promise the offline queue until both sides turn it on;cloud.queue_enabled()(the same constant) keepswelcome.queuedand a plan'squeueout of the panel and the CLI, leavesitemframes unfiled and unacknowledged, and the queue wording out of the relayed tool descriptors (tests/fixtures/cloud/bridge/hello.json). It readsOPENMYMEMO_CLOUD_URL(the service; overrides the one chosen in Settings, defaulthttps://api.mymemo.ai) andOPENMYMEMO_CLOUD_DIR(the folder ofcloud.jsonandsettings.json).tests/conftest.pygives every test its own HOME, cloud folder and a local URL; set both yourself before tryingopenmymemo cloud …or the panel's sign-in by hand, so nothing touches your real link or MyMemo. - MyMemo Memory sync (product-design.md §20,
sync.py) is off until the person turns it on, a consent of its own on a linked computer;hello()listssynconly then (read fromsync.jsonat each connection, socloud.FEATURESandqueue_enabled()are unchanged), and turning it on or off makes the bridge say hello again. Its state issync.jsonnext tocloud.json, bound to the link's device, and never holds memory text. What comes from MyMemo is filed throughproposals.pyascloud-*connectors, except the person's own edits on MyMemo's memory page (from: "user"), which are applied as theirs after a version check. Tests drive it withFakeSyncintests/cloud_fake.py; the frames are pinned bytests/fixtures/cloud/sync/, byte-identical in the MyMemo service (tests/test_sync.pychecks the folder's digest). - The OpenMyMemo Assistant is off until turned on in Settings → Assistant or with
openmymemo assistant set. Code that uses it buildsAssistant.from_settings(vault), checksavailable(feature), callsjson_callwith material inDatablocks (never mixed into the instructions; memories throughassistant.memory_data(...), topic lists fromassistant.topic_choices(), both of which leave out what may not be sent;guard.pyrefuses anything else that would leak) and falls back to the rule path on None; it writes only throughAssistant.propose. Tests use a fake endpoint (openmymemo.assistant.client.TRANSPORT, anhttpx2.MockTransport);tests/conftest.pykeeps the Keychain and the shell'sOPENMYMEMO_ASSISTANT_*variables out. - Search by meaning is optional twice over: the local model is opt-in (Settings → Assistant → "Search by meaning on this computer",
openmymemo semantic on), downloaded on demand, never bundled;onnxruntimeandtokenizersare thesemanticextra, never default dependencies. A model is downloadable only once its files are pinned insrc/openmymemo/recall/model_pins.jsonbyscripts/pin_embedding_models.py. Tests never download:tests/recall/recall_harness.pyhas a deterministic stand-in embedder and replays recorded model answers;tests/recall/is the recall test set (hit@k must not fall belowbaseline.json). - The Mac app adds nothing to the panel's API: it is the same panel in another layout. New panel features go into the web screens; give the Mac layout its own version only where the design has one. A native command the panel calls must be listed in
desktop/src-tauri/build.rsand granted incapabilities/default.json, because the panel is a remote origin to Tauri.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

