oto_doc
Docs (markdown pages tree inside a project; inherit the project's access — except ONE page shared on its own, see shared_with_me). A reference page is a DOC, in the PROJECT it belongs to (that project's « Documents » zone in the dashboard): CAPTURE the sourced facts of a piece of work there (kind=source/note) as you learn them, and search it before the web. How the org works (rules, conventions) belongs in its guide (oto_guide, read back by oto_context), and what concerns the person in their profile card (oto_profile) — not in a page. op=create (project_id, title; optional parent_id/body_md/kind) / bulk_create (project_id + pages=[{title, body_md?, kind?, description?, parent_index? | parent_id?}] → N pages in ONE call, build a tree via parent_index = an earlier page in the batch, or hang a page under an existing one via its parent_id; a top-level parent_id is the default parent of the batch; any other page key is refused) / list (project_id → the page INDEX, build the tree via parent_id: titles and body_md_length, NOT the bodies — pick a page here, then op=get it. fields=["*"] returns whole pages, fields=[…] picks columns) / search (project_id + query → full-text hits {id,title,kind,snippet}: LOCATE a page, then get its content) / get (the whole page, incl. rev, an ETag; pass fields=[…] to read ONLY those columns — fields=["id","rev"] gets the rev for an optimistic patch without paying for the body; section=a heading, or region="preamble", reads ONLY that region, addressed exactly as op=patch addresses it — heading and sub-sections included) / outline (doc_id → the headings: level, line, length of each section — pick the one to get or patch without reading the page) / update (title/body_md/kind, full body; snapshots the prior version; pass expected_rev from op=get for optimistic conflict detection → 409 if the page changed since) / patch (edit ONE region in place, WITHOUT re-emitting the page — this is how you edit a page too long to re-send: mode replace|append|prepend|delete, and ONE target, either section=its markdown heading + body_md = that section's BODY, WITHOUT repeating the heading (the server keeps it) — matched on the heading TEXT, level and case ignored: when several headings match, the patch is REFUSED with their list (never applied to the first), OR region="preamble" = everything ABOVE the first heading (provenance banner, "Last verified" line, front-matter) — it belongs to no section, so no section value can ever reach it; that is a SEPARATE axis, never a reserved heading name like "preamble" (a page may legitimately have such a heading, and it stays reachable via section). Passing both, or neither, is refused. mode=delete removes the target INCLUDING its heading (pass no body_md) — the only way to drop a heading without rewriting the page; to merely empty a section and keep its heading, use mode=replace with an empty body_md. Two authors on different regions don't clobber; every mode honours expected_rev and snapshots a revision. SCOPE: a section runs to the next heading of EQUAL-OR-HIGHER level, so its NESTED sub-sections are part of it — replacing OR deleting a ### also takes its #### children (the response then lists removed_subsections). To keep them, target the sub-heading itself or use mode=append. replace/delete responses say what went under removed (line bounds, line_count, subsections) — lines after a heading with no heading between belong to it; dry_run: true writes nothing and returns that same removed plus the rev to pass as expected_rev) / A SUCCESSFUL WRITE (create/update/patch/move) returns a RECEIPT, not the page: id, title, url, rev, updated_at and body_md_length — you just wrote the body, so it is not replayed back at you. Add fields=["*"] if you really want the stored page back, or fields=[…] to pick columns. / A page's description is a chapô you STORE: leave it out and the index DERIVES one from the first prose line of the body (marked description_derived), so it moves with every body edit — that is not an overwrite. Pass description explicitly to pin one that stops following the body. / EVERY page carries url — the web address to READ it, in the reader's own product. That is the answer to "where is it?": hand it over as-is, never rebuild an address from a pattern. null means that reader's product has no such view — then say where it lives (project + title) rather than invent a link. / revisions (doc_id → version history, newest first; each row's id is what op=revert takes) / revert (doc_id + revision_id from op=revisions → puts that past title+body back). A revert moves FORWARD: the current state is snapshotted first, so nothing is lost and a revert can itself be reverted; the response echoes reverted_from. It honours expected_rev too — pass it or you may silently overwrite a peer's edit. It restores a VERSION of a page that still exists; it does NOT undo a delete (a deleted page took its revisions with it) / backlinks (doc_id → the pages that CITE this one). LINK PAGES with [[Exact page title]] in body_md — that wiki-link is the ONLY thing that creates a backlink (prose mentions, text and text create none). Resolved AT WRITE TIME against the page's own project first, then every project the ORGANIZATION owns — never a team project, a personal project or another organization's — case- and edge-space-insensitive. A title carried by pages of SEVERAL org projects (none in the page's own) is AMBIGUOUS: nothing is linked and the write lists the candidates under citations_ambigues — make the title unique rather than guessing. A title that doesn't exist yet is kept as a stub and links itself once the page is created or renamed. ⚠️ That is the reach of RESOLUTION, not of the graph, and they differ BOTH ways. (a) The graph is not symmetric: a page of a team or personal project resolves into the org's projects, but no page of an org project can ever link to it. A page can therefore be cited and still read as an orphan here: do not use backlinks as a completeness or orphan check without knowing that. (b) op=backlinks shows every STORED link whatever its project, including one left behind by a page MOVED between projects — no resolution would make it today, and it disappears, silently, the next time the citing page is written. So a cross-project backlink is not proof that the same [[…]], written now, would resolve. (c) The list is filtered by YOUR access: citations living in projects you cannot read are removed. When that happens the response says hidden_by_access: true — « nobody cites this page » and « three pages cite it, you cannot see them » call for opposite moves, so the second is never reported as the first. The COUNT of hidden ones is deliberately not given: it would tell you how many pages exist in projects that are closed to you. Every write says which of its [[…]] found nothing, under citations_sans_cible / shared_with_me (→ the pages shared WITH YOU one by one — to you, your org or your team, via oto_resource op=share resource_type="doc": {id, title, updated_at, role, via, shared_by, url}; scope narrows it: me = shared with you as a person, org = shared with the organization you act in and your teams in it (in your personal org, to you as well); omitted = all of it, across all your organizations; served in any organization. Such a page is readable with op=get ALONE: its project, sibling pages, sub-pages, revisions and backlinks stay closed, and it is read-only) / archive (doc_id → the page AND its live sub-pages leave oto_search and op=list — include_archived: true lists them — but stay readable with op=get; nothing is destroyed; unarchive undoes that same gesture) / verify (doc_id → stamps verified_at/verified_by = re-read and still true; body, updated_at and versions untouched) / set_public (public: true → shareable public read-only link to THIS PAGE ALONE: the reader gets its title and body, and nothing else — not the project, not the sibling pages, not this page's own sub-pages, which each need their own link ; false → private ; returns public_url. Opening takes being a member of the org that owns the project — the parent org of an owning team, the owner themself for a personal project — or 403 publish_requires_membership; closing stays open to whoever writes) / delete (removes the page AND its whole subtree, revisions included — irreversible, there is no trash and no undelete. The response says how many pages went with it (descendants); ask FIRST with dry_run: true, which deletes nothing and returns the same count, whenever a human has to confirm) / move (reparent/reorder in-project via parent_id [null=top-level] + position; OR cross-project via to_project=target project id → moves the page AND its subtree there, write required on both. ⚠️ A move is NOT free for links: the page's own [[…]] are re-resolved in the TARGET project (some become stubs), while the links pointing AT it are left stored though now out of reach — they still show in op=backlinks and die on the citing page's next write. After reorganising a tree, rewrite the citing pages and read their citations_sans_cible). kind ∈ doc|note|source. EMBED A LIVE DATASTORE in a page body with a fenced block oto-data<newline><namespace-name-or-id><newline> → the viewer renders that datastore's table LIVE (always up to date). Prefer this over a hand-typed summary table when the data lives in a datastore (single source of truth, no drift).
Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"op": "<op>"
},
"name": "oto_doc"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| op | Yes | ||
| _org | No | ||
| kind | No | ||
| mode | No | ||
| pages | No | ||
| query | No | ||
| scope | No | shared_with_me only: `me` = pages shared with YOU as a person; `org` = pages shared with the organization you are acting in (and your teams in it) — in your PERSONAL org, those shared with you as a person too. Omitted = both, across all your organizations. Served in any organization. | |
| title | No | ||
| doc_id | No | ||
| fields | No | Output projection on list/get/create/update/patch/move (refused elsewhere). Omitted, a list returns its index and a write its receipt; `["*"]` returns the whole page everywhere; a list of names picks columns. | |
| public | No | ||
| region | No | ||
| _run_id | No | OBLIGATOIRE à chaque appel dès `run_start` ou `data_claim_next` : sans lui, l'écriture sur une ligne réservée est refusée. Hérité seulement si le serveur tient un run actif — n'y compte pas. | |
| body_md | No | ||
| dry_run | No | ||
| section | No | ||
| position | No | ||
| parent_id | No | ||
| project_id | No | ||
| to_project | No | ||
| description | No | ||
| revision_id | No | ||
| expected_rev | No | ||
| include_archived | No |