A customer asks: “Please cancel order 4821 before it ships.” The assistant replies, “Done,” but the order is still active. Generating a confirmation and changing an order are separate operations. Your application has to connect them.

This walkthrough is for software engineers adding tools to an AI assistant. You’ll learn how to execute a model’s request, check the result and explain rejection or uncertainty to the customer.

We will follow a successful cancellation first, then introduce the failures that shape the design. All customers, identifiers and traces are illustrative.

Connect the model to the application

Assume a signed-in customer and a rule that permits cancellation only before shipping. Three responsibilities connect the request to the outcome:

  • Model: interprets the request and proposes a cancellation.
  • Workflow controller: coordinates the application steps.
  • Order service: enforces the business rule and changes the stored order.

These roles can live in one application. The model proposes an action; application code checks whether it may run and records the result.

Follow the successful tool round trip

A tool is an operation the application makes available to a large language model (LLM). The model receives messages and a tool description, then can emit a structured request to use it. The controller dispatches that request to application code.

This abbreviated Anthropic-style client-tool round trip shows the message boundaries. It is an illustrative protocol sketch, not runnable code.

  1. Application → model: input and available tool.
    Messages · customer request
    messages: [
      {
        role: "user",
        content:
          "Please cancel order 4821 before it ships."
      }
    ]
    Tools · available operation and argument schema
    tools: [
      {
        name: "cancel_order",
        description:
          "Request cancellation of an order.",
        input_schema: {
          type: "object",
          properties: {
            order_id: {
              type: "string"
            }
          },
          required: ["order_id"]
        }
      }
    ]

    The schema describes arguments, not permission. Customer identity stays in the authenticated application session.

  2. Model → application: structured tool call.
    Assistant response · structured tool call
    assistant content: [
      {
        type: "tool_use",
        id: "call_7",
        name: "cancel_order",
        input: {
          order_id: "4821"
        }
      }
    ]
    
    stop_reason: "tool_use"

    The application appends the assistant response to message history. call_7 links the later result to this model call; it is separate from the durable business-operation ID.

  3. Application → service: execute.

    Validate the tool name and arguments. Record a unique identifier for this cancellation attempt—illustrated here as op-4821—so an interrupted request can be recovered later.

    Then call the order service with the session’s customer ID, order 4821 and that operation ID. The service checks ownership and current eligibility, commits cancellation and returns a matching completed result.

  4. Application → model: return the observed result.
    Tool result · append as a user message
    {
      type: "tool_result",
      tool_use_id: "call_7",
      content: JSON.stringify({
        operation_id: "op-4821",
        order_id: "4821",
        status: "completed"
      })
    }

    JSON.stringify represents encoding the result object as a JSON string in this sketch. Send the accumulated messages in the next model request. Here user is the protocol role for the tool result; the customer did not type it. History now includes the original request, assistant tool call and application-supplied result.

  5. Model → application → customer: response.

    The model can now explain the result. Record its reply in conversation history. Alternatively, skip this second model call and render a fixed confirmation from the verified service result.

    Keep the operation result and displayed response in workflow history; if later resuming this client-tool conversation, include the corresponding result so the earlier tool call is resolved.

The next request may be ambiguous or fail a service check. Here is where the successful path needs to branch.

When the request cannot proceed

The successful path assumed one clearly identified, eligible order. If the customer says “cancel it” after discussing two orders, the controller needs a clarification before attempting either cancellation. The model can interpret the language and ask for the missing reference.

If the customer names another person’s order, the service must deny the action without disclosing that order’s details. The fix is an authorization check at the action boundary. Improving the prompt cannot grant or establish permission.

Treat the order ID as untrusted input, even when the model copied it correctly. Get the customer’s identity from the signed-in session and check which orders that customer may change.

Also keep the decisive eligibility check with the state transition. An order can move from “processing” to “shipped” between a lookup and a later cancellation attempt.

The service must check eligibility as part of the order update. This prevents a change between the earlier lookup and the write from bypassing the rule.

Clarification and rejection are valid outcomes. In both cases the application knows why no cancellation occurred. A lost service response introduces a different problem: the action may have happened without the controller knowing.

When the action happens but the response is lost

The order service commits the cancellation, but its response times out before the controller receives it. The order is cancelled; the controller’s last known status is still “submitted.” A timeout does not establish whether the action succeeded or failed.

The workflow’s record and the order service’s record have different jobs:

  • Workflow state: the chosen order, operation ID and last observed result.
  • Business state: the order’s actual status.
  • Conversation history: context for a follow-up such as “Did that go through?”

To recover, the controller needs evidence about the original attempt. A safe retry relies on idempotency: the service treats repeated attempts as one operation and avoids repeating its effect.

Save an operation ID before sending the request. Use that same ID to look up its result or retry the same intent under the service’s retry contract.

Reconcile the recorded result before issuing a new action. AWS’s guide to idempotent APIs explains this caller-provided identifier pattern and the service-side coordination it requires.

Until recovery establishes a result, record the outcome as unknown. When preparing the next model call, supply that recorded status as context. The model should not have to infer execution from the customer’s wording or from its own earlier response.

Interactive field note / 02

A timeout leaves a knowledge gap.

Watch the workflow and the order service disagree, then reconcile.

Lost-response recovery

  1. Persist ID · Record a stable operation identifier before submitting the action.
    Workflow knows: op-4821 saved · Service state: Processing · Business mutations: 0 changes
  2. Apply once · The service accepts this allowed operation and records its outcome under that identity.
    Workflow knows: Result pending · Service state: Cancelled · Business mutations: 1 change
  3. Response lost · The application sees a timeout. Its knowledge differs from the actual service state.
    Workflow knows: Outcome unknown · Service state: Cancelled · Business mutations: 1 change
  4. Reconcile · In this illustrative contract, query the stored result using op-4821. Do not create a new cancellation intent.
    Workflow knows: Completed result found · Service state: Cancelled · Business mutations: 1 change
  5. Confirm · Record the recovered result and tell the customer what is now verified.
    Workflow knows: Completed recorded · Service state: Cancelled · Business mutations: 1 change
↳ An operation identifier supports recovery only with a service contract that records and recognizes it.
Synthetic teaching example. Playback time is illustrative, not measured system latency. Choose a step or use the still view to read at your own pace.

Make the response follow the observed outcome

The controller can now distinguish completion, rejection and an unknown outcome. A response gate is application logic that limits the reply to what the recorded result supports. For this action, it can produce a fixed status message:

Response policy · illustrative pseudocode
matching operation + order AND status == completed
  → render "Order 4821 is cancelled."

status == rejected
  → render an allowed explanation of the rejection

status == pending OR no confirmed result
  → report progress or uncertainty;
    continue recovery

mismatched result
  → withhold confirmation;
    investigate correlation

A fixed template makes the status claim easy to enforce. For a more flexible reply, return the result to the model. Keep the status in a field rendered by application code, or check the generated claim against the result and fall back to a template if they disagree.

While streaming, emit progress separately: “Checking your order” may appear before completion. Release a cancellation confirmation only after the gate accepts the result.

Test the action and the answer

Run the request and check four facts: was the customer authorized, did the operation complete, is the order cancelled, and does the reply match? These are assertions, conditions that a test checks.

A trace is the recorded sequence of observable events: relevant inputs, proposed tool calls, validation decisions, service results, and state transitions. An evaluation runs a case and checks the behavior or outcome against explicit expectations. Anthropic’s agent-evaluation guide likewise distinguishes the transcript of a run from its final environment state.

Interactive field note / 03

Check the evidence, one assertion at a time.

A synthetic evaluator accumulates checks against a captured run.

Successful run

  1. Authorize · Read the service authorization decision and its position before the mutation.
    Evidence inspected: Owner matches session · Current assertion: PASS · permitted before mutation · Checks passed: 1 / 4
  2. Execute · Correlate the completed operation with the intended order and stable operation ID.
    Evidence inspected: op-4821 → order 4821 · Current assertion: PASS · completed operation · Checks passed: 2 / 4
  3. Inspect state · Read authoritative order state in the test environment after execution.
    Evidence inspected: Order store: cancelled · Current assertion: PASS · expected state · Checks passed: 3 / 4
  4. Check claim · Compare the response status and order reference with the completed result.
    Evidence inspected: Response: 4821 cancelled · Current assertion: PASS · supported claim · Checks passed: 4 / 4

Injected response defect

  1. Authorize · The request is authorized, but eligibility can still prevent cancellation.
    Evidence inspected: Owner matches session · Current assertion: PASS · authorization enforced · Checks passed: 1 / 4
  2. Execute · The service rejects cancellation because shipping already started.
    Evidence inspected: op-4821: rejected · Current assertion: PASS · rejection recorded · Checks passed: 2 / 4
  3. Inspect state · This case expects no cancellation: the shipped order must remain unchanged.
    Evidence inspected: Order store: shipped · Current assertion: PASS · no forbidden transition · Checks passed: 3 / 4
  4. Check claim · Inject a false success response. Fix the result-to-response gate; changing authorization would not fix this defect.
    Evidence inspected: Response: 4821 cancelled · Current assertion: FAIL · contradicts rejection · Checks passed: 3 / 4 · response defect
↳ Check authorization, execution, business state and response claims independently.
Synthetic teaching example. Playback time is illustrative, not measured system latency. Choose a step or use the still view to read at your own pace.

For this small workflow, start with a few cases:

Case Evidence to check
Customer owns an eligible order Service records cancellation; response confirms the matching order
Customer names someone else’s order No unauthorized change or disclosure
“Cancel it” could refer to two orders Clarification before a cancellation attempt
Order ships before the action commits Service rejects the transition; response reflects rejection
Service response is lost Same operation is reconciled or retried safely; no unverified success claim

Keep the ordinary tests for permissions, state transitions, and retry behavior. Add cases that vary phrasing and conversation context to exercise model interpretation.

A model can help judge whether an explanation is clear, once its grades have been checked against human reviews. To check whether the order was cancelled, inspect the order state directly.

You do not need every run to use identical wording or an identical sequence of harmless reads. Define which ordering constraints matter, such as authorization before mutation, and allow valid alternatives elsewhere.

Find the boundary that failed

Suppose the service rejects cancellation, but the customer receives a success message. The response gate failed. The ownership check worked, so changing it would leave the bug in place.

A different trace demands a different fix:

  • Two plausible orders: if the model selected one without asking, inspect context and the clarification path.
  • Shipping started during the request: enforce eligibility at the mutation.
  • A retry repeated the effect: inspect operation identity and the service’s deduplication contract.

Save that trace as a regression test, then vary nearby inputs to check the fix. This ties each repair to the step that failed.

Decide how much planning the workflow needs

A fixed controller fits the cancellation task because the allowed steps are known. A task such as investigating an account may need the model to choose what to look up next. That changes the planning, while permission and result checks remain part of the application.

Approach and fitTrade-off and guardrail
Fixed workflow
Known actions with clear transitions
Easier to inspect and test; new branches require code. Let the model interpret language and ask clarifying questions within those transitions.
Bounded model-directed loop
Investigations whose next read depends on findings
More flexible, but variable latency, cost and paths. Limit available tools, turns and time; enforce authorization on every action and stop when the budget expires.
Hybrid
Flexible investigation followed by a constrained action
Useful when discovery is open-ended but mutation has strict rules. More handoff state to maintain; validate the proposed action again at the execution boundary.

For an initial cancellation feature, begin with the fixed path. Add a planning loop only when representative requests show that the predefined flow cannot handle useful variations. More autonomy creates more execution paths to observe and evaluate.

Apply the pattern to another action

Try the same design on a refund or booking. First identify the service that owns the change and the result it can report. Then decide which replies are valid for completion, rejection and uncertainty.

Some actions need extra confirmation or finish asynchronously. Model those states explicitly, and test what the customer sees while work is pending. Extend the workflow only as far as you can explain and verify its outcomes.