Skip to content

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. Every block has an id of the form <session>.<counter>, such as 2kx9q1.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 .N after the first full one, so 2kx9q1.15 followed by .16 means 2kx9q1.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 as rev_expired, and the agent reads the outline again.

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), side is start or end. Next to a block it is before or after, and end on a heading means the end of its section: up to the next heading of the same or a higher level. Into a cell it is start or end, and next to quoted words before or after.
  • 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 comments tool.
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).

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. A mermaid code block is drawn as its diagram and a latex code 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}.
    • index is 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 enum form one set: its options and name are given once, and later chips name the enum alone. 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.

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.

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.

{
"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.

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