How agents edit
The model under an agent's edits in Entz, from XML reads, block ids and fingerprints to the six ops and their targets, guards, placeholders, chips and the answer to a write.
An agent never sends a document back whole. It reads it as XML, then sends a list of small edits, called ops, each naming what it changes by a block’s id or by quoted words. This page describes that model for the curious, and for anyone writing their own prompts or agents on top of the connector. The agent reads the same rules in the connector’s guide, the index topic of the guide tool, and The eight tools lists each tool’s parameters.
read gives a document, or one of its tabs, as one line of XML, in one of six kinds:
| Kind | Takes | Shows |
|---|---|---|
outline |
Every block, its text cut at 80 characters, tables folded. The default, and where the agent starts. | |
view |
id |
One block in full: for a table, its rows and cells. |
search |
text |
The blocks holding the words, case ignored, each match wrapped in <hit n>, the rest folded. A table shows only the rows and cells with a match. |
since |
rev |
The blocks changed since that revision, in full and where they now stand; the rest folded; removed blocks as <gone id/>. |
at |
rev |
The whole document as it was at that revision. |
full |
Every block in full. For short documents. |
Folded blocks show as <gap blocks='n'/>. The agent is told to start a later turn with a since read of its last revision, which shows at once what people changed in between. To bring back words someone deleted, it reads them with at and inserts them again, as a new block.
An outline of a new document, broken into lines here:
<doc rev='2' outline='true'> <heading id='2kx9q1.15' h='5c1e0a9b' level='1'>Q3 launch plan</heading> <paragraph id='.16' h='0f3d27e4'>Q3 ships the connector to every workspace.</paragraph> <pending id='.17' h='a90b1c44' intent='Goals: the three outcomes'/> <pending id='.18' h='61d2f0a3' intent='Staffing: who owns each workstream'/></doc>The block tags are paragraph, heading (with its level), list_item (with list, which is bullet, ordered or task, and indent and checked when set), quote, code (with its language), table (with rows and cols, holding row and cell elements, a cell with its background), image, equation, divider, drawing and pending (with its intent). Inside text, marks read as <b>, <i>, <u>, <s>, <code>, <a href>, <mark> (a highlight), <color> (a text colour) and <math>. A date chip reads as its label inside <date value='2026-10-06'>, and a dropdown as its pick inside <dropdown>, with its set, name, index and options.
Ids, fingerprints and revisions
Section titled “Ids, fingerprints and revisions”- Ids. Every block has an id of the form
<session>.<counter>, such as2kx9q1.15: the session label of whoever made the block, then a counter. The id stays with the block through edits, moves and changes of kind; splitting a block makes a new one. A read shortens a run of ids from one session to.Nafter the first full one, so2kx9q1.15followed by.16means2kx9q1.16. Writes always take the full id. - Fingerprints. Every block carries
h, eight hexadecimal digits computed from its kind, its attributes, its text with its marks, and its rows. Any change to the block’s words, marks, attributes or cells changes it; a move does not. - Revisions. A revision,
rev, counts the versions of a tab the connector has seen. It starts at 1 and rises whenever the tab changed since the connector last looked. Every read and every write’s answer names the current one. The connector keeps the newest 256 for each tab, and starts again at 1 when it restarts, or when it reopens a document it closed after 10 minutes unused. A revision it no longer holds is refused asrev_expired, and the agent reads the outline again.
Writes
Section titled “Writes”update takes 1 to 64 ops for one tab. They apply in order and land as one change: when a call holds several ops, the connector tries them on a copy of the document first, so a refusal anywhere means nothing lands. The change is signed as the agent’s, with its name and the time, which is how editors know to fade its words in.
| Op | Fields | What it does |
|---|---|---|
insert |
target, side, source |
Adds blocks at the start or end of the tab, before or after a block, at the end of a heading’s section, or into a table cell. With quoted words as the target and a source as: "text", adds words before or after them. |
replace |
target, with, a guard |
Swaps whole blocks for the source’s blocks, re-words one block in place, changes quoted words, or replaces a cell’s contents. |
delete |
target, a guard |
Removes whole blocks, or quoted words. A tab keeps at least one block. |
move |
target, to |
Moves top-level blocks to a place given as {"target": …, "side": …}. |
set |
target, attrs |
Sets attributes on blocks, such as {"kind": "heading", "level": 2}, {"list": "task"} or {"checked": true}; null removes one. On a cell, sets its background. |
format |
target, add, remove |
Adds or removes marks on blocks or quoted words: bold, italic, underline, strike, code, link, highlight, color, math. A mark with a value is written {"link": "https://…"} or {"highlight": "yellow"}, and any mark may be written {"type": "bold"}. |
A few rules keep the ops predictable:
- Sides. On the tab (
root),sideisstartorend. Next to a block it isbeforeorafter, andendon a heading means the end of its section: up to the next heading of the same or a higher level. Into a cell it isstartorend, and next to quoted wordsbeforeorafter. - Re-wording in place. Replacing one block
as: "text"keeps the block: its id, its unchanged words and the comments on them stay. One paragraph replaced by one paragraph as Markdown is re-worded the same way, then takes the new paragraph’s marks and chips. A heading, a list, or several blocks are replaced as new blocks. - Quoted words. Replaced words keep the marks of the first word replaced. Deleted words are quoted exactly, a leading space included.
- Tables. A table never goes inside a cell. Blocks inside cells do not move: the table moves. A move’s place cannot be one of the blocks moved, a block inside a table or a cell.
- Not marks. Dates and dropdowns are chips, and comments are made with the
commentstool.
Targets
Section titled “Targets”| Target | Names |
|---|---|
{"kind": "blocks", "ids": ["2kx9q1.16"]} |
Blocks, by full id. |
{"kind": "find", "text": "every workspace"} |
Words within one block, quoted exactly as a read shows them, without Markdown and with the same capitals. nth, counted from 1, picks one of several matches; within limits the search to one block, a table or one cell; parentId limits it to the section under a heading, or to one block. |
{"kind": "root"} |
The tab itself, for inserts at its start or end. |
{"kind": "cell", "table": "2kx9q1.30", "row": 1, "col": 2} |
A table cell by position, counted from 0 with row 0 the header row, or from the end with -1 for the last. A cell takes replace (its contents), insert and set (its background: gray, red, orange, yellow, green, teal, blue, purple, pink or #rrggbb, null for none). |
Sources
Section titled “Sources”The content an op writes is a source: {"from": {"kind": "inline", "content": …}, "as": …}, where as is one of:
markdown: headings, lists, task lists, quotes, tables, code blocks and links. Amermaidcode block is drawn as its diagram and alatexcode block as its formula. Markdown also carries chips.text: plain text.blocks: blocks in Entz’s own JSON form.
A chip is a <?entz block key?> token in Markdown, with its entry under the source’s blocks (for create and a new tab, the call’s own blocks), one key per chip. Every token needs an entry, and every entry a token.
- Block chips stand alone on their line:
{"type": "pending", "intent": "…"}, a placeholder;{"type": "paragraph"}, an empty paragraph; and{"type": "divider"}. - Date chips go anywhere in text, table cells included:
{"type": "date", "value": "2026-10-06"}, shown as its label, such as Oct 6, 2026. - Dropdown chips go anywhere in text too:
{"type": "dropdown", "enum": "status", "name": "Status", "options": ["Not started", "In progress", "Done"], "index": 1}.indexis the pick, counted from 0; it is left out when people will pick.- An option is a name or
{"name": …, "color": …}, the colour one of those a cell takes. An option without one takes the colour of its place in the list. - Chips naming the same
enumform one set: itsoptionsandnameare given once, and later chips name theenumalone. A chip nobody picked from reads as the set’s name, or Select when the set has none. - The agent changes a pick by replacing the chip.
Guards
Section titled “Guards”A replace or delete of whole blocks, and a replace of a whole cell by position, needs a guard, or it is refused as missing_guard:
| Guard | On | Refused when |
|---|---|---|
"ifHash": "<h>" |
one block | The block’s fingerprint is no longer that one. |
"ifRev": <rev> |
blocks | Any of them changed since that revision. A move does not count. |
"ifRev": <rev> |
a cell by position | The table’s rows or columns changed since that revision (the position may name another cell now), or the cell’s words changed. |
"force": true |
blocks or a cell | Never. The agent is told to use it only when you asked it to overwrite whatever is there. |
Placeholders and quoted words need no guard: a quote touches only its own words, and is refused if they are gone. Beside the ops, a write-level ifRev refuses the whole write unless the tab is still at that revision.
Placeholders
Section titled “Placeholders”A placeholder is a block of kind pending with an intent, which the editor draws as the intent in grey brackets. The agent plants one per section, usually through create, then replaces each with its section, one update per section. Replacing one needs no guard, and the answer lists it under pending_ended. The guide asks the agent to fill them in reading order, the prose first and the diagrams after: a diagram takes longer to write, and its placeholder tells readers it is coming. An intent can plan the picture, as in “Rollout: a gantt of the milestones by team”. Placeholders are left out of Markdown, HTML, Typst and plain text exports.
What a write answers
Section titled “What a write answers”{ "rev": 3, "session": "2kx9q1", "xml": "<doc rev='3'><op n='0'><heading id='2kx9q1.42' h='8d02c6a1' level='2'>Goals</heading><list_item id='.43' h='31e7f0b2' list='bullet'>Ship to every workspace</list_item><list_item id='.44' h='c90a5d17' list='bullet'>500 weekly active teams</list_item></op></doc>", "notices": [{ "notice": "pending_ended", "ids": ["2kx9q1.17"] }], "last": "2kx9q1.44", "synced": true, "link": "https://entz.app/doc/…"}| Field | Holds |
|---|---|
rev |
The tab’s revision after the write. |
session |
The writer’s session label, which starts every id it made. |
xml |
The blocks each op wrote, as a read shows them, op by op. |
keys |
Each block chip’s key and the full ids it became, such as {"s1": ["2kx9q1.40"]}. Dates and dropdowns are marks on their label, so they have none. |
notices |
pending_ended, with the placeholders the write replaced, and detached, with the comment threads left without their words. |
last |
The last block written, where the agent’s caret rests. |
synced |
Whether Entz confirmed the write within 15 seconds. |
link |
The document’s link. |
The agent aims its next op at the ids in the answer, without reading again between its own writes.
Limits
Section titled “Limits”| What | Limit |
|---|---|
Ops in one update |
64 |
| Text an outline shows per block | 80 characters |
| Revisions kept for each tab | the newest 256 |
| Time a write waits for Entz to confirm it | 15 seconds |
| Words that fade in per write | 300; the rest appear at once |
| A document the connector leaves unused | closed after 10 minutes |
| The agent’s presence after its last work on a document | 90 seconds |
| One write’s changes on the server | 1 MiB |
| A comment’s text | 4,096 bytes |
| Comment threads on one document or tab | 1,000, resolved ones included |
| Comments in one thread | 100 |
| A tab’s name | 200 characters |