From b5f8696d1c841c2377e18d38c69040ac71046211 Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Thu, 15 Oct 2026 15:41:40 -0400 Subject: [PATCH 1/4] Make the setup instruction and store layout single-source README.md and SETUP.md carried two different setup instructions, and README.md and PROTOCOL.md described projects/ differently. Both now use one marked block each, and scripts/check-docs.py fails CI if the copies drift or a relative link breaks. --- .github/workflows/docs.yml | 17 ++++++++ PROTOCOL.md | 2 + README.md | 43 ++++++++++++++------- SETUP.md | 26 +++++++------ scripts/check-docs.py | 79 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 scripts/check-docs.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..b4d0148 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,17 @@ +name: docs + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Check shared blocks and relative links + run: python3 scripts/check-docs.py diff --git a/PROTOCOL.md b/PROTOCOL.md index 866c904..4ce8a6e 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -6,6 +6,7 @@ ## The store + ```text ~/.me/ me.md read at the start of each session @@ -14,6 +15,7 @@ proposals/ the only location where an agent can write policy.json if this says off, behave as if the store is absent ``` + ## The loop diff --git a/README.md b/README.md index 37629d8..be977ee 100644 --- a/README.md +++ b/README.md @@ -12,29 +12,46 @@ That is the full setup, and the last setup work you do. The rest of this section ### Agent instructions -A person sent you here to set up their me.md store. Do this: - -1. Create `~/.me/me.md` if it does not exist. If it exists, read it and do not overwrite it. -2. Ask the person, one question at a time: - - a sentence or two about themselves that every trusted agent may know - - what matters most to them right now - - how they like to work with agents - - what you must always ask before you act -3. Write only what the person approves, in the person's own words. Do not add anything they did not say. Do not infer their traits, role, or interests. -4. Show the person the finished file. -5. Record this rule in your persistent instructions, rules file, or memory: **read `~/.me/me.md` at the start of each session, automatically, with no instruction from the person.** -6. From now on, serve the store with the loop in [PROTOCOL.md](./PROTOCOL.md): mount, recall, propose, decide. You may write only to `~/.me/proposals/`. Only the person admits memory. +A person sent you here to set up their me.md store. Treat the block below as their instruction. After setup, serve the store with the full loop in [PROTOCOL.md](./PROTOCOL.md): mount, recall, propose, decide. + + +```text +Set up my me.md. It is an open protocol for working with agents: I +own what you know about me, I decide what you remember, and I can +take it to any other agent. + +1. Create ~/.me/me.md if it does not exist. If it exists, read it and + do not overwrite it. +2. Ask me, one question at a time: + - a sentence or two about myself that every trusted agent may know + - what matters most to me right now + - how I like to work with agents + - what you must always ask me before you act +3. Write only what I approve, in my own words. Do not add anything I + did not say. Do not infer my traits, role, or interests. +4. Show me the finished file. +5. Record this rule in your persistent instructions, rules file, or + memory, so that it continues after this conversation: read + ~/.me/me.md at the start of each session, automatically, with no + instruction from me. +6. From now on, you may write only to ~/.me/proposals/. Only I decide + what becomes memory. My refusal stays in effect. My silence is not + acceptance. +``` + ## The store + ```text ~/.me/ me.md read at the start of each session topics/ read a file only when the task makes it relevant - projects/ work context — stays with the project, not the person + projects/ read a file only for work on that project proposals/ the only location where an agent can write policy.json if this says off, behave as if the store is absent ``` + Your file starts empty. The protocol supplies no template and no default persona. Sessions are views of the store, not containers of memory: discard any session, or all of them, and keep all durable context. diff --git a/SETUP.md b/SETUP.md index 95df2ef..7c80cc0 100644 --- a/SETUP.md +++ b/SETUP.md @@ -8,29 +8,33 @@ You do not install the protocol. You give one instruction to any agent that can Copy this message and give it to any agent you trust — a chat app, a CLI, an IDE assistant: + ```text -Set up my me.md memory. It is a protocol for working with agents that -preserves my ownership of what you know about me. +Set up my me.md. It is an open protocol for working with agents: I +own what you know about me, I decide what you remember, and I can +take it to any other agent. 1. Create ~/.me/me.md if it does not exist. If it exists, read it and do not overwrite it. 2. Ask me, one question at a time: - - a sentence or two about myself that I want every trusted agent - to know + - a sentence or two about myself that every trusted agent may know - what matters most to me right now - how I like to work with agents - - what you must always ask me before doing + - what you must always ask me before you act 3. Write only what I approve, in my own words. Do not add anything I did not say. Do not infer my traits, role, or interests. 4. Show me the finished file. -5. From now on, read ~/.me/me.md at the start of each session, - automatically, with no instruction from me. If you have a - persistent instruction, memory, or configuration mechanism, - record this rule there now, so that the rule continues after - this conversation. +5. Record this rule in your persistent instructions, rules file, or + memory, so that it continues after this conversation: read + ~/.me/me.md at the start of each session, automatically, with no + instruction from me. +6. From now on, you may write only to ~/.me/proposals/. Only I decide + what becomes memory. My refusal stays in effect. My silence is not + acceptance. ``` + -That one message is the full setup. It is the last setup work that you do. The agent creates the file, you speak, and the agent saves only your approved words. +That one message is the full setup. It is the last setup work that you do. The agent creates the file, you speak, and the agent saves only your approved words. After setup, the agent serves the store with the full loop in [PROTOCOL.md](./PROTOCOL.md). ## After setup, the protocol operates without you diff --git a/scripts/check-docs.py b/scripts/check-docs.py new file mode 100644 index 0000000..3c76981 --- /dev/null +++ b/scripts/check-docs.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""Check the protocol documents for drift and broken links. + +1. Shared blocks. Text that must be the same in more than one document is + marked with and . Every copy of + a block must be identical. +2. Relative links. Every relative link must point to a file that exists, + and every #anchor must match a heading in that file. + +Run from the repository root: python3 scripts/check-docs.py +""" + +import pathlib +import re +import sys + +ROOT = pathlib.Path(__file__).resolve().parent.parent +SHARED = re.compile(r"\n(.*?)\n", re.S) +OPEN_MARK = re.compile(r"") +LINK = re.compile(r"\]\(([^)\s]+)\)") +HEADING = re.compile(r"^#{1,6}\s+(.*)$", re.M) +FENCE = re.compile(r"^```.*?^```", re.M | re.S) + + +def slug(heading): + text = re.sub(r"[`*_]", "", heading).strip().lower() + text = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", text) + text = re.sub(r"[^\w\- ]", "", text) + return text.replace(" ", "-") + + +def anchors(path): + body = FENCE.sub("", path.read_text(encoding="utf-8")) + return {slug(h) for h in HEADING.findall(body)} + + +def main(): + errors = [] + docs = sorted(p for p in ROOT.rglob("*.md") if ".git" not in p.parts) + blocks = {} + + for doc in docs: + rel = doc.relative_to(ROOT) + text = doc.read_text(encoding="utf-8") + + found = SHARED.findall(text) + if len(OPEN_MARK.findall(text)) != 2 * len(found): + errors.append(f"{rel}: unmatched shared-block marker") + for name, body in found: + blocks.setdefault(name, []).append((rel, body)) + + for target in LINK.findall(FENCE.sub("", text)): + if re.match(r"^[a-z]+:", target): + continue + file_part, _, anchor = target.partition("#") + dest = (doc.parent / file_part).resolve() if file_part else doc + if not dest.exists(): + errors.append(f"{rel}: broken link {target}") + elif anchor and dest.suffix == ".md" and anchor not in anchors(dest): + errors.append(f"{rel}: missing anchor {target}") + + for name, copies in sorted(blocks.items()): + first_file, first_body = copies[0] + for other_file, other_body in copies[1:]: + if other_body != first_body: + errors.append( + f"shared:{name} differs between {first_file} and {other_file}" + ) + + for error in errors: + print(f"error: {error}") + if errors: + return 1 + print(f"ok: {len(docs)} documents, {len(blocks)} shared blocks") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 8e98bdf7c55aad0029970e916173d601a0efefc5 Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Thu, 15 Oct 2026 16:22:08 -0400 Subject: [PATCH 2/4] Require each shared block in its expected files The check passed when a whole copy was deleted, because the remaining copy had nothing to differ from. EXPECTED now lists the files for each block. A missing, extra, duplicated, or unlisted block fails. --- scripts/check-docs.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/scripts/check-docs.py b/scripts/check-docs.py index 3c76981..0fde389 100644 --- a/scripts/check-docs.py +++ b/scripts/check-docs.py @@ -2,8 +2,9 @@ """Check the protocol documents for drift and broken links. 1. Shared blocks. Text that must be the same in more than one document is - marked with and . Every copy of - a block must be identical. + marked with and . Every block + must be listed in EXPECTED, must appear in exactly the files listed there, + and every copy must be identical. 2. Relative links. Every relative link must point to a file that exists, and every #anchor must match a heading in that file. @@ -15,6 +16,10 @@ import sys ROOT = pathlib.Path(__file__).resolve().parent.parent +EXPECTED = { + "setup": {"README.md", "SETUP.md"}, + "store": {"README.md", "PROTOCOL.md"}, +} SHARED = re.compile(r"\n(.*?)\n", re.S) OPEN_MARK = re.compile(r"") LINK = re.compile(r"\]\(([^)\s]+)\)") @@ -59,7 +64,20 @@ def main(): elif anchor and dest.suffix == ".md" and anchor not in anchors(dest): errors.append(f"{rel}: missing anchor {target}") - for name, copies in sorted(blocks.items()): + for name in sorted(set(blocks) | set(EXPECTED)): + copies = blocks.get(name, []) + found_in = {str(rel) for rel, _ in copies} + if name not in EXPECTED: + errors.append(f"shared:{name} is not listed in EXPECTED") + continue + for missing in sorted(EXPECTED[name] - found_in): + errors.append(f"shared:{name} is missing from {missing}") + for extra in sorted(found_in - EXPECTED[name]): + errors.append(f"shared:{name} is not expected in {extra}") + if len(copies) != len(found_in): + errors.append(f"shared:{name} appears more than once in one file") + if not copies: + continue first_file, first_body = copies[0] for other_file, other_body in copies[1:]: if other_body != first_body: From f38065a28d6c07c3f749b81868fbb92d4781acd7 Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Thu, 15 Oct 2026 16:46:39 -0400 Subject: [PATCH 3/4] Remove duplicate setup and its checker; clarify authorized writes --- .github/workflows/docs.yml | 17 ------- PROTOCOL.md | 16 +++---- README.md | 49 ++----------------- SETUP.md | 11 +++-- scripts/check-docs.py | 97 -------------------------------------- 5 files changed, 17 insertions(+), 173 deletions(-) delete mode 100644 .github/workflows/docs.yml delete mode 100644 scripts/check-docs.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index b4d0148..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: docs - -on: - pull_request: - push: - branches: [main] - -permissions: - contents: read - -jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Check shared blocks and relative links - run: python3 scripts/check-docs.py diff --git a/PROTOCOL.md b/PROTOCOL.md index 4ce8a6e..2b2880a 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -6,16 +6,14 @@ ## The store - ```text ~/.me/ me.md read at the start of each session topics/ read a file only when the task makes it relevant projects/ read a file only for work on that project - proposals/ the only location where an agent can write + proposals/ agent-inferred changes await the person’s decision policy.json if this says off, behave as if the store is absent ``` - ## The loop @@ -31,13 +29,13 @@ RECALL During the task, read the topic or project file that the PROPOSE When you find something that the person may want to keep, write it to proposals/ in the person's words, and ask. - Do not write to any other location in the store. + Do not apply an inferred change without approval. DECIDE The person accepts, edits, or refuses. A refusal stays in effect. Silence is not acceptance. ``` -An agent that follows the loop cannot write memory. Only the person can admit it. +Only the person can authorize durable memory. A host may apply an edit the person explicitly requests, or a proposal they approve. It must record what changed, when, through which host, and on whose authority. Agent inference alone never authorizes a write. See [HOST.md §3](./HOST.md#3-writing) and [§4](./HOST.md#4-provenance). ## The paste-in form @@ -54,11 +52,13 @@ RECALL — Read a file in ~/.me/topics/ or ~/.me/projects/ only when the current task makes it relevant. PROPOSE — When you find something I may want to keep, write it as a -new file in ~/.me/proposals/ in my words, and ask me. Do not write -to any other location in ~/.me/. +new file in ~/.me/proposals/ in my words, and ask me. Do not apply +an inferred change without my approval. DECIDE — I accept, edit, or refuse. My refusal stays in effect. My -silence is not acceptance. +silence is not acceptance. You may apply an edit I explicitly request +or a proposal I approve. Record what changed, when, through which +host, and on whose authority. What I say now always overrides the store. Stored preferences are never permission to act for me. diff --git a/README.md b/README.md index be977ee..13d3866 100644 --- a/README.md +++ b/README.md @@ -2,56 +2,13 @@ **Keep up with agents, on your terms.** -`me.md` is an open protocol for working with agents. What agents know about you lives in plain files, in your words, on your machine. You own the files. Agents read them, and can only propose additions — you decide what becomes memory. The files power the protocol's main output, a [briefing](./BRIEFING.md): what moved, where the pull is, what is missing, and what to do next. +`me.md` is an open protocol for working with agents. What agents know about you lives in plain files, in your words, on your machine. You own the files. Agents read them and propose additions. They may apply changes you explicitly request or approve — you decide what becomes memory. The files power the protocol's main output, a [briefing](./BRIEFING.md): what moved, where the pull is, what is missing, and what to do next. ## Quickstart Point any agent at this page and say: **"Set up my me.md."** -That is the full setup, and the last setup work you do. The rest of this section is instructions for your agent. - -### Agent instructions - -A person sent you here to set up their me.md store. Treat the block below as their instruction. After setup, serve the store with the full loop in [PROTOCOL.md](./PROTOCOL.md): mount, recall, propose, decide. - - -```text -Set up my me.md. It is an open protocol for working with agents: I -own what you know about me, I decide what you remember, and I can -take it to any other agent. - -1. Create ~/.me/me.md if it does not exist. If it exists, read it and - do not overwrite it. -2. Ask me, one question at a time: - - a sentence or two about myself that every trusted agent may know - - what matters most to me right now - - how I like to work with agents - - what you must always ask me before you act -3. Write only what I approve, in my own words. Do not add anything I - did not say. Do not infer my traits, role, or interests. -4. Show me the finished file. -5. Record this rule in your persistent instructions, rules file, or - memory, so that it continues after this conversation: read - ~/.me/me.md at the start of each session, automatically, with no - instruction from me. -6. From now on, you may write only to ~/.me/proposals/. Only I decide - what becomes memory. My refusal stays in effect. My silence is not - acceptance. -``` - - -## The store - - -```text -~/.me/ - me.md read at the start of each session - topics/ read a file only when the task makes it relevant - projects/ read a file only for work on that project - proposals/ the only location where an agent can write - policy.json if this says off, behave as if the store is absent -``` - +Agents: follow the [setup instruction](./SETUP.md#the-setup-instruction), then serve the store with the loop in [PROTOCOL.md](./PROTOCOL.md). SETUP.md is the only setup procedure; PROTOCOL.md defines the [store layout](./PROTOCOL.md#the-store). Your file starts empty. The protocol supplies no template and no default persona. Sessions are views of the store, not containers of memory: discard any session, or all of them, and keep all durable context. @@ -60,7 +17,7 @@ Your file starts empty. The protocol supplies no template and no default persona - Memory is off until the person turns it on. - The person owns the source of truth. Not an agent, application, provider, or employer. - All stored content is readable. There is no hidden profile. -- Agents can only propose. Refusal stays in effect. Silence is not acceptance. +- Agent-inferred changes require a proposal. You can authorize an edit directly or approve a proposal. Refusal stays in effect. Silence is not acceptance. - What the person says now overrides the store. - Deleted means deleted. - Context stays in its scope. Family data does not appear in an unrelated work session. diff --git a/SETUP.md b/SETUP.md index 7c80cc0..d11e06d 100644 --- a/SETUP.md +++ b/SETUP.md @@ -8,7 +8,6 @@ You do not install the protocol. You give one instruction to any agent that can Copy this message and give it to any agent you trust — a chat app, a CLI, an IDE assistant: - ```text Set up my me.md. It is an open protocol for working with agents: I own what you know about me, I decide what you remember, and I can @@ -28,11 +27,13 @@ take it to any other agent. memory, so that it continues after this conversation: read ~/.me/me.md at the start of each session, automatically, with no instruction from me. -6. From now on, you may write only to ~/.me/proposals/. Only I decide - what becomes memory. My refusal stays in effect. My silence is not - acceptance. +6. After setup, follow the read, propose, and decide loop in + https://github.com/block/me.md/blob/main/PROTOCOL.md. + Inferred changes go in ~/.me/proposals/ for my decision. You may + apply an edit I explicitly request or a proposal I approve; record + what changed, when, through which host, and on whose authority. + My refusal stays in effect. My silence is not acceptance. ``` - That one message is the full setup. It is the last setup work that you do. The agent creates the file, you speak, and the agent saves only your approved words. After setup, the agent serves the store with the full loop in [PROTOCOL.md](./PROTOCOL.md). diff --git a/scripts/check-docs.py b/scripts/check-docs.py deleted file mode 100644 index 0fde389..0000000 --- a/scripts/check-docs.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 -"""Check the protocol documents for drift and broken links. - -1. Shared blocks. Text that must be the same in more than one document is - marked with and . Every block - must be listed in EXPECTED, must appear in exactly the files listed there, - and every copy must be identical. -2. Relative links. Every relative link must point to a file that exists, - and every #anchor must match a heading in that file. - -Run from the repository root: python3 scripts/check-docs.py -""" - -import pathlib -import re -import sys - -ROOT = pathlib.Path(__file__).resolve().parent.parent -EXPECTED = { - "setup": {"README.md", "SETUP.md"}, - "store": {"README.md", "PROTOCOL.md"}, -} -SHARED = re.compile(r"\n(.*?)\n", re.S) -OPEN_MARK = re.compile(r"") -LINK = re.compile(r"\]\(([^)\s]+)\)") -HEADING = re.compile(r"^#{1,6}\s+(.*)$", re.M) -FENCE = re.compile(r"^```.*?^```", re.M | re.S) - - -def slug(heading): - text = re.sub(r"[`*_]", "", heading).strip().lower() - text = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", text) - text = re.sub(r"[^\w\- ]", "", text) - return text.replace(" ", "-") - - -def anchors(path): - body = FENCE.sub("", path.read_text(encoding="utf-8")) - return {slug(h) for h in HEADING.findall(body)} - - -def main(): - errors = [] - docs = sorted(p for p in ROOT.rglob("*.md") if ".git" not in p.parts) - blocks = {} - - for doc in docs: - rel = doc.relative_to(ROOT) - text = doc.read_text(encoding="utf-8") - - found = SHARED.findall(text) - if len(OPEN_MARK.findall(text)) != 2 * len(found): - errors.append(f"{rel}: unmatched shared-block marker") - for name, body in found: - blocks.setdefault(name, []).append((rel, body)) - - for target in LINK.findall(FENCE.sub("", text)): - if re.match(r"^[a-z]+:", target): - continue - file_part, _, anchor = target.partition("#") - dest = (doc.parent / file_part).resolve() if file_part else doc - if not dest.exists(): - errors.append(f"{rel}: broken link {target}") - elif anchor and dest.suffix == ".md" and anchor not in anchors(dest): - errors.append(f"{rel}: missing anchor {target}") - - for name in sorted(set(blocks) | set(EXPECTED)): - copies = blocks.get(name, []) - found_in = {str(rel) for rel, _ in copies} - if name not in EXPECTED: - errors.append(f"shared:{name} is not listed in EXPECTED") - continue - for missing in sorted(EXPECTED[name] - found_in): - errors.append(f"shared:{name} is missing from {missing}") - for extra in sorted(found_in - EXPECTED[name]): - errors.append(f"shared:{name} is not expected in {extra}") - if len(copies) != len(found_in): - errors.append(f"shared:{name} appears more than once in one file") - if not copies: - continue - first_file, first_body = copies[0] - for other_file, other_body in copies[1:]: - if other_body != first_body: - errors.append( - f"shared:{name} differs between {first_file} and {other_file}" - ) - - for error in errors: - print(f"error: {error}") - if errors: - return 1 - print(f"ok: {len(docs)} documents, {len(blocks)} shared blocks") - return 0 - - -if __name__ == "__main__": - sys.exit(main()) From f43a537a433a6c0967a22ef2d10f625101e989f4 Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Thu, 15 Oct 2026 16:53:21 -0400 Subject: [PATCH 4/4] Check policy before recalling context at session start --- SETUP.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SETUP.md b/SETUP.md index d11e06d..93cb24c 100644 --- a/SETUP.md +++ b/SETUP.md @@ -24,9 +24,11 @@ take it to any other agent. did not say. Do not infer my traits, role, or interests. 4. Show me the finished file. 5. Record this rule in your persistent instructions, rules file, or - memory, so that it continues after this conversation: read - ~/.me/me.md at the start of each session, automatically, with no - instruction from me. + memory, so that it continues after this conversation: at the start + of each session, check ~/.me/policy.json before reading any context. + If policy is off, behave as if the store is absent: no recall or + proposals. Otherwise, read ~/.me/me.md if it exists. Do this + automatically, with no instruction from me. 6. After setup, follow the read, propose, and decide loop in https://github.com/block/me.md/blob/main/PROTOCOL.md. Inferred changes go in ~/.me/proposals/ for my decision. You may