> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sidecars.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Documents

> List, read, create and edit shared markdown documents while people edit them live, and list their versions; every read returns version and every write returns new_version.

List, read, create and edit collaborative markdown documents, and inspect their version history.

| Tool                                              | Scope             | Kind               |
| ------------------------------------------------- | ----------------- | ------------------ |
| [`list_documents`](#list-documents)               | `documents:read`  | read-only          |
| [`read_document`](#read-document)                 | `documents:read`  | read-only          |
| [`create_document`](#create-document)             | `documents:write` | write              |
| [`edit_document`](#edit-document)                 | `documents:write` | write, destructive |
| [`get_document_versions`](#get-document-versions) | `documents:read`  | read-only          |

## list\_documents

**Scope:** `documents:read` (step-up) · **Kind:** read-only · **Annotations:** `readOnlyHint: true`, `destructiveHint: false`, `openWorldHint: false`

> List the documents and pages you can reach. Narrow to one conversation
> (conversation\_id), one pod (pod\_id), or a library section (`scope`:
> `mine` = ones you own, `shared` = shared with you, `pod` = every pod you
> belong to, `org` = shared with the whole workspace); with none of these,
> everything you can reach is merged. Each row carries `scope` and your
> `role` (owner | editor | commenter | viewer) — a viewer can read but not
> edit or comment. Optional `query` filters by title/content. Returns
> metadata only; use read\_document for content.

### Parameters

Pass **at most one** of `scope`, `conversation_id` and `pod_id`.

<ParamField body="scope" type="'mine' | 'shared' | 'pod' | 'org'">
  A library section. `mine` = artifacts you own; `shared` = artifacts
  explicitly shared with you; `pod` = every pod you belong to; `org` =
  artifacts shared with the whole workspace.
</ParamField>

<ParamField body="conversation_id" type="string">
  Only artifacts linked to this conversation.
</ParamField>

<ParamField body="pod_id" type="string">
  Only artifacts in this pod.
</ParamField>

<ParamField body="query" type="string">
  Case-insensitive substring filter over title and body, applied to the rows
  already read. It is a funnel, not a ranked search; use
  [`search_knowledge`](/reference/knowledge#search_knowledge) for retrieval.
</ParamField>

<ParamField body="cursor" type="string">
  The `next_cursor` from the previous page of the same listing. Opaque and
  bound to the arguments that produced it: passing it with a different
  `scope`, `pod_id`, `query` or `include_archived` is refused.
</ParamField>

<ParamField body="include_archived" type="boolean" default="false">
  Include archived artifacts.
</ParamField>

<ParamField body="limit" type="number" default="50">
  Rows to return, 1 to 50. Clamped.
</ParamField>

### Returns

<ResponseField name="documents" type="array">
  <Expandable title="row">
    <ResponseField name="document_id" type="string">The artifact id.</ResponseField>

    <ResponseField name="title" type="string" />

    <ResponseField name="type" type="'document' | 'html_app' | 'table'" />

    <ResponseField name="status" type="'active' | 'archived'" />

    <ResponseField name="characters" type="number">
      Length of the derived markdown. Absent when the projection has not been
      computed yet, which does not mean empty.
    </ResponseField>

    <ResponseField name="created_at" type="number">Milliseconds since epoch.</ResponseField>
    <ResponseField name="last_edited_at" type="number">Milliseconds since epoch.</ResponseField>

    <ResponseField name="version" type="number">
      Informational. Take the write token from `read_document`.
    </ResponseField>

    <ResponseField name="conversation_id" type="string">Present when the artifact is linked to a conversation.</ResponseField>
    <ResponseField name="pod_id" type="string">Present when the artifact lives in a pod.</ResponseField>

    <ResponseField name="scope" type="'user' | 'pod' | 'org'" />

    <ResponseField name="role" type="'owner' | 'editor' | 'commenter' | 'viewer'">Your effective role.</ResponseField>
    <ResponseField name="owner_label" type="string">The owner's display name, when known.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="next_cursor" type="string | null">
  Where the next page starts. A string on the paged listings (`mine`,
  `shared`, `org`, `pod_id`) while more rows exist; `null` when the listing
  is exhausted, and always `null` on the merged listing, `scope: "pod"` and
  `conversation_id`, which cannot page.
</ResponseField>

<ResponseField name="truncated" type="boolean">
  `true` when more rows exist than were returned: on a paged listing that is
  the same as `next_cursor` being a string; on the merged listings it means
  narrow with `pod_id` or `scope` (or an org-wide listing stopped after 50
  pods).
</ResponseField>

The text block has one line per row:

```text theme={null}
- <title> (<type>) [<role>] — document_id: <id>, scope <scope>, owner <name>, <n> chars, edited <ISO time>
[…more documents: call again with `cursor` = next_cursor]
```

or `No documents found.`

### Completion checklist

* On `mine`, `shared`, `org` and `pod_id`, follow `next_cursor` until it is
  `null` before concluding a document does not exist; `query` filters each
  page, so a page can be short without being the last.
* On the merged listing and `scope: "pod"`, `truncated: true` is
  inconclusive. Narrow with `pod_id`, `conversation_id` or a paged `scope`
  and list again.
* A row's `conversation_id` and `pod_id` are the ids the other tools take;
  [`list_conversations`](/reference/conversations) turns a room or pod name
  into them.
* The task is done when a read-back shows it: after any write, call
  `read_document` and check the content, not only the tool's success.

### Permissions and side effects

* Needs `documents:read`.
* Rows you have no role on are dropped, never surfaced.
* A `conversation_id` or `pod_id` you cannot reach fails with
  `Artifact not found`, the same as a made-up id.
* Reads only. Recorded in the agent activity history.

### Errors

| Text                                                                     | Cause                           |
| ------------------------------------------------------------------------ | ------------------------------- |
| `Tool failed: \`scope\` must be mine \| shared \| pod \| org\`           | Unknown scope                   |
| `Tool failed: Pass only one of conversation_id, pod_id or scope (got …)` | Two selectors passed together   |
| `Tool failed: … Artifact not found`                                      | Unreachable conversation or pod |

### Example

**Request**

```json theme={null}
{
  "name": "list_documents",
  "arguments": { "scope": "pod", "query": "launch", "limit": 10 }
}
```

**Response (structuredContent)**

```json theme={null}
{
  "documents": [
    {
      "document_id": "k97fq2example000000000000000000",
      "title": "Q3 launch plan",
      "type": "document",
      "status": "active",
      "characters": 4820,
      "created_at": 1757100000000,
      "last_edited_at": 1757160729000,
      "version": 41,
      "conversation_id": "j52launchroom0000000000000000000",
      "pod_id": "p31growthpod00000000000000000000",
      "scope": "pod",
      "role": "editor",
      "owner_label": "Priya Raman"
    }
  ],
  "truncated": false,
  "next_cursor": null
}
```

## read\_document

**Scope:** `documents:read` (step-up) · **Kind:** read-only · **Annotations:** `readOnlyHint: true`, `destructiveHint: false`, `openWorldHint: false`

> Read a document as markdown (an HTML page returns its source). ALWAYS
> returns `version` — the token edit\_document's expected\_version and
> replace\_all writes are checked against. Long documents window: pass
> `offset` to continue past 24000 characters.

### Parameters

<ParamField body="document_id" type="string" required>
  The artifact id.
</ParamField>

<ParamField body="offset" type="number" default="0">
  Character offset to start from. Minimum 0.
</ParamField>

<ParamField body="window" type="number" default="24000">
  Characters to return, 1 to 24,000. Clamped.
</ParamField>

### Returns

<ResponseField name="document_id" type="string" />

<ResponseField name="title" type="string" />

<ResponseField name="type" type="'document' | 'html_app' | 'table'" />

<ResponseField name="markdown" type="string">
  The requested slice. For an `html_app` this is the page's HTML source.
</ResponseField>

<ResponseField name="version" type="number">
  The document's current version. Pass it as `expected_version` to
  `edit_document`.
</ResponseField>

<ResponseField name="size_chars" type="number">Total length of the full text.</ResponseField>
<ResponseField name="offset" type="number">The offset that was applied.</ResponseField>

<ResponseField name="truncated" type="boolean">
  `true` when text remains after this slice. Call again with
  `offset = offset + window`.
</ResponseField>

<ResponseField name="archived" type="boolean" />

<ResponseField name="parent" type="object">
  Present when the artifact is linked to a conversation:
  `{ "conversation_id": "<id>" }`.
</ResponseField>

The text block is:

```text theme={null}
# <title>
(version <n>[, showing <from>–<to> of <total> chars — pass offset to continue])

<markdown>
```

### Permissions and side effects

* Needs `documents:read` and at least the `viewer` role on the artifact.
* Reads only. Recorded in the agent activity history.

### Errors

| Text                                  | Cause                                              |
| ------------------------------------- | -------------------------------------------------- |
| `Tool failed: … Artifact not found`   | Not visible to you, wrong workspace, or no such id |
| `Artifact not found` (isError result) | The artifact exists but has no readable projection |

### Limitations

* Comment marks and other editor-only annotations are not part of the
  markdown.
* For a `table`, the returned text is whatever markdown projection the
  table exposes; tables cannot be edited through MCP.
* Reading a page here returns raw HTML. [`read_page`](/reference/pages#read_page)
  is the page-specific tool and returns the same `version`.

### Example

**Request**

```json theme={null}
{ "name": "read_document", "arguments": { "document_id": "k97fq2example000000000000000000" } }
```

**Response (structuredContent)**

```json theme={null}
{
  "document_id": "k97fq2example000000000000000000",
  "title": "Q3 launch plan",
  "type": "document",
  "markdown": "## Goals\n\n- Ship the Acme integration by 15 October\n- Onboard three design partners\n\n## Risks\n\n- Vendor SDK still in beta\n",
  "version": 41,
  "size_chars": 118,
  "offset": 0,
  "truncated": false,
  "archived": false,
  "parent": { "conversation_id": "j52launchroom0000000000000000000" }
}
```

## create\_document

**Scope:** `documents:write` (step-up) · **Kind:** write · **Annotations:** `readOnlyHint: false`, `destructiveHint: false`, `idempotentHint: false`, `openWorldHint: false`

> Create a new collaborative document, optionally filled with your markdown.
> Either in a conversation (`conversation_id` — its card is posted to the
> room) or in the person's library with a `scope`: `user` (private to them,
> the default), `pod` (needs `pod_id`), or `org` (every workspace member can
> edit). The caller becomes its owner. Returns {document_id, version}. Pass
> `idempotency_key` to make retries safe: a repeat with the same key within
> 10 minutes returns the original document instead of creating a twin.

### Parameters

Pass **either** `conversation_id` **or** `scope` (with `pod_id` when the
scope is `pod`), never both.

<ParamField body="title" type="string" required>
  The document's title. Must be non-empty.
</ParamField>

<ParamField body="conversation_id" type="string">
  Create inside this conversation. A document card is posted to the room.
</ParamField>

<ParamField body="scope" type="'user' | 'pod' | 'org'" default="user">
  Where the document lives when no `conversation_id` is given. `user` is
  private to the caller; `pod` needs `pod_id`; `org` is editable by every
  workspace member. No card is posted anywhere; the document gets its own
  chat.
</ParamField>

<ParamField body="pod_id" type="string">
  Required with `scope: "pod"`.
</ParamField>

<ParamField body="content_markdown" type="string">
  Initial body. Written verbatim, up to 60,000 characters. Whitespace-only
  content is treated as empty.
</ParamField>

<ParamField body="idempotency_key" type="string">
  Any string you choose. A second call with the same key within 10 minutes
  returns the original document (`deduplicated: true`) instead of creating
  another. Keyed on the key only, never the title, so two deliberate creates
  with one title both succeed.
</ParamField>

### Returns

<ResponseField name="document_id" type="string" />

<ResponseField name="title" type="string" />

<ResponseField name="version" type="number">
  `0` for an empty document; otherwise the version after the initial fill.
  On a deduplicated retry this is present only when the retry filled in
  missing content.
</ResponseField>

<ResponseField name="deduplicated" type="boolean">
  Present and `true` when an `idempotency_key` matched a recent create.
</ResponseField>

<ResponseField name="healed" type="boolean">
  Present and `true` when a deduplicated retry found the original empty and
  filled it with `content_markdown`.
</ResponseField>

Text: `Created "<title>" (document_id: <id>, version <n>).` or, on a
retry, `Document already created: "<title>" (document_id: <id>). Returning the original instead of creating a duplicate — …`.

### Permissions and side effects

* Needs `documents:write`.
* With `conversation_id`: you must be able to participate in that
  conversation. The gate runs inside the same transaction as the insert.
* With `scope: "pod"`: you must be a member of the pod.
* The caller becomes the owner. The initial fill is a checkpointed write
  attributed to you via the host, and appears in
  `get_document_versions`.
* With `conversation_id`, a card lands in the room. With a `scope`, nothing
  is posted anywhere.

### Errors

| Text                                                                                                                    | Cause                                                                                                |
| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `Tool failed: Pass either \`conversation\_id\` or \`scope\`, not both\`                                                 | Both targets given                                                                                   |
| `Tool failed: \`scope\` must be user \| pod \| org\`                                                                    | Unknown scope                                                                                        |
| `Tool failed: \`pod\_id\` is required for scope pod\`                                                                   | Pod scope without a pod                                                                              |
| `Tool failed: … Artifact not found`                                                                                     | Conversation or pod you cannot reach                                                                 |
| `That conversation is archived or gone, so nothing was created.`                                                        | Archived room                                                                                        |
| `A retried create found the original document … but it is busy …` with `error: "write_conflict"`, `retry_after_seconds` | A deduplicated retry could not confirm the initial content because another writer holds the document |

### Example

**Request**

```json theme={null}
{
  "name": "create_document",
  "arguments": {
    "scope": "pod",
    "pod_id": "p31growthpod00000000000000000000",
    "title": "Acme integration kickoff notes",
    "content_markdown": "## Attendees\n\n- Priya Raman\n- Jordan Lee\n\n## Decisions\n\n- Sandbox credentials by Friday\n",
    "idempotency_key": "kickoff-notes-2026-09-06"
  }
}
```

**Response (structuredContent)**

```json theme={null}
{
  "document_id": "k41kickoff0example0000000000000000",
  "title": "Acme integration kickoff notes",
  "version": 1
}
```

## edit\_document

**Scope:** `documents:write` (step-up) · **Kind:** write, destructive · **Annotations:** `readOnlyHint: false`, `destructiveHint: true`, `idempotentHint: false`, `openWorldHint: false`

> Write verbatim markdown into a document while humans may be editing it
> live. Modes: `append` (add at the end), `replace_section` (replace the
> region an EXACT `section_excerpt` from read\_document resolves to — a stale
> excerpt fails loudly with candidates, it can never clobber), `replace_all`
> (whole body; conflicts if `expected_version` moved), `fill_empty` (only if
> nobody typed yet). Human edits made mid-write are KEPT and reported in
> `kept_blocks`, never overwritten. Every write is checkpointed and undoable.
> `dry_run` resolves the target and writes nothing.

### Parameters

<ParamField body="document_id" type="string" required />

<ParamField body="mode" type="'append' | 'replace_section' | 'replace_all' | 'fill_empty'" required>
  What to do with `content_markdown`.
</ParamField>

<ParamField body="content_markdown" type="string" required>
  The markdown to write, verbatim. Up to 60,000 characters; longer content
  stops early with `stop_reason: "cap"`.
</ParamField>

<ParamField body="section_excerpt" type="string">
  `replace_section` only, and required there. An exact passage quoted from
  `read_document`. It is matched whitespace-insensitively and resolved to
  the contiguous blocks it covers; those blocks are replaced.
</ParamField>

<ParamField body="expected_version" type="number">
  The `version` a prior `read_document` returned. **Required for
  `replace_all`**, where a mismatch is a hard conflict. On other modes it is
  optional and only reports drift.
</ParamField>

<ParamField body="dry_run" type="boolean" default="false">
  Resolve the target and version checks, write nothing.
</ParamField>

### How `expected_version` is tiered

| Mode              | Freshness check                     | On mismatch                                                                                                             |
| ----------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `replace_all`     | `expected_version` (required)       | Refused: `version_conflict` with `current_version` and, when a checkpoint reaches back that far, `blocks_changed_since` |
| `replace_section` | The excerpt itself must still match | Write proceeds; `version_drift: true` and `blocks_changed_since` are reported                                           |
| `append`          | None                                | Write proceeds; drift reported if `expected_version` was passed                                                         |
| `fill_empty`      | The document must be empty          | Refused: `not_empty`                                                                                                    |

### Returns

On success:

<ResponseField name="new_version" type="number">The document's version after the write.</ResponseField>

<ResponseField name="written_chars" type="number" />

<ResponseField name="stopped_early" type="boolean">Present when the write did not complete.</ResponseField>

<ResponseField name="stop_reason" type="'cap' | 'anchor_gone'">
  Why it stopped. `cap`: the 60,000-character budget was hit; continue with
  another call. `anchor_gone`: someone removed the section being written
  into; do not re-add it.
</ResponseField>

<ResponseField name="next_step" type="string">What the agent should do about the early stop.</ResponseField>
<ResponseField name="version_drift" type="boolean">Present when `expected_version` no longer matched (non-`replace_all` modes).</ResponseField>
<ResponseField name="blocks_changed_since" type="number">How many blocks changed since `expected_version`, when known.</ResponseField>

<ResponseField name="kept_blocks" type="object">
  Present when a person edited blocks while this write ran. Their edits are
  kept next to yours: `{ "count": n, "excerpts": ["…"] }`.
</ResponseField>

On `dry_run`:

```json theme={null}
{ "dry_run": true, "mode": "replace_section", "resolved": true, "block_ids": ["…"], "current_version": 41, "version_drift": true }
```

Text: `Wrote <n> characters (<mode>); the document is now at version <v>.` followed by
`Stopped early (<reason>).` and/or `Kept <n> block(s) a person edited while you were writing — their text stands next to yours.` when applicable.

### Permissions and side effects

* Needs `documents:write` and the `editor` or `owner` role.
* Checkpoints the document before writing; the checkpoint appears in
  `get_document_versions` and is undoable in the app.
* Takes a short write lease. Concurrent agent writers queue briefly, then
  receive `write_conflict` with a retry hint.
* Attributed to you via the host on version rows and cursors.
* Works only on `document` artifacts; pages and tables are refused with a
  message naming the right tool.

### Errors

| `error`             | Text (abridged)                                                                                                                                 | What to do                                                     |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| —                   | `Tool failed: \`mode\` must be append \| replace\_section \| replace\_all \| fill\_empty\`                                                      | Fix the mode                                                   |
| —                   | `Tool failed: \`expected\_version\` is required for replace\_all — …\`                                                                          | Read first, pass the version                                   |
| —                   | `Tool failed: \`section\_excerpt\` is required for replace\_section\`                                                                           | Quote a passage                                                |
| —                   | `That artifact is an HTML page, not a document — use write_page.`                                                                               | Use `write_page`                                               |
| —                   | `That artifact is a table, not a document. …cannot be edited through this tool.`                                                                | Tables are not editable                                        |
| `excerpt_not_found` | `The excerpt did not match the current document — it may be stale. … Nearest passages: "…" \| "…". Re-read the document and quote one exactly.` | Re-read; use a `candidates[].excerpt` (each with `similarity`) |
| `excerpt_ambiguous` | `The excerpt matches N places — quote a longer passage that names exactly one: …`                                                               | Quote more context; `locations[].excerpt` shows each match     |
| `version_conflict`  | `The document moved from version A to B since you read it. …`                                                                                   | Re-read, merge, retry with the new version                     |
| `not_empty`         | `The document already has content, so fill_empty wrote nothing. …`                                                                              | Read it, then append or replace                                |
| `selection_gone`    | `The targeted section no longer exists, so nothing was written. …`                                                                              | Re-read and resolve a fresh excerpt                            |
| `write_conflict`    | `Another agent is writing in that document right now. Retry in ~Ns.`                                                                            | Wait `retry_after_seconds`                                     |
| —                   | `Tool failed: … This document is read-only for you`                                                                                             | You are a viewer                                               |
| —                   | `Tool failed: … You can comment on this document but not edit it`                                                                               | You are a commenter                                            |
| —                   | `Tool failed: … This document is archived`                                                                                                      | Archived                                                       |
| —                   | `Tool failed: … Artifact not found`                                                                                                             | Not visible, wrong workspace or no such id                     |

A failed `replace_section` is **never** degraded into an append.

### Recovering from refusals

| `error`                                 | Recovery                                                                                   |
| --------------------------------------- | ------------------------------------------------------------------------------------------ |
| `excerpt_not_found`                     | Re-read. Pick from `candidates[].excerpt`, ranked by `similarity`, and quote one exactly.  |
| `excerpt_ambiguous`                     | Quote a longer passage; `locations[].excerpt` shows each match with context.               |
| `version_conflict` (`replace_all` only) | Re-read, merge your change onto the current text, write again with `current_version`.      |
| `write_conflict`                        | Another agent holds the write lease. Wait `retry_after_seconds`, then retry the same call. |
| `not_empty` (`fill_empty` only)         | The document has content. Read it and use `append` or `replace_section`.                   |

Retrying without duplicating: a result with `stopped_early: true` wrote part
of the content, so re-read and append only what is missing rather than the
whole payload again. A call that timed out with no result may still have
landed; read before retrying. A `version_conflict` on `replace_section` is
not a stale excerpt: the excerpt still resolves, so the retry is the same
call with the fresh `version`.

### Example

**Request**

```json theme={null}
{
  "name": "edit_document",
  "arguments": {
    "document_id": "k97fq2example000000000000000000",
    "mode": "replace_section",
    "section_excerpt": "## Risks\n\n- Vendor SDK still in beta",
    "content_markdown": "## Risks\n\n- Vendor SDK still in beta (GA promised 1 October)\n- Design-partner availability over the holidays",
    "expected_version": 41
  }
}
```

**Response (structuredContent)**

```json theme={null}
{
  "new_version": 43,
  "written_chars": 111,
  "version_drift": true,
  "blocks_changed_since": 1,
  "kept_blocks": {
    "count": 1,
    "excerpts": ["- Onboard three design partners (Jordan owns)"]
  }
}
```

Here a person edited the Goals list while the agent was writing; the version
had moved to 42 before the write, the excerpt still matched, and the
person's edit was kept.

## get\_document\_versions

**Scope:** `documents:read` (step-up) · **Kind:** read-only · **Annotations:** `readOnlyHint: true`, `destructiveHint: false`, `openWorldHint: false`

> Recent versions of a document, newest first — automatic checkpoints (every
> agent write checkpoints before touching it, so this is the undo history)
> and named versions a person saved (`pinned: true`, with their `label`;
> never pruned). Metadata only; restoring and naming stay human acts in the
> app.

### Parameters

<ParamField body="document_id" type="string" required />

### Returns

<ResponseField name="versions" type="array">
  The 20 most recent rows, newest first.

  <Expandable title="row">
    <ResponseField name="version_id" type="string">Identifier of the checkpoint row. No tool takes it as input.</ResponseField>
    <ResponseField name="doc_version" type="number">The document version this checkpoint captured.</ResponseField>

    <ResponseField name="reason" type="'agent_edit' | 'edit' | 'restore' | 'export' | 'named'">
      Why the row exists: an agent write's checkpoint, a person's editing
      session, a restore, an export snapshot, or a named version.
    </ResponseField>

    <ResponseField name="label" type="string">A person's name for a named version.</ResponseField>
    <ResponseField name="pinned" type="boolean">`true` for a named version: kept forever and restorable in the app.</ResponseField>
    <ResponseField name="author_label" type="string">Who triggered it, when known.</ResponseField>
    <ResponseField name="instruction" type="string">The instruction that produced the write, for example `MCP edit_document (replace_section)`.</ResponseField>
    <ResponseField name="created_at" type="number">Milliseconds since epoch.</ResponseField>
    <ResponseField name="kept_blocks" type="object">Present when human edits were kept during that write: `{ count, excerpts[] }`.</ResponseField>
  </Expandable>
</ResponseField>

Text, one line per row:

```text theme={null}
- [★ "<label>" · ]<ISO time> · v<doc_version> · <reason> · <author> · "<instruction>"[ · kept <n> human-edited block(s)]
```

or `No versions yet.`

### Permissions and side effects

* Needs `documents:read` and any role on the artifact.
* Reads only. Recorded in the agent activity history.

### Errors

| Text                                | Cause                                      |
| ----------------------------------- | ------------------------------------------ |
| `Tool failed: … Artifact not found` | Not visible, wrong workspace or no such id |

### Limitations

* Only the 20 most recent rows are returned; there is no paging.
* Automatic checkpoints are pruned over time in the app; named versions are
  not.
* There is no MCP tool to restore, name or diff versions.

### Example

**Request**

```json theme={null}
{ "name": "get_document_versions", "arguments": { "document_id": "k97fq2example000000000000000000" } }
```

**Response (structuredContent)**

```json theme={null}
{
  "versions": [
    {
      "version_id": "v88example000000000000000000000000",
      "doc_version": 41,
      "reason": "agent_edit",
      "pinned": false,
      "author_label": "Priya Raman · via Claude Code",
      "instruction": "MCP edit_document (replace_section)",
      "created_at": 1757160729000
    },
    {
      "version_id": "v87example000000000000000000000000",
      "doc_version": 30,
      "reason": "named",
      "label": "Reviewed with leadership",
      "pinned": true,
      "author_label": "Priya Raman",
      "instruction": "Named version",
      "created_at": 1757090000000
    }
  ]
}
```

<Note>
  `instruction` is a free-form label set by whatever produced the row; the
  values shown are illustrative.
</Note>
