Skip to main content

Overview

Mode gives fine-grained control over which schema fields are exposed to the LLM. It matters most for APIs where one resource is used by several operations with different field sets — Gmail’s Message being the canonical case: send accepts only raw, while reads return a structured payload tree. It has a second job that is easy to miss and is arguably the more valuable one: because the LLM view is computed from these declarations before any request is made, Mode is also field-level data minimisation toward your model vendor — a withheld field cannot reach a context window, by construction. See egress control, including the audit artifact that renders it.

Core concepts

The marker

mode_marker.py

Special modes

Always enforced, whatever mode the tool declares:
  • response_only — the API returns it and never accepts it. Hidden from the LLM. IDs, timestamps, computed fields.
  • request_only — input-only. Always shown.
  • disabled — never exposed. Internal or deprecated fields.

Custom modes

Any other string. A custom mode narrows the view only when the tool declares a mode. With no mode= on the tool, custom-moded fields are all visible — setting mode= is what makes create and update mutually exclusive.

Features

Multi-mode

Cascading

Child fields inherit the parent’s modes, so you mark the parent once:
cascading_modes.py
A response_only parent hides its whole subtree.

Mode + Format together

Processing order

  1. Mode filtering — fields are checked against the effective mode.
  2. Format transformation — only included fields are retyped.
  3. Metadata preservation — every marker survives onto the generated schema.

Declaring a mode on a tool

The builder’s mode= parameter picks the effective mode for that tool:
messages_send_tool.py

Worked example: Gmail

Problem. messages.send accepts only raw (RFC-822). Reads return a structured payload MIME tree. One schema for both confuses the model into sending fields the endpoint ignores. Solution.
gmail_message_schema.py
Result:
  • mode="A" sees raw, thread_id
  • mode="B" sees payload, thread_id
  • mode=None sees everything except response_only and disabled

Inspecting the result

Tool.llm_schema builds the filtered view — what the model is actually handed:

Do’s and don’ts

Do
  • lean on cascading instead of marking every nested field
  • say why a field has a mode, in the field description
  • test each mode configuration
  • use semantic mode names
Don’t
  • mark every nested field redundantly
  • mix concerns into one name (Mode("admin_read_v2"))
  • forget to set mode= on the tool when the schema depends on it
  • use modes for authorization — they are a schema-visibility tool, not a permission system
  • Mode — the marker, its values, and the cascade rules
  • Format — the retyping that runs after mode filtering
  • Tool.llm_schema — the filtered schema the model sees
  • Egress control — the audit artifact these declarations produce