Simon Willison released datasette-agent-edit 0.1a0 on June 7, 2026, solving a specific problem: he needed the same text-editing primitives across multiple Datasette Agent plugins (Markdown editing, SQL query updates, SVG file manipulation). Instead of duplicating tool definitions, he extracted Claude’s text editor design into a base plugin layer.
This is not a wrapper around Claude. It is a reusable implementation of the view/str_replace/insert pattern that any plugin can adapt to its own storage backend.
Why These Three Primitives
Agentic text editing fails when models try to rewrite entire files. Context windows get expensive, diffs become unreadable, and partial failures leave corrupted state. Claude’s text editor design solves this with three operations:
- view: Returns file sections with line numbers. The agent sees structure without loading everything into context.
- str_replace: Finds an exact
old_strand replaces it withnew_str. Fails if the original string is not unique. - insert: Adds text after a specific line number, with explicit positioning (before or after).
The uniqueness constraint in str_replace is the key safety mechanism. If the agent hallucinates or the file has changed since the last view, the operation fails instead of corrupting unrelated sections. This turns multi-step edits into verifiable transactions.
Plugin Architecture
datasette-agent-edit does not implement storage. It provides tool definitions and validation logic that domain-specific plugins can wire to their own backends.
A plugin using this base layer needs to:
- Implement a storage adapter (file system, database blob, in-memory buffer).
- Map the generic tool calls (view, str_replace, insert) to read/write operations on that storage.
- Register the tools with Datasette Agent’s orchestration layer.
The contract is simple: the base plugin handles line numbering, uniqueness checks, and error messages. The domain plugin handles persistence and retrieval.
This separation means the orchestration layer (Datasette Agent) does not need to know whether it is editing a Markdown file, a SQL query, or an SVG. It just calls view, str_replace, or insert. The plugin translates those calls into storage operations.
State Verification and Idempotency
The str_replace uniqueness requirement forces the agent to verify state before each edit. If the model issues:
str_replace(
old_str="SELECT * FROM users",
new_str="SELECT id, email FROM users"
)
and the file contains two instances of SELECT * FROM users, the operation fails. The agent must issue a more specific view call, identify the correct context, and include enough surrounding text to make old_str unique.
This prevents a common failure mode: the agent makes three edits in sequence, but edit two changes the line numbers or text that edit three depends on. With uniqueness enforcement, edit three fails instead of corrupting the file.
Insert operations use line numbers, which are fragile across edits. The plugin does not solve this. If the agent inserts at line 42 and then tries to insert at line 50, the second operation may target the wrong location. The agent must re-view the file between inserts.
Tool Call Flow
A typical editing session looks like this:
- Agent calls
view(start_line=1, end_line=50)to see the file structure. - Agent identifies a section to change.
- Agent calls
str_replace(old_str="...", new_str="...")with enough context to ensure uniqueness. - Plugin validates uniqueness, applies the replacement, persists the change.
- Agent calls
viewagain to verify the edit. - Agent proceeds to the next edit or signals completion.
The orchestration layer (Datasette Agent) handles retries, logs tool calls, and manages the conversation history. The base plugin handles validation. The domain plugin handles I/O.
Failure Modes and Observability
| Failure Mode | Detection | Recovery |
|---|---|---|
Non-unique old_str | Plugin returns error before applying change | Agent must re-view and provide more context |
| Concurrent edits | Storage layer conflict (if implemented) | Retry with fresh view |
Line number drift in insert | Silent corruption (no built-in detection) | Agent must re-view between inserts |
Model hallucinates old_str | Uniqueness check fails | Agent retries with corrected context |
| Storage backend unavailable | Plugin returns error, no partial state | Orchestration layer retries or aborts |
The plugin does not provide observability hooks. Logging happens at the Datasette Agent level (tool call history) or the storage adapter level (database transactions, file writes). There is no built-in diff tracking or rollback mechanism. If you need audit trails, implement them in the storage adapter.
When to Use This Pattern
This extraction makes sense when:
- You have multiple agent workflows that edit text in different formats or storage systems.
- You want consistent error handling and validation across those workflows.
- You need to prevent the model from rewriting entire files or making non-idempotent edits.
- You are building on Datasette Agent’s plugin architecture and want to share tool definitions.
Avoid this pattern when:
- You only have one editing workflow (just implement the tools directly).
- Your edits are append-only or single-operation (you do not need multi-step state verification).
- You need advanced diff tracking, conflict resolution, or collaborative editing (this is a primitive layer, not a CRDT).
- Your storage backend has its own transactional semantics that conflict with the view/replace/insert model.
Technical Verdict
datasette-agent-edit solves the tool duplication problem by extracting Claude’s text editor primitives into a storage-agnostic base layer. The uniqueness constraint in str_replace is the critical safety feature, forcing agents to verify state before each edit. The plugin does not handle concurrency, rollback, or observability, those are the responsibility of the storage adapter or orchestration layer.
Use this when you need consistent, verifiable text editing across multiple agent plugins. Skip it if you only have one editing workflow or need more sophisticated version control semantics.