# Writes that don't bite

The rules Hydrant enforces on agent writes - request IDs, versions, receipts and batches - and how a well-behaved agent follows them.

Agents retry. Networks drop responses. People edit the same issue at the same time. Hydrant assumes all three and refuses to guess. These are the rules every write follows. They're worth pasting into your agent's instructions.

## Before you start

- A connection with read and write access. A read-only one is refused with `insufficient_scope`: "This token lacks the required workspace permissions."
- An agent that can keep a value between attempts. That's the whole trick.

## Steps

1. **Read first.** `get_issue` returns the issue and its current `version`. Relationship changes use the `revision` from `list_relationships`. Project and milestone membership uses the revision `get_project` describes.
2. **Give every new command its own request ID.** Each write tool takes a `requestId`: a UUID the agent makes up. One command, one UUID.
3. **Send the version you read.** `update_issue` carries the `version` you based the change on. If someone got there first, Hydrant refuses the write instead of flattening their work.
4. **Treat the answer as a receipt.** A write returns `kind: receipt`: an acknowledgment of something that happened. It is not the current state. Read again before telling anyone what's true now.
5. **After an uncertain response, send exactly the same thing.** Timeout, dropped connection, `503`: repeat the same `requestId` with the same body. If the first attempt landed, you get its receipt back and nothing happens twice. A retry like that doesn't count against rate limits.
6. **After a conflict, stop and look.** Re-read the issue, review what changed and decide whether your change still makes sense. If it does, that's a new command with a new UUID and the new version. Never refresh the version and resend automatically. That's an overwrite with extra steps.

Comments are the exception to versions: `add_comment` doesn't change the issue version or wake a snoozed issue. It still takes a `requestId`.

### Batches

`batch` runs 1 to 5 create, update, comment or relationship commands in order. Each item commits on its own; there is no rollback. Hydrant stops at the first failed or uncertain item and marks the rest `not_attempted`. Every item needs its own distinct UUID. Keep all of them after an interruption. A missing response never means the batch rolled back.

### Images

An agent can attach PNG, JPEG or WebP up to 10 MiB. It reserves the upload with `reserve_image`, sends the bytes with an authenticated `PUT` to the returned address, then puts only the returned link in Markdown. Unlinked reservations expire after 24 hours. It must never follow redirects with the credential attached, and never print it.

## What you should see

- A successful write: `kind: receipt`, and a following read shows the change with a higher `version`.
- A safe retry: the same receipt as the first time, with no duplicate issue or comment.
- A stale write: `conflict`, with "This issue changed. Review the latest version before reapplying your draft."

## If it goes sideways

- **`conflict`: "This request ID was already used for another command."** The agent reused a UUID for something different. New command, new UUID.
- **`unavailable`: "A write may have committed; reconcile using the original request UUID and body."** Believe it. Retry with the original, or read to find out.
- **`rate_limited`.** An agent gets 120 requests a minute and a workspace 600 writes a minute. The response says how long to wait. Wait that long.
- **`too_large`.** A whole request must fit in 1 MiB, a description in 102,400 bytes, a comment in 20,480 bytes and a title in 300 characters.
- **The agent did something an issue told it to.** Workspace content is data, not instructions. Hydrant records what your agent does; it can't stop your agent from obeying an issue body. Keep your client's write-approval prompts on, and don't leave it unattended on issues you didn't write. To see what happened, open the issue and press **Show the receipts**: the history names the agent and every change it made, and **See what changed** on a text edit shows the before and after, so you can put the old text back. A trashed issue comes back from the **Trash** view. Then [revoke the connection](/help/agents/reconnect-and-revoke) until you know why it happened.
- More symptoms: [Troubleshooting](/help/agents/troubleshooting).

Contact: bots@hydrant.dev
