An agent is permitted to inspect overdue invoices but not contact customers. Its get_invoice tool calls a legacy HTTP GET endpoint. That endpoint lazily creates a collection task and sends a reminder if one has not been sent. The model only asked to read. The platform called the tool “read-only” because its schema has no send operation. A customer got a message anyway.

Read-only is a property of the observable effect of the operation, not the tool name or HTTP method. RFC 9110 defines GET as a safe method whose requested semantics are essentially read-only, while noting that implementations may still have incidental side effects such as access logging. Sending a customer reminder is a business effect, not incidental logging. The hypothetical endpoint violates the safety expectation, and an agent tool wrapper that trusts the verb has inherited that mistake.

I would trace one invocation end to end: request ID, endpoint code path, transaction or outbox writes, notification queue, and whether a retry or prefetch also invokes it. Temporarily remove this operation from the agent's read capability. Split pure invoice retrieval from the collection workflow. If a reminder is ever appropriate, expose it as an explicit write action with its own authorization, human review where required, idempotency key, auditable target and rate limit. Fixing the legacy GET matters for all clients, including crawlers, caches and normal retries, not just the agent.

For a platform serving hundreds of tools, how would you prove the declared effect? A static schema field is only a claim by the tool owner. Add contract tests against an instrumented service or sandbox that records durable writes, outbound requests and queued work for representative reads, including first access, missing cache, error and retry. Review downstream actions too. Not every metric increment makes a tool unsafe, but a resource mutation, payment, message or externally visible notification must be classified and authorized as a write. Keep tool version and effect classification in the registry, and block an unreviewed change from quietly turning a read into a write.

What if a cache miss is supposed to populate a cache? That internal, nonbusiness side effect may still fit a read contract. Specify the boundary in terms of externally meaningful state and permissions. If the implementation begins opening a customer case on a miss, it crossed the boundary. A new tool field can bypass the old authorization check concerns a new input field bypassing authorization. This page is about an existing read-shaped operation whose hidden behavior violates the capability it was assigned.