---
name: mrkdwn-collab
description: Collaborate on a live mrkdwn markdown document — read it, make surgical edits, comment, and respond to @mentions over a simple REST API. Use when given a mrkdwn document URL and bearer token.
---

# Collaborating on a mrkdwn document

mrkdwn is a realtime collaborative markdown editor. Humans edit in a web UI; you edit
through this API. Everything you do — edits, comments, even your cursor — shows up for
humans instantly, and their changes merge with yours conflict-free (CRDT-backed, no locks,
no "file changed" errors).

## Setup

Your invite carries the server URL, a bearer token, and usually the page it's
for. You choose your own identity: a short stable handle (letters, digits,
dashes — e.g. `claude`) and a human-facing display name.

```bash
export MRKDWN_URL="https://cmr3wmpfb01pyyjcmjec9d87i.ewr.prisma.build"        # server base url (from the invite)
export MRKDWN_TOKEN="<bearer token>"       # from the invite
export MRKDWN_AGENT="claude"               # pick your own @handle
export MRKDWN_AGENT_NAME="Claude"          # pick your own display name
```

Send all three headers on every request — the display name is how humans see
you on avatars, cursors, and comments (your @handle stays the stable id, and
humans reach you by writing `@<handle>`):

```bash
alias mk='curl -s -H "Authorization: Bearer $MRKDWN_TOKEN" -H "X-Agent: $MRKDWN_AGENT" -H "X-Agent-Name: $MRKDWN_AGENT_NAME"'
```

On joining, announce yourself (shows your avatar to humans):
`mk -X POST "$MRKDWN_URL/api/presence?page=<id>"` — then **read the page
before doing anything else**. Work is often already waiting: unchecked
to-dos, open questions, or asks addressed to "the agent" count even when
nobody typed your @handle. Your first poll of `/api/notifications` also
delivers any @mentions of your handle written before you joined.

## Pages (the workspace)

Documents live in a workspace; each page has a fixed `id`, a title-derived
`slug`, and one of three kinds:

- **markdown** (default) — collaborative text; edit with surgical text edits.
- **canvas** — a JSON Canvas board of nodes and edges; edit as JSON.
- **html** — a full HTML document you author; humans view it rendered live
  in a sandboxed iframe.

```bash
mk $MRKDWN_URL/api/workspace               # { workspace, pages: [{ id, title, slug, kind, path }] }
mk -X POST $MRKDWN_URL/api/pages -d '{"title": "Meeting notes"}'                    # markdown
mk -X POST $MRKDWN_URL/api/pages -d '{"title": "Launch board", "kind": "canvas"}'   # canvas
mk -X POST $MRKDWN_URL/api/pages -d '{"title": "Burndown", "kind": "html"}'         # html
```

Every doc/comment endpoint below targets the **first page by default**; add
`?page=<id>` to work on another page. Notifications tell you which page a
mention came from (`page: { id, title }`). In doc text, `@page-slug`
references link to that page — and a line containing only `![[page-slug]]`
**embeds** that page (any kind: markdown, canvas, or html) as a live block
humans see rendered inside the document.

## Canvas pages

Some pages are **canvases** — [JSON Canvas 1.0](https://jsoncanvas.org) boards of
nodes and edges instead of markdown. `GET /api/workspace` marks them with
`"kind": "canvas"`; create one with `POST /api/pages {"title": "...", "kind": "canvas"}`.

```bash
mk "$MRKDWN_URL/api/doc?page=<id>"      # → { kind: "canvas", canvas: { nodes: [...], edges: [...] } }
mk -X PUT "$MRKDWN_URL/api/doc?page=<id>" -d '{
  "canvas": {
    "nodes": [
      { "id": "a1b2c3d4e5f60708", "type": "text", "x": 40, "y": 40,
        "width": 240, "height": 150, "color": "3", "text": "## Plan\n- [ ] ship it" },
      { "id": "b2c3d4e5f6071809", "type": "file", "x": 360, "y": 40,
        "width": 380, "height": 300, "file": "runbook.md" }
    ],
    "edges": [
      { "id": "c3d4e5f607182910", "fromNode": "a1b2c3d4e5f60708", "fromSide": "right",
        "toNode": "b2c3d4e5f6071809", "toSide": "left", "toEnd": "arrow" }
    ]
  }
}'
```

The PUT replaces the whole canvas but is **merged per node**: read first, apply
your changes, send everything back. Nodes you return unchanged keep any
concurrent human edits (someone dragging a note mid-request loses nothing);
nodes you omit are deleted, so never send a partial list.

Node types: `text` (markdown in `text` — a sticky note; `color` is "1"–"6"
or "#rrggbb"), `file` (`"slug.md"` embeds that markdown page live,
`"slug.html"` embeds an html page rendered live, `"/api/images/<id>"` shows
an uploaded image), `link` (`url`), `group` (`label`). Page embeds may
carry a `pageId` (mrkdwn extension) — include it when you know it; it keeps
the embed working when the page is renamed. Sizes are pixels — notes read
well around 240×150; place related nodes near each other and connect them
with edges. @mentions inside text nodes notify agents exactly like doc text.

## HTML pages

An **html** page is a complete HTML document you author — markup, CSS, and
JS in one file. Humans don't edit it; they watch it render live in a
sandboxed iframe (scripts run, but in an opaque origin: no cookies, no
storage, no app credentials). Use them for dashboards, visualizations,
interactive widgets, prototypes.

**The document must declare its render size** with a meta tag in `<head>`
(CSS pixels, min 240x160, max 1600x1200) — writes without it are rejected:

```html
<meta name="mrkdwn-size" content="960x600">
```

```bash
mk "$MRKDWN_URL/api/doc?page=<id>"                  # → { kind: "html", html, size }
mk "$MRKDWN_URL/api/doc?page=<id>&format=html"      # raw source only
mk -X PUT "$MRKDWN_URL/api/doc?page=<id>" -d '{
  "html": "<!doctype html><html><head><meta charset=\"utf-8\"><meta name=\"mrkdwn-size\" content=\"960x600\"><style>body{font-family:system-ui}</style></head><body><h1>Live</h1><script>/* js runs */</script></body></html>"
}'
```

Surgical edits work exactly like markdown (the source is text under the same
CRDT), so prefer them for small changes — `POST /api/doc/edits` with
oldText/newText; the edit is rejected if it would break the size declaration.
`append` doesn't apply to html pages. Humans see your update the moment you
write it — send complete, valid documents rather than streaming fragments.
An html page can be embedded on a canvas as a `file` node (`"slug.html"`).

## Reading a document

```bash
mk $MRKDWN_URL/api/doc                     # JSON: { title, markdown, heads, page, openComments }
mk "$MRKDWN_URL/api/doc?format=markdown"   # raw markdown only
mk "$MRKDWN_URL/api/doc?page=<id>"         # a specific page
```

Always read before editing — humans may have changed things since you last looked.

## Editing

**Preferred: exact-match edits.** Same contract as your file-editing tools: `oldText`
must match the current document exactly and uniquely.

```bash
mk -X POST $MRKDWN_URL/api/doc/edits -d '{
  "edits": [
    { "oldText": "## Draft heading", "newText": "## Final heading" },
    { "oldText": "teh", "newText": "the", "replaceAll": true }
  ]
}'
```

- Edits apply in order, atomically — if any edit fails, none apply.
- `409 not found` → re-read the doc; the text changed under you.
- `409 ambiguous` → include more surrounding context, or pass `"replaceAll": true`.

**Appending** (new sections, notes at the end):

```bash
mk -X POST $MRKDWN_URL/api/doc/append -d '{"markdown": "## Open questions\n\n- ..."}'
```

**Full replace** — `PUT /api/doc {"markdown": "..."}` (also accepts `{"title": "..."}`).
The server diffs your text against the current doc so concurrent human edits survive,
but prefer surgical edits: they produce cleaner merges and read better in realtime.

## Comments

```bash
mk $MRKDWN_URL/api/comments                                  # open threads (+ ?includeResolved=1)
mk -X POST $MRKDWN_URL/api/comments -d '{
  "anchorText": "exact text from the doc to attach to",
  "body": "Should this section mention pricing? cc @sorenschmidt"
}'
mk -X POST $MRKDWN_URL/api/comments/<id>/replies -d '{"body": "Done — reworded it."}'
mk -X POST $MRKDWN_URL/api/comments/<id>/resolve
```

Omit `anchorText` for a document-level comment. If `anchorText` is ambiguous, pass
`"occurrence": 2` (1-based).

## Mentions → your task queue

When anyone writes `@$MRKDWN_AGENT` in the doc or in a comment, you get a notification:

```bash
mk "$MRKDWN_URL/api/notifications?wait=25"
# → { "notifications": [ { "id", "kind": "doc-mention"|"comment-mention",
#      "snippet": "…the line that mentions you…", "commentId"? } ] }
```

`wait` long-polls up to that many seconds — loop it while you work so humans can reach you:

```bash
while true; do
  mk "$MRKDWN_URL/api/notifications?wait=25" | jq -c '.notifications[]'
done
```

**Stay on the page.** Keep this loop running for at least **30 minutes** after you join,
and every time a notification (or any new instruction from a human) arrives, extend the
window — restart the 30 minutes from that moment. Humans edit at human pace; leaving
after one task usually means missing the follow-up. Only stop polling when the window
expires with nothing new.

For each notification: do what it asks. For `comment-mention`s, reply in that thread
(`POST /api/comments/<commentId>/replies`) so the conversation stays attached. Then ack:

```bash
mk -X POST $MRKDWN_URL/api/notifications/ack -d '{"ids": ["n_..."]}'
```

Unacked notifications are redelivered on every poll.

## Etiquette (humans are editing live)

- On joining a page (and after being away), re-read it — pick up asks from the
  content itself, not just from @mention notifications.
- Make small, targeted edits; don't rewrite sections nobody asked you to touch.
- Match the document's tone and formatting; it's markdown all the way down.
- When you complete a mention-task from the doc text, it's often nice to leave the
  mention in place but tick the checkbox / adjust the text it sits in, or reply in-thread
  for comments. Don't delete other people's questions.
- Heartbeat `POST /api/presence` (or just keep long-polling) roughly every 30s while
  active, so your avatar reflects reality.
- You share one bearer token with other agents; your `X-Agent` handle is what
  distinguishes you. Don't impersonate other handles.
