Lesson 1 of 4 · 35 min

A tool call is a request, not a result

Write a tool contract whose success can be independently checked.

An agent proposes an action. The application decides whether that action is valid and performs it. Keeping these responsibilities separate makes failures easier to diagnose. A valid JSON object proves only that the arguments have the expected shape. It does not prove that the user owns the object, that the action is still allowed, or that a remote service applied it.
Consider a calendar assistant. A tool called update_event with arbitrary fields is convenient for an API developer, but it leaves many decisions hidden. Does an omitted attendee list keep the existing list or remove it? Is the time local or UTC? Does a timeout mean no meeting was changed? Write these meanings into the contract before tuning prompts.
A useful contract identifies the actor, resource, preconditions, permitted changes, and observable result. The actor comes from the authenticated application session, not from an email address supplied by the model. The resource uses an immutable event ID. A version number prevents the assistant from overwriting a change that happened after it read the event. An explicit operation ID identifies this requested change across retries.
The tool result should distinguish rejection, completion, and uncertainty. Returning ok: true after merely queuing work misleads the agent. A queued response needs a job ID and a status tool. A completed response can include the event ID, new version, and changed fields. An uncertain response tells the controller to reconcile the operation before trying another write. These are application states, not moods that the model should infer from prose.

Worked example

This teaching case starts with event E17 at version 4, scheduled for 10:00 UTC. The user asks to move it to 11:00 UTC and keep attendees unchanged. The proposed call has event ID E17, expected version 4, new start 11:00 UTC, duration 30 minutes, and operation ID O91. The server obtains the user ID from the session. It checks ownership and the version, then updates the event atomically.
The completed result is E17, version 5, start 11:00 UTC, duration 30 minutes, with the same attendee IDs. The assistant can now report the change. If the stored version was already 5 before the request, the server returns a conflict with the current event. It does not silently replace it. The assistant reads that state and asks for a new decision only if the user's intent is no longer clear.

Exercise and solution

Design a tool to rename document D8 from "Draft" to "Budget". The caller owns D8 at version 12. Another user changes the title to "Budget final" before the write. Supply the preconditions and expected response.
The solution requires document ID D8, expected version 12, new title, and an operation ID. The server checks authorization and performs a conditional update. Since the current version is 13, it returns a conflict and makes no change. Re-reading version 13 is useful, but automatically overwriting it is not justified. Award one point each for server-side identity, version precondition, explicit conflict, and preserving the concurrent edit.

Repair the contract

Inspect this deliberately incomplete proposal schema. It is pseudocode for a teaching exercise, not a production implementation.
code
1update_event({2  event_id: string,3  start: string,4  attendees?: string[],5  actor_email: string6}) -> { ok: boolean }
There are four independent ambiguities. The timestamp does not declare its time zone. The optional attendees field does not define omission. The actor email is controlled by the caller. The result cannot distinguish a queued job from an applied update. A schema validator can accept every field while all four problems remain.
A repaired contract makes the application derive identity from its authenticated context. It requires a timestamp with offset, an expected resource version, and an explicit change set. The server may expose separate operations for moving an event and changing attendees. That choice reduces the risk that an agent unintentionally replaces the attendee list while it only moves a time.
code
1move_event({2  event_id: "E17",3  expected_version: 4,4  new_start: "2026-09-24T11:00:00Z",5  duration_minutes: 30,6  operation_id: "O91"7})8completed -> { event_id: "E17", version: 5, operation_id: "O91" }9conflict  -> { event_id: "E17", current_version: 5 }10rejected  -> { reason: "not_authorized" }11pending   -> { operation_id: "O91", status_url: "/operations/O91" }
The contract still omits production details. Authentication, durable operation storage, rate limits, input-size limits, and audit retention need implementations. The example also does not guarantee that calendar notifications participate in the same transaction as the event update. If the provider sends notifications separately, the tool must state that effect and its completion rules.

A second failure case: partial completion

Suppose the event update succeeds but notification delivery fails. A single success flag forces the caller to choose between two false simplifications: everything worked or nothing worked. The more useful result distinguishes event state from notification state. The user can then decide whether a separate notification retry is needed without moving the meeting again.
ComponentStateEvidencePermitted next action
Event timeChangedE17 version 5Read or leave unchanged
NotificationsFailedDelivery job N8 rejectedRetry N8 under its own contract
Whole requestPartialBoth component recordsReport the partial result
This case shows why a tool boundary should follow a meaningful transaction rather than the number of internal API calls. If the calendar service provides a true atomic update-and-notify operation, document that guarantee. If it does not, do not invent it in the agent's wrapper. A wrapper can improve clarity but cannot create remote atomicity merely by returning one object.

Misconceptions to reject

"Strict structured output makes the action safe" confuses syntax with permission and current state. A syntactically perfect request can target another user's event. The server must reject it independently of the model.
"A successful HTTP status means the user's whole goal is complete" confuses transport or acceptance with business completion. A 202 response may mean pending, and a 200 response may contain a partial application result. The documented service contract determines the interpretation.

Transfer exercise

A file-sharing tool creates a folder and then invites three collaborators. Folder creation succeeds, two invitations succeed, and one address is invalid. Write the result and recovery plan. The model solution reports the folder ID, the two completed invitation IDs, and the invalid address as a rejected sub-operation. It retries only a corrected invitation after the relevant user decision. It does not recreate the folder or resend the two completed invitations.
Score one point for each preserved effect, one for the rejected address, and one for a recovery action that cannot duplicate prior effects. The learner should be able to trace every claimed outcome to a service record.

Interview probe

Original practice: Why is schema validation insufficient for an agent tool? A strong answer separates argument shape, authorization, current-state preconditions, and completion evidence. Follow up with a concurrent edit and ask which check detects it. A weak answer adds a more detailed prompt but leaves the server permissive.

Sources

docsAnthropic: writing effective tools for agentsanthropic.comdocsAnthropic: building effective agentsanthropic.com

Checkpoint

The event service accepts a job and returns J9, but no completion record. What may the assistant claim?

ARetry with a new operation ID immediately.BThe event changed because the request was valid.CThe request was accepted; completion is pending.DThe operation failed because no event ID was returned.
Sign up free to answer and see why

Checkpoint

A model submits a valid event ID belonging to another tenant. Which control must reject it?

AA server-side authorization check using trusted caller context.BA longer tool description listing tenant names.CA resource-version check alone.DJSON schema validation of the ID format.
Sign up free to answer and see why

Checkpoint

The event moved, but notification delivery failed. Which result best preserves recovery options?

ADelete the event automatically to restore the old state.BReturn generic failure and repeat the whole tool.CReturn generic success because the event exists.DReturn component states and retry only the failed notification under its contract.
Sign up free to answer and see why

Checkpoint

Expected version is 4; the current resource is version 5. What should a conditional update do?

ARetry until the update wins.BReject the stale update and expose the conflict for reconciliation.COverwrite version 5 because the user originally requested a change.DChange the expected version to 5 without reading it.
Sign up free to answer and see why

Checkpoint

A folder exists and two of three invitations succeeded. The third address is invalid. What should a repair target?

AOnly the invalid invitation after its address is corrected under the user's scope.BResend all invitations with new IDs.CTreat the entire task as complete because most effects succeeded.DRecreate the folder and all invitations.
Sign up free to answer and see why

Without looking at the worked solution, explain how you would validate an event move, distinguish partial completion, and preserve a concurrent edit. Name the service evidence you need. Rate confidence from 1 to 5 and identify the boundary you still cannot justify.

Not yetGetting thereConfident

Wrap-up

  • Give the model a clear proposal interface. Let the service enforce invariants and return evidence of what actually happened.

Sources

Free to read · better with Enzo

Learn it with Enzo

Save your progress, answer the checkpoints, and let Enzo quiz you on what you just read.