ADR 0005 - Proactive parameter-name schema clarity
- Status: Accepted
- Date: 2026-06-24
- Tracking: internal pre-Codeberg tracker (1604, 1605; builds on 1577 bounded did-you-mean, 1593 no negative steers)
Context
Agents reach for a “natural” argument name that is not the tool’s canonical parameter, the JSON unmarshaler silently drops it, and a wasted round-trip follows. Recurring, real instances:
madt_finishis keyed bytitle; an agent passedcommit_message(andmessage) — “the commit message” is the reflex word for a commit subject.madt_searchis keyed byq; an agent passedquery.
Each time one of these surfaces, the reflex is to reach for the reactive
side — extend the unknown-argument did-you-mean with another synonym, a
compound/_-token splitter, or a broad alias table — until “no guess ever
fails.” That instinct keeps getting re-explored from scratch, and it pulls
toward exactly the failure mode we want to avoid: a never-fail matcher that is
confidently wrong (user_message → body?, review_id → id). This ADR records
the decision so a future “wrong parameter name” bug resolves by reference.
Three facts shape the decision:
The documented best practice is proactive. Anthropic’s tool-design guidance
(Define tools) states that “extremely detailed descriptions … [are] by far
the most important factor in tool performance,” including “what each parameter
means.” Consistent naming across a tool suite is called out by the MCP tool
guidance as load-bearing — inconsistent names “cause the LLM to make mistakes.”
Reactive “did you mean X?” error-correction is nowhere endorsed; a
wrong-guess-then-retry is a failure the schema should prevent, not a feature to
build. (Tool-use examples, the secondary proactive lever cited in Advanced
tool use, are an Anthropic Messages-API feature with no field on the MCP
mcp.Tool type — so via the MCP server the descriptions ARE the lever.)
Our canonical names are deliberately the gh/tea/API names, which are the
agent training-data anchor. madt_finish uses title because that is
gh pr create --title; madt_search uses q because that is the Forgejo/Gitea
search API. The apparent internal inconsistency — “the commit message” is title
on finish but message on the five commit-creating tools (files, wiki, tags,
gitobjects, prs-merge) — is not arbitrary: it mirrors the git-commit vs gh-PR
vocabularies those tools wrap. Renaming to make the internal surface uniform
would walk away from the external vocabulary agents are trained on, and break
the gh/tea parity guard (docs/contributing/naming-conventions.md). So the
consistency that matters is consistency with gh/tea/API, not internal
uniformity.
Fuzzy matching is the precedent we are warned off. The existing did-you-mean
(internal/mcp/arg_suggest.go) is deliberately bounded — Levenshtein ceiling
2, snake_case token containment only on an exactly one hit, identifier synonym
classes kept tight and resolved ambiguity-aware. Every prior fix in this lineage
taught one discrete, conservative lesson and added an escape hatch
rather than a broad heuristic. Coercion never rewrites keys; suggestion never
fires when ambiguous. Broadening that into compound/token matching is the
“never-fail-but-buggy” pattern those bounds exist to prevent.
Decision
A wrong parameter-name guess is fixed PROACTIVELY, at the schema, not REACTIVELY, in the matcher. When an agent passes a natural-but-wrong argument name, the fix is the parameter’s description, and — if the canonical name has drifted from gh/tea/API — the name. It is not a new did-you-mean entry, a fuzzy/compound matcher, an alias, or a rename toward internal uniformity.
The recipe (apply this when a “wrong param name” bug surfaces)
- Confirm the canonical name matches gh/tea/API. If it does not, that is the bug — align the name to the external vocabulary (and update the parity matrix). If it does, do not rename it to chase internal uniformity.
- Fix the description, positively. Make the parameter’s
jsonschemadescription surface the concept the agent reached for, with an inline example — e.g. finishtitle: “Commit message subject and PR title — the one-line conventional-commit summary applied to both the commit and the PR (e.g. ‘feat: add auth’).” An agent scanning the schema for “commit message” now lands ontitlewithout guessing. - No negative steers. State what the field is, never “pass as X, not Y/Z” — an enumerated forbidden set is brittle, never complete, and reads as nagging.
- Do not extend the reactive matcher for this. No new synonym for the
compound, no
_/-token-splitting, no broad alias table.
The reactive did-you-mean stays a bounded backstop
installUnknownArgumentRejection remains valuable: an unknown key is
rejected before the handler runs, named, with the valid-argument list and a
confident suggestion when one exists. But it is a backstop, not the primary
fix, and its bounds are deliberate and not to be loosened:
- Levenshtein ceiling stays 2 (typos only —
numbr→number, notmessage→title, distance 6). - Token containment fires only on an exactly one hit.
- Synonym classes stay tight (interchangeable peers only) and resolve ambiguity-aware (no suggestion when a tool declares two members of one class).
Tighten, never broaden. A semantic synonym that is not a typo (message,
query, commit_message) is the schema description’s job, not the matcher’s.
Names stay anchored to gh/tea/API
No rename “fixes” an internal inconsistency that is really two external
vocabularies. title (gh PR), q (search API), and message (git commit) each
match what they wrap. The parity test in internal/parity/ is the guard; the
description carries any cross-vocabulary nuance.
Consequences
- Fixing a param-name bug is a one-line description edit, gated by the
normal
docs generate --verify. No new matcher surface, no growing alias table, no behavior change to the bounded suggester. Both tracked parameter-name ergonomics fixes reduced to exactly this. - The did-you-mean does not creep. Its bounds are now a recorded decision, not a thing to be re-argued each time a non-typo guess appears.
- The “adding a command” guidance gains a pointer: a new parameter’s description must positively name the concept (and example) an agent reaches for, in the gh/tea/API vocabulary — the proactive lever, applied at authoring time rather than after a bug.
Alternatives considered
- Extend the did-you-mean with the synonym / compound guess (the first
reflex, prototyped). Rejected: the docs do not endorse reactive
correction;
message/queryare not typos so they fall outside the bounded tiers by design; and the natural next step (token-splittingcommit_message) is the over-firing matcher (user_message → body?) the bounds exist to prevent. A suggestion is also only a confident retry, not a first-call land — the description is what makes the first call land. - An explicit enumerated alias table (
message→title,query→q, …) as a deterministic suggester. Rejected as the primary fix: it is still reactive (post-failure), it is a table that grows with every observed miss, and it competes with the one place that should carry the meaning — the description. - Accept the wrong name as an alias (silently treat
messageastitleon finish). Rejected: forks the one-canonical-name rule (docs/contributing/naming-conventions.md); the agent never learns the real name. - Rename to internal consistency (e.g. finish
title→message). Rejected: walks away from the gh/tea vocabulary agents are trained on, and breaks the parity guard. The split is two external vocabularies, not a defect.
Canonical source: docs/adr/0005-parameter-name-ergonomics.md in the madtea repo.