You cannot make an AI agent incapable of proposing a bad edit, but you can design a document API so one stale, oversized, duplicated, or partially authorized request cannot silently corrupt the source of truth. The core controls are stable block identity, version preconditions, atomic operations, idempotent retries, least privilege, audit evidence, and recoverable history.
Doco series · Article 14 · API engineering guide
“Agents cannot break documents” is a useful design goal only after it is translated into failure modes. No API can guarantee that a grammatically valid change is factually wise. It can guarantee that concurrency, retries, partial failures, and permission mistakes behave predictably.
Doco is the document system I am building, so the examples below describe its first-party contract. The principles apply to any API that lets autonomous or semi-autonomous clients maintain important text.
Define “break” before defining endpoints
An agent breaks a document system when it can cause one of these outcomes without an explicit, recoverable signal:
| Failure | Unsafe behavior | Required control |
|---|---|---|
| Lost update | Old content silently replaces a newer edit | Version precondition |
| Wrong target | A moved paragraph can no longer be addressed | Stable block ID |
| Oversized mutation | One sentence change replaces the whole document | Bounded block operation |
| Partial batch | Two of five edits apply before the third fails | Atomic transaction |
| Duplicate retry | Network retry creates the same section twice | Idempotency key |
| Excess authority | Read workflow can delete or publish | Narrow scopes |
| Invisible origin | Nobody can explain who changed what | Audit event and provenance |
| Bad recovery | A mistake is permanent | Snapshots and rollback |
This table is more valuable than a long endpoint list. It turns a slogan into acceptance tests.
Give every editable block a stable identity
A path identifies a file. A line number identifies a temporary position. A heading identifies a label that users routinely rename. None is a durable paragraph address.
Store a stable block ID in the canonical structured document:
{
"id": "block_01K0Y7R5F…",
"type": "paragraph",
"content": [{ "type": "text", "text": "Rollback begins after five minutes." }]
}
Moving the block preserves the ID. Copying it creates a new one. Deleting it makes the old address explicitly absent rather than accidentally selecting whatever text now occupies line 42.
Stable IDs also improve audit logs and citations: the same identifier can connect a search result, read response, write operation, change event, and browser highlight.
Make every write conditional on what the agent read
A safe edit begins with a read that returns a version:
GET /v1/documents/doc_123?view=tiptap
ETag: "sha256:abc123"
The write must include that version:
PATCH /v1/documents/doc_123/blocks/block_456
If-Match: "sha256:abc123"
Content-Type: application/json
{"text":"Rollback begins after three minutes."}
If another actor changed the document, the server rejects the request and returns the current version. RFC 9110 defines If-Match specifically for conditional requests and avoiding lost updates.
Do not hide the conflict behind an automatic last-write-wins retry. The agent must reread the new state, reconsider its intended change, and either stop or submit a new proposal.
Keep edits narrow but version the whole document
Block-level operations reduce the mutation surface:
- insert after
block_A; - replace
block_B; - delete
block_C; - move
block_Dunder headingblock_E.
Yet the precondition should represent the canonical document content, not only the target block. A change elsewhere may alter the meaning of the planned edit. For example, a teammate may add an exception above a policy paragraph without touching the paragraph itself.
Use JSON Patch only when its positional path semantics match the document model. In tree-shaped collaborative documents, domain operations over stable IDs are often clearer than array indexes such as /content/17.
Make batches atomic
Agents frequently perform multi-step changes: create a heading, insert two paragraphs, and update a summary. Applying each request independently can leave half a result when the third operation fails.
Accept an ordered batch under one document version and execute it in one transaction:
{
"operations": [
{"op":"insert_after","after":"block_A","block":{"type":"heading","text":"Recovery"}},
{"op":"insert_after","after":"$0","block":{"type":"paragraph","text":"Restore the latest snapshot."}},
{"op":"replace","block_id":"block_SUMMARY","text":"Recovery procedure added."}
]
}
Either every operation validates and applies, or none does. References such as $0 can target the stable ID allocated by an earlier operation in the same batch.
Design retries before production traffic does it for you
Networks fail after the server commits but before the client receives the response. The agent retries because it cannot know whether the first request succeeded. Without idempotency, “append this checklist” creates two checklists.
Creation and batch endpoints should accept an idempotency key bound to the authenticated actor and normalized request body:
Idempotency-Key: task-8f4a-create-recovery-section
Replaying the same successful request returns the original result. Reusing the key with different input returns an explicit conflict. Failed validation should not become a permanently cached result that blocks a corrected retry.
Separate capability from intent
An agent prompt may say “do not delete,” but the server should not issue delete authority to a workflow that only reads.
Useful scope boundaries include:
documents:readdocuments:writedocuments:createdocuments:deletesharing:manageknowledge_bases:admin
Begin with read-only tokens, short lifetimes, and the smallest workspace boundary. Store token hashes, support revocation, and rate-limit write bursts separately from reads.
The MCP tools specification recommends human visibility and the ability to deny tool invocations. Tool annotations can describe read-only or destructive behavior, but enforcement belongs in authentication and authorization, not labels alone.
Return errors an agent can act on
An HTML error page or ambiguous 400 invites guesses. Return stable codes and recovery data:
{
"error": {
"code": "version_conflict",
"message": "Document changed after it was read.",
"current_version": "sha256:def789",
"request_id": "req_01K0…"
}
}
Distinguish at least:
- missing precondition;
- stale version;
- missing scope;
- unknown or deleted block;
- invalid document structure;
- rate limit with retry guidance;
- idempotency-key conflict;
- incomplete or stale search cursor.
The model may still choose poorly. It should not need to infer the state transition from prose.
Preserve provenance and recovery
Every successful mutation should record actor, token or agent identity, request ID, target blocks, source version, resulting version, timestamp, and origin channel. W3C's PROV overview provides a general model for describing entities, activities, and agents; a document API does not need the full standard to benefit from the same provenance questions.
Snapshots should be taken before consequential writes or at a bounded cadence. Rollback should create a new version rather than erase the history that explains why recovery happened.
Test the hostile cases
A happy-path integration test proves almost nothing. The acceptance suite should include:
- two writers using the same initial version;
- a block moved between read and write;
- a duplicate request after a simulated timeout;
- failure in the middle of a five-operation batch;
- a read-only token attempting a write;
- a write token attempting to manage sharing;
- malformed rich-text nodes and duplicate block IDs;
- rollback after a valid but unwanted edit;
- a search cursor used after the index changes;
- a retry storm hitting per-document rate limits.
The API is safe only when each failure has a deterministic, observable result.
FAQ
Can an API prevent an agent from writing incorrect information?
Not completely. Structural controls prevent silent corruption and limit blast radius. Factual quality still requires source evidence, review rules, and human judgment for consequential content.
Why not use last-write-wins for simplicity?
Because it makes a successful HTTP response indistinguishable from silent data loss. Explicit conflicts cost an extra read but preserve both actors' work.
Are block IDs enough to make writes safe?
No. They identify targets. Version preconditions, authorization, atomicity, idempotency, validation, audit logs, and recovery address different failure modes.
Should every tool call require human approval?
Not necessarily. Read-only, low-risk operations can run autonomously within a narrow scope. Publication, deletion, permission changes, and ambiguous conflict resolution deserve explicit review.
Bottom line
An unbreakable document API does not promise perfect agent judgment. It makes dangerous state transitions narrow, conditional, atomic, attributable, and reversible. When every stale write fails visibly and every successful edit has a durable address and history, agents can participate without turning collaboration into data-loss roulette.
Originally published on Doco.
Doco is an open-source document workspace where humans and AI agents write together. Explore Doco.
Top comments (0)