| name | remargin |
| description | Document access layer and structured commenting system for markdown files. Use remargin MCP tools (ls, get, write, comment, ack, query) instead of Read/Edit/Write when working with markdown documents. Provides threaded multi-player comments, integrity checks, batch operations, sandbox staging, plan-based previews for every mutating op, and file access through MCP. |
| user-invocable | true |
Remargin
Remargin is a document access layer and structured commenting system for markdown files. It replaces direct filesystem access with MCP tools (and a mirrored CLI) that read, write, comment on, and query markdown documents while enforcing identity, signatures, comment-preservation, and a per-directory enforcement mode.
A realm is any directory tree containing a .remargin.yaml (discovered by walking up from cwd, like .git). Every .md inside a realm is managed. No per-file opt-in.
Trigger phrases: "remargin that", "let's discuss", "review this document", "comment on", "what comments are pending", "acknowledge", "any pending comments".
Slash commands (plugin-only)
When the remargin plugin is installed, these slash commands are available:
/remargin:process-file <path> — process a single managed markdown file. Trigger phrases: "process this file", "submit this file", "process ", "run remargin on this".
/remargin:process-sandbox-group <prompt-name> — process one sandbox group at a time. Trigger phrases: "process the group", "submit the group". Use when the user names a specific prompt group.
/remargin:process-sandbox — process every sandboxed file in the vault, one subagent per resolved prompt group (context isolation per group). Trigger phrases: "process the sandbox", "process sandboxed items in this vault", "submit the sandbox", "run the sandbox", "process everything I staged".
/remargin:process-folder <path> — process a folder driven by activity: first read the full activity delta across ALL identities to build awareness, then act only on items pending to this identity or open/unassigned. Groups by resolved system prompt and spawns one subagent per group. Does NOT touch sandbox markers. Trigger phrases: "process this folder", "process the folder ", "go through this folder", "what changed in this folder and handle my part".
/remargin:activity [path] — report what changed since the caller last acted on each managed .md under path. Read-only. Trigger phrases: "what's new", "what happened since I was last here", "what changed in this workspace", "any activity I missed", "anything for me".
/remargin:consolidate <path> — re-create a single managed markdown file so its body reflects everything in its comment threads (agreements, decisions, open issues, actionable items, memos). Distinct from process-file, which only replies to pending comments. Comments preserved by default; --delete-comments removes them after the rewrite. Authorized for humans; for agents only when the resolved system prompt permits it. Trigger phrases: "consolidate this", "consolidate the document", "consolidate ".
Routing rules:
- When the user names a single file, route to
/remargin:process-file.
- When the user names a specific prompt group, route to
/remargin:process-sandbox-group.
- When the user asks for the sandbox / staging area / "everything I staged" / similar without naming a group, route to
/remargin:process-sandbox (vault-wide, subagents per group).
- When the user names a folder (a directory, not a single file and not the sandbox / staging area), route to
/remargin:process-folder (activity-driven). Branch on the path itself, not its spelling: inspect whether the path is a directory rather than guessing from the name. If the path is ambiguous (file vs folder), resolve by inspecting the path.
- When the user asks what's new / what happened / what changed, route to
/remargin:activity.
- When the user asks to consolidate a document (in chat, or a comment containing
/remargin:consolidate), route to /remargin:consolidate — never reproduce its recreate logic inline.
- If the user gives no path and no sandbox cue, ask which they mean. Do not pick.
- Never bypass a slash command and reproduce its logic inline. The slash command is the canonical entry point; the skill is the router.
If you hit a permission denial, read this first
If a native tool (Read / Edit / Write / NotebookEdit / Bash)
is denied on a .md file or any file under a .remargin.yaml realm:
this is by design. Switch to the equivalent mcp__remargin__* op
(get, write, comment, delete, metadata, query, ...) and
proceed. Do NOT ask the user, do NOT propose alternatives, do NOT
surface the denial as a blocker. The skill exists precisely so this
question doesn't need to be asked.
EXCEPTION — if the denial cites a trusted_roots or deny_ops rule,
the user has declared this path off-limits. Surface the denial.
Always ask before unrestrict — never call
remargin claude unrestrict (or any other path that would reverse a
user-declared restriction) on your own initiative. That is the worst
failure mode of the permissions system. Wait for explicit consent
before any action that would clear the obstacle.
Compound-denial priority order
When a denial mentions multiple reasons, evaluate in this order and
stop at the first match:
trusted_roots or deny_ops rule named → surface the denial
to the user. Never auto-recover. Ask before unrestrict.
path escapes sandbox → configuration issue (insufficient
trusted_roots for the MCP). Surface to the user; do not retry.
- Otherwise (managed-file native-tool denial) → switch to the
equivalent
mcp__remargin__* op and proceed silently.
Symptom -> action map
| Symptom | Action |
|---|
Read denied on a .md file | mcp__remargin__get |
Edit / Write denied on a .md file | mcp__remargin__write (or comment if it's a thread reply) |
Bash(cat ...) / Bash(sed ...) / Bash(rm ...) denied | mcp__remargin__get / mcp__remargin__write / mcp__remargin__delete |
NotebookEdit denied on a .md file | mcp__remargin__write |
Op denied with trusted_roots rule | surface the denial; ask before unrestrict; do not call remargin claude unrestrict |
Op denied with deny_ops rule | surface the denial; user has explicitly disallowed this op on this path |
path escapes sandbox from MCP | configuration issue (insufficient trusted_roots); surface to user; do not retry |
Anti-patterns
- Anti-pattern: stopping on a permission denial and asking the user
"should I try a different tool?" — the answer is always "yes, use
remargin." The skill exists precisely so this question doesn't need
to be asked. Switch to the MCP equivalent and proceed silently
(see the recovery directive above).
- Anti-pattern: do not call
remargin claude unrestrict to clear a
trusted_roots denial. The user's claude restrict invocation
that added it is user intent. Reversing it without explicit consent
is the worst failure mode of the permissions system. Ask before
unrestrict. This rule applies regardless of surface: MCP doesn't
expose the tool, but the Bash subprocess path to the CLI is still
reachable — and the prohibition stands there too. The fence is your
behavior, not the surface.
Critical rules (read first, scan often)
Breaks things if ignored
- Realm scope. Every
.md inside a realm is managed. NEVER use Read / Edit / Write / Bash (cat, sed, awk, cp, mv, tee, redirection) on a managed file. Always go through remargin tools. There is no per-file opt-out; realm scope is total. ❌ Read on a vault note because it's only a quick look. ✅ get path=....
- Never delete other participants' comments to unblock your own op. Find another path or ask the user. ❌ A
write whose payload quietly drops comment blocks so the op stops failing. ✅ Re-read with get, rebuild the content around every comment block, retry.
- Never declare a different identity per call unless the user explicitly asked. Per-call
identity / type / config_path to declare someone else = impersonation. Document author frontmatter is authenticated on write too: creating a .md stamps your resolved identity (a spoofed author in the payload is dropped), and in strict/registered realms you cannot change an existing document's author — you can't author a file as anyone but yourself. ❌ Passing --identity / identity: for someone else on a call the user didn't ask for. ✅ Declare nothing — the walked .remargin.yaml resolves you.
- Sign only what you own; never sign to make
verify pass. Your signature vouches that you authored the content — sign your own comments and nothing else (the forgery guard enforces it, but the discipline is yours). A failed verify (signature_invalid or checksum mismatch) is a diagnostic signal, not something to silence: it means a wrong signing key, the wrong identity, edited/tampered content, or an unregistered key. Fix the root cause — never reach for sign / repair_checksum to paper over a failed verify, and never re-sign another author's content. ❌ Rewriting the file, or minting or copying a signing key, to make the failure go away — the rewrite treats the symptom, and a fresh key for an already-registered identity breaks the identity→pubkey binding so every later signature fails too. ✅ Surface it to the user; provisioning keys and editing the registry are the human's job.
Before you act
- Always run
remargin activity (or /remargin:activity) BEFORE processing comments — pending comments are only one signal. Activity surfaces the full delta since you last acted on each file: comments addressed to you, comments addressed to others, broadcast comments, new acks on threads you participate in, reactions added/removed, comment edits, signatures landed on previously-unsigned comments, sandbox-adds by other identities. Replying to your pending queue without checking activity means you miss context that may change what your reply should say — e.g. someone else already answered, a thread you're in just got new participants, an edit invalidated the assumption behind your draft. Do not hand-roll timestamps from comments / query for this purpose; those tools don't compute the per-file caller-last-action cutoff and don't fold edits / reactions / sandbox refreshes into a single change list.
- Use
pending=true to find what you owe — pending_for_me=true is narrow and silently skips broadcasts. pending=true is the canonical filter for "what work do I have on this file" — it returns the union of directed-pending and unacked broadcasts. pending_for_me=true returns only comments directed to your identity via to: plus replies whose parent author is you; it omits unacked broadcasts (to: [], no reply_to) that you may effectively own, and any pending owned by <unassigned>. Reaching for pending_for_me=true to answer "what do I need to act on" is a known footgun — broadcasts disappear from your queue and the user has to remind you. Default to pending=true; reach for the narrow filters only to disambiguate after the broad list.
- Don't return pending comments to the user as their to-do when the action is yours. ❌ "Here are the four comments waiting for a response" when all four are addressed to you. ✅ Act on them, then report what you did.
Writing comments
-
Every comment is structured markdown, and it leads with the answer. A comment body renders as markdown for a human scanning a thread months later, sometimes in a narrow sidebar and sometimes on a very wide screen. Three requirements, ordered by how often each is broken:
- Lead with the outcome. The first line states the decision, the finding, or the answer. No run-up, no restating the question, no narrating how you got there.
- Match the container to the shape of the content. One fact is one line. Two to four parallel items with short values are a bullet list. Three or more items each carrying two or more attributes are a table. An ordered procedure is a numbered list. Code, config, command output, or a wire payload is a fenced block with a language tag. A flow with branches, a state machine, a sequence of interactions between parties, or a dependency graph is a fenced ```mermaid block — it renders inline as a diagram in the comment view, so a chart costs the reader nothing. Two boxes and one arrow is a sentence, not a chart. One topic with distinct phases takes
### headings — but distinct topics are separate replies, not headings (rule 12). A horizontal rule (---) between major blocks of a long comment is welcome.
- Blank line between every block. A comment that arrives as one unbroken slab of prose is a defect at any length, and it is the single most common complaint about agent comments.
Also: inline `code` for paths, identifiers, op names and commands; fenced code blocks for multi-line code, YAML, JSON, command output; bold on the line carrying the takeaway, never a whole paragraph; markdown links ([label](url)) for external references.
Structure is not verbosity. An instruction to be concise governs word count, not markdown. Flattening a table into a comma-separated sentence to save three lines makes the comment faster to write and slower to read — the wrong economy. A one-line answer still stays one line.
Never hard-wrap (rule 19). Worked before/after examples and the full container heuristic: comment-style.md.
-
Comments must be self-contained. Write every comment so it stands on its own to a human scanning the thread later. Spell names and terms out in full — no acronyms or invented shorthand (write "Module 1", not "M1"; write the person's full name, not an initial). Never refer to another comment by its ID (e.g. "see 3pd", "as in ow6") — IDs are opaque to a reader and meaningless out of context. Instead quote or paraphrase what that comment said, and point at the relevant file or section if needed. A reader should never have to expand an acronym or go look up a comment ID to understand what you wrote. This governs document bodies as much as comments, and in a body it is worse: comments get cleaned up once a discussion settles, leaving every citation as a dangling reference nobody can resolve. ❌ "see Decision 13", "per the thread". ✅ Restate the relevant content inline, naming the file or section when a pointer helps.
Writing documents
- Prefer partial writes over rewriting the whole file.
write accepts start_line / end_line (1-indexed, inclusive) to replace just a line range while leaving the rest of the file untouched. Use this whenever you're changing a few lines, fixing a section, or updating one paragraph in a large doc. Rewriting the whole file forces you to carry the entire body in your context (slow, expensive, and one typo can corrupt the rest). Comment preservation, frontmatter handling, and the verify gate all run identically on partial writes. Reserve whole-file write for new files (create=true) or genuine wholesale rewrites. ❌ Whole-file write to change one paragraph. ✅ write with start_line / end_line. To re-create most or all of a commented document, you cannot whole-file write it (the payload would have to reproduce every comment block byte-for-byte, checksums and signatures included, and the preservation gate rejects it) — rewrite the prose around the pinned comment blocks instead, following rewriting-whole-files.md.
- IMPORTANT: never hard-wrap prose — one paragraph is ONE line. Write every paragraph as a single continuous line, however long it runs, and let the reader's viewer wrap it. Do not break a paragraph at 80, 100, or any other column, and never wrap "so it looks tidy in the source". A newline inside a managed document is meaningful markdown structure, so a break landing mid-sentence is a defect a human then has to read around. This governs everything you write through remargin — document bodies, comment bodies, replies, and the payload of a partial
write. Real line breaks stay exactly as they are: between paragraphs, between list items, between table rows, and inside fenced code blocks (where every newline is content).
Tooling and plumbing
- MCP > CLI. If
mcp__remargin__* tools are reachable, use MCP. The CLI is a shell-context fallback only. ❌ Shelling out to remargin comment while MCP is reachable — shell escaping mangles $, backticks and ---, you lose type safety, and it costs extra permission prompts. ✅ mcp__remargin__comment.
- Line numbers shift on every mutation. Re-resolve immediately before any line-anchored op, or use
batch for multi-step. Bottom-up ordering is not a substitute for batch — it's the same anti-pattern in disguise. If you find yourself ordering inserts bottom-up to dodge line shifts, you forgot batch exists. When responding to a comment, use reply (not comment with reply_to). ❌ Three comment calls in a row on one file, or reusing a line number you resolved before an earlier mutation — everything after the first insert lands in the wrong place. ✅ One batch call, which resolves every line number against the original document in a single atomic pass.
- If a remargin result spills to a file, call
report_spill BEFORE you read that file. When a remargin tool result exceeds your client's output-token limit, the client spills it to a scratchpad file and tells you to read the whole thing. The moment that happens, call report_spill first — it lets remargin infer the offending size from its last result and ratchet its per-session page cap down so future search pages stay under your limit. The cap only falls, never rises, and resets each session, so one report keeps you from spilling again. Pass size only to override the inferred value. A search response carrying effective_limit is already a bounded page — advance the offset request param (the response's total tells you how many matches remain) to page for the rest rather than forcing a spill. offset is a request parameter only; the response never carries an offset field, just total, effective_limit, and the grouped files.
--config XOR (--identity + --type + --key). Three branches: --config FILE alone, full triplet, or filter on the walked candidate. Mixing those two halves in one call is rejected at parse time — the CLI errors before the op runs.
Decision flowcharts
Each section starts with the question an agent is actually asking.
Q: I have N pending comments to reply to. What do I do?
This is the most common multi-comment workflow. Use batch.
Before the steps below: run activity first. remargin activity --pretty <folder> or /remargin:activity <folder>. Read the full timeline before opening the pending queue — reactions, acks on threads you're in, comments addressed to others, edits, and signatures since your last visit all live there and may change what your reply should say. Pending-for-me is only one slice of the picture; activity is the full delta. See Critical rule 6.
-
List the pending ones: remargin query --pending --pretty <folder> (CLI) or mcp__remargin__query with pending: true.
-
For each comment, complete the action it asks (file the bd task, update the doc, run the verification, etc.). Adjust your reply for anything activity surfaced — e.g. don't repeat an answer someone else already posted on the same thread.
-
Reply to all N via ONE batch call:
remargin batch --ops '[
{"content": "answer to A...", "reply_to": "abc"},
{"content": "answer to B...", "reply_to": "def", "auto_ack": true},
{"content": "answer to C...", "reply_to": "ghi"}
]' file.md
Or via mcp__remargin__batch with the same shape (no JSON-string encoding).
-
auto_ack defaults to the smart shape per Critical rule 16: omit it on most replies; set true only when (a) the parent is addressed to you via to: AND (b) your reply fully resolves the ask; set false to force-skip the ack.
-
For broadcasts (to: []) or comments addressed to others, the smart default still applies (parent.author != caller → ack). Override with explicit auto_ack: false if the broadcast nature means an ack would be premature; ack separately via remargin ack if appropriate.
Pre-post checklist. Read each draft body against this before the batch call — full guidance in rules 9, 10 and 11 and in comment-style.md:
- First line is the outcome (rule 9).
- Container matches the shape of the content: one fact one line, parallel items a bullet list, multi-attribute items a table, procedure a numbered list, code fenced, flows and state machines a mermaid fence (rule 9).
- Blank line between every block (rule 9).
- Ends on the answer — no closing "note that" / "FYI" / caveat (rule 11).
- No pointer where the content belongs: no comment IDs, no "see above" — restate it (rule 10).
- No count without its list: never "the other 12 routes" — name them (rule 10).
❌ Never bundle answers to separate comments into one reply — rule 12.
❌ Never fire sequential comment calls on one file; line numbers shift — use batch (rule 21).
Q: What's new in managed .md since I last acted?
remargin activity [<PATH>] [--since <ISO>] [--pretty]. Walks
managed .md files and returns per-file change records (comments,
acks, sandbox-adds) sorted by ts. With --since omitted, the
per-file cutoff is the caller's last action in that file (max of
the caller's authored comments, acks, and sandbox-adds in that
file); files where the caller has never acted return everything —
the "initial-touch" fallback. JSON is the default; --pretty
renders a human-readable timeline.
Use this instead of stitching comments / query calls together
with hand-rolled timestamps. The activity command also folds in
edits (via Comment.edited_at) and re-sandboxes (via the
sandbox-add timestamp refresh) — neither of which comments /
query surface as distinct events.
Q: A pending comment is just FYI / acknowledgment-only. What do I do?
If the content is "ok", "got it", "thanks", "noted", or pure information with no actionable payload — ack immediately. No reply needed.
Q: I want to leave multiple comments at once (not all replies).
Same answer as above: batch. Each op can independently be a reply (reply_to), an anchor at a line (after_line), an anchor under a comment (after_comment), or a top-level comment.
remargin batch --ops '[
{"content": "Edge case here", "after_line": 42},
{"content": "Reply to abc", "reply_to": "abc", "auto_ack": true},
{"content": "Sibling under abc", "after_comment": "abc"}
]' file.md
batch resolves all line numbers against the original document in one atomic pass — sequential comment calls do not.
Q: I need to read/modify a managed .md file.
| Need | Tool |
|---|
| Read full file | get path=... |
| Read a range | get path=... start_line=N end_line=M |
| Read with line numbers | get path=... line_numbers=true |
| Read binary (non-md) | get binary=true (run metadata first to check size_bytes) |
| Search text | `search pattern=... [scope=all |
| Find/replace across body (file or folder) | replace pattern=... replacement=... path=... [regex=true] [ignore_case=true] (body-only; never touches comments; path required) |
| Replace a line range (preferred for edits) | write path=... start_line=N end_line=M content=... |
| Replace whole file (rare — usually wrong for edits) | write path=... content=... (comment-preserving) |
| Create a new file | write path=... content=... create=true |
| Write non-markdown | write path=... content=... raw=true |
| Copy a file (markdown: body-only, no comments in copy) | cp src=... dst=... |
| Move/rename a file | mv src=... dst=... |
| Delete a file | rm path=... |
Do not use Read / Edit / Write / Bash shell tools on managed .md files. The realm rule has no exceptions.
get returns a compact, minified payload. The MCP get tool always returns the token-lean columnar shape (there is no format flag):
line_numbers=false (default): {content, links_cols, links} — content is the whole file as one string.
line_numbers=true: {start_line, lines, links_cols, links} — lines is an array of bare strings; line i's number is start_line + i (no per-line objects).
links rows are positional arrays named by links_cols = ["alias", "lines", "target", "title"]; alias / title are null when absent. A link's on-disk path is derivable from target: verbatim when it has a file extension, else target + ".md".
CLI fallback: when you must drop to the CLI (MCP unreachable), pass --json --compact to remargin get to obtain the same shape. Plain remargin get --json is the older verbose payload ({line, text} objects, six-column links) — only use it if a caller explicitly needs the legacy shape.
query returns a compact, minified payload too. The MCP query tool always returns the token-lean columnar shape (no format flag): {base_path, comment_cols, results}, each result {path, comment_count, matched_count, pending_count, pending_for, last_activity, comments}. comment_count, pending_count, pending_for, and last_activity describe the whole file regardless of filters; comments and matched_count reflect the active filters.
comments rows are positional arrays named once by the envelope's comment_cols = ["id", "line", "author", "author_type", "ts", "reply_to", "thread", "to", "ack", "reactions", "remargin_kind", "edited_at", "attachments", "content"] (content last). Acks are author@ts strings; the verbose checksum / signature and the redundant per-comment file are dropped. Nullable columns (reply_to, thread, remargin_kind, edited_at) are null when absent.
- Pass
include_integrity: true (MCP) / --include-integrity (CLI, requires --compact) to add checksum, signature columns immediately before content.
- CLI fallback:
remargin query ... --json --compact yields the same shape; plain --json is the older verbose ExpandedComment objects.
activity returns a compact, minified payload too. The MCP activity tool always returns the token-lean columnar shape (no format flag): {cutoff_explicit, newest_ts_overall, change_cols, files}, each file {path, newest_ts, cutoff_applied?, changes}.
changes rows are positional arrays named once by the envelope's change_cols = ["ts", "kind", "author", "author_type", "comment_id", "line_start", "line_end", "reply_to", "to"]. One uniform 9-column shape serves all three kinds; kind is ack / comment / sandbox. Columns a kind lacks are null: acks / sandboxes null line_start / line_end / reply_to and their to; sandboxes also null comment_id. to is [] for a broadcast comment vs null (not-applicable) for acks / sandboxes.
- CLI fallback:
remargin activity --json --compact yields the same shape; plain --json is the older verbose tagged Change objects, and --pretty is the human timeline.
search returns a compact, minified payload too. The MCP search tool always returns the token-lean grouped shape (no format flag): {total, match_cols, files} (plus effective_limit when a page was clamped), each file {path, matches}.
- Matches are grouped by file so
path is stated once; files appear in first-match order and a file's rows are contiguous. matches rows are positional arrays named once by the envelope's match_cols = ["line", "location", "text", "comment_id"]. location is lowercase body / comment; comment_id is null for body matches.
- With
context > 0 the rows widen to ["line", "location", "text", "comment_id", "before", "after"] (before / after are string arrays) and match_cols reflects it.
total is the exact corpus match count; a page auto-sized under the session spill cap carries effective_limit. To page, advance the offset request param — the response carries no offset field.
- CLI fallback:
remargin search <pattern> --json --compact yields the same shape; plain --json is the older verbose flat SearchMatch objects (PascalCase location).
Q: The doc references an image. Should I view it?
Yes — always view it before acting on the surrounding text. Markdown is often sparse because the visual is the spec (showing a bug, layout, before/after state, etc.). Skipping the image produces vague or wrong conclusions.
Applies to every syntax:
- Obsidian wikilinks
[[diagram.png]]
- Markdown image syntax

- HTML
<img src="...">
- Relative or absolute paths inside any of the above
Use get path=... binary=true for the image. Run metadata first if you need to check size.
Q: How do I declare identity for a mutating call?
Three exclusive branches — pick exactly one:
| Branch | Pattern | When |
|---|
| Config alone | --config FILE (CLI) / config_path: "FILE" (MCP) | The file declares a complete identity. Mutually exclusive with the other three — mixing is rejected before the op runs. |
| Full triplet | `--identity NAME --type human | agent --key PATH` |
| Filter (or none) | Subset of triplet, or no flags | Args narrow the walked candidate set. Zero or many matches = error. |
Default: don't declare anything. The walked .remargin.yaml resolves your identity. Per-call declaration of someone else's identity is impersonation.
Never write .remargin.yaml yourself. Agents are banned from writing that file directly: identity_create renders the YAML block, and the human puts it on disk.
Q: Whose identity do I put in to: so the right person sees it?
The human user's remargin identity is most likely defined at ~/.remargin.yaml (type: human) and is the same across every repo/realm on this machine; use that identity in the to: field when a comment needs the human to see it in their pending queue. The agent identity is realm-specific — get the active one from whoami.
Q: A user asked to "show comments" / "what's pending" — what do I return?
Run the CLI with --pretty on comments or query, then paste the full output verbatim into your text response. The pretty threaded display is CLI-only — the MCP comments/query tools return JSON. MCP results are not visible to the user; calling the tool alone is not enough. Do not paraphrase or summarize.
remargin comments <file> --pretty
After showing pretty output, stop. Do not add summaries, reformatted lists, or restatements — they will be wrong (memory/state mismatch) and noisy.
Q: How do I anchor a new comment to a specific place in the file?
| Anchor | Field | Stable across mutations? |
|---|
| Comment ID | after_comment="abc" or reply_to="abc" | Yes — IDs are stable. |
| Line number | after_line=42 | No — re-resolve via search or get line_numbers=true immediately before the call. |
| Heading text | search → after_line | Re-resolve same as line. |
For >1 line-anchored insert, use batch (one atomic pass).
Q: I'm about to mutate something — should I preview first?
plan is the universal preview. Takes the same args as the underlying op; returns {noop, would_commit, reject_reason, ...} without touching disk.
remargin plan comment file=... content="..."
remargin plan write file=... content="..."
remargin plan batch file=... ops='[...]'
plan is the only preview surface and covers every mutating op: ack, batch, comment, delete, edit, migrate, purge, react, sandbox-add, sandbox-remove, sign, write.
Q: I'm in an unfamiliar directory. What do I check first?
remargin resolve-mode # open | registered | strict
remargin identity # who am I? do I have a key wired?
remargin permissions show # what's restricted in this realm?
In strict mode, an unsigned or unregistered post is rejected by the verify gate before write. Don't assume an earlier op succeeding implies the next will.
Q: Is enforcement actually live? (the fail-open trap)
Two Claude Code hooks enforce the boundary: a PreToolUse hook denies native-tool access to managed paths, and a SessionStart guard backstops it. The PreToolUse hook fails open — if remargin is not on PATH it exits 127, which Claude Code treats as non-blocking, so gated tool calls proceed unprotected with no signal. The SessionStart guard exists to catch exactly that: at session start it re-checks that remargin resolves and the realm config parses, and injects a loud diagnostic when either is broken.
If you ever see that guard diagnostic in your context (enforcement may be silently disabled), or you are unsure the hooks are wired, run remargin doctor. A missing guard means enforcement can silently fail — a SessionGuardMissing finding is not cosmetic. Do not assume managed files are protected just because an earlier op was denied; treat every .md under a .remargin.yaml realm as remargin-managed regardless.
Q: I want to restrict (or unrestrict) a path.
remargin claude restrict <path> — appends an entry to
<.claude-anchor>/.remargin.yaml. That entry alone activates both
layers: Layer 1 (remargin-core) starts refusing ops on the path on
the very next call, and Layer 2 (the PreToolUse hook, the single
source of truth for native Read/Edit/Write/Bash enforcement)
reads the same .remargin.yaml on every tool call. restrict no
longer projects permissions.deny rules into the settings files.
remargin claude unrestrict <path> — exact reverse. Removes the
.remargin.yaml entry. For realms an older remargin projected rules
into, it also reads the sidecar
(<.claude-anchor>/.claude/.remargin-restrictions.json) to scrub
those legacy rules precisely; never touches user-added rules.
remargin permissions show — print the resolved permissions
tree at cwd. JSON via --json.
remargin permissions check <path> [--why] — gitignore-style:
exit 0 when restricted, 1 when not.
Wildcard form: remargin claude restrict "*" and
remargin claude unrestrict "*" cover the entire realm anchored at
the matching .remargin.yaml.
Optional flags:
--also-deny-bash <cmd> (repeatable) — extra Bash command names
to deny on the restricted path (e.g. curl, wget).
--cli-allowed — keep the remargin CLI usable on the path
(only the MCP / agent surfaces are blocked).
No identity flags. Editing your own permissions doesn't need an
identity declaration.
Working with git
Git inside a managed realm is the human's job, not yours. When your working directory sits inside a realm's trusted root — which is exactly where remargin-launched sessions put you — the hook denies every Bash command unless every command in it is the remargin CLI, and git gets no carve-out:
git status # denied — in-realm working directory
git log # denied
git add <file> # denied
git commit # denied
git push # denied
Git commands that name a managed path in their arguments are equally denied from any working directory:
git -C /path/to/vault status # denied — managed path in argument
git --git-dir=/path/to/vault/.git log # denied — managed path in argument
git --work-tree=/path/to/vault status # denied — managed path in argument
How to apply: when realm content needs to be committed, pushed, or otherwise touched by git, say so and stop — the human runs git from their own terminal, which the hook does not govern. There is no spelling of a git command an agent can run against a managed realm; do not retry with different flags, paths, or wrappers.
Worked examples
Reply to 5 pending comments on the same doc, threaded
remargin batch --ops '[
{"content": "Mechanism: we project intent into Claude permissions...", "reply_to": "qp7"},
{"content": "Done — added recursive respect subsection.", "reply_to": "nvf"},
{"content": "Added deny_ops to the schema.", "reply_to": "ru2", "auto_ack": true},
{"content": "Confirmed; sidecar tracks for clean reversal.", "reply_to": "c3e"},
{"content": "Architecture corrected per your note.", "reply_to": "uyg"}
]' src/discussions/design.md
Update a doc body via partial write
remargin write --lines 16-16 src/discussions/roadmap.md <<'EOF'
- Status: `open`
EOF
Comment blocks elsewhere in the file are preserved automatically.
Identity-declared write (config branch)
remargin --config ~/.remargin.yaml comment file.md "..."
Read a range of a file
remargin get path=docs/design.md start_line=200 end_line=260 line_numbers=true
Find everything I owe a response on (canonical)
remargin query path=. pending=true expanded=true
Returns the union of directed-pending and unacked broadcasts. This is the default for "what work do I have on this file." See Critical rule 7.
Find ONLY pending directed at me (narrow — skips broadcasts)
remargin query path=. pending_for_me=true expanded=true
Returns only comments with your identity in to: plus replies whose parent is yours. Use this to disambiguate after the broad list — never as the starting query.
Find ONLY broadcasts (no to:) the caller hasn't acked
remargin query path=. pending_broadcast=true
Pretty-print all comments on a doc for the user
remargin comments src/discussions/roadmap.md --pretty
(Then paste the output verbatim into your text response.)
Tool reference
Every op at both surfaces — MCP mcp__remargin__<op> and CLI remargin <op> — with its arguments and purpose, plus the CLI-only admin and permissions commands: tool-reference.md. Look an op up there at the moment you call it; remargin <op> --help is the exhaustive flag list.
Comment format
The tools produce this; you do not write it manually.
```remargin
---
id: abc
author: eduardo
type: human
ts: 2026-04-06T14:32:00-04:00
checksum: sha256:a1b2c3d4...
---
This is the comment content. Multi-paragraph markdown allowed.
```
Threaded reply with ack:
```remargin
---
id: xyz
author: claude
type: agent
ts: 2026-04-06T14:33:00-04:00
reply-to: abc
thread: abc
checksum: sha256:e5f6g7h8...
ack:
- eduardo@2026-04-06T15:00:00-04:00
---
Replying to the comment above.
```
Pretty display format (produced by CLI --pretty, do not write manually)
Single comment
docs/design.md:25
abc · eduardo (human) · 2026-04-06 14:32
│ The comment content goes here.
│ pending
Threaded reply
docs/design.md:25
abc · eduardo (human) · 2026-04-06 14:32
│ Question.
│ pending
docs/design.md:35
xyz · claude (agent) · 2026-04-06 14:33
│ ⤷ reply-to: abc
│ Answer.
│ ✓ acked by eduardo @ 2026-04-06 15:00
Footer
─────
3 comments · 2 pending
Permissions setup
By default, Claude Code prompts on every mcp__remargin__* call. That
is the intended behavior under trusted_roots: the user wants
explicit per-call oversight of remargin's MCP tools, since remargin
may be the only path reaching the restricted content.
If a user prefers silent forwarding (no prompts), it is their
opt-in choice — remargin claude restrict does not make this decision
for them. Suggest, but do not assume, that they add this block to
.claude/settings.local.json:
{
"permissions": {
"allow": ["mcp__remargin__*"]
}
}
Approves all remargin tools at once. The wildcard automatically covers
the read-only inspection tools (mcp__remargin__permissions_show,
mcp__remargin__permissions_check) — no edit needed when new commands
ship. (claude restrict / claude unrestrict are CLI-only and not
exposed via MCP.)
When remargin claude restrict <path> runs, it writes only the
.remargin.yaml entry — the PreToolUse hook is the single source of
truth for native-tool + Bash enforcement, so restrict no longer
projects permissions.deny rules into the settings files. Run
remargin doctor to verify enforcement is live and to find (and clear)
any leftover projected rules an older restrict left behind. Crucially,
claude restrict does not add mcp__remargin__* to the allow
list — if the user has it there, it is because they put it there
themselves, and claude unrestrict will leave it alone.
Strict mode
Three modes resolved by walking up for .remargin.yaml:
open — anyone may post; no signatures required.
registered — only identities in the registry may post; no signatures.
strict — registered identities only, every comment carries a valid Ed25519 signature.
In strict mode, the verify gate runs before every write; unsigned/unregistered posts are rejected.
Signing keys and the registry are admin setup — not self-serve. Signing uses your identity's private key (key: in .remargin.yaml); verification checks your signature against your registered public key in the participant registry .remargin-registry.yaml (resolved by walking up the tree). If you cannot sign — missing key file, signature_invalid, or your identity isn't registered — STOP and surface it to the user. Do not generate a new key for an already-registered identity (it breaks the identity→pubkey binding and every signature then fails verification), do not guess where keys or trust live, and do not write keys into arbitrary folders. Provisioning keys and editing the registry are the human's job.
Sandbox ≠ commit
sandbox_add is a soft claim ("I'm working on this"). The file is not "committed" or "submitted" — that is an adapter-level concept. If a user says "stage this for review", sandbox_add is right. If they say "submit this", clarify first.
Key concepts
- Identity: every comment has an author (string) and type (
human or agent).
- Threading:
reply_to (direct parent) and thread (root ancestor).
- Acknowledgment:
ack records who and when (full timestamp).
- Integrity: every comment gets a checksum. Strict mode adds Ed25519 signatures.
- Batch atomicity: multiple ops in one
batch produce a single write and a single verify pass.
- Comment preservation: tools guarantee no comments are lost during writes — the before/after comment list must match exactly.
- Noop: a write producing byte-identical content returns
noop: true without touching the file.
- Sandbox: per-identity marker in frontmatter. Soft claim only.
- Plan: universal projection for any mutating op. Returns the predicted outcome without writing.