Spurlock Studios
Contact
Tool Schemas Agents Follow: Descriptions, Enums, and Killing the Omnibus Tool

Agents invent arguments when your tool schema is vague. Treat JSON Schema as agent-facing UX: every property gets a description, finite sets become enums, required fields are honest, and the omnibus do_anything tool dies before it becomes privilege escalation. Schema quality is not documentation polish — it is how you stop silent production bugs.

This spoke belongs to the Agentic Systems Operating Manual. It pairs with tool-use sandboxes (where tools execute) and evaluators before agents (how you prove argument accuracy). Schemas tell the model what shape to emit; sandboxes and gates decide whether that shape may run.

The short answer

  • Descriptions are the UX. Empty property descriptions force the model to guess units, formats, and when to omit fields.
  • Enums beat free text for status codes, channels, and action verbs the handler actually supports.
  • required must match reality. Optional-in-handler but required-in-schema (or the reverse) creates retry storms.
  • Strict mode grammar-constrains valid JSON — and rejects schemas outside the provider’s supported subset.
  • Split omnibus tools. One mega-tool with a free-form action string is a confused deputy with an API.
  • Measure argument accuracy on a golden set the same way you measure task pass rate.

What makes a tool description agent-facing UX

The top-level description answers three questions the model asks every turn:

  1. When should I call this (triggers)?
  2. When must I not call it (negative triggers)?
  3. What side effect happens if I do?

Weak:

Updates a CRM record.

Stronger:

Update an existing CRM contact by contact_id. Call when the user confirmed a field change
(email, phone, lifecycle stage). Do not call to create contacts — use create_contact.
Do not call when the change is still a draft awaiting human approval.

Prescriptive when beats poetic what. Recent models that reach for tools conservatively especially need trigger language (Anthropic’s own tool-definition guidance stresses this).

Why empty property descriptions cause production bugs

The model sees property names. Names lie.

Schema smellWhat the model inventsProduction bug
amount with no descriptionDollars vs centsOff-by-100 charges
date as string, no formattomorrow, 02/03/26, unixHandler parse fail → retry loop
status free stringclosed, Complete, doneDownstream enum reject
user vs user_idEmail stuffed into id field404 / wrong tenant
Nested options: {} emptyHallucinated keysStrict mode 400 or silent ignore

Worked failure: a “schedule_meeting” tool exposed duration with no description. The model sent 30 (minutes) on Monday and "30m" on Tuesday after a prompt tweak. Half the calendar API calls 400’d; the agent retried until budget died. One sentence — "Duration in minutes as an integer, e.g. 30" — would have prevented the week of noise.

JSON Schema shape that agents can follow

Minimal production pattern (Anthropic input_schema / OpenAI parameters / MCP inputSchema — same core object):

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "contact_id": {
      "type": "string",
      "description": "CRM contact UUID from search_contacts. Never invent an id."
    },
    "lifecycle_stage": {
      "type": "string",
      "enum": ["subscriber", "lead", "opportunity", "customer", "churned"],
      "description": "Exact lifecycle stage value accepted by the CRM. Use only these enums."
    },
    "note": {
      "type": ["string", "null"],
      "description": "Optional internal note, max ~500 chars. Null if no note."
    }
  },
  "required": ["contact_id", "lifecycle_stage", "note"]
}

Rules that hold across providers in 2026:

RuleWhy
additionalProperties: false on every objectRequired for strict / structured paths; stops mystery keys
Describe every propertyBiggest accuracy win per edit
Prefer enum for finite setsCollapses inventable strings
Express optionals as nullable + required listing (strict mode style)Providers differ; portable pattern is “all keys listed, null allowed”
Keep nesting shallowDeep trees confuse models and hit provider limits

Do not rely on minimum / maximum / pattern for portability: OpenAI may accept and not enforce; Anthropic strict mode may reject unsupported keywords. Validate bounds in the handler and return a clear tool error the model can fix.

Strict mode — when it helps and when it hurts

Helps: write tools where invalid JSON arguments must never reach the handler; high-volume agents where “almost valid” burns retries.

Hurts / fails closed:

  • Schema uses unsupported keywords → API 400 before the model runs
  • You needed a loosely typed escape hatch for rare admin ops
  • You generated schemas from rich Pydantic/Zod models without a “strict-safe” transform

Procedure when enabling strict:

  1. Freeze the schema subset your provider documents
  2. Turn on strict: true (OpenAI tools / Anthropic tool definitions)
  3. Run the golden set; collect schema compiler errors separately from model errors
  4. Keep handler validation anyway — strict is not authorization

Strict mode is grammar. It is not a policy gate.

Killing the omnibus tool

An omnibus tool looks like this:

{
  "name": "crm",
  "description": "Do anything in the CRM",
  "input_schema": {
    "type": "object",
    "properties": {
      "action": { "type": "string" },
      "payload": { "type": "object" }
    },
    "required": ["action", "payload"]
  }
}

Why it fails in production:

  1. No enum on action → invents verbs the handler does not implement
  2. payload is a bag → no property descriptions, no required fields
  3. Privilege escalation → one allowlisted tool name unlocks every CRM write
  4. Eval blindness → you cannot score “correct tool choice” when there is only one tool

Split by side-effect class and audience:

Split toolSide effectWho may call
search_contactsReadAgent
update_contact_stageWrite (reversible)Agent + policy
merge_contactsWrite (hard)Human approval only
export_contacts_csvRead / bulkDeny in prod agent

Fewer, sharper tools beat one god-tool every time. If you need composition, put it in your code — not in a free-form action string.

MCP tool schemas vs provider-native function schemas

Same JSON Schema idea; different wrappers and field names:

SurfaceParameters keyWrapper
OpenAI function toolsparameters{ "type": "function", "name", "description", "parameters", "strict?" }
Anthropic toolsinput_schemaTop-level { name, description, input_schema, strict? }
MCP toolsinputSchemaServer-advertised tool; host translates for the model

Consequences:

  • You cannot paste an MCP tool record into OpenAI’s tools array without translation
  • MCP hosts discover tools at runtime — giant catalogs blow context (token tax)
  • Execution location differs: native function calling usually runs in your harness; MCP often runs in a server process with its own credentials

Practical pattern: keep one canonical schema (Zod/Pydantic/JSON), generate provider adapters, and generate MCP inputSchema from the same source. Do not hand-maintain three drifting copies.

Should schemas be generated from Pydantic / Zod?

Yes — with a strict-safe export path.

Checklist:

  • Generator emits additionalProperties: false
  • Every field has a description string (enforce in CI)
  • Enums for closed sets, not open strings
  • Target flag matches the provider (openAi / Anthropic-safe)
  • Golden-set fixtures assert on generated schema hashes so silent regen diffs fail CI

Generated schemas without descriptions are how teams ship empty UX at scale.

Measuring tool-call argument accuracy

Do not wait for “task failed” to learn the schema is wrong.

Golden-set columns that matter:

ColumnExample
expected_toolupdate_contact_stage
expected_args{ "contact_id": "…", "lifecycle_stage": "customer" }
forbidden_toolsmerge_contacts
notesAmbiguous user text on purpose

Score separately:

  1. Tool choice accuracy — right tool name
  2. Argument exact-match / schema-valid — right shape
  3. Semantic arg match — same meaning after normalization (dates, phones)

A schema change that lifts argument exact-match 20 points with flat task pass rate still shipped value — fewer retries, lower cost, fewer weird writes.

First schema review checklist

  • Tool name is a verb_noun the handler implements (update_contact_stage, not crm)
  • Top-level description has triggers and negative triggers
  • Every property has a non-empty description
  • Finite sets are enum
  • required matches handler reality
  • additionalProperties: false on objects
  • No omnibus payload: object without inner schema
  • Side-effect class documented for the policy gate
  • Example args in docs or input_examples if your provider supports them
  • Golden cases cover the three failure modes you fear most

Failure mode: schema drift after the CRM upgrade

What broke: CRM added lifecycle_stage values; the tool schema enum stayed frozen; the model correctly wanted evangelist; strict mode / handler rejected; agent looped.

Fix path:

  1. Schema version in the tool registry
  2. Contract test against a live or stubbed CRM enum endpoint
  3. Golden case for each new stage before deploy
  4. Alert on repeated invalid_enum tool errors online

Schemas are APIs. Version them.

Pilot minimum

In a Spurlock $1,500 · 5-day pilot: inventory tools and kill omnibus entries; rewrite descriptions + enums on the write path; add a 20–40 case argument-accuracy slice; return handler validation errors the model can fix (not opaque 500s). Zero empty property descriptions on tools that send email or move money.

FAQ

When should I split one “do_anything” tool into many?

As soon as one tool name can perform more than one side-effect class, or when action/payload is free-form. Split by verb and risk: reads vs reversible writes vs irreversible writes. Omnibus tools hide privilege and make evals meaningless.

Do input_examples help more than longer descriptions?

They help different jobs. Descriptions win for when/when-not triggers and units. Examples win for formats the model keeps wrong. For write tools, use both — but put closed sets in enum before you write a novel.

Strict mode — when does it hurt?

When your schema uses keywords the provider’s strict compiler rejects, or when you still need a rare loosely typed admin escape hatch. It also does not replace authorization. Use strict on high-volume write tools after the schema is inside the supported subset; keep handler validation.

How do MCP tool schemas differ from provider function schemas?

MCP advertises inputSchema; OpenAI expects parameters inside a function tool object; Anthropic expects input_schema. The JSON Schema core can be shared, but the wrappers differ — translate from one canonical source. MCP also shifts discovery/execution to servers, which changes credential boundaries and context cost.

Should schemas be generated from Pydantic/Zod?

Yes, if CI enforces descriptions, enums, additionalProperties: false, and a provider-safe export. Generated schemas without those constraints just industrialize empty UX. Hash schemas in golden-set CI so silent regen cannot drift production.

What’s a good first schema review checklist?

Name, triggers, negative triggers, every property described, enums for finite sets, honest required, no omnibus payload bags, side-effect class for the gate, and golden cases for the scary paths. If a write tool fails that list, it does not ship.

CTA

Want schemas that stop inventing arguments on a real job in five days? Start at /agentic or /contact?intent=agentic-pilot.

Start a pilot