Blog · · 10 min read

AI agent tool pagination without silent data loss

AI agent tool pagination without silent data loss

AI agent tool pagination fails when the model cannot tell whether it saw every matching record. A tool may return the first page, trim a valid response at a byte limit, or keep loading until the context overflows. The agent can then claim that no matching order, incident, or account exists even though it never inspected the full set. This guide defines a result contract with source-side filters, stable snapshots, scoped handles, explicit completeness, bounded page reads, and full-record lookup. It also gives you failure rules and tests that catch skipped, duplicated, hidden, or unauthorized data before the tool reaches production.

The result must say what was not returned

A large result creates context pressure and weakens the evidence behind the agent's answer.

Suppose an agent asks a product tool for failed payouts from the last 30 days. The database has 2,400 matches. The tool returns 100 rows and omits pagination metadata. The model finds no payout for one customer and reports that none failed. Its conclusion sounds precise, but the tool never gave it enough evidence.

A complete result contract must answer five questions:

  1. What query and authorization produced this result?
  2. Which stable dataset or snapshot is being traversed?
  3. How many items were returned, and may more exist?
  4. How can the caller retrieve another page or one full record?
  5. Which budget or policy stopped further inspection?

The current Model Context Protocol pagination specification defines opaque cursors, a nextCursor, server-selected page sizes, and invalid-cursor errors. It applies that mechanism to list operations such as tools/list and resources/list. It does not define pagination for arbitrary business records inside a tools/call result. Your tool therefore needs an application-level contract for those records, even when its transport is MCP.

Why large tool results break in different ways

Returning everything moves the failure downstream

Fetching every upstream page can move the limit from the product API to the tool server, network envelope, conversation store, or model context.

An Oracle workflow example follows all API pages and flattens the collected data into one final model input. That can work for a bounded dataset. It does not establish a safe upper bound or stable snapshot when the result grows or changes during traversal.

OpenAI's context-engineering guidance warns that redundant tool results can overwhelm even a large context. It also notes that trimming by recent items can fail when one recent tool result is itself huge, while summarization can lose or distort details. Context management is necessary, but it cannot replace a truthful data contract.

Silent truncation corrupts meaning and shape

Truncation may remove the row that answers the user's question. It may also turn a schema-valid result into an invalid one.

A Cloudflare Agents issue report describes structured output values being replaced by truncation markers. In the reported path, that broke the declared schema and later replay. Treat this as a report about a specific implementation, not a guarantee about every runtime. Put truncation state beside the typed payload. Do not insert an arbitrary marker where a number, object, or required string should be.

A Kubernaut issue report records an agent repeating a list call seven times after result truncation. The repair removed duplicate payload data, preserved a total count, and bounded the full response envelope by bytes. Retries need a new cursor or narrower query. Repeating the same oversized call is not recovery.

Medium results can overflow in aggregate

A per-tool limit is not enough. Five responses that each fit can still exceed the remaining run budget. An OpenClaw issue report describes this aggregate case and a session that could no longer recover.

Enforce two limits:

  • a per-result byte and token budget before a tool result enters the conversation;
  • an aggregate budget for unresolved tool data in the current run.

The aggregate check must happen before dispatching another broad query, not after the model context is already full.

Build an AI agent tool pagination contract

Use separate operations for query creation, page retrieval, and full-record retrieval. Do not overload one tool with several incompatible response shapes.

Start with a filtered query, not a bulk dump

The first operation should accept the narrowest fields your product can enforce at the data source:

  • tenant and user context from trusted runtime state, never model arguments;
  • time range and status filters;
  • stable sort keys;
  • a requested projection or named view;
  • an upper page-size hint that the server may reduce.

Reject an unbounded scan when the product can ask for a time range, owner, state, or identifier. Source-side filtering prevents irrelevant records from entering the tool envelope and reduces the chance that sensitive columns reach the model.

Return a summary and handle before returning many rows. The handle should be opaque to the model and scoped to the authenticated principal, tenant, normalized query, sort order, and snapshot.

{
  "query": {
    "status": "failed",
    "created_from": "2026-08-01T00:00:00Z",
    "created_to": "2026-08-27T00:00:00Z"
  },
  "result_handle": "rh_7Qf9...",
  "snapshot_at": "2026-08-27T10:42:31Z",
  "summary": {
    "matching_count": 2400,
    "returned_count": 50,
    "has_more": true
  },
  "page": {
    "items": [],
    "next_cursor": "c_4mx..."
  },
  "limits": {
    "page_size_applied": 50,
    "max_pages_per_run": 8,
    "response_bytes": 9120,
    "truncated": false
  }
}

Keep truncated false when the returned items array is complete for that page. Use has_more to say that the result set has additional pages. These fields describe different facts.

Bind cursors to a stable view

Offset pagination is easy to implement, but inserts and deletes can shift records between requests. The Microsoft MCP pagination guide recommends cursor-based approaches for large or changing datasets and lists consistency, invalid cursors, empty pages, and resource limits among the cases to test.

Choose one consistency rule and expose it:

  • Snapshot: freeze the eligible record versions for the handle's lifetime.
  • High-water mark: bind the query to an upper timestamp or monotonically increasing key.
  • Declared live traversal: permit changes, but state that counts and membership may move.

Snapshot or high-water-mark traversal is usually safer when the agent must prove it checked every record. A cursor should encode or reference the result handle, last stable sort key, direction, and integrity protection. It should not expose raw SQL offsets, tenant identifiers, or authorization claims.

Every page request must reauthenticate the caller and reauthorize the handle. Revoking access after page one must block page two. Do not trust authorization copied into a long-lived cursor.

Separate page summaries from full records

Most tasks do not need every field for every row. Return a compact page with stable identifiers and fields needed for screening. Add a separate get_result_record operation for the selected item.

This pattern keeps the page schema predictable and limits sensitive data exposure. It also makes citations and action receipts clearer because a later decision can refer to the exact record version retrieved in full.

A Temporal Deputy proposal uses the same basic split for large scan results: create a scan handle, return summary counts, page through filtered findings, and fetch one full finding. The issue explicitly notes that protocol-level MCP pagination does not cover arbitrary tool results. Treat the proposal as implementation evidence, not an adopted cross-platform standard.

Make early stopping explicit

An agent should not read 200 pages by default. It should also not imply exhaustive inspection after reading two.

Give each task a stopping policy:

  • Find one: stop after a matching record is verified.
  • Find up to N: stop after N verified records and report that more may exist.
  • Aggregate: use a server-side aggregate instead of loading raw rows.
  • Exhaustive check: traverse every page within a declared snapshot and fail incomplete if the budget expires.

The final answer should carry a completeness statement such as verified_match, complete_no_match, partial_no_match, or incomplete_budget_exhausted. Only complete_no_match supports the claim that no matching record exists.

Research on context-window overflow in AI agents proposes storing arbitrary-length tool output outside the active context and giving the agent pointers to it. That is useful for retention and later retrieval. A pointer still needs scope, expiry, authorization, version, and completeness metadata before it becomes a safe product result handle.

Handle failures without restarting the scan

Return typed failures that tell the orchestrator whether to retry, narrow the query, restart from a new snapshot, or stop.

FailureRequired behaviorUnsafe behavior
Cursor invalid or tamperedReject and log the handle and principalGuess or restart silently
Handle expiredReturn expiry and a restart optionReuse stale authorization
Permission revokedDeny the next page and terminate traversalFinish from cached grants
Snapshot unavailableMark the result incompleteSwitch to live data silently
Page exceeds byte budgetReduce page size before serializationCut bytes inside typed values
Aggregate context budget reachedStop with an incomplete receiptDispatch the same broad tool again
Duplicate or skipped sort keyFail the completeness checkHide the anomaly from the model
Full record changed after summaryReturn the new version and require re-evaluationAct on the stale summary

The agent runtime should own retry budgets. A tool can return a retryable transport error, but it should not launch an unbounded internal loop that hides elapsed time and page progress. An Azure MCP author report describes a large result causing repeated tool calls until the issue was acknowledged and migrated. Persist page progress outside the model's prose so a retry resumes from a known cursor instead of asking the model to reconstruct state.

Verify completeness, security, and context use

Test the contract at three layers.

Data-layer tests

Create fixtures with stable identifiers and deterministic sort keys. Cover:

  • zero rows, one row, exactly one page, and one item over a page boundary;
  • insertions and deletions between requests;
  • equal primary sort values that require a unique tie-breaker;
  • duplicate delivery and skipped-record detection;
  • snapshot expiry and restart behavior;
  • a total count that differs from traversed count.

For an exhaustive traversal, assert that every fixture identifier appears exactly once and that the final receipt says complete.

Authorization tests

Use two tenants and two users with different row access. Try to replay a handle and cursor across each boundary. Revoke access between page requests. Change a row's classification before full-record retrieval. The expected result is denial or explicit re-evaluation, never stale success.

Log the authenticated principal, tenant, normalized query hash, handle ID, snapshot, page sequence, returned counts, byte counts, and terminal completeness state. Do not log raw sensitive rows merely to prove pagination worked.

Agent-behavior tests

Give the model tasks with different stopping rules. Verify that it:

  • narrows an unbounded query before scanning;
  • uses has_more rather than guessing from page length;
  • does not repeat an identical oversized request;
  • fetches a full record before making a high-impact decision;
  • reports partial inspection when its budget ends;
  • claims no match only after a complete traversal or authoritative server-side aggregate.

Run one adversarial case where page one contains a plausible answer but a later page contains the decisive contradiction. This catches agents that stop because the first answer looks convenient.

Common implementation mistakes

MCP list pagination covers tools, prompts, and resources. It does not automatically paginate records returned by a business tool.

A trimmed response is not a page. It has no guaranteed continuation point or complete page boundary.

Keep the total count, has_more, truncation state, and cursors in typed fields so validators can inspect them. Do not hide these values in prose.

Tenant and user scope come from authenticated runtime state. The model may request narrower filters, never broader authority.

Records can move between offsets in a mutable dataset. Bind traversal to a snapshot or high-water mark and a unique stable sort key.

Track the aggregate context budget before the next tool call. A valid individual result can still push the full run past its limit.

Implement one safe path first

Pick the highest-volume list or search tool in your product. Add a scoped result handle, stable snapshot rule, opaque cursor, compact page schema, full-record lookup, and terminal completeness receipt. Then run the page-boundary, mutation, cross-tenant replay, revocation, byte-limit, and aggregate-context tests above.

Do not ship until a test proves the negative case: the agent cannot say "no matching record exists" after inspecting only part of the result set. A partial traversal must end with a partial or incomplete status, never complete_no_match.

References

Be first in line.

Join the waitlist and we'll email you the moment it's ready. No sales call.

Try the demo →
or talk to us