{"slug": "designing-edit-operations-for-ai-agents", "title": "Designing Edit Operations for AI Agents", "summary": "IWE, a markdown knowledge base for AI agents, introduced a block-editing language to address structural malformations and rising curation costs observed in benchmarks. The language, which includes operations like find, append, and replaceText with strict guards on the MCP surface, enforces identity constraints and canonicalization rules to prevent duplicate entries. The design lessons emphasize state blast radius, identity as a constraint, recoverable mistakes, and treating error messages as documentation for agents.", "body_md": "*Four lessons from building IWE's block-editing language for LLM writers: state the blast radius, make identity a constraint, fail toward the recoverable mistake, and treat error messages as the documentation agents actually read.*\n\nWhile benchmarking IWE as memory for AI agents (the full story is in [📝 The Benchmark That Built the Tools](https://iwe.md/blog/the-benchmark-that-built-the-tools)), we watched LLM curators maintain a markdown knowledge base across hundreds of writes, and the failure statistics kept pointing at one design flaw. Adding a single fact to a page meant re-emitting the whole document — and an agent re-emitting a full document under pressure will eventually misplace a frontmatter block. In our first benchmark round, 9% of the store's event pages came out structurally malformed, and curation cost grew with the store, because every small edit paid for the whole file.\n\nThe fix became a language: **block operations**. IWE's query language could already select and shape *documents*; we extended it to address the structural nodes inside them — headers, paragraphs, list items, code blocks, tables — with one grammar used everywhere:\n\n`find --matches '(?i)todo'`\n\ngreps every block in the store and answers with `key › section path › text`\n\nlines. `find --blocks PRED`\n\nreturns matching blocks as structured data.`$append`\n\na line under a section, `$replaceText`\n\none line, `$insertAfter`\n\na block — each operator carrying its own selection predicate, validated fully before anything is written.Designing this *for agents* forced decisions that a human-oriented tool would never make. Four are worth recording.\n\nEach operation can carry an `expect`\n\nguard — \"this edit should touch exactly one target\" — and the operation fails loudly, listing what it actually selected, if reality disagrees. We then went a step further and split enforcement by surface: on the CLI, guards are opt-in (`--strict`\n\n), because a human with git behind them shouldn't pay ceremony; over MCP — the agent surface — **strict is always on**, because the population most likely to skip the guard is exactly the one that needs it.\n\nThe workflow this creates is *locate, count, pin, mutate*: find your targets, learn the count, write it into the edit, and the store refuses to run an edit whose shape surprises you.\n\nA document's key is a primary key: creating at an existing key is a conflict error, not a silent second copy. Let the agent set the key and derive it from metadata — the entity's name, the session's date — and a whole failure class disappears structurally: the same event cannot be recorded twice, a crashed ingest retries idempotently (skip-if-exists is an explicit mode, not a fallback you hope for), and cross-links become computable — a timeline line can point at `2024-05-21`\n\nwithout a lookup, before that page even exists. We learned this from its absence: when keys were minted from title wording, every retry and rephrasing was a fresh identity, and the store quietly accumulated near-duplicates that no prompt admonition prevented. Like `expect`\n\n, it converts a discipline (\"search before you write\") into an engine guarantee.\n\nThe guarantee is exactly as strong as the derivation is canonical, and we got a live demonstration of the boundary: the first automated run under the new scheme created a relationship page twice — `joanna-and-nate`\n\n*and* `nate-and-joanna`\n\n— because a pair of people has two spellings and the prompt hadn't pinned one. Both keys were unique; the identity wasn't. The fix is a canonicalization rule (names in alphabetical order), the same reason compound database keys fix a column order. A uniqueness constraint dedups identities, not intentions — the derivation has to leave the writer no choices.\n\nThat lesson is now a command. `iwe create`\n\nmakes identity explicit on every surface: content mode requires the key — it *is* the document's identity — and a collision fails by default, with `--if-exists`\n\nturning the retry policy into an argument (`skip`\n\nfor idempotent ingest, `suffix`\n\nor `override`\n\nonly when you name them). Template mode goes further and takes the derivation out of the writer's hands entirely: a configured `key_template`\n\n— `journal/{{today}}`\n\n, `people/{{slug}}`\n\n— mints the key from metadata, so the canonicalization rule lives in project config where the agent cannot re-decide it. And `--strict`\n\nrefuses to write a document that violates its bound schema — one more prompt admonition converted into an engine guarantee.\n\nOur first draft gave update operators subtree semantics — replace a header, get its whole section replaced. Hands-on testing showed this is a data-loss trap: an agent that \"renames\" a header by replacing it silently destroys everything beneath. The shipped semantics follow text-editing intuition instead: a header selection is the heading *line*; its contents re-attach and re-level, exactly as if you'd deleted the line in an editor. Destroying a whole section requires *naming* the section.\n\nThe failure modes now point in the safe direction — the recoverable mistake (leftover visible content) instead of the silent one (a vanished subtree).\n\nA guard violation prints the count, every selected target, and the fix:\n\n```\nerror: $replaceText expects 1 block, selected 2\n  jon › \"He is wrapping up his business plan and actively searching for investors…\"\n  jon-actively-searching-for-investors-27-may-2023 › \"Jon is actively searching for…\"\nhint: narrow with $within or $matches, or raise expect\n```\n\nWe watched agents recover from these errors in a single turn, using nothing but the message. When the strict layer names the two specific guards you're missing, a small model can drive a mutation language it has never seen.\n\nThe first store curated entirely through block operations had **zero structural defects** — the malformed-frontmatter class that hit 9% of pages in round one is unrepresentable when metadata travels through a targeted `$set`\n\n. Across hundreds of answering sessions where the agent *held a mutation-capable tool*, the store diff showed **zero stray writes**: strict mode held in the field, not just in tests.\n\nAnd when we swapped the frontier-model curator for Haiku — a small model at a fraction of the cost — the structural hygiene did not move: zero malformed pages, in every run, in every store since. Structural quality had become a property of the tool workflow, not of the model driving it. That result is what let the benchmark's final configuration put the cheap model on the writing side entirely.\n\nGuards beat instructions: an `expect`\n\nclause or a key-collision error catches what a prompt admonition never will. And enforcement should live at the *surface*, not the grammar — humans get freedom, agents get strictness, and the language means the same thing everywhere.\n\nThe benchmark that forced these designs — losing to grep, retracting our own best number, and ending with a $4.50 curator reading at 96% of a hand-built ceiling — is written up in [📝 The Benchmark That Built the Tools](https://iwe.md/blog/the-benchmark-that-built-the-tools/). IWE is open source at [iwe.md](https://iwe.md).", "url": "https://wpnews.pro/news/designing-edit-operations-for-ai-agents", "canonical_source": "https://dev.to/gimalay/designing-edit-operations-for-ai-agents-16e8", "published_at": "2026-08-02 01:02:36+00:00", "updated_at": "2026-08-02 01:10:02.831385+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["IWE"], "alternates": {"html": "https://wpnews.pro/news/designing-edit-operations-for-ai-agents", "markdown": "https://wpnews.pro/news/designing-edit-operations-for-ai-agents.md", "text": "https://wpnews.pro/news/designing-edit-operations-for-ai-agents.txt", "jsonld": "https://wpnews.pro/news/designing-edit-operations-for-ai-agents.jsonld"}}