CodeRigor

LESSON

Return bounded values and useful failure codes

Normalize tool outcomes into JSON data, preserve request IDs, redact exception details, and enforce a UTF-8 byte budget on returned values.

A tool result is part of the next model input, so its structure deserves the same care as the request. Returning arbitrary Python objects or free-form exception strings makes the loop hard to test and can reveal unintended context. Keep a small envelope with id, ok, value and error. The request ID remains useful even when the action fails.

The invocation helper classifies expected input, permission and lookup failures with stable codes. Unexpected exceptions become tool_failed. The detailed exception can belong in a deliberately designed private diagnostic channel, but this teaching helper never sends its text to the model.

Both outcomes preserve correlation
start / 3
A tool call becomes either a JSON value or a stable error code under the original request ID.
Both outcomes preserve correlation
A tool call becomes either a JSON value or a stable error code under the original request ID.

Successful output is constrained too. Our value vocabulary is JSON null, booleans, integers, finite floats, strings, lists and dictionaries with string keys. Tuples, sets, cycles, NaN and infinity are rejected. The supplied recursive helper checks that vocabulary; the build focuses on the normalization and size boundary after the handler returns.

Encode with ensure_ascii=False and allow_nan=False, then count UTF-8 bytes including JSON syntax. A code-point count can substantially underestimate a non-ASCII response. A budget that says bytes should count the bytes that would actually cross the boundary.

Python
import json
encoded = json.dumps('東京', ensure_ascii=False, allow_nan=False)
print(encoded)
print(len(encoded), len(encoded.encode('utf-8')))

The result cap is post-call. It can reject an oversized returned value, but it cannot undo memory allocation, bound handler runtime or roll back an effect that already happened. Those need separate execution and resource policies. The current course runs within the existing OS sandbox and uses small supplied records.

Copy arguments before invocation so handler mutation does not rewrite the accepted request. Decode successful JSON into a fresh result value as well. Ownership tests make these boundaries visible: edit the returned nested list and verify that the original fixture remains unchanged.

The cap measures returned data
start / 3
A post-call byte check limits the accepted value, not the computation that produced it.
The cap measures returned data
A post-call byte check limits the accepted value, not the computation that produced it.
Python
import json
original = {'items': [1]}
returned = json.loads(json.dumps(original))
returned['items'].append(2)
print(original['items'], returned['items'])

Test expected error categories, a synthetic private error marker, Unicode output at the exact byte boundary and invalid JSON-shaped values. Use an invocation counter if testing failures that must not be retried. These cases will feed the loop in the next lesson, where an error result becomes observable feedback rather than an instruction to repeat the action blindly.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Watch the tool-and-feedback discussion. Our exercises use a deterministic teaching protocol, not a vendor SDK.