update_document
Modifies an existing document — title, content, language, attachment or archiving. For the content, contentMarkdown (GFM Markdown, RECOMMENDED PATH) or content (raw ProseMirror tree): either one REPLACES the whole tree, not a partial patch. appendMarkdown ADDS at the END without re-sending the document (exclusive with the two others). Each field PROVIDED is written, the others stay intact. cellId: null detaches the document; cellId omitted does not touch the attachment — same semantics as update_task.cellId. expectedVersion (required) is the version read via get_document before writing: if it has diverged (someone else modified the document in the meantime), the call fails naming the current version rather than silently overwriting their work; reading the document again gives the up-to-date version. ⚠️ HISTORY: a content write does NOT always create a revision — if the last revision of the document is by the SAME author, written through the same kind of writer (human or agent), and less than 10 minutes old, the previous version is OVERWRITTEN without being kept (list_document_revisions will never show it); a change of writer (a person, then their own agent, or the reverse) does create one. When this happens, the response carries revisionSkipped ({ reason, windowMinutes, lastRevisionAt }: the last kept version); field ABSENT = a revision was created (or the document has no history, like an HTML document), which does not mean "nothing was lost". warnings (ABSENT when nothing is altered) names what the Markdown conversion could not carry; raw HTML, checkboxes and nesting beyond 100 levels are REFUSED with their reason. A request blocked by the network firewall (a bare HTTP 403 "Forbidden") answers before the application runs, so it carries NO application message; a document body that is read as an attack pattern (for instance a path traversal such as ../../, or a URL whose host is an IPv4 address) can trigger it. The response carries url: the DIRECT LINK of the document (opens the project on this document), which the human follows with one click, unlike the title.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| title | No | Document title (1 to 200 characters). | |
| cellId | No | `null` DETACHES the document from any block. Field omitted: does not touch the attachment. A value REPLACES any existing attachment (pattern `update_task.assignees`). | |
| content | No | Replaces the ENTIRE content — not a partial patch of the tree. Reserved for cases that build the tree programmatically; `contentMarkdown` is the recommended path. Providing both at once is an explicit REFUSAL (ambiguous, so it is not resolved silently). | |
| archived | No | Archives (true) or unarchives (false) the document. Omitted: does not change the state. | |
| documentId | Yes | Identifier of the document to modify. | |
| htmlSource | No | REPLACES the ENTIRE source of a `kind: "html"` document — not a partial patch. On a richtext document, this field is REFUSED by naming it (the form is immutable). The document search text is recomputed from the new source. The COMPLETE, SELF-CONTAINED HTML document: markup + `<style>` + `<script>` inline, in a single string (maximum 250000 characters). Meant for what a richtext document cannot carry — an overview page, an interactive report, a visualization, a calculator. Long documentation belongs in an ordinary richtext document (`contentMarkdown`), which stays editable by hand. ⚠️ RISK, STATED TO THE USER WHEN THEY CLICK "▶ Run the document": the sandbox protects the user's SESSION (opaque origin, no cookie or storage) but does NOT guarantee that the document's content stays inside OrgGen — the WebRTC channel is not closed (measured in a browser): anything a document embeds can leave OrgGen. ⚠️ SELF-CONTAINED IN THE STRICT SENSE: the document is served in a sandbox WITHOUT ordinary network access (`fetch`, WebSocket, remote resource). Any REMOTE or RELATIVE resource (`<script src>`, `<link rel=stylesheet>`, `<img src="/x.png">`, a Google font, a CSS `@import`) is REFUSED AT WRITE TIME, naming the offending URL — rather than accepting a document half of which would die silently at display time. Images are embedded as `data:`, CSS and JS are written inline. ⚠️ TWO MANDATORY LINES, whose omission raises NO error — it just yields a document that will look broken. (a) `body` EXPLICITLY declares `background` AND `color`: the document is served in an ISOLATED frame, it inherits nothing from the application theme, so without a declared background it displays in WHITE in the middle of a dark panel. The viewer sets `color-scheme` and `data-og-theme` on `<html>`, but `color-scheme` is a hint to the rendering engine, NOT a stylesheet — it invents no color. (b) a `<meta name="viewport" content="width=device-width, initial-scale=1">`, and content that fits in ~390 px: the panel is readable on a phone. The viewer injects this `<meta>` if it is missing, but this safety net designs nothing — a grid in fixed widths will stay off-screen. ⚠️ NINE COMMON CONSTRUCTIONS ARE DEAD in this sandbox, WITHOUT any error being reported (the write succeeds, the document displays, the button stays inert) — including `alert()`, `window.print()`, `localStorage`, forms, `eval`, and `confirm()`, which returns `false` in every case: the "cancel" branch runs, it is the only one whose result is WRONG instead of inert. The nine, with what works instead, are listed in `get_playbook({topic: "html"})`. | |
| visibility | No | 'private' makes the document invisible to EVERYONE EXCEPT ITS CREATOR (including ordinary project editors); 'inherited' (default) makes it visible to whoever has access to the project/block. Settable ONLY by the creator of the document — another caller gets a named error. Omitted: does not touch the visibility. | |
| contentLocale | No | `null` clears the declared language. Field omitted: does not touch the language. | |
| appendMarkdown | No | APPENDS Markdown (GFM, same dialect as contentMarkdown) AFTER the existing content, which is kept node for node — so a long document is never re-sent in full just to add a section. Richtext documents only (an html document is refused: its source is replaced with `htmlSource`). Mutually exclusive with `content` and `contentMarkdown` (providing both is an explicit REFUSAL). Same write as any content change: `expectedVersion` applies, a revision is kept (or reported as `revisionSkipped`). Refusals name the line, counted from the first line of THIS text. There is no partial replace of a section: to rewrite one, send the whole document with `contentMarkdown`. | |
| contentMarkdown | No | Replaces the ENTIRE content, as Markdown (GFM) — RECOMMENDED PATH, same dialect as create_document (headings, lists, tables, quotes, code blocks, bold/italic/strikethrough/inline code, links, images, horizontal rules). This is not a partial patch: the new text REPLACES all of the old. An unsupported construct (raw HTML, YAML frontmatter, or a TASK list `- [ ]`/`- [x]`) is REFUSED with the offending node type named — it is not silently omitted from the document. | |
| expectedVersion | Yes | The document version you believe you are editing (obtained via get_document). If it has diverged since, the call fails with the current version rather than overwriting a concurrent write. | |
| expectedProjectId | No | UUID of the project targeted by THIS call. ACTUALLY targets this project instead of the session project. Refused without changing anything if access is not granted. |