Separate transport, invocation, and business meaning
How to study this lesson
Read one section, trace the worked example, and try the knowledge check. Bookmark saves a shortcut; notes appear in My notebook. Mark lesson complete is your own assessment and does not mark its lab passed.
By the end, you can
- Distinguish HTTP delivery from JSON-RPC invocation and domain success.
- Correlate responses by request ID.
- Design errors that support a specific recovery decision.
Three contracts on one wire
A tool call crosses three contracts. The transport contract describes how bytes arrive. The invocation contract identifies a method, arguments, and the matching response. The business contract describes what success means in the application. Keeping these separate prevents a common incident: an assistant announces that an order was reserved because an HTTP request returned successfully, even though the business result says there was insufficient stock.
In an inventory integration, HTTP might deliver a JSON-RPC message, while the method reserve_stock implements a warehouse rule. That same domain function can be tested without either protocol. This boundary makes failures easier to reproduce: a unit test exercises stock accounting; a contract test exercises envelopes; an integration test exercises delivery and credentials. Each layer answers a different question.
An envelope is a correlation mechanism
JSON-RPC 2.0 defines request and response objects independently of a transport. A request identifies its method and normally includes an ID; a response echoes that ID and contains either a result or an error. A request without an ID is a notification and does not receive a response. These details come from the JSON-RPC specification, not from a particular model provider.
Imagine submitting request 41 for product A and request 42 for product B. B finishes first. Appending results to a list and assuming submission order silently assigns B's inventory to A. A dictionary keyed by request ID preserves the association. The example deliberately returns responses in reverse order so that this mistake becomes visible before a real network introduces timing variation.
Status is evidence at a particular layer
HTTP status codes describe the HTTP exchange. A successful HTTP response can carry a domain rejection, depending on the API's documented mapping. Conversely, a gateway timeout leaves the application's outcome uncertain: the warehouse may have committed a reservation before the response was lost. A caller cannot infer rollback merely from absence of a response.
Design domain results such as reserved, insufficient_stock, and unknown_outcome. Associate a recovery path with each. Insufficient stock suggests changing quantity or reporting the shortage. An unknown outcome suggests reconciliation by operation key. Invalid arguments suggest repairing the request. Authorization failure suggests obtaining an appropriate grant or declining the action. Retrying all four failures identically creates unnecessary load and can duplicate effects.
A worked boundary trace
Suppose a caller wants three units and sends invocation ID 41 with operation key cart-8-line-2. The server accepts the envelope, authenticates the caller, validates quantity, and commits the reservation. Its response disappears. The caller should retain two identifiers: 41 describes that attempted exchange; the operation key describes the intended reservation. A later exchange can use ID 43 while asking about the same operation.
This distinction is essential for observability. Record the attempt ID, operation key, method, elapsed time, and outcome category. Keep credentials and unnecessary customer text out of traces. A dashboard that reports only HTTP success rate will miss business rejections. A dashboard that reports only completed reservations will hide a growing transport problem until users begin seeing delays.
Narrow adapters make systems replaceable
Put envelope parsing and response construction in an adapter around a deterministic domain function. Avoid letting the function inspect HTTP headers or invent status codes. The adapter should translate known exceptions into documented errors and suppress internal stack details in external responses. Unexpected errors still need internal diagnostics linked by a safe correlation identifier.
The executable example models response matching only. It does not implement an HTTP server or the full JSON-RPC grammar. Production code also needs message size limits, timeouts, duplicate response handling, cancellation semantics, and malformed-message tests. Start with a narrow contract and make every omitted feature explicit. A small honest adapter is easier to extend than a broad interface whose behaviors emerge accidentally from its implementation.
Work through the code
Two in-memory response envelopes arrive in reverse order. ID lookup restores the correct product mapping. Add an unknown response ID to observe the current strict failure; a production adapter would classify that protocol fault explicitly.
import json
requests = [
{"jsonrpc": "2.0", "id": 41, "method": "stock", "params": {"sku": "A"}},
{"jsonrpc": "2.0", "id": 42, "method": "stock", "params": {"sku": "B"}},
]
responses = [
{"jsonrpc": "2.0", "id": 42, "result": {"available": 8}},
{"jsonrpc": "2.0", "id": 41, "result": {"available": 3}},
]
pending = {request["id"]: request for request in requests}
resolved = {}
for response in responses:
request = pending.pop(response["id"])
sku = request["params"]["sku"]
resolved[sku] = response["result"]["available"]
print(json.dumps(resolved, sort_keys=True))
print("pending:", len(pending))
{"A": 3, "B": 8}
pending: 0Run Python snippets locally with the prerequisites named above. The module coding lab runs directly in your browser.
Pause and reason
Request 17 times out after the server may have committed a reservation. Which identifiers should a retry preserve, and what should the assistant tell the user before reconciliation?
Check your understanding
Two concurrent tool calls finish in reverse order. Which association is reliable?
Your notes
Explain the mechanism in your own words. Add a failure you want to test.